@kaizenreport/kensho-viewer 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +19 -0
- package/assets/app.js +1396 -0
- package/assets/app.jsx +860 -0
- package/assets/charts.js +1156 -0
- package/assets/charts.jsx +593 -0
- package/assets/colors_and_type.css +219 -0
- package/assets/components.js +894 -0
- package/assets/components.jsx +520 -0
- package/assets/data-bridge.js +49 -0
- package/assets/data-bridge.jsx +55 -0
- package/assets/data-loader.js +543 -0
- package/assets/kaizen-mark.svg +5 -0
- package/assets/kensho-wordmark.svg +18 -0
- package/assets/pages.js +1472 -0
- package/assets/pages.jsx +822 -0
- package/assets/test-detail.js +1058 -0
- package/assets/test-detail.jsx +502 -0
- package/assets/tokens.css +357 -0
- package/assets/tree-detail.js +1705 -0
- package/assets/tree-detail.jsx +947 -0
- package/dist/component.d.ts +115 -0
- package/dist/component.js +698 -0
- package/index.html +35 -0
- package/package.json +65 -0
- package/scripts/build.js +117 -0
- package/src/component.d.ts +115 -0
- package/src/component.jsx +340 -0
- package/src/data.js +538 -0
package/assets/pages.js
ADDED
|
@@ -0,0 +1,1472 @@
|
|
|
1
|
+
/* Auto-generated from pages.jsx by packages/viewer/scripts/build.js. Edit the .jsx — DO NOT edit this file. */
|
|
2
|
+
/* global React, TreeDetailPage, TrendChartV2, HBars, DurationHistogram, FlakeScatter, TimelineGantt, SuiteHeatmap, RetryWaterfall */
|
|
3
|
+
const {
|
|
4
|
+
useState: useStateP
|
|
5
|
+
} = React;
|
|
6
|
+
|
|
7
|
+
// ============== GRAPHS PAGE ==============
|
|
8
|
+
function GraphsPage() {
|
|
9
|
+
const TREND_RUNS = window.TREND_RUNS || [];
|
|
10
|
+
const HISTOGRAM = window.HISTOGRAM || [];
|
|
11
|
+
const RICH_TESTS = window.RICH_TESTS || {};
|
|
12
|
+
const CATEGORIES = window.CATEGORIES || [];
|
|
13
|
+
const fmt = window._kenshoFmtDuration || (ms => ms + 'ms');
|
|
14
|
+
const [showAllSuites, setShowAllSuites] = useStateP(false);
|
|
15
|
+
const SUITE_CAP = 12;
|
|
16
|
+
|
|
17
|
+
// Highlight banner derivations — surface the three "punchiest" facts
|
|
18
|
+
// about the run so a stakeholder can scan and act in 5 seconds.
|
|
19
|
+
const allRich = Object.values(RICH_TESTS);
|
|
20
|
+
const slowest = allRich.filter(t => t.durMs > 0).sort((a, b) => b.durMs - a.durMs)[0];
|
|
21
|
+
const mostRetried = allRich.filter(t => t.retries > 0).sort((a, b) => b.retries - a.retries)[0];
|
|
22
|
+
const topCategory = CATEGORIES[0];
|
|
23
|
+
|
|
24
|
+
// Derive "Status by suite" from RICH_TESTS, grouped by first suite segment.
|
|
25
|
+
const suiteBuckets = {};
|
|
26
|
+
Object.values(RICH_TESTS).forEach(t => {
|
|
27
|
+
const suiteName = (t.suite || '').split('›')[0].trim() || 'Default';
|
|
28
|
+
if (!suiteBuckets[suiteName]) suiteBuckets[suiteName] = {
|
|
29
|
+
passed: 0,
|
|
30
|
+
failed: 0,
|
|
31
|
+
broken: 0,
|
|
32
|
+
skipped: 0
|
|
33
|
+
};
|
|
34
|
+
if (suiteBuckets[suiteName][t.status] != null) suiteBuckets[suiteName][t.status]++;
|
|
35
|
+
});
|
|
36
|
+
const suiteStatusBars = Object.entries(suiteBuckets).map(([label, b]) => {
|
|
37
|
+
const segs = ['failed', 'broken', 'skipped', 'passed'].filter(k => b[k] > 0).map(k => ({
|
|
38
|
+
k,
|
|
39
|
+
n: b[k]
|
|
40
|
+
}));
|
|
41
|
+
const total = b.passed + b.failed + b.broken + b.skipped;
|
|
42
|
+
return {
|
|
43
|
+
label,
|
|
44
|
+
segs,
|
|
45
|
+
total,
|
|
46
|
+
failures: b.failed + b.broken
|
|
47
|
+
};
|
|
48
|
+
}).sort((a, b) => b.failures - a.failures || b.total - a.total);
|
|
49
|
+
const visibleSuites = showAllSuites ? suiteStatusBars : suiteStatusBars.slice(0, SUITE_CAP);
|
|
50
|
+
const hiddenSuiteCount = suiteStatusBars.length - visibleSuites.length;
|
|
51
|
+
const totalTests = Object.values(RICH_TESTS).length;
|
|
52
|
+
const hasFlake = Object.values(RICH_TESTS).some(t => t.flakeRate > 0);
|
|
53
|
+
return /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("h1", {
|
|
54
|
+
className: "k-h1",
|
|
55
|
+
style: {
|
|
56
|
+
marginBottom: 4
|
|
57
|
+
}
|
|
58
|
+
}, "Graphs"), /*#__PURE__*/React.createElement("div", {
|
|
59
|
+
className: "k-meta",
|
|
60
|
+
style: {
|
|
61
|
+
marginBottom: 18
|
|
62
|
+
}
|
|
63
|
+
}, "Distributions and trends across the active run \xB7 ", totalTests, " tests"), (slowest || mostRetried || topCategory) && /*#__PURE__*/React.createElement("div", {
|
|
64
|
+
style: {
|
|
65
|
+
display: 'grid',
|
|
66
|
+
gridTemplateColumns: 'repeat(3, 1fr)',
|
|
67
|
+
gap: 16,
|
|
68
|
+
marginBottom: 16
|
|
69
|
+
}
|
|
70
|
+
}, slowest ? /*#__PURE__*/React.createElement(HighlightStat, {
|
|
71
|
+
overline: "Slowest test",
|
|
72
|
+
value: slowest.dur,
|
|
73
|
+
valueColor: "var(--status-broken-fg)",
|
|
74
|
+
subtitle: slowest.name,
|
|
75
|
+
accent: "var(--status-broken)",
|
|
76
|
+
onClick: () => window.__openTest?.(slowest.id),
|
|
77
|
+
title: `Open ${slowest.name} →`
|
|
78
|
+
}) : /*#__PURE__*/React.createElement(HighlightStat, {
|
|
79
|
+
overline: "Slowest test",
|
|
80
|
+
value: "\u2014",
|
|
81
|
+
subtitle: "No timing data",
|
|
82
|
+
accent: "var(--line)"
|
|
83
|
+
}), mostRetried ? /*#__PURE__*/React.createElement(HighlightStat, {
|
|
84
|
+
overline: "Most retried",
|
|
85
|
+
value: `${mostRetried.retries}×`,
|
|
86
|
+
valueColor: "#B69CFF",
|
|
87
|
+
subtitle: mostRetried.name,
|
|
88
|
+
accent: "#7C5CFF",
|
|
89
|
+
onClick: () => window.__openTest?.(mostRetried.id),
|
|
90
|
+
title: `Open ${mostRetried.name} →`
|
|
91
|
+
}) : /*#__PURE__*/React.createElement(HighlightStat, {
|
|
92
|
+
overline: "Most retried",
|
|
93
|
+
value: "0",
|
|
94
|
+
subtitle: "No retries this run \u2014 clean execution",
|
|
95
|
+
accent: "var(--line)"
|
|
96
|
+
}), topCategory ? /*#__PURE__*/React.createElement(HighlightStat, {
|
|
97
|
+
overline: "Top failure category",
|
|
98
|
+
value: String(topCategory.count),
|
|
99
|
+
valueColor: "var(--status-failed-fg)",
|
|
100
|
+
subtitle: topCategory.kind,
|
|
101
|
+
accent: "var(--status-failed)",
|
|
102
|
+
onClick: () => window.__navTo?.('categories'),
|
|
103
|
+
title: "Open Categories \u2192"
|
|
104
|
+
}) : /*#__PURE__*/React.createElement(HighlightStat, {
|
|
105
|
+
overline: "Top failure category",
|
|
106
|
+
value: "0",
|
|
107
|
+
subtitle: "No failures this run",
|
|
108
|
+
accent: "var(--status-passed)"
|
|
109
|
+
})), /*#__PURE__*/React.createElement("div", {
|
|
110
|
+
style: {
|
|
111
|
+
display: 'grid',
|
|
112
|
+
gridTemplateColumns: '1fr 1fr',
|
|
113
|
+
gap: 16
|
|
114
|
+
}
|
|
115
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
116
|
+
className: "card",
|
|
117
|
+
style: {
|
|
118
|
+
gridColumn: 'span 2'
|
|
119
|
+
}
|
|
120
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
121
|
+
className: "hd"
|
|
122
|
+
}, /*#__PURE__*/React.createElement("h3", null, "Trend \xB7 ", TREND_RUNS.length, " run", TREND_RUNS.length === 1 ? '' : 's'), /*#__PURE__*/React.createElement("div", {
|
|
123
|
+
className: "meta"
|
|
124
|
+
}, "stacked status counts")), TREND_RUNS.length > 0 ? /*#__PURE__*/React.createElement(TrendChartV2, {
|
|
125
|
+
runs: TREND_RUNS
|
|
126
|
+
}) : /*#__PURE__*/React.createElement("div", {
|
|
127
|
+
style: {
|
|
128
|
+
padding: 30,
|
|
129
|
+
color: 'var(--fg3)',
|
|
130
|
+
textAlign: 'center',
|
|
131
|
+
fontFamily: 'var(--font-mono)',
|
|
132
|
+
fontSize: 12
|
|
133
|
+
}
|
|
134
|
+
}, "No run history yet. Run kensho generate over multiple runs to populate.")), /*#__PURE__*/React.createElement("div", {
|
|
135
|
+
className: "card"
|
|
136
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
137
|
+
className: "hd"
|
|
138
|
+
}, /*#__PURE__*/React.createElement("h3", null, "Status by suite"), /*#__PURE__*/React.createElement("div", {
|
|
139
|
+
className: "meta"
|
|
140
|
+
}, showAllSuites ? `all ${suiteStatusBars.length}` : `top ${visibleSuites.length} of ${suiteStatusBars.length}`, " \xB7 sorted by failures")), /*#__PURE__*/React.createElement("div", {
|
|
141
|
+
style: {
|
|
142
|
+
display: 'flex',
|
|
143
|
+
flexDirection: 'column',
|
|
144
|
+
gap: 8
|
|
145
|
+
}
|
|
146
|
+
}, visibleSuites.map((s, i) => /*#__PURE__*/React.createElement("div", {
|
|
147
|
+
key: i,
|
|
148
|
+
style: {
|
|
149
|
+
display: 'grid',
|
|
150
|
+
gridTemplateColumns: '160px 1fr 30px',
|
|
151
|
+
alignItems: 'center',
|
|
152
|
+
gap: 10
|
|
153
|
+
}
|
|
154
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
155
|
+
style: {
|
|
156
|
+
fontFamily: 'var(--font-mono)',
|
|
157
|
+
fontSize: 12,
|
|
158
|
+
color: 'var(--fg1)',
|
|
159
|
+
overflow: 'hidden',
|
|
160
|
+
textOverflow: 'ellipsis',
|
|
161
|
+
whiteSpace: 'nowrap'
|
|
162
|
+
}
|
|
163
|
+
}, s.label), /*#__PURE__*/React.createElement("div", {
|
|
164
|
+
style: {
|
|
165
|
+
height: 14,
|
|
166
|
+
background: 'var(--bg-sunken)',
|
|
167
|
+
borderRadius: 3,
|
|
168
|
+
display: 'flex',
|
|
169
|
+
overflow: 'hidden'
|
|
170
|
+
}
|
|
171
|
+
}, s.segs.map((g, j) => /*#__PURE__*/React.createElement("div", {
|
|
172
|
+
key: j,
|
|
173
|
+
style: {
|
|
174
|
+
width: `${g.n / s.total * 100}%`,
|
|
175
|
+
background: `var(--status-${g.k})`
|
|
176
|
+
}
|
|
177
|
+
}))), /*#__PURE__*/React.createElement("div", {
|
|
178
|
+
style: {
|
|
179
|
+
fontFamily: 'var(--font-mono)',
|
|
180
|
+
fontSize: 11,
|
|
181
|
+
color: 'var(--fg3)',
|
|
182
|
+
textAlign: 'right'
|
|
183
|
+
}
|
|
184
|
+
}, s.total)))), hiddenSuiteCount > 0 && /*#__PURE__*/React.createElement("div", {
|
|
185
|
+
style: {
|
|
186
|
+
display: 'flex',
|
|
187
|
+
justifyContent: 'center',
|
|
188
|
+
marginTop: 14,
|
|
189
|
+
paddingTop: 12,
|
|
190
|
+
borderTop: '1px solid var(--line)'
|
|
191
|
+
}
|
|
192
|
+
}, /*#__PURE__*/React.createElement("button", {
|
|
193
|
+
className: "btn btn-ghost",
|
|
194
|
+
style: {
|
|
195
|
+
height: 28,
|
|
196
|
+
fontSize: 12
|
|
197
|
+
},
|
|
198
|
+
onClick: () => setShowAllSuites(true)
|
|
199
|
+
}, "Show ", hiddenSuiteCount, " more suites \u2192")), showAllSuites && suiteStatusBars.length > SUITE_CAP && /*#__PURE__*/React.createElement("div", {
|
|
200
|
+
style: {
|
|
201
|
+
display: 'flex',
|
|
202
|
+
justifyContent: 'center',
|
|
203
|
+
marginTop: 14,
|
|
204
|
+
paddingTop: 12,
|
|
205
|
+
borderTop: '1px solid var(--line)'
|
|
206
|
+
}
|
|
207
|
+
}, /*#__PURE__*/React.createElement("button", {
|
|
208
|
+
className: "btn btn-ghost",
|
|
209
|
+
style: {
|
|
210
|
+
height: 28,
|
|
211
|
+
fontSize: 12
|
|
212
|
+
},
|
|
213
|
+
onClick: () => setShowAllSuites(false)
|
|
214
|
+
}, "\u2191 Collapse to top ", SUITE_CAP))), /*#__PURE__*/React.createElement("div", {
|
|
215
|
+
className: "card"
|
|
216
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
217
|
+
className: "hd"
|
|
218
|
+
}, /*#__PURE__*/React.createElement("h3", null, "Duration distribution"), /*#__PURE__*/React.createElement("div", {
|
|
219
|
+
className: "meta"
|
|
220
|
+
}, "all ", totalTests, " tests")), /*#__PURE__*/React.createElement(DurationHistogram, {
|
|
221
|
+
buckets: HISTOGRAM
|
|
222
|
+
})), /*#__PURE__*/React.createElement("div", {
|
|
223
|
+
className: "card",
|
|
224
|
+
style: {
|
|
225
|
+
gridColumn: 'span 2'
|
|
226
|
+
}
|
|
227
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
228
|
+
className: "hd"
|
|
229
|
+
}, /*#__PURE__*/React.createElement("h3", null, "Flake rate vs duration"), /*#__PURE__*/React.createElement("div", {
|
|
230
|
+
className: "meta"
|
|
231
|
+
}, "last 80 runs \xB7 circle size = sample count")), hasFlake ? /*#__PURE__*/React.createElement(FlakeScatter, {
|
|
232
|
+
tests: Object.values(RICH_TESTS).filter(t => t.flakeRate > 0).map(t => ({
|
|
233
|
+
name: t.name,
|
|
234
|
+
runs: 80,
|
|
235
|
+
flakeRate: t.flakeRate,
|
|
236
|
+
avgDur: t.avgDurMs
|
|
237
|
+
}))
|
|
238
|
+
}) : /*#__PURE__*/React.createElement("div", {
|
|
239
|
+
style: {
|
|
240
|
+
padding: 30,
|
|
241
|
+
color: 'var(--fg3)',
|
|
242
|
+
textAlign: 'center',
|
|
243
|
+
fontFamily: 'var(--font-mono)',
|
|
244
|
+
fontSize: 12
|
|
245
|
+
}
|
|
246
|
+
}, "Flake-rate analysis requires run history. Run kensho generate over multiple runs to populate."))));
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// ============== TIMELINE PAGE ==============
|
|
250
|
+
function TimelinePage() {
|
|
251
|
+
const TIMELINE_TESTS = window.TIMELINE_TESTS || [];
|
|
252
|
+
const RICH_TESTS = window.RICH_TESTS || {};
|
|
253
|
+
const KENSHO_INDEX = window.KENSHO_INDEX || {};
|
|
254
|
+
const fmt = window._kenshoFmtDuration || (ms => ms + 'ms');
|
|
255
|
+
|
|
256
|
+
// View modes — at scale (>50 tests) the Gantt becomes unreadable. Default to
|
|
257
|
+
// 'top' (longest 25), with toggles for failures-only and full view.
|
|
258
|
+
const [mode, setMode] = useStateP(TIMELINE_TESTS.length > 50 ? 'top' : 'all');
|
|
259
|
+
const failuresOnly = TIMELINE_TESTS.filter(t => t.status === 'failed' || t.status === 'broken');
|
|
260
|
+
let visibleTests;
|
|
261
|
+
if (mode === 'top') {
|
|
262
|
+
visibleTests = [...TIMELINE_TESTS].sort((a, b) => b.durMs - a.durMs).slice(0, 25);
|
|
263
|
+
} else if (mode === 'failures') {
|
|
264
|
+
visibleTests = failuresOnly;
|
|
265
|
+
} else {
|
|
266
|
+
visibleTests = TIMELINE_TESTS;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Re-base start times so the Gantt fills the canvas regardless of mode —
|
|
270
|
+
// showing 25 longest tests with sparse start times leaves dead space at
|
|
271
|
+
// the front of the chart otherwise.
|
|
272
|
+
const minStart = visibleTests.length ? Math.min(...visibleTests.map(t => t.start)) : 0;
|
|
273
|
+
const rebased = visibleTests.map(t => ({
|
|
274
|
+
...t,
|
|
275
|
+
start: t.start - minStart
|
|
276
|
+
}));
|
|
277
|
+
const totalMs = rebased.length ? Math.max(...rebased.map(t => t.start + t.durMs)) + 200 : 1000;
|
|
278
|
+
const fullDurationMs = TIMELINE_TESTS.length ? Math.max(...TIMELINE_TESTS.map(t => t.start + t.durMs)) : 0;
|
|
279
|
+
const hasHistory = (KENSHO_INDEX.history?.length || 0) > 0;
|
|
280
|
+
const hasRetries = Object.values(RICH_TESTS).some(t => t.retries > 0);
|
|
281
|
+
const runId = KENSHO_INDEX.runId ? '#' + KENSHO_INDEX.runId : '';
|
|
282
|
+
const workers = KENSHO_INDEX.env?.workers || 1;
|
|
283
|
+
const MODES = [['top', 'Top 25 longest', TIMELINE_TESTS.length > 0 ? Math.min(25, TIMELINE_TESTS.length) : 0], ['failures', 'Failures only', failuresOnly.length], ['all', 'All', TIMELINE_TESTS.length]].filter(([id, _, n]) => n > 0);
|
|
284
|
+
return /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("h1", {
|
|
285
|
+
className: "k-h1",
|
|
286
|
+
style: {
|
|
287
|
+
marginBottom: 4
|
|
288
|
+
}
|
|
289
|
+
}, "Timeline"), /*#__PURE__*/React.createElement("div", {
|
|
290
|
+
className: "k-meta",
|
|
291
|
+
style: {
|
|
292
|
+
marginBottom: 18
|
|
293
|
+
}
|
|
294
|
+
}, "Run ", runId, " \xB7 ", workers, " parallel worker", workers !== 1 ? 's' : '', " \xB7 total ", fmt(fullDurationMs)), /*#__PURE__*/React.createElement("div", {
|
|
295
|
+
className: "card"
|
|
296
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
297
|
+
className: "hd"
|
|
298
|
+
}, /*#__PURE__*/React.createElement("h3", null, "Per-test execution \xB7 Gantt"), /*#__PURE__*/React.createElement("div", {
|
|
299
|
+
className: "meta"
|
|
300
|
+
}, "click any bar to open the test in detail")), /*#__PURE__*/React.createElement("div", {
|
|
301
|
+
style: {
|
|
302
|
+
display: 'flex',
|
|
303
|
+
gap: 6,
|
|
304
|
+
marginBottom: 14,
|
|
305
|
+
flexWrap: 'wrap'
|
|
306
|
+
}
|
|
307
|
+
}, MODES.map(([id, label, n]) => {
|
|
308
|
+
const active = mode === id;
|
|
309
|
+
return /*#__PURE__*/React.createElement("button", {
|
|
310
|
+
key: id,
|
|
311
|
+
onClick: () => setMode(id),
|
|
312
|
+
style: {
|
|
313
|
+
display: 'inline-flex',
|
|
314
|
+
alignItems: 'center',
|
|
315
|
+
gap: 6,
|
|
316
|
+
padding: '4px 10px',
|
|
317
|
+
borderRadius: 999,
|
|
318
|
+
border: '1px solid ' + (active ? 'var(--brand-blue-500)' : 'var(--line)'),
|
|
319
|
+
background: active ? 'var(--brand-blue-500)' : 'var(--bg-elev)',
|
|
320
|
+
color: active ? '#fff' : 'var(--fg2)',
|
|
321
|
+
fontFamily: 'var(--font-body)',
|
|
322
|
+
fontSize: 12,
|
|
323
|
+
fontWeight: 600,
|
|
324
|
+
cursor: 'pointer',
|
|
325
|
+
transition: 'all var(--dur-fast)'
|
|
326
|
+
}
|
|
327
|
+
}, label, /*#__PURE__*/React.createElement("span", {
|
|
328
|
+
style: {
|
|
329
|
+
fontFamily: 'var(--font-mono)',
|
|
330
|
+
fontSize: 11,
|
|
331
|
+
opacity: 0.9
|
|
332
|
+
}
|
|
333
|
+
}, n));
|
|
334
|
+
})), rebased.length > 0 ? /*#__PURE__*/React.createElement(TimelineGantt, {
|
|
335
|
+
tests: rebased,
|
|
336
|
+
totalMs: totalMs,
|
|
337
|
+
onOpen: t => {
|
|
338
|
+
if (t.id && RICH_TESTS[t.id]) {
|
|
339
|
+
window.__openTest?.(t.id);
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
const match = Object.values(RICH_TESTS).find(r => r.name === t.name);
|
|
343
|
+
if (match) window.__openTest?.(match.id);
|
|
344
|
+
}
|
|
345
|
+
}) : /*#__PURE__*/React.createElement("div", {
|
|
346
|
+
style: {
|
|
347
|
+
padding: 30,
|
|
348
|
+
color: 'var(--fg3)',
|
|
349
|
+
textAlign: 'center',
|
|
350
|
+
fontFamily: 'var(--font-mono)',
|
|
351
|
+
fontSize: 12
|
|
352
|
+
}
|
|
353
|
+
}, mode === 'failures' ? 'No failing tests in this run.' : 'No tests with timing data in this run.')), /*#__PURE__*/React.createElement("div", {
|
|
354
|
+
className: "card",
|
|
355
|
+
style: {
|
|
356
|
+
marginTop: 16
|
|
357
|
+
}
|
|
358
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
359
|
+
className: "hd"
|
|
360
|
+
}, /*#__PURE__*/React.createElement("h3", null, "Suite execution heatmap"), /*#__PURE__*/React.createElement("div", {
|
|
361
|
+
className: "meta"
|
|
362
|
+
}, "last 8 runs")), hasHistory ? /*#__PURE__*/React.createElement("div", {
|
|
363
|
+
style: {
|
|
364
|
+
padding: 30,
|
|
365
|
+
color: 'var(--fg3)',
|
|
366
|
+
textAlign: 'center',
|
|
367
|
+
fontFamily: 'var(--font-mono)',
|
|
368
|
+
fontSize: 12
|
|
369
|
+
}
|
|
370
|
+
}, "Heatmap view coming soon. History is populated.") : /*#__PURE__*/React.createElement("div", {
|
|
371
|
+
style: {
|
|
372
|
+
padding: 30,
|
|
373
|
+
color: 'var(--fg3)',
|
|
374
|
+
textAlign: 'center',
|
|
375
|
+
fontFamily: 'var(--font-mono)',
|
|
376
|
+
fontSize: 12
|
|
377
|
+
}
|
|
378
|
+
}, "Run history not yet populated.")), /*#__PURE__*/React.createElement("div", {
|
|
379
|
+
className: "card",
|
|
380
|
+
style: {
|
|
381
|
+
marginTop: 16
|
|
382
|
+
}
|
|
383
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
384
|
+
className: "hd"
|
|
385
|
+
}, /*#__PURE__*/React.createElement("h3", null, "Retry waterfall"), /*#__PURE__*/React.createElement("div", {
|
|
386
|
+
className: "meta"
|
|
387
|
+
}, "attempt-by-attempt status & duration")), hasRetries ? /*#__PURE__*/React.createElement("div", {
|
|
388
|
+
style: {
|
|
389
|
+
padding: 30,
|
|
390
|
+
color: 'var(--fg3)',
|
|
391
|
+
textAlign: 'center',
|
|
392
|
+
fontFamily: 'var(--font-mono)',
|
|
393
|
+
fontSize: 12
|
|
394
|
+
}
|
|
395
|
+
}, "Retry attempt details coming soon. Open a retried test from the suites view.") : /*#__PURE__*/React.createElement("div", {
|
|
396
|
+
style: {
|
|
397
|
+
padding: 30,
|
|
398
|
+
color: 'var(--fg3)',
|
|
399
|
+
textAlign: 'center',
|
|
400
|
+
fontFamily: 'var(--font-mono)',
|
|
401
|
+
fontSize: 12
|
|
402
|
+
}
|
|
403
|
+
}, "Run history not yet populated.")));
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// ============== CATEGORIES PAGE — error-type classification ==============
|
|
407
|
+
function CategoriesPage() {
|
|
408
|
+
const ERROR_TYPES = window.CATEGORIES || [];
|
|
409
|
+
const RICH_TESTS = window.RICH_TESTS || {};
|
|
410
|
+
const [selectedKind, setKind] = useStateP(ERROR_TYPES[0]?.kind ?? null);
|
|
411
|
+
if (ERROR_TYPES.length === 0) {
|
|
412
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
413
|
+
className: "card",
|
|
414
|
+
style: {
|
|
415
|
+
padding: 30,
|
|
416
|
+
textAlign: 'center',
|
|
417
|
+
color: 'var(--fg3)',
|
|
418
|
+
fontFamily: 'var(--font-mono)',
|
|
419
|
+
fontSize: 13
|
|
420
|
+
}
|
|
421
|
+
}, "No failures in this run. Nothing to categorize.");
|
|
422
|
+
}
|
|
423
|
+
const sel = ERROR_TYPES.find(e => e.kind === selectedKind) || ERROR_TYPES[0];
|
|
424
|
+
const totalIssues = ERROR_TYPES.reduce((a, b) => a + b.count, 0);
|
|
425
|
+
return /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("div", {
|
|
426
|
+
style: {
|
|
427
|
+
display: 'flex',
|
|
428
|
+
alignItems: 'baseline',
|
|
429
|
+
justifyContent: 'space-between',
|
|
430
|
+
marginBottom: 14
|
|
431
|
+
}
|
|
432
|
+
}, /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("h1", {
|
|
433
|
+
className: "k-h1",
|
|
434
|
+
style: {
|
|
435
|
+
marginBottom: 2
|
|
436
|
+
}
|
|
437
|
+
}, "Categories"), /*#__PURE__*/React.createElement("div", {
|
|
438
|
+
className: "k-meta"
|
|
439
|
+
}, "Failures grouped by error type \xB7 ", ERROR_TYPES.length, " types \xB7 ", totalIssues, " tests")), /*#__PURE__*/React.createElement("div", {
|
|
440
|
+
style: {
|
|
441
|
+
display: 'flex',
|
|
442
|
+
gap: 6
|
|
443
|
+
}
|
|
444
|
+
}, /*#__PURE__*/React.createElement("span", {
|
|
445
|
+
className: "badge b-failed"
|
|
446
|
+
}, /*#__PURE__*/React.createElement("span", {
|
|
447
|
+
className: "dot"
|
|
448
|
+
}), ERROR_TYPES.filter(e => e.family === 'failed').reduce((a, b) => a + b.count, 0), " product"), /*#__PURE__*/React.createElement("span", {
|
|
449
|
+
className: "badge b-broken"
|
|
450
|
+
}, /*#__PURE__*/React.createElement("span", {
|
|
451
|
+
className: "dot"
|
|
452
|
+
}), ERROR_TYPES.filter(e => e.family === 'broken').reduce((a, b) => a + b.count, 0), " test-defects"), /*#__PURE__*/React.createElement("span", {
|
|
453
|
+
className: "badge b-skipped"
|
|
454
|
+
}, /*#__PURE__*/React.createElement("span", {
|
|
455
|
+
className: "dot"
|
|
456
|
+
}), ERROR_TYPES.filter(e => e.family === 'skipped').reduce((a, b) => a + b.count, 0), " environment"))), /*#__PURE__*/React.createElement("div", {
|
|
457
|
+
className: "card",
|
|
458
|
+
style: {
|
|
459
|
+
marginBottom: 16
|
|
460
|
+
}
|
|
461
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
462
|
+
className: "hd"
|
|
463
|
+
}, /*#__PURE__*/React.createElement("h3", null, "Distribution by error type"), /*#__PURE__*/React.createElement("div", {
|
|
464
|
+
className: "meta"
|
|
465
|
+
}, totalIssues, " test failures")), /*#__PURE__*/React.createElement("div", {
|
|
466
|
+
style: {
|
|
467
|
+
display: 'flex',
|
|
468
|
+
flexDirection: 'column',
|
|
469
|
+
gap: 8
|
|
470
|
+
}
|
|
471
|
+
}, ERROR_TYPES.map(e => /*#__PURE__*/React.createElement("div", {
|
|
472
|
+
key: e.kind,
|
|
473
|
+
style: {
|
|
474
|
+
display: 'grid',
|
|
475
|
+
gridTemplateColumns: 'minmax(220px, 280px) 1fr 36px',
|
|
476
|
+
alignItems: 'center',
|
|
477
|
+
gap: 10
|
|
478
|
+
}
|
|
479
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
480
|
+
style: {
|
|
481
|
+
display: 'flex',
|
|
482
|
+
alignItems: 'center',
|
|
483
|
+
gap: 6,
|
|
484
|
+
fontFamily: 'var(--font-mono)',
|
|
485
|
+
fontSize: 12.5,
|
|
486
|
+
color: 'var(--fg1)'
|
|
487
|
+
}
|
|
488
|
+
}, /*#__PURE__*/React.createElement("span", {
|
|
489
|
+
style: {
|
|
490
|
+
width: 8,
|
|
491
|
+
height: 8,
|
|
492
|
+
borderRadius: 2,
|
|
493
|
+
background: e.color,
|
|
494
|
+
flexShrink: 0
|
|
495
|
+
}
|
|
496
|
+
}), e.kind), /*#__PURE__*/React.createElement("div", {
|
|
497
|
+
style: {
|
|
498
|
+
height: 18,
|
|
499
|
+
background: 'var(--bg-sunken)',
|
|
500
|
+
borderRadius: 3,
|
|
501
|
+
position: 'relative',
|
|
502
|
+
overflow: 'hidden'
|
|
503
|
+
}
|
|
504
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
505
|
+
style: {
|
|
506
|
+
width: `${e.count / totalIssues * 100}%`,
|
|
507
|
+
height: '100%',
|
|
508
|
+
background: e.color
|
|
509
|
+
}
|
|
510
|
+
})), /*#__PURE__*/React.createElement("div", {
|
|
511
|
+
style: {
|
|
512
|
+
fontFamily: 'var(--font-mono)',
|
|
513
|
+
fontSize: 12,
|
|
514
|
+
color: 'var(--fg2)',
|
|
515
|
+
textAlign: 'right',
|
|
516
|
+
fontVariantNumeric: 'tabular-nums'
|
|
517
|
+
}
|
|
518
|
+
}, e.count))))), /*#__PURE__*/React.createElement("div", {
|
|
519
|
+
style: {
|
|
520
|
+
display: 'grid',
|
|
521
|
+
gridTemplateColumns: 'minmax(260px, 320px) 1fr',
|
|
522
|
+
gap: 0,
|
|
523
|
+
background: 'var(--bg-elev)',
|
|
524
|
+
border: '1px solid var(--line)',
|
|
525
|
+
borderRadius: 12,
|
|
526
|
+
overflow: 'hidden',
|
|
527
|
+
minHeight: 480
|
|
528
|
+
}
|
|
529
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
530
|
+
style: {
|
|
531
|
+
borderRight: '1px solid var(--line)'
|
|
532
|
+
}
|
|
533
|
+
}, ERROR_TYPES.map(e => /*#__PURE__*/React.createElement("div", {
|
|
534
|
+
key: e.kind,
|
|
535
|
+
onClick: () => setKind(e.kind),
|
|
536
|
+
style: {
|
|
537
|
+
display: 'grid',
|
|
538
|
+
gridTemplateColumns: '1fr auto',
|
|
539
|
+
alignItems: 'center',
|
|
540
|
+
gap: 10,
|
|
541
|
+
padding: '12px 14px',
|
|
542
|
+
cursor: 'pointer',
|
|
543
|
+
borderLeft: selectedKind === e.kind ? `2px solid ${e.color}` : '2px solid transparent',
|
|
544
|
+
background: selectedKind === e.kind ? 'var(--accent-soft)' : 'transparent',
|
|
545
|
+
borderBottom: '1px solid var(--line)'
|
|
546
|
+
}
|
|
547
|
+
}, /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("div", {
|
|
548
|
+
style: {
|
|
549
|
+
display: 'flex',
|
|
550
|
+
alignItems: 'center',
|
|
551
|
+
gap: 6,
|
|
552
|
+
fontFamily: 'var(--font-mono)',
|
|
553
|
+
fontSize: 12.5,
|
|
554
|
+
fontWeight: 600,
|
|
555
|
+
color: 'var(--fg1)'
|
|
556
|
+
}
|
|
557
|
+
}, /*#__PURE__*/React.createElement("span", {
|
|
558
|
+
style: {
|
|
559
|
+
width: 8,
|
|
560
|
+
height: 8,
|
|
561
|
+
borderRadius: 2,
|
|
562
|
+
background: e.color,
|
|
563
|
+
flexShrink: 0
|
|
564
|
+
}
|
|
565
|
+
}), e.kind), /*#__PURE__*/React.createElement("div", {
|
|
566
|
+
style: {
|
|
567
|
+
fontSize: 11,
|
|
568
|
+
color: 'var(--fg3)',
|
|
569
|
+
marginTop: 3,
|
|
570
|
+
textTransform: 'uppercase',
|
|
571
|
+
letterSpacing: '.08em'
|
|
572
|
+
}
|
|
573
|
+
}, e.family)), /*#__PURE__*/React.createElement("span", {
|
|
574
|
+
style: {
|
|
575
|
+
background: e.color,
|
|
576
|
+
color: '#fff',
|
|
577
|
+
fontSize: 11,
|
|
578
|
+
fontWeight: 700,
|
|
579
|
+
padding: '2px 8px',
|
|
580
|
+
borderRadius: 3,
|
|
581
|
+
fontFamily: 'var(--font-mono)'
|
|
582
|
+
}
|
|
583
|
+
}, e.count)))), /*#__PURE__*/React.createElement("div", {
|
|
584
|
+
style: {
|
|
585
|
+
padding: 24
|
|
586
|
+
}
|
|
587
|
+
}, sel && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
|
|
588
|
+
className: "k-overline",
|
|
589
|
+
style: {
|
|
590
|
+
marginBottom: 6
|
|
591
|
+
}
|
|
592
|
+
}, sel.family, " \xB7 error type"), /*#__PURE__*/React.createElement("h2", {
|
|
593
|
+
className: "k-h2",
|
|
594
|
+
style: {
|
|
595
|
+
fontSize: 22,
|
|
596
|
+
fontFamily: 'var(--font-mono)',
|
|
597
|
+
fontWeight: 600,
|
|
598
|
+
marginBottom: 8
|
|
599
|
+
}
|
|
600
|
+
}, sel.kind), /*#__PURE__*/React.createElement("p", {
|
|
601
|
+
className: "k-body",
|
|
602
|
+
style: {
|
|
603
|
+
marginBottom: 18
|
|
604
|
+
}
|
|
605
|
+
}, sel.description), /*#__PURE__*/React.createElement("div", {
|
|
606
|
+
className: "k-overline",
|
|
607
|
+
style: {
|
|
608
|
+
marginBottom: 8
|
|
609
|
+
}
|
|
610
|
+
}, "Affected tests \xB7 ", sel.count), /*#__PURE__*/React.createElement("div", {
|
|
611
|
+
style: {
|
|
612
|
+
border: '1px solid var(--line)',
|
|
613
|
+
borderRadius: 8,
|
|
614
|
+
overflow: 'hidden'
|
|
615
|
+
}
|
|
616
|
+
}, sel.tests.map((tid, i) => {
|
|
617
|
+
const t = RICH_TESTS[tid];
|
|
618
|
+
const name = t ? t.name : tid;
|
|
619
|
+
const status = t ? t.status : sel.family;
|
|
620
|
+
const file = t ? t.file : '';
|
|
621
|
+
const msg = t?.error?.message || '';
|
|
622
|
+
const canOpen = !!t;
|
|
623
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
624
|
+
key: i,
|
|
625
|
+
onClick: canOpen ? () => window.__openTest?.(t.id) : undefined,
|
|
626
|
+
title: canOpen ? `Open ${name} →` : 'Test not found in this run',
|
|
627
|
+
style: {
|
|
628
|
+
display: 'grid',
|
|
629
|
+
gridTemplateColumns: '24px 1fr auto',
|
|
630
|
+
gap: 10,
|
|
631
|
+
padding: '12px 14px',
|
|
632
|
+
borderTop: i ? '1px solid var(--line)' : 'none',
|
|
633
|
+
alignItems: 'flex-start',
|
|
634
|
+
cursor: canOpen ? 'pointer' : 'default',
|
|
635
|
+
transition: 'background 120ms'
|
|
636
|
+
},
|
|
637
|
+
onMouseEnter: canOpen ? e => e.currentTarget.style.background = 'var(--bg-hover)' : undefined,
|
|
638
|
+
onMouseLeave: canOpen ? e => e.currentTarget.style.background = 'transparent' : undefined
|
|
639
|
+
}, /*#__PURE__*/React.createElement("span", {
|
|
640
|
+
className: `s-icon ${status}`,
|
|
641
|
+
style: {
|
|
642
|
+
marginTop: 2
|
|
643
|
+
}
|
|
644
|
+
}, status === 'passed' ? '✓' : status === 'failed' ? '✕' : status === 'broken' ? '!' : '⊘'), /*#__PURE__*/React.createElement("div", {
|
|
645
|
+
style: {
|
|
646
|
+
minWidth: 0
|
|
647
|
+
}
|
|
648
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
649
|
+
style: {
|
|
650
|
+
fontFamily: 'var(--font-body)',
|
|
651
|
+
fontSize: 13,
|
|
652
|
+
fontWeight: 600,
|
|
653
|
+
color: 'var(--fg1)'
|
|
654
|
+
}
|
|
655
|
+
}, name), file && /*#__PURE__*/React.createElement("div", {
|
|
656
|
+
style: {
|
|
657
|
+
fontFamily: 'var(--font-mono)',
|
|
658
|
+
fontSize: 11,
|
|
659
|
+
color: 'var(--fg3)',
|
|
660
|
+
marginTop: 2
|
|
661
|
+
}
|
|
662
|
+
}, file), msg && /*#__PURE__*/React.createElement("div", {
|
|
663
|
+
style: {
|
|
664
|
+
fontFamily: 'var(--font-mono)',
|
|
665
|
+
fontSize: 11.5,
|
|
666
|
+
color: 'var(--status-failed-fg)',
|
|
667
|
+
marginTop: 6,
|
|
668
|
+
padding: '6px 8px',
|
|
669
|
+
background: 'var(--status-failed-bg)',
|
|
670
|
+
border: '1px solid var(--status-failed-border)',
|
|
671
|
+
borderRadius: 4
|
|
672
|
+
}
|
|
673
|
+
}, msg)), canOpen && /*#__PURE__*/React.createElement("span", {
|
|
674
|
+
style: {
|
|
675
|
+
alignSelf: 'center',
|
|
676
|
+
fontFamily: 'var(--font-mono)',
|
|
677
|
+
fontSize: 11,
|
|
678
|
+
color: 'var(--fg3)',
|
|
679
|
+
display: 'inline-flex',
|
|
680
|
+
alignItems: 'center',
|
|
681
|
+
gap: 4,
|
|
682
|
+
whiteSpace: 'nowrap'
|
|
683
|
+
}
|
|
684
|
+
}, "Open ", /*#__PURE__*/React.createElement("span", {
|
|
685
|
+
style: {
|
|
686
|
+
fontSize: 13
|
|
687
|
+
}
|
|
688
|
+
}, "\u2192")));
|
|
689
|
+
}))))));
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// ============== FLAKY PAGE ==============
|
|
693
|
+
//
|
|
694
|
+
// Single-run flake derivation — Kensho is the OSS, single-run report; rolling
|
|
695
|
+
// flake-rate analysis lives in Kaizen (the SaaS platform). What we CAN derive
|
|
696
|
+
// from one run is meaningful enough to act on:
|
|
697
|
+
//
|
|
698
|
+
// · "Recovered" — passed on retry (real flake — non-deterministic test)
|
|
699
|
+
// · "Broken" — status=broken (test infra failed mid-execution)
|
|
700
|
+
// · "Failed retry" — failed even after one or more retries (flaky AND broken)
|
|
701
|
+
//
|
|
702
|
+
// Each card explains the signal in plain language so users understand the
|
|
703
|
+
// difference between a flake and a regular failure. Severity ordering:
|
|
704
|
+
// recovered > broken > failed-with-retry, since recoveries are pure flakes.
|
|
705
|
+
function FlakyPage() {
|
|
706
|
+
const RICH_TESTS = window.RICH_TESTS || {};
|
|
707
|
+
const allTests = Object.values(RICH_TESTS);
|
|
708
|
+
const fmt = window._kenshoFmtDuration || (ms => ms + 'ms');
|
|
709
|
+
|
|
710
|
+
// Bucket every test into one of three flake categories (or none).
|
|
711
|
+
const buckets = {
|
|
712
|
+
recovered: [],
|
|
713
|
+
broken: [],
|
|
714
|
+
failedWithRetries: []
|
|
715
|
+
};
|
|
716
|
+
for (const t of allTests) {
|
|
717
|
+
if (t.retries > 0 && t.status === 'passed') buckets.recovered.push(t);else if (t.status === 'broken') buckets.broken.push(t);else if (t.retries > 0 && (t.status === 'failed' || t.status === 'broken')) buckets.failedWithRetries.push(t);
|
|
718
|
+
}
|
|
719
|
+
const flakeTotal = buckets.recovered.length + buckets.broken.length + buckets.failedWithRetries.length;
|
|
720
|
+
const [filter, setFilter] = useStateP('all');
|
|
721
|
+
|
|
722
|
+
// Stable order for rendering — recoveries first (highest signal-to-noise),
|
|
723
|
+
// then broken, then failed-with-retries. Within each bucket, sort by retry
|
|
724
|
+
// count desc so the highest-friction tests bubble up.
|
|
725
|
+
const ALL_FLAKY = [...buckets.recovered.map(t => ({
|
|
726
|
+
...t,
|
|
727
|
+
_bucket: 'recovered'
|
|
728
|
+
})), ...buckets.broken.map(t => ({
|
|
729
|
+
...t,
|
|
730
|
+
_bucket: 'broken'
|
|
731
|
+
})), ...buckets.failedWithRetries.map(t => ({
|
|
732
|
+
...t,
|
|
733
|
+
_bucket: 'failedWithRetries'
|
|
734
|
+
}))].sort((a, b) => (b.retries || 0) - (a.retries || 0));
|
|
735
|
+
const visible = filter === 'all' ? ALL_FLAKY : ALL_FLAKY.filter(t => t._bucket === filter);
|
|
736
|
+
|
|
737
|
+
// j/k/Enter shortcuts on the Flaky list — keep selection local; pressing
|
|
738
|
+
// Enter delegates to the global window.__openTest hook from app.jsx.
|
|
739
|
+
const [selectedIdx, setSelectedIdx] = useStateP(-1);
|
|
740
|
+
React.useEffect(() => {
|
|
741
|
+
const onMove = e => {
|
|
742
|
+
if (visible.length === 0) return;
|
|
743
|
+
const delta = e.detail?.delta || 0;
|
|
744
|
+
setSelectedIdx(prev => {
|
|
745
|
+
const idx = prev === -1 ? delta > 0 ? 0 : visible.length - 1 : prev + delta;
|
|
746
|
+
return Math.max(0, Math.min(visible.length - 1, idx));
|
|
747
|
+
});
|
|
748
|
+
};
|
|
749
|
+
const onOpen = () => {
|
|
750
|
+
if (selectedIdx >= 0 && visible[selectedIdx]) window.__openTest?.(visible[selectedIdx].id);
|
|
751
|
+
};
|
|
752
|
+
window.addEventListener('kensho:move-selection', onMove);
|
|
753
|
+
window.addEventListener('kensho:open-selection', onOpen);
|
|
754
|
+
return () => {
|
|
755
|
+
window.removeEventListener('kensho:move-selection', onMove);
|
|
756
|
+
window.removeEventListener('kensho:open-selection', onOpen);
|
|
757
|
+
};
|
|
758
|
+
}, [selectedIdx, visible]);
|
|
759
|
+
const passRate = allTests.length > 0 ? Math.round((allTests.length - flakeTotal) / allTests.length * 100) : 100;
|
|
760
|
+
const flakeRate = allTests.length > 0 ? (flakeTotal / allTests.length * 100).toFixed(1) : '0';
|
|
761
|
+
if (allTests.length === 0) {
|
|
762
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
763
|
+
className: "card",
|
|
764
|
+
style: {
|
|
765
|
+
padding: 30,
|
|
766
|
+
textAlign: 'center',
|
|
767
|
+
color: 'var(--fg3)'
|
|
768
|
+
}
|
|
769
|
+
}, "No tests in this report.");
|
|
770
|
+
}
|
|
771
|
+
if (flakeTotal === 0) {
|
|
772
|
+
return /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("h1", {
|
|
773
|
+
className: "k-h1",
|
|
774
|
+
style: {
|
|
775
|
+
marginBottom: 4
|
|
776
|
+
}
|
|
777
|
+
}, "Flaky tests"), /*#__PURE__*/React.createElement("div", {
|
|
778
|
+
className: "k-meta",
|
|
779
|
+
style: {
|
|
780
|
+
marginBottom: 24
|
|
781
|
+
}
|
|
782
|
+
}, "Tests that retried, recovered, or broke during execution"), /*#__PURE__*/React.createElement("div", {
|
|
783
|
+
className: "card",
|
|
784
|
+
style: {
|
|
785
|
+
padding: '48px 32px',
|
|
786
|
+
textAlign: 'center',
|
|
787
|
+
background: 'linear-gradient(180deg, var(--status-passed-bg), transparent)'
|
|
788
|
+
}
|
|
789
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
790
|
+
style: {
|
|
791
|
+
width: 64,
|
|
792
|
+
height: 64,
|
|
793
|
+
borderRadius: 999,
|
|
794
|
+
background: 'var(--status-passed-bg)',
|
|
795
|
+
border: '2px solid var(--status-passed-border)',
|
|
796
|
+
display: 'inline-flex',
|
|
797
|
+
alignItems: 'center',
|
|
798
|
+
justifyContent: 'center',
|
|
799
|
+
marginBottom: 16
|
|
800
|
+
}
|
|
801
|
+
}, /*#__PURE__*/React.createElement("span", {
|
|
802
|
+
style: {
|
|
803
|
+
fontSize: 30,
|
|
804
|
+
color: 'var(--status-passed)'
|
|
805
|
+
}
|
|
806
|
+
}, "\u2713")), /*#__PURE__*/React.createElement("h2", {
|
|
807
|
+
className: "k-h2",
|
|
808
|
+
style: {
|
|
809
|
+
marginBottom: 8,
|
|
810
|
+
fontSize: 22
|
|
811
|
+
}
|
|
812
|
+
}, "No flaky tests detected"), /*#__PURE__*/React.createElement("p", {
|
|
813
|
+
className: "k-body",
|
|
814
|
+
style: {
|
|
815
|
+
maxWidth: 460,
|
|
816
|
+
margin: '0 auto',
|
|
817
|
+
color: 'var(--fg2)'
|
|
818
|
+
}
|
|
819
|
+
}, "Every test ran cleanly on the first attempt \u2014 no retries, no broken executions. Flaky-test detection here is single-run; rolling flake-rate analysis (last N runs) lives in the Kaizen platform.")));
|
|
820
|
+
}
|
|
821
|
+
const FILTERS = [['all', 'All flaky', flakeTotal], ['recovered', 'Recovered', buckets.recovered.length], ['broken', 'Broken', buckets.broken.length], ['failedWithRetries', 'Failed retry', buckets.failedWithRetries.length]].filter(([id, _, n]) => id === 'all' || n > 0);
|
|
822
|
+
return /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("div", {
|
|
823
|
+
style: {
|
|
824
|
+
display: 'flex',
|
|
825
|
+
alignItems: 'baseline',
|
|
826
|
+
justifyContent: 'space-between',
|
|
827
|
+
marginBottom: 14,
|
|
828
|
+
flexWrap: 'wrap',
|
|
829
|
+
gap: 16
|
|
830
|
+
}
|
|
831
|
+
}, /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("h1", {
|
|
832
|
+
className: "k-h1",
|
|
833
|
+
style: {
|
|
834
|
+
marginBottom: 4
|
|
835
|
+
}
|
|
836
|
+
}, "Flaky tests"), /*#__PURE__*/React.createElement("div", {
|
|
837
|
+
className: "k-meta"
|
|
838
|
+
}, flakeTotal, " of ", allTests.length, " tests showed instability \xB7 ", flakeRate, "% flake rate")), /*#__PURE__*/React.createElement("div", {
|
|
839
|
+
style: {
|
|
840
|
+
fontFamily: 'var(--font-mono)',
|
|
841
|
+
fontSize: 11,
|
|
842
|
+
color: 'var(--fg3)',
|
|
843
|
+
maxWidth: 380,
|
|
844
|
+
textAlign: 'right',
|
|
845
|
+
lineHeight: 1.55
|
|
846
|
+
}
|
|
847
|
+
}, "Single-run signal \u2014 derived from ", /*#__PURE__*/React.createElement("code", {
|
|
848
|
+
style: {
|
|
849
|
+
color: 'var(--fg2)'
|
|
850
|
+
}
|
|
851
|
+
}, "retries"), " + ", /*#__PURE__*/React.createElement("code", {
|
|
852
|
+
style: {
|
|
853
|
+
color: 'var(--fg2)'
|
|
854
|
+
}
|
|
855
|
+
}, "broken"), ". For rolling flake-rate over many runs, use the Kaizen platform.")), /*#__PURE__*/React.createElement("div", {
|
|
856
|
+
style: {
|
|
857
|
+
display: 'grid',
|
|
858
|
+
gridTemplateColumns: 'repeat(3, 1fr)',
|
|
859
|
+
gap: 16,
|
|
860
|
+
marginBottom: 18
|
|
861
|
+
}
|
|
862
|
+
}, /*#__PURE__*/React.createElement(FlakyStatCard, {
|
|
863
|
+
accent: "var(--status-broken)",
|
|
864
|
+
icon: "rotate-ccw",
|
|
865
|
+
label: "Recovered",
|
|
866
|
+
value: buckets.recovered.length,
|
|
867
|
+
desc: "Passed on retry \u2014 a real flake (non-deterministic). Investigate the test for hidden timing or state assumptions.",
|
|
868
|
+
onClick: buckets.recovered.length > 0 ? () => setFilter('recovered') : null
|
|
869
|
+
}), /*#__PURE__*/React.createElement(FlakyStatCard, {
|
|
870
|
+
accent: "#7C5CFF",
|
|
871
|
+
icon: "zap-off",
|
|
872
|
+
label: "Broken",
|
|
873
|
+
value: buckets.broken.length,
|
|
874
|
+
desc: "Test infrastructure failed mid-execution (setup, teardown, or fixture). Often unrelated to product behavior.",
|
|
875
|
+
onClick: buckets.broken.length > 0 ? () => setFilter('broken') : null
|
|
876
|
+
}), /*#__PURE__*/React.createElement(FlakyStatCard, {
|
|
877
|
+
accent: "var(--status-failed)",
|
|
878
|
+
icon: "alert-triangle",
|
|
879
|
+
label: "Failed retry",
|
|
880
|
+
value: buckets.failedWithRetries.length,
|
|
881
|
+
desc: "Failed even after one or more retries. Could be a real defect masquerading as a flake \u2014 prioritize.",
|
|
882
|
+
onClick: buckets.failedWithRetries.length > 0 ? () => setFilter('failedWithRetries') : null
|
|
883
|
+
})), /*#__PURE__*/React.createElement("div", {
|
|
884
|
+
className: "card",
|
|
885
|
+
style: {
|
|
886
|
+
marginBottom: 16
|
|
887
|
+
}
|
|
888
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
889
|
+
className: "hd"
|
|
890
|
+
}, /*#__PURE__*/React.createElement("h3", null, "Affected tests"), /*#__PURE__*/React.createElement("div", {
|
|
891
|
+
className: "meta"
|
|
892
|
+
}, visible.length, " shown \xB7 sorted by retry count")), /*#__PURE__*/React.createElement("div", {
|
|
893
|
+
style: {
|
|
894
|
+
display: 'flex',
|
|
895
|
+
gap: 6,
|
|
896
|
+
padding: '2px 0 14px',
|
|
897
|
+
flexWrap: 'wrap'
|
|
898
|
+
}
|
|
899
|
+
}, FILTERS.map(([id, label, n]) => {
|
|
900
|
+
const active = filter === id;
|
|
901
|
+
return /*#__PURE__*/React.createElement("button", {
|
|
902
|
+
key: id,
|
|
903
|
+
onClick: () => setFilter(id),
|
|
904
|
+
style: {
|
|
905
|
+
display: 'inline-flex',
|
|
906
|
+
alignItems: 'center',
|
|
907
|
+
gap: 6,
|
|
908
|
+
padding: '4px 10px',
|
|
909
|
+
borderRadius: 999,
|
|
910
|
+
border: '1px solid ' + (active ? 'var(--brand-blue-500)' : 'var(--line)'),
|
|
911
|
+
background: active ? 'var(--brand-blue-500)' : 'var(--bg-elev)',
|
|
912
|
+
color: active ? '#fff' : 'var(--fg2)',
|
|
913
|
+
fontFamily: 'var(--font-body)',
|
|
914
|
+
fontSize: 12,
|
|
915
|
+
fontWeight: 600,
|
|
916
|
+
cursor: 'pointer',
|
|
917
|
+
transition: 'all var(--dur-fast)'
|
|
918
|
+
}
|
|
919
|
+
}, label, /*#__PURE__*/React.createElement("span", {
|
|
920
|
+
style: {
|
|
921
|
+
fontFamily: 'var(--font-mono)',
|
|
922
|
+
fontSize: 11,
|
|
923
|
+
opacity: 0.9
|
|
924
|
+
}
|
|
925
|
+
}, n));
|
|
926
|
+
})), visible.length === 0 ? /*#__PURE__*/React.createElement("div", {
|
|
927
|
+
style: {
|
|
928
|
+
padding: '30px 0',
|
|
929
|
+
textAlign: 'center',
|
|
930
|
+
color: 'var(--fg3)',
|
|
931
|
+
fontFamily: 'var(--font-mono)',
|
|
932
|
+
fontSize: 12
|
|
933
|
+
}
|
|
934
|
+
}, "No tests in this category.") : /*#__PURE__*/React.createElement("div", {
|
|
935
|
+
style: {
|
|
936
|
+
display: 'flex',
|
|
937
|
+
flexDirection: 'column'
|
|
938
|
+
}
|
|
939
|
+
}, visible.map((t, i) => /*#__PURE__*/React.createElement(FlakyTestRow, {
|
|
940
|
+
key: t.id,
|
|
941
|
+
test: t,
|
|
942
|
+
index: i,
|
|
943
|
+
selected: i === selectedIdx
|
|
944
|
+
})))));
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
// FlakyStatCard — stat banner card. Subtle gradient + accent stripe.
|
|
948
|
+
function FlakyStatCard({
|
|
949
|
+
accent,
|
|
950
|
+
icon,
|
|
951
|
+
label,
|
|
952
|
+
value,
|
|
953
|
+
desc,
|
|
954
|
+
onClick
|
|
955
|
+
}) {
|
|
956
|
+
const clickable = !!onClick;
|
|
957
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
958
|
+
onClick: onClick || undefined,
|
|
959
|
+
style: {
|
|
960
|
+
position: 'relative',
|
|
961
|
+
padding: '18px 20px',
|
|
962
|
+
background: 'var(--bg-elev)',
|
|
963
|
+
border: '1px solid var(--line)',
|
|
964
|
+
borderRadius: 12,
|
|
965
|
+
cursor: clickable ? 'pointer' : 'default',
|
|
966
|
+
overflow: 'hidden',
|
|
967
|
+
transition: 'transform var(--dur-fast), border-color var(--dur-fast), background var(--dur-fast)'
|
|
968
|
+
},
|
|
969
|
+
onMouseEnter: clickable ? e => {
|
|
970
|
+
e.currentTarget.style.borderColor = accent;
|
|
971
|
+
e.currentTarget.style.transform = 'translateY(-1px)';
|
|
972
|
+
} : undefined,
|
|
973
|
+
onMouseLeave: clickable ? e => {
|
|
974
|
+
e.currentTarget.style.borderColor = 'var(--line)';
|
|
975
|
+
e.currentTarget.style.transform = 'translateY(0)';
|
|
976
|
+
} : undefined
|
|
977
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
978
|
+
style: {
|
|
979
|
+
position: 'absolute',
|
|
980
|
+
left: 0,
|
|
981
|
+
top: 0,
|
|
982
|
+
bottom: 0,
|
|
983
|
+
width: 4,
|
|
984
|
+
background: accent
|
|
985
|
+
}
|
|
986
|
+
}), /*#__PURE__*/React.createElement("div", {
|
|
987
|
+
style: {
|
|
988
|
+
position: 'absolute',
|
|
989
|
+
right: -30,
|
|
990
|
+
top: -30,
|
|
991
|
+
width: 120,
|
|
992
|
+
height: 120,
|
|
993
|
+
borderRadius: '50%',
|
|
994
|
+
background: `radial-gradient(circle, ${accent}22 0%, transparent 70%)`
|
|
995
|
+
}
|
|
996
|
+
}), /*#__PURE__*/React.createElement("div", {
|
|
997
|
+
style: {
|
|
998
|
+
display: 'flex',
|
|
999
|
+
alignItems: 'center',
|
|
1000
|
+
gap: 8,
|
|
1001
|
+
marginBottom: 8,
|
|
1002
|
+
position: 'relative'
|
|
1003
|
+
}
|
|
1004
|
+
}, /*#__PURE__*/React.createElement("span", {
|
|
1005
|
+
style: {
|
|
1006
|
+
width: 28,
|
|
1007
|
+
height: 28,
|
|
1008
|
+
borderRadius: 8,
|
|
1009
|
+
background: `${accent}1F`,
|
|
1010
|
+
color: accent,
|
|
1011
|
+
display: 'inline-flex',
|
|
1012
|
+
alignItems: 'center',
|
|
1013
|
+
justifyContent: 'center'
|
|
1014
|
+
}
|
|
1015
|
+
}, /*#__PURE__*/React.createElement(Icon, {
|
|
1016
|
+
name: icon,
|
|
1017
|
+
size: 14
|
|
1018
|
+
})), /*#__PURE__*/React.createElement("span", {
|
|
1019
|
+
style: {
|
|
1020
|
+
fontFamily: 'var(--font-mono)',
|
|
1021
|
+
fontSize: 10.5,
|
|
1022
|
+
color: 'var(--fg3)',
|
|
1023
|
+
letterSpacing: '.14em',
|
|
1024
|
+
textTransform: 'uppercase'
|
|
1025
|
+
}
|
|
1026
|
+
}, label)), /*#__PURE__*/React.createElement("div", {
|
|
1027
|
+
style: {
|
|
1028
|
+
fontFamily: 'var(--font-display)',
|
|
1029
|
+
fontSize: 42,
|
|
1030
|
+
fontWeight: 700,
|
|
1031
|
+
letterSpacing: -0.8,
|
|
1032
|
+
color: 'var(--fg1)',
|
|
1033
|
+
lineHeight: 1,
|
|
1034
|
+
marginBottom: 10
|
|
1035
|
+
}
|
|
1036
|
+
}, value), /*#__PURE__*/React.createElement("div", {
|
|
1037
|
+
style: {
|
|
1038
|
+
fontFamily: 'var(--font-body)',
|
|
1039
|
+
fontSize: 12.5,
|
|
1040
|
+
color: 'var(--fg2)',
|
|
1041
|
+
lineHeight: 1.55
|
|
1042
|
+
}
|
|
1043
|
+
}, desc));
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
// FlakyTestRow — single affected test in the list.
|
|
1047
|
+
function FlakyTestRow({
|
|
1048
|
+
test,
|
|
1049
|
+
index,
|
|
1050
|
+
selected
|
|
1051
|
+
}) {
|
|
1052
|
+
const BUCKET_META = {
|
|
1053
|
+
recovered: {
|
|
1054
|
+
color: 'var(--status-broken)',
|
|
1055
|
+
label: 'RECOVERED',
|
|
1056
|
+
pillBg: 'var(--status-broken-bg)',
|
|
1057
|
+
pillFg: 'var(--status-broken-fg)',
|
|
1058
|
+
icon: 'rotate-ccw'
|
|
1059
|
+
},
|
|
1060
|
+
broken: {
|
|
1061
|
+
color: '#7C5CFF',
|
|
1062
|
+
label: 'BROKEN',
|
|
1063
|
+
pillBg: 'rgba(124,92,255,0.15)',
|
|
1064
|
+
pillFg: '#B69CFF',
|
|
1065
|
+
icon: 'zap-off'
|
|
1066
|
+
},
|
|
1067
|
+
failedWithRetries: {
|
|
1068
|
+
color: 'var(--status-failed)',
|
|
1069
|
+
label: 'FAILED RETRY',
|
|
1070
|
+
pillBg: 'var(--status-failed-bg)',
|
|
1071
|
+
pillFg: 'var(--status-failed-fg)',
|
|
1072
|
+
icon: 'alert-triangle'
|
|
1073
|
+
}
|
|
1074
|
+
};
|
|
1075
|
+
const m = BUCKET_META[test._bucket] || BUCKET_META.broken;
|
|
1076
|
+
|
|
1077
|
+
// Compute a rough "stability score" for visual weight: lower = flakier.
|
|
1078
|
+
// 100 base, –20 per retry, –30 if broken, –10 if failed.
|
|
1079
|
+
let stability = 100 - test.retries * 20;
|
|
1080
|
+
if (test.status === 'broken') stability -= 30;else if (test.status === 'failed') stability -= 10;
|
|
1081
|
+
stability = Math.max(0, Math.min(100, stability));
|
|
1082
|
+
const stColor = stability >= 60 ? 'var(--status-broken)' : 'var(--status-failed)';
|
|
1083
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
1084
|
+
onClick: () => window.__openTest?.(test.id),
|
|
1085
|
+
style: {
|
|
1086
|
+
display: 'grid',
|
|
1087
|
+
gridTemplateColumns: 'auto 110px 1fr 110px auto',
|
|
1088
|
+
gap: 14,
|
|
1089
|
+
alignItems: 'center',
|
|
1090
|
+
padding: '12px 4px',
|
|
1091
|
+
cursor: 'pointer',
|
|
1092
|
+
borderTop: index ? '1px solid var(--line)' : 'none',
|
|
1093
|
+
background: selected ? 'var(--accent-soft)' : 'transparent',
|
|
1094
|
+
borderLeft: selected ? '2px solid var(--brand-blue-500)' : '2px solid transparent',
|
|
1095
|
+
transition: 'background var(--dur-fast)'
|
|
1096
|
+
},
|
|
1097
|
+
onMouseEnter: e => {
|
|
1098
|
+
if (!selected) e.currentTarget.style.background = 'var(--bg-hover)';
|
|
1099
|
+
},
|
|
1100
|
+
onMouseLeave: e => {
|
|
1101
|
+
if (!selected) e.currentTarget.style.background = 'transparent';
|
|
1102
|
+
}
|
|
1103
|
+
}, /*#__PURE__*/React.createElement("span", {
|
|
1104
|
+
style: {
|
|
1105
|
+
width: 32,
|
|
1106
|
+
height: 32,
|
|
1107
|
+
borderRadius: 8,
|
|
1108
|
+
background: `${m.color}1F`,
|
|
1109
|
+
color: m.color,
|
|
1110
|
+
display: 'inline-flex',
|
|
1111
|
+
alignItems: 'center',
|
|
1112
|
+
justifyContent: 'center',
|
|
1113
|
+
flexShrink: 0
|
|
1114
|
+
}
|
|
1115
|
+
}, /*#__PURE__*/React.createElement(Icon, {
|
|
1116
|
+
name: m.icon,
|
|
1117
|
+
size: 14
|
|
1118
|
+
})), /*#__PURE__*/React.createElement("span", {
|
|
1119
|
+
style: {
|
|
1120
|
+
display: 'inline-flex',
|
|
1121
|
+
alignItems: 'center',
|
|
1122
|
+
justifyContent: 'center',
|
|
1123
|
+
padding: '3px 0',
|
|
1124
|
+
background: m.pillBg,
|
|
1125
|
+
color: m.pillFg,
|
|
1126
|
+
fontFamily: 'var(--font-mono)',
|
|
1127
|
+
fontSize: 10.5,
|
|
1128
|
+
fontWeight: 700,
|
|
1129
|
+
letterSpacing: 0.5,
|
|
1130
|
+
borderRadius: 4
|
|
1131
|
+
}
|
|
1132
|
+
}, m.label), /*#__PURE__*/React.createElement("div", {
|
|
1133
|
+
style: {
|
|
1134
|
+
minWidth: 0
|
|
1135
|
+
}
|
|
1136
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
1137
|
+
style: {
|
|
1138
|
+
fontFamily: 'var(--font-body)',
|
|
1139
|
+
fontSize: 13.5,
|
|
1140
|
+
fontWeight: 600,
|
|
1141
|
+
color: 'var(--fg1)',
|
|
1142
|
+
overflow: 'hidden',
|
|
1143
|
+
textOverflow: 'ellipsis',
|
|
1144
|
+
whiteSpace: 'nowrap'
|
|
1145
|
+
}
|
|
1146
|
+
}, test.name), /*#__PURE__*/React.createElement("div", {
|
|
1147
|
+
style: {
|
|
1148
|
+
fontFamily: 'var(--font-mono)',
|
|
1149
|
+
fontSize: 11,
|
|
1150
|
+
color: 'var(--fg3)',
|
|
1151
|
+
marginTop: 3,
|
|
1152
|
+
overflow: 'hidden',
|
|
1153
|
+
textOverflow: 'ellipsis',
|
|
1154
|
+
whiteSpace: 'nowrap'
|
|
1155
|
+
}
|
|
1156
|
+
}, test.suite ? /*#__PURE__*/React.createElement("span", null, test.suite) : null, test.suite && test.file ? /*#__PURE__*/React.createElement("span", null, " \xB7 ") : null, test.file ? /*#__PURE__*/React.createElement("span", null, test.file) : null)), /*#__PURE__*/React.createElement("div", {
|
|
1157
|
+
style: {
|
|
1158
|
+
display: 'flex',
|
|
1159
|
+
flexDirection: 'column',
|
|
1160
|
+
gap: 4,
|
|
1161
|
+
alignItems: 'flex-end'
|
|
1162
|
+
}
|
|
1163
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
1164
|
+
style: {
|
|
1165
|
+
fontFamily: 'var(--font-mono)',
|
|
1166
|
+
fontSize: 11,
|
|
1167
|
+
color: 'var(--fg3)'
|
|
1168
|
+
}
|
|
1169
|
+
}, test.retries > 0 ? `${test.retries} ${test.retries === 1 ? 'retry' : 'retries'}` : 'no retries'), /*#__PURE__*/React.createElement("div", {
|
|
1170
|
+
style: {
|
|
1171
|
+
width: 90,
|
|
1172
|
+
height: 4,
|
|
1173
|
+
background: 'var(--bg-sunken)',
|
|
1174
|
+
borderRadius: 999,
|
|
1175
|
+
overflow: 'hidden'
|
|
1176
|
+
}
|
|
1177
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
1178
|
+
style: {
|
|
1179
|
+
width: `${stability}%`,
|
|
1180
|
+
height: '100%',
|
|
1181
|
+
background: stColor,
|
|
1182
|
+
transition: 'width var(--dur-fast)'
|
|
1183
|
+
}
|
|
1184
|
+
})), /*#__PURE__*/React.createElement("div", {
|
|
1185
|
+
style: {
|
|
1186
|
+
fontFamily: 'var(--font-mono)',
|
|
1187
|
+
fontSize: 10,
|
|
1188
|
+
color: 'var(--fg4)',
|
|
1189
|
+
letterSpacing: '.08em',
|
|
1190
|
+
textTransform: 'uppercase'
|
|
1191
|
+
}
|
|
1192
|
+
}, "stability ", stability, "%")), /*#__PURE__*/React.createElement("div", {
|
|
1193
|
+
style: {
|
|
1194
|
+
fontFamily: 'var(--font-mono)',
|
|
1195
|
+
fontSize: 12,
|
|
1196
|
+
color: 'var(--fg3)'
|
|
1197
|
+
}
|
|
1198
|
+
}, test.dur));
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
// ============== SUITES PAGE — uses TreeDetailPage ==============
|
|
1202
|
+
function SuitesView({
|
|
1203
|
+
onOpen
|
|
1204
|
+
}) {
|
|
1205
|
+
const tree = window.SUITE_TREE || [];
|
|
1206
|
+
if (tree.length === 0) {
|
|
1207
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
1208
|
+
className: "card",
|
|
1209
|
+
style: {
|
|
1210
|
+
padding: 30,
|
|
1211
|
+
textAlign: 'center',
|
|
1212
|
+
color: 'var(--fg3)'
|
|
1213
|
+
}
|
|
1214
|
+
}, "No suites in this report.");
|
|
1215
|
+
}
|
|
1216
|
+
return /*#__PURE__*/React.createElement(TreeDetailPage, {
|
|
1217
|
+
title: "Suites",
|
|
1218
|
+
subtitle: "Tests grouped by suite organization",
|
|
1219
|
+
tree: tree
|
|
1220
|
+
});
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
// ============== PACKAGES PAGE — file-path tree, opens detail in-place ==============
|
|
1224
|
+
function PackagesPage() {
|
|
1225
|
+
const RICH_TESTS = window.RICH_TESTS || {};
|
|
1226
|
+
const allTests = Object.values(RICH_TESTS);
|
|
1227
|
+
if (allTests.length === 0) {
|
|
1228
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
1229
|
+
className: "card",
|
|
1230
|
+
style: {
|
|
1231
|
+
padding: 30,
|
|
1232
|
+
textAlign: 'center',
|
|
1233
|
+
color: 'var(--fg3)'
|
|
1234
|
+
}
|
|
1235
|
+
}, "No tests in this report.");
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
// Build a tree from RICH_TESTS file paths, e.g. src/test/auth/login.spec.ts → src › test › auth › login.spec.ts
|
|
1239
|
+
const root = {
|
|
1240
|
+
_children: {}
|
|
1241
|
+
};
|
|
1242
|
+
allTests.forEach(t => {
|
|
1243
|
+
if (!t.file) return;
|
|
1244
|
+
const parts = t.file.split(':')[0].split('/');
|
|
1245
|
+
let node = root;
|
|
1246
|
+
parts.forEach((p, i) => {
|
|
1247
|
+
if (!node._children[p]) node._children[p] = {
|
|
1248
|
+
_children: {},
|
|
1249
|
+
_name: p,
|
|
1250
|
+
_isFile: i === parts.length - 1
|
|
1251
|
+
};
|
|
1252
|
+
node = node._children[p];
|
|
1253
|
+
if (node._isFile) node._test = t.id;
|
|
1254
|
+
});
|
|
1255
|
+
});
|
|
1256
|
+
const toTree = (node, prefix = '') => Object.entries(node._children).map(([name, child]) => {
|
|
1257
|
+
const id = prefix + '/' + name;
|
|
1258
|
+
if (child._isFile) {
|
|
1259
|
+
return {
|
|
1260
|
+
id,
|
|
1261
|
+
testId: child._test
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
1264
|
+
return {
|
|
1265
|
+
id,
|
|
1266
|
+
name,
|
|
1267
|
+
children: toTree(child, id)
|
|
1268
|
+
};
|
|
1269
|
+
});
|
|
1270
|
+
|
|
1271
|
+
// Collapse single-child folders for cleaner display
|
|
1272
|
+
const collapse = nodes => nodes.map(n => {
|
|
1273
|
+
if (!n.children) return n;
|
|
1274
|
+
let kids = collapse(n.children);
|
|
1275
|
+
while (kids.length === 1 && kids[0].children) {
|
|
1276
|
+
n = {
|
|
1277
|
+
...n,
|
|
1278
|
+
name: n.name + ' › ' + kids[0].name,
|
|
1279
|
+
children: kids[0].children,
|
|
1280
|
+
id: kids[0].id
|
|
1281
|
+
};
|
|
1282
|
+
kids = n.children;
|
|
1283
|
+
}
|
|
1284
|
+
return {
|
|
1285
|
+
...n,
|
|
1286
|
+
children: kids
|
|
1287
|
+
};
|
|
1288
|
+
});
|
|
1289
|
+
const tree = collapse(toTree(root));
|
|
1290
|
+
if (tree.length === 0) {
|
|
1291
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
1292
|
+
className: "card",
|
|
1293
|
+
style: {
|
|
1294
|
+
padding: 30,
|
|
1295
|
+
textAlign: 'center',
|
|
1296
|
+
color: 'var(--fg3)'
|
|
1297
|
+
}
|
|
1298
|
+
}, "No source-file metadata in this report. Add filePath to your test cases to populate this tree.");
|
|
1299
|
+
}
|
|
1300
|
+
return /*#__PURE__*/React.createElement(TreeDetailPage, {
|
|
1301
|
+
title: "Packages",
|
|
1302
|
+
subtitle: "Tests grouped by source-code package",
|
|
1303
|
+
tree: tree
|
|
1304
|
+
});
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
// ============== BEHAVIORS PAGE — Epic › Feature › Story tree ==============
|
|
1308
|
+
function BehaviorsPage() {
|
|
1309
|
+
const tree = window.BEHAVIOR_TREE || [];
|
|
1310
|
+
if (tree.length === 0) {
|
|
1311
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
1312
|
+
className: "card",
|
|
1313
|
+
style: {
|
|
1314
|
+
padding: 30,
|
|
1315
|
+
textAlign: 'center',
|
|
1316
|
+
color: 'var(--fg3)'
|
|
1317
|
+
}
|
|
1318
|
+
}, "No BDD/behavior annotations in this report. Add behavior.epic/feature/scenario to your test cases to populate this tree.");
|
|
1319
|
+
}
|
|
1320
|
+
return /*#__PURE__*/React.createElement(TreeDetailPage, {
|
|
1321
|
+
title: "Behaviors",
|
|
1322
|
+
subtitle: "BDD tree \xB7 Epic \u203A Feature \u203A Story \xB7 Given/When/Then in test details",
|
|
1323
|
+
tree: tree
|
|
1324
|
+
});
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
// ============== HISTORY PAGE ==============
|
|
1328
|
+
function HistoryPage() {
|
|
1329
|
+
const HISTORY_RUNS = window.HISTORY_RUNS || [];
|
|
1330
|
+
const TREND_RUNS = window.TREND_RUNS || [];
|
|
1331
|
+
return /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("h1", {
|
|
1332
|
+
className: "k-h1",
|
|
1333
|
+
style: {
|
|
1334
|
+
marginBottom: 4
|
|
1335
|
+
}
|
|
1336
|
+
}, "History"), /*#__PURE__*/React.createElement("div", {
|
|
1337
|
+
className: "k-meta",
|
|
1338
|
+
style: {
|
|
1339
|
+
marginBottom: 18
|
|
1340
|
+
}
|
|
1341
|
+
}, "Last ", HISTORY_RUNS.length, " run", HISTORY_RUNS.length !== 1 ? 's' : '', " \xB7 main & PR branches"), /*#__PURE__*/React.createElement("div", {
|
|
1342
|
+
className: "card",
|
|
1343
|
+
style: {
|
|
1344
|
+
marginBottom: 16
|
|
1345
|
+
}
|
|
1346
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
1347
|
+
className: "hd"
|
|
1348
|
+
}, /*#__PURE__*/React.createElement("h3", null, "Pass-rate trend"), /*#__PURE__*/React.createElement("div", {
|
|
1349
|
+
className: "meta"
|
|
1350
|
+
}, "last ", TREND_RUNS.length, " run", TREND_RUNS.length !== 1 ? 's' : '')), TREND_RUNS.length > 0 ? /*#__PURE__*/React.createElement(TrendChartV2, {
|
|
1351
|
+
runs: TREND_RUNS
|
|
1352
|
+
}) : /*#__PURE__*/React.createElement("div", {
|
|
1353
|
+
style: {
|
|
1354
|
+
padding: 30,
|
|
1355
|
+
color: 'var(--fg3)',
|
|
1356
|
+
textAlign: 'center',
|
|
1357
|
+
fontFamily: 'var(--font-mono)',
|
|
1358
|
+
fontSize: 12
|
|
1359
|
+
}
|
|
1360
|
+
}, "No run history yet. Run kensho generate over multiple runs to populate.")), /*#__PURE__*/React.createElement("div", {
|
|
1361
|
+
className: "card kv-flush"
|
|
1362
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
1363
|
+
style: {
|
|
1364
|
+
display: 'grid',
|
|
1365
|
+
gridTemplateColumns: '24px 200px 1fr 110px 110px 80px',
|
|
1366
|
+
gap: 12,
|
|
1367
|
+
padding: '10px 20px',
|
|
1368
|
+
background: 'var(--bg-sunken)',
|
|
1369
|
+
borderBottom: '1px solid var(--line)',
|
|
1370
|
+
fontSize: 11,
|
|
1371
|
+
letterSpacing: '.12em',
|
|
1372
|
+
textTransform: 'uppercase',
|
|
1373
|
+
fontWeight: 700,
|
|
1374
|
+
color: 'var(--fg3)'
|
|
1375
|
+
}
|
|
1376
|
+
}, /*#__PURE__*/React.createElement("div", null), /*#__PURE__*/React.createElement("div", null, "Run"), /*#__PURE__*/React.createElement("div", null, "Distribution"), /*#__PURE__*/React.createElement("div", null, "Branch \xB7 actor"), /*#__PURE__*/React.createElement("div", null, "Duration"), /*#__PURE__*/React.createElement("div", {
|
|
1377
|
+
style: {
|
|
1378
|
+
textAlign: 'right'
|
|
1379
|
+
}
|
|
1380
|
+
}, "When")), HISTORY_RUNS.map((r, i) => {
|
|
1381
|
+
const total = r.passed + r.failed + r.broken + r.skipped;
|
|
1382
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
1383
|
+
key: i,
|
|
1384
|
+
style: {
|
|
1385
|
+
display: 'grid',
|
|
1386
|
+
gridTemplateColumns: '24px 200px 1fr 110px 110px 80px',
|
|
1387
|
+
gap: 12,
|
|
1388
|
+
padding: '12px 20px',
|
|
1389
|
+
borderBottom: i < HISTORY_RUNS.length - 1 ? '1px solid var(--line)' : 'none',
|
|
1390
|
+
alignItems: 'center',
|
|
1391
|
+
cursor: 'pointer'
|
|
1392
|
+
}
|
|
1393
|
+
}, /*#__PURE__*/React.createElement("span", {
|
|
1394
|
+
className: `s-icon ${r.status}`
|
|
1395
|
+
}, r.status === 'passed' ? '✓' : '✕'), /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("div", {
|
|
1396
|
+
style: {
|
|
1397
|
+
fontFamily: 'var(--font-mono)',
|
|
1398
|
+
fontSize: 12,
|
|
1399
|
+
color: 'var(--fg1)'
|
|
1400
|
+
}
|
|
1401
|
+
}, r.id), /*#__PURE__*/React.createElement("div", {
|
|
1402
|
+
style: {
|
|
1403
|
+
fontFamily: 'var(--font-mono)',
|
|
1404
|
+
fontSize: 11,
|
|
1405
|
+
color: 'var(--fg3)'
|
|
1406
|
+
}
|
|
1407
|
+
}, r.passed, " passed \xB7 ", r.failed, " failed \xB7 ", r.broken, " broken \xB7 ", r.skipped, " skipped")), /*#__PURE__*/React.createElement("div", {
|
|
1408
|
+
style: {
|
|
1409
|
+
height: 12,
|
|
1410
|
+
background: 'var(--bg-sunken)',
|
|
1411
|
+
borderRadius: 3,
|
|
1412
|
+
display: 'flex',
|
|
1413
|
+
overflow: 'hidden'
|
|
1414
|
+
}
|
|
1415
|
+
}, r.passed > 0 && /*#__PURE__*/React.createElement("div", {
|
|
1416
|
+
style: {
|
|
1417
|
+
width: `${r.passed / total * 100}%`,
|
|
1418
|
+
background: 'var(--status-passed)'
|
|
1419
|
+
}
|
|
1420
|
+
}), r.failed > 0 && /*#__PURE__*/React.createElement("div", {
|
|
1421
|
+
style: {
|
|
1422
|
+
width: `${r.failed / total * 100}%`,
|
|
1423
|
+
background: 'var(--status-failed)'
|
|
1424
|
+
}
|
|
1425
|
+
}), r.broken > 0 && /*#__PURE__*/React.createElement("div", {
|
|
1426
|
+
style: {
|
|
1427
|
+
width: `${r.broken / total * 100}%`,
|
|
1428
|
+
background: 'var(--status-broken)'
|
|
1429
|
+
}
|
|
1430
|
+
}), r.skipped > 0 && /*#__PURE__*/React.createElement("div", {
|
|
1431
|
+
style: {
|
|
1432
|
+
width: `${r.skipped / total * 100}%`,
|
|
1433
|
+
background: 'var(--status-skipped)'
|
|
1434
|
+
}
|
|
1435
|
+
})), /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("div", {
|
|
1436
|
+
style: {
|
|
1437
|
+
fontFamily: 'var(--font-mono)',
|
|
1438
|
+
fontSize: 12,
|
|
1439
|
+
color: 'var(--fg1)'
|
|
1440
|
+
}
|
|
1441
|
+
}, r.branch), /*#__PURE__*/React.createElement("div", {
|
|
1442
|
+
style: {
|
|
1443
|
+
fontFamily: 'var(--font-mono)',
|
|
1444
|
+
fontSize: 11,
|
|
1445
|
+
color: 'var(--fg3)'
|
|
1446
|
+
}
|
|
1447
|
+
}, r.actor)), /*#__PURE__*/React.createElement("div", {
|
|
1448
|
+
style: {
|
|
1449
|
+
fontFamily: 'var(--font-mono)',
|
|
1450
|
+
fontSize: 12,
|
|
1451
|
+
color: 'var(--fg2)'
|
|
1452
|
+
}
|
|
1453
|
+
}, r.dur), /*#__PURE__*/React.createElement("div", {
|
|
1454
|
+
style: {
|
|
1455
|
+
fontFamily: 'var(--font-mono)',
|
|
1456
|
+
fontSize: 11,
|
|
1457
|
+
color: 'var(--fg3)',
|
|
1458
|
+
textAlign: 'right'
|
|
1459
|
+
}
|
|
1460
|
+
}, r.when));
|
|
1461
|
+
})));
|
|
1462
|
+
}
|
|
1463
|
+
Object.assign(window, {
|
|
1464
|
+
GraphsPage,
|
|
1465
|
+
TimelinePage,
|
|
1466
|
+
CategoriesPage,
|
|
1467
|
+
BehaviorsPage,
|
|
1468
|
+
PackagesPage,
|
|
1469
|
+
HistoryPage,
|
|
1470
|
+
SuitesView,
|
|
1471
|
+
FlakyPage
|
|
1472
|
+
});
|