Skip to content
This repository was archived by the owner on Jan 28, 2026. It is now read-only.

Commit 6471306

Browse files
committed
optimize test
1 parent 8c1c649 commit 6471306

1 file changed

Lines changed: 35 additions & 192 deletions

File tree

scripts/generate-flowchart.js

Lines changed: 35 additions & 192 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
11
#!/usr/bin/env node
22
/**
3-
* generate-flowchart.js - Enhanced with test categorization
4-
* Creates a visual flowchart showing:
5-
* - Test organization by category
6-
* - Pass/fail status with visual indicators
7-
* - Performance metrics per test
8-
* - Test distribution statistics
3+
* generate-flowchart.js (L→R layout, detached legend)
4+
* Reads playwright-metrics.json from root or artifacts/.
95
*/
106
const fs = require('fs');
117
const path = require('path');
@@ -27,241 +23,88 @@ fs.mkdirSync(ART, { recursive: true });
2723
const safe = s => (s.replace(/[^A-Za-z0-9_]/g,'_').replace(/^_+|_+$/g,'')||'id').replace(/^[^A-Za-z]/,'id_$&');
2824
const esc = s => s.replace(/\\/g,'\\\\').replace(/"/g,'\\"');
2925

30-
/* Categorize tests based on file path and name */
31-
const categorizeTest = (filePath, testName) => {
32-
const lowerPath = filePath.toLowerCase();
33-
const lowerName = testName.toLowerCase();
34-
35-
// Check for common test categories
36-
if (lowerPath.includes('smoke') || lowerName.includes('smoke')) return 'smoke';
37-
if (lowerPath.includes('e2e') || lowerName.includes('e2e')) return 'e2e';
38-
if (lowerPath.includes('unit') || lowerName.includes('unit')) return 'unit';
39-
if (lowerPath.includes('integration') || lowerName.includes('integration')) return 'integration';
40-
if (lowerPath.includes('api') || lowerName.includes('api')) return 'api';
41-
if (lowerPath.includes('performance') || lowerName.includes('performance')) return 'performance';
42-
43-
// Default category based on file location
44-
if (lowerPath.includes('component')) return 'component';
45-
return 'general';
46-
};
47-
48-
/* build mermaid source with enhanced visualization */
26+
/* build mermaid source (unchanged from earlier) */
4927
const m=[];
5028
m.push(`%%{init:{ "theme":"base","themeVariables":{
5129
"primaryColor":"#1976D2","primaryTextColor":"#fff","primaryBorderColor":"#0D47A1",
52-
"lineColor":"#5E35B1","tertiaryColor":"#E8F5E9",
53-
"mainBkg":"#263238","darkMode":true} }}%%`);
54-
m.push('flowchart TB');
30+
"lineColor":"#5E35B1","tertiaryColor":"#E8F5E9"} }}%%`);
31+
m.push('flowchart LR');
5532

56-
// Enhanced style definitions
5733
m.push(' classDef fileStyle fill:#E3F2FD,stroke:#1976D2,stroke-width:2px,color:#0D47A1,font-weight:bold');
5834
m.push(' classDef suiteStyle fill:#F3E5F5,stroke:#7B1FA2,stroke-width:1px,color:#4A148C,font-weight:bold');
5935
m.push(' classDef passStyle fill:#C8E6C9,stroke:#43A047,stroke-width:2px,color:#1B5E20');
6036
m.push(' classDef failStyle fill:#FFCDD2,stroke:#E53935,stroke-width:2px,color:#B71C1C,font-weight:bold');
6137
m.push(' classDef skipStyle fill:#FFF9C4,stroke:#FBC02D,stroke-width:2px,color:#F57F17');
6238
m.push(' classDef rootStyle fill:#1976D2,stroke:#0D47A1,stroke-width:4px,color:#FFF,font-weight:bold');
63-
m.push(' classDef categoryStyle fill:#37474F,stroke:#263238,stroke-width:2px,color:#ECEFF1,font-weight:bold');
64-
m.push(' classDef perfWarn fill:#FF8A65,stroke:#D84315,stroke-width:2px,color:#FFF');
65-
m.push(' classDef perfGood fill:#81C784,stroke:#388E3C,stroke-width:2px,color:#FFF');
6639

67-
// Collect and categorize all tests
68-
const tests=[];
69-
const categories = {};
70-
const fileTestCounts = {};
40+
m.push(' LEGEND_ANCHOR[" "]:::rootStyle');
41+
m.push(' subgraph legendBox["📋 Legend"]');
42+
m.push(' direction TB');
43+
m.push(' P["✅ Passed"]:::passStyle');
44+
m.push(' F["❌ Failed"]:::failStyle');
45+
m.push(' S["⏭️ Skipped"]:::skipStyle');
46+
m.push(' end');
47+
m.push(' LEGEND_ANCHOR -.-> legendBox');
48+
49+
m.push(' ROOT["🧪 Playwright Test Run"]:::rootStyle');
7150

51+
const tests=[];
7252
METRICS.suites.forEach(f=>{
7353
const fileTitle=f.title||path.basename(f.file);
74-
fileTestCounts[fileTitle] = { total: 0, passed: 0, failed: 0, skipped: 0 };
75-
7654
f.suites.forEach(s=>{
7755
s.specs.forEach(sp=>{
78-
const test = {
56+
tests.push({
7957
file:fileTitle,
8058
suite:s.title||'NO_SUITE',
8159
spec:sp.title||'NO_SPEC',
8260
status:sp.tests[0]?.results[0]?.status??'unknown',
83-
dur:sp.tests[0]?.results[0]?.duration??0,
84-
error:sp.tests[0]?.results[0]?.error?.message
85-
};
86-
test.category = categorizeTest(f.file, test.spec);
87-
88-
tests.push(test);
89-
fileTestCounts[fileTitle].total++;
90-
91-
// Count by status
92-
if (['expected','passed'].includes(test.status)) {
93-
fileTestCounts[fileTitle].passed++;
94-
} else if (test.status === 'failed') {
95-
fileTestCounts[fileTitle].failed++;
96-
} else if (test.status === 'skipped') {
97-
fileTestCounts[fileTitle].skipped++;
98-
}
99-
100-
// Group by category
101-
if (!categories[test.category]) {
102-
categories[test.category] = [];
103-
}
104-
categories[test.category].push(test);
61+
dur:sp.tests[0]?.results[0]?.duration??0
62+
});
10563
});
10664
});
10765
});
108-
109-
// Calculate summary statistics
11066
const summary={
11167
total:tests.length,
11268
passed:tests.filter(t=>['expected','passed'].includes(t.status)).length,
11369
failed:tests.filter(t=>t.status==='failed').length,
11470
skipped:tests.filter(t=>t.status==='skipped').length,
11571
dur:METRICS.stats?.duration??0
11672
};
117-
118-
// Performance analysis
119-
const avgDuration = summary.total > 0 ? Math.round(summary.dur / summary.total) : 0;
120-
const slowTests = tests.filter(t => t.dur > avgDuration * 2);
121-
122-
// Root node
123-
m.push(` ROOT["🧪 Playwright Test Suite<br/><small>${new Date().toLocaleDateString()}</small>"]:::rootStyle`);
124-
125-
// Summary banner
126-
m.push(` BANNER["📊 Total: ${summary.total} | ✅ ${summary.passed} | ❌ ${summary.failed} | ⏭️ ${summary.skipped}<br/>⏱️ ${(summary.dur/1000).toFixed(1)}s | Avg: ${avgDuration}ms/test"]`);
73+
m.push(` BANNER["📊 ${summary.total} • ✅ ${summary.passed} • ❌ ${summary.failed} • ⏭️ ${summary.skipped} • ⏱️ ${summary.dur}s"]`);
12774
m.push(' ROOT --> BANNER');
12875

129-
// Category distribution subgraph
130-
m.push(' subgraph CATEGORIES["📂 Test Categories"]');
131-
m.push(' direction LR');
132-
Object.entries(categories).forEach(([cat, catTests]) => {
133-
const catId = safe(`CAT_${cat}`);
134-
const catPassed = catTests.filter(t => ['expected','passed'].includes(t.status)).length;
135-
const catFailed = catTests.filter(t => t.status === 'failed').length;
136-
const catIcon = {
137-
'smoke': '🔥',
138-
'e2e': '🌐',
139-
'unit': '🧩',
140-
'integration': '🔗',
141-
'api': '📡',
142-
'performance': '⚡',
143-
'component': '🧱',
144-
'general': '📝'
145-
}[cat] || '📁';
146-
147-
m.push(` ${catId}["${catIcon} ${cat.toUpperCase()}<br/>${catTests.length} tests<br/>✅ ${catPassed}${catFailed}"]:::categoryStyle`);
148-
});
149-
m.push(' end');
150-
m.push(' BANNER --> CATEGORIES');
151-
152-
// Performance warnings if any
153-
if (slowTests.length > 0) {
154-
m.push(` PERF_WARN["⚠️ ${slowTests.length} Slow Test${slowTests.length > 1 ? 's' : ''}<br/><small>>2x avg duration</small>"]:::perfWarn`);
155-
m.push(' BANNER --> PERF_WARN');
156-
}
157-
158-
// Legend
159-
m.push(' subgraph LEGEND["📋 Legend"]');
160-
m.push(' direction TB');
161-
m.push(' L1["✅ Passed Test"]:::passStyle');
162-
m.push(' L2["❌ Failed Test"]:::failStyle');
163-
m.push(' L3["⏭️ Skipped Test"]:::skipStyle');
164-
m.push(' L4["⚡ Performance Issue"]:::perfWarn');
165-
m.push(' end');
166-
m.push(' ROOT --> LEGEND');
167-
168-
// Main test flow
16976
const files=[...new Set(tests.map(t=>t.file))];
170-
files.forEach((file, fileIndex)=>{
77+
let prev='BANNER';
78+
files.forEach(file=>{
17179
const fid=safe(file);
172-
const counts = fileTestCounts[file];
173-
const fileStatus = counts.failed > 0 ? '❌' : counts.skipped === counts.total ? '⏭️' : '✅';
174-
175-
m.push(` ${fid}["📁 ${esc(file)}<br/>${fileStatus} ${counts.passed}/${counts.total} passed"]:::fileStyle`);
176-
m.push(` CATEGORIES --> ${fid}`);
80+
m.push(` ${fid}["📁 ${esc(file)}"]:::fileStyle`);
81+
m.push(` ${prev} --> ${fid}`);
82+
prev=fid;
17783

178-
m.push(` subgraph ${fid}_tests["${esc(file)} Tests"]`);
84+
m.push(` subgraph ${fid}_grp[ ]`);
17985
m.push(' direction TB');
180-
181-
// Group tests by suite within file
18286
const suites=[...new Set(tests.filter(t=>t.file===file).map(t=>t.suite))];
183-
suites.forEach((suite, suiteIndex)=>{
87+
suites.forEach(suite=>{
18488
const sid=safe(`${fid}_${suite}`);
185-
const suiteTests = tests.filter(t=>t.file===file && t.suite===suite);
186-
const suitePassed = suiteTests.filter(t=>['expected','passed'].includes(t.status)).length;
187-
const suiteFailed = suiteTests.filter(t=>t.status==='failed').length;
188-
189-
m.push(` ${sid}["📦 ${esc(suite)}<br/><small>${suitePassed}/${suiteTests.length} passed</small>"]:::suiteStyle`);
190-
191-
// Individual tests
192-
suiteTests.forEach((t, testIndex)=>{
193-
const spid=safe(`${sid}_${t.spec}_${testIndex}`);
89+
m.push(` ${sid}["📦 ${esc(suite)}"]:::suiteStyle`);
90+
m.push(` ${fid} --> ${sid}`);
91+
tests.filter(t=>t.file===file && t.suite===suite).forEach(t=>{
92+
const spid=safe(`${sid}_${t.spec}`);
19493
const cls=t.status==='failed'?'failStyle':t.status==='skipped'?'skipStyle':'passStyle';
19594
const icon=t.status==='failed'?'❌':t.status==='skipped'?'⏭️':'✅';
196-
const perfIcon = t.dur > avgDuration * 2 ? ' 🐌' : t.dur < avgDuration / 2 ? ' 🚀' : '';
197-
198-
let testLabel = `${icon} ${esc(t.spec)}${perfIcon}<br/><small>${t.dur}ms`;
199-
if (t.category !== 'general') {
200-
testLabel += ` | ${t.category}`;
201-
}
202-
testLabel += '</small>';
203-
204-
// Add error message for failed tests
205-
if (t.status === 'failed' && t.error) {
206-
const shortError = t.error.split('\n')[0].substring(0, 50);
207-
testLabel += `<br/><small style="color:red">${esc(shortError)}...</small>`;
208-
}
209-
210-
m.push(` ${sid} --> ${spid}["${testLabel}"]:::${cls}`);
211-
212-
// Link slow tests to performance warning
213-
if (t.dur > avgDuration * 2 && slowTests.length > 0) {
214-
m.push(` ${spid} -.-> PERF_WARN`);
215-
}
95+
m.push(` ${sid} --> ${spid}["${icon} ${esc(t.spec)}<br/><small>${t.dur}ms</small>"]:::${cls}`);
21696
});
21797
});
21898
m.push(' end');
21999
});
220100

221-
// Add test distribution pie chart data as comment
222-
m.push(`
223-
%% Test Distribution Data
224-
%% Categories: ${Object.entries(categories).map(([k,v]) => `${k}:${v.length}`).join(', ')}
225-
%% Status: Passed:${summary.passed}, Failed:${summary.failed}, Skipped:${summary.skipped}
226-
%% Performance: Slow:${slowTests.length}, Normal:${tests.length - slowTests.length}
227-
`);
228-
229-
/* write & render PNG with larger dimensions for better readability */
101+
/* write & render PNG */
230102
fs.writeFileSync(`${ART}/flowchart.mmd`, m.join('\n'));
231103
fs.writeFileSync('puppeteer.json','{ "args":["--no-sandbox","--disable-setuid-sandbox"] }');
232-
233-
console.log('📊 Generating enhanced flowchart...');
234104
execSync(
235105
'npx -y @mermaid-js/mermaid-cli@10.6.1 ' +
236106
'-p puppeteer.json -i artifacts/flowchart.mmd -o artifacts/flowchart.png ' +
237-
'-w 10000 -H 3000 -b white',
107+
'-w 8000 -H 2600 -b white',
238108
{ stdio:'inherit' }
239109
);
240-
241-
// Also generate a test categorization summary
242-
const categorySummary = {
243-
totalTests: tests.length,
244-
categories: Object.entries(categories).map(([name, tests]) => ({
245-
name,
246-
count: tests.length,
247-
passed: tests.filter(t => ['expected','passed'].includes(t.status)).length,
248-
failed: tests.filter(t => t.status === 'failed').length,
249-
skipped: tests.filter(t => t.status === 'skipped').length,
250-
avgDuration: tests.reduce((sum, t) => sum + t.dur, 0) / tests.length
251-
})),
252-
slowTests: slowTests.map(t => ({
253-
name: `${t.file} > ${t.suite} > ${t.spec}`,
254-
duration: t.dur,
255-
category: t.category
256-
})),
257-
failedTests: tests.filter(t => t.status === 'failed').map(t => ({
258-
name: `${t.file} > ${t.suite} > ${t.spec}`,
259-
category: t.category,
260-
error: t.error
261-
}))
262-
};
263-
264-
fs.writeFileSync(`${ART}/test-categorization.json`, JSON.stringify(categorySummary, null, 2));
265-
266-
console.log('✅ Enhanced flow-chart with categorization → artifacts/flowchart.png');
267-
console.log('📊 Test categorization summary → artifacts/test-categorization.json');
110+
console.log('✅ Flow-chart with detached legend → artifacts/flowchart.png');

0 commit comments

Comments
 (0)