reporting-labs 0.6.8 → 0.6.10
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/README.md +4 -0
- package/dist/template.html +46 -18
- package/dist/template.js +46 -18
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -613,6 +613,10 @@ Your Slack step can then read `results.json` for pass / fail counts and top fail
|
|
|
613
613
|
|
|
614
614
|
The same report, from Java: `dev.reportinglabs` on Maven Central, for TestNG and JUnit 5, with zero-code add-ons for Selenium, REST Assured and Playwright for Java, and a Cucumber JVM plugin (one row per scenario, Given/When/Then as steps). Source: [reporting-labs-java](https://github.com/naveenautomationlabs/reporting-labs-java). Guides: [reportinglabs.dev/get-started/java](https://reportinglabs.dev/get-started/java).
|
|
615
615
|
|
|
616
|
+
## Python teams
|
|
617
|
+
|
|
618
|
+
The same report, from Python: `pip install reporting-labs` ([PyPI](https://pypi.org/project/reporting-labs/)). A pytest plugin that turns on the moment it is installed, with zero-code support for Playwright and Selenium, and a Robot Framework listener (one row per test, keywords as steps). Source: [reporting-labs-python](https://github.com/naveenautomationlabs/reporting-labs-python). Guides: [reportinglabs.dev/get-started/python](https://reportinglabs.dev/get-started/python).
|
|
619
|
+
|
|
616
620
|
## Roadmap
|
|
617
621
|
|
|
618
622
|
- WebdriverIO, Cypress, Jest/Vitest and JUnit XML adapters
|
package/dist/template.html
CHANGED
|
@@ -166,7 +166,7 @@ a{color:var(--accent)}
|
|
|
166
166
|
.slow li{display:flex;gap:10px;align-items:center;padding:6px 0;border-top:1px solid var(--line);font-size:13px}
|
|
167
167
|
.slow li:first-child{border-top:0}
|
|
168
168
|
.slow .bar{height:6px;border-radius:3px;background:var(--accent);opacity:.7}
|
|
169
|
-
.slow .t{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
169
|
+
.slow .t{flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
170
170
|
.slow .d{font:12px var(--mono);color:var(--ink-3)}
|
|
171
171
|
.slow button{text-align:left;width:100%;display:flex;gap:10px;align-items:center}
|
|
172
172
|
.slow button:hover .t{color:var(--accent)}
|
|
@@ -202,7 +202,7 @@ a{color:var(--accent)}
|
|
|
202
202
|
/* ---------- detail ---------- */
|
|
203
203
|
.detail{background:var(--surface);border:1px solid var(--line);border-radius:var(--radius);padding:22px;min-width:0}
|
|
204
204
|
.crumb{font-size:12px;color:var(--ink-3)} .crumb span+span::before{content:" / ";color:var(--line)}
|
|
205
|
-
.detail h3{font-size:20px;font-weight:600;margin:6px 0 10px;letter-spacing:-.01em}
|
|
205
|
+
.detail h3{font-size:20px;font-weight:600;margin:6px 0 10px;letter-spacing:-.01em;overflow-wrap:anywhere;min-width:0}
|
|
206
206
|
.badges{display:flex;gap:6px;flex-wrap:wrap;align-items:center;margin-bottom:18px}
|
|
207
207
|
.badge{font-size:12px;padding:3px 9px;border-radius:999px;font-weight:500}
|
|
208
208
|
.badge.passed{background:var(--pass-bg);color:var(--pass)} .badge.failed,.badge.timedOut,.badge.interrupted{background:var(--fail-bg);color:var(--fail)}
|
|
@@ -300,8 +300,8 @@ li.collapsed>ul{display:none}
|
|
|
300
300
|
.tags .bar i{display:block;height:100%}
|
|
301
301
|
/* meta chips in detail */
|
|
302
302
|
.metas{display:flex;flex-wrap:wrap;gap:6px;margin:-8px 0 18px}
|
|
303
|
-
.meta{display:inline-flex;align-items:center;gap:6px;font-size:12px;padding:4px 9px;border:1px solid var(--line);border-radius:4px;background:var(--surface-2)}
|
|
304
|
-
.meta .k{color:var(--ink-3)} .meta .v{font-weight:500}
|
|
303
|
+
.meta{display:inline-flex;align-items:center;gap:6px;font-size:12px;padding:4px 9px;border:1px solid var(--line);border-radius:4px;background:var(--surface-2);max-width:100%;min-width:0}
|
|
304
|
+
.meta .k{color:var(--ink-3);flex:none} .meta .v{font-weight:500;overflow-wrap:anywhere;min-width:0}
|
|
305
305
|
.meta.priority .v,.meta.severity .v{color:var(--fail)}
|
|
306
306
|
.meta.priority.low .v,.meta.severity.low .v{color:var(--ink)}
|
|
307
307
|
|
|
@@ -483,7 +483,7 @@ html,body{-webkit-font-smoothing:antialiased}
|
|
|
483
483
|
.dim .row[aria-pressed=true]{background:var(--accent-tint);outline:0}
|
|
484
484
|
.legend-inline{font-size:12px;gap:14px;margin-top:12px} .legend-inline span::before{width:9px;height:9px;border-radius:3px;margin-right:6px}
|
|
485
485
|
.slow li{padding:9px 0} .slow button{display:grid;grid-template-columns:1fr auto;gap:10px;align-items:start}
|
|
486
|
-
.slow .t{white-space:normal}
|
|
486
|
+
.slow .t{white-space:normal;overflow-wrap:anywhere;min-width:0}
|
|
487
487
|
.slow .bar{display:block;height:5px;border-radius:999px;background:var(--accent-soft);opacity:1;margin-top:6px;position:relative;overflow:hidden;width:100%!important}
|
|
488
488
|
.slow .bar i{position:absolute;inset:0;width:var(--w);background:var(--accent);border-radius:999px}
|
|
489
489
|
.slow button:hover .t{color:var(--accent-2)}
|
|
@@ -542,15 +542,17 @@ html,body{-webkit-font-smoothing:antialiased}
|
|
|
542
542
|
.attn2 td:first-child{border-radius:6px 0 0 6px} .attn2 td:last-child{border-radius:0 6px 6px 0}
|
|
543
543
|
.attn2 .pr{width:96px;white-space:nowrap;font-size:12.5px;line-height:1.35} .attn2 .pr .dot{display:inline-block;width:8px;height:8px;border-radius:50%;margin-right:7px}
|
|
544
544
|
.attn2 .pr span{font-weight:600;color:var(--ink)} .attn2 .pr small{display:block;color:var(--ink-3);font-size:11.5px;padding-left:15px}
|
|
545
|
-
.attn2 .tt .n{font-weight:500;color:var(--ink);line-height:1.35} .attn2 .tt .s{font-size:12px;color:var(--ink-3);margin-top:2px} .attn2 .tt .s b.new{color:var(--fail);font-weight:600} .attn2 .tt .s .rsn{color:var(--ink-2)}
|
|
545
|
+
.attn2 .tt{min-width:0} .attn2 .tt .n{font-weight:500;color:var(--ink);line-height:1.35;overflow-wrap:anywhere} .attn2 .tt .s{font-size:12px;color:var(--ink-3);margin-top:2px;overflow-wrap:anywhere} .attn2 .tt .s b.new{color:var(--fail);font-weight:600} .attn2 .tt .s .rsn{color:var(--ink-2)}
|
|
546
546
|
.attn2 .ow{text-align:right;color:var(--ink-3);font-size:12.5px;white-space:nowrap;width:1%;padding-top:10px}
|
|
547
547
|
.attn-foot{margin-top:auto;padding-top:12px;border-top:1px solid var(--line)} .attn-foot button{font-size:12.5px;font-weight:500;color:var(--accent-2)} .attn-foot button:hover{text-decoration:underline}
|
|
548
548
|
.clu2{list-style:none;margin:0;padding:0}
|
|
549
549
|
.clu2 li{display:grid;grid-template-columns:44px 1fr;gap:12px;padding:11px 0;border-top:1px solid var(--line)} .clu2 li:first-child{border-top:0;padding-top:2px}
|
|
550
550
|
.clu2 .n{font-size:20px;font-weight:600;color:var(--fail);font-variant-numeric:tabular-nums;line-height:1.1} .clu2 .n small{display:block;font-size:10.5px;color:var(--ink-3);font-weight:500;letter-spacing:.04em;margin-top:1px}
|
|
551
551
|
.clu2 .b{min-width:0} .clu2 .msg{font:12.5px/1.45 var(--mono);color:var(--ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
552
|
-
.clu2 .who{font-size:12.5px;color:var(--ink-2);margin-top:
|
|
553
|
-
.clu2 .who
|
|
552
|
+
.clu2 .who{font-size:12.5px;color:var(--ink-2);margin-top:5px;line-height:1.45;display:flex;flex-direction:column;align-items:flex-start;gap:3px}
|
|
553
|
+
.clu2 .who button{color:var(--accent-2);font-weight:500;text-align:left;white-space:normal;overflow-wrap:anywhere;max-width:100%;padding:0 0 0 14px;text-indent:-14px} .clu2 .who button .tri{display:inline-block;width:14px;text-indent:0;color:var(--ink-3);font-weight:400}
|
|
554
|
+
.clu2 .who button:hover{text-decoration:underline} .clu2 .who button small{color:var(--ink-3);font-weight:400;font-size:11px}
|
|
555
|
+
.clu2 .who button.ex{color:var(--ink-3);font-weight:400;cursor:pointer;text-indent:0} .clu2 .who button.ex:hover{color:var(--accent-2);text-decoration:underline}
|
|
554
556
|
@media (max-width:640px){.attn2 .ow{display:none} .attn2 .pr{width:72px} .attn2 .pr small{display:none}}
|
|
555
557
|
/* ---------- run status, errors outside tests, snippets ---------- */
|
|
556
558
|
.att-err{padding:12px 14px;background:var(--fail-bg);color:var(--ink);border-radius:8px 8px 0 0;line-height:1.5} .att-err code{font:12px var(--mono);background:var(--surface);padding:1px 5px;border-radius:4px}
|
|
@@ -559,9 +561,9 @@ html,body{-webkit-font-smoothing:antialiased}
|
|
|
559
561
|
.badge.xfail{background:var(--flaky-bg);color:var(--flaky-ink)}
|
|
560
562
|
.errwrap+.errwrap{margin-top:14px}
|
|
561
563
|
.why{border:1px solid var(--line);border-radius:var(--radius);padding:10px 14px;margin-bottom:10px;background:var(--surface)}
|
|
562
|
-
.why-c{font-size:12.5px;color:var(--ink);margin-bottom:2px;line-height:1.4} .why-c .why-k{margin-right:2px}
|
|
564
|
+
.why-c{font-size:12.5px;color:var(--ink);margin-bottom:2px;line-height:1.4;overflow-wrap:anywhere} .why-c .why-k{margin-right:2px}
|
|
563
565
|
.why-l{display:flex;align-items:center;gap:8px;margin-bottom:4px} .why-k{font-size:12px;font-weight:600;color:var(--fail)} .why-a{font:11.5px var(--mono);color:var(--ink-3)}
|
|
564
|
-
.why-s{font-size:var(--fs);color:var(--ink);line-height:1.45} .why-h{font-size:12.5px;color:var(--ink-2);margin-top:4px;line-height:1.45}
|
|
566
|
+
.why-s{font-size:var(--fs);color:var(--ink);line-height:1.45;overflow-wrap:anywhere} .why-h{font-size:12.5px;color:var(--ink-2);margin-top:4px;line-height:1.45;overflow-wrap:anywhere}
|
|
565
567
|
.why.assertion .why-k,.why.visual .why-k{color:#9A6A00}
|
|
566
568
|
.why.script .why-k,.why.thrown .why-k,.why.file .why-k{color:var(--ink-2)}
|
|
567
569
|
.errloc{font-family:var(--mono);color:var(--ink-3);margin-bottom:6px} .errloc a{color:var(--accent-2);text-decoration:none} .errloc a:hover{text-decoration:underline}
|
|
@@ -578,18 +580,18 @@ html,body{-webkit-font-smoothing:antialiased}
|
|
|
578
580
|
.badges .dots{margin-left:2px;margin-right:4px}
|
|
579
581
|
.flk{list-style:none;margin:0;padding:0} .flk li{border-top:1px solid var(--line)} .flk li:first-child{border-top:0}
|
|
580
582
|
.flk button{width:100%;text-align:left;padding:9px 4px;display:grid;grid-template-columns:1fr auto;gap:4px 12px;align-items:center}
|
|
581
|
-
.flk .t{font-size:13px;font-weight:500;color:var(--ink)} .flk .m{grid-column:1/-1;font-size:12px;color:var(--ink-3)}
|
|
583
|
+
.flk .t{font-size:13px;font-weight:500;color:var(--ink);overflow-wrap:anywhere;min-width:0} .flk .m{grid-column:1/-1;font-size:12px;color:var(--ink-3)}
|
|
582
584
|
.own{list-style:none;margin:0;padding:0;display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:10px} .own li{border:1px solid var(--line);border-radius:10px;min-width:0}
|
|
583
585
|
.own button{width:100%;text-align:left;padding:10px 12px;display:grid;grid-template-columns:1fr auto;gap:2px 12px;align-items:center;border-radius:10px}
|
|
584
586
|
.own button:hover{background:var(--surface-2)}
|
|
585
|
-
.own .nm{font-weight:600} .own .cnts{font-size:12.5px;color:var(--ink-3);white-space:nowrap} .own .cnts b{font-weight:600}
|
|
587
|
+
.own .nm{font-weight:600;overflow-wrap:anywhere;min-width:0} .own .cnts{font-size:12.5px;color:var(--ink-3);white-space:nowrap} .own .cnts b{font-weight:600}
|
|
586
588
|
.own .cnts b.f{color:var(--fail)} .own .cnts b.k{color:var(--flaky-ink)}
|
|
587
589
|
.own .ts{grid-column:1/-1;font-size:12px;color:var(--ink-3);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
588
590
|
.fx-tools{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:-4px} .fx-tools .fx-sum{font-size:13px;color:var(--ink-2);font-weight:500} .fx-tools .spacer{flex:1}
|
|
589
591
|
a.btn{text-decoration:none}
|
|
590
592
|
.skp{list-style:none;margin:0;padding:0} .skp li{border-top:1px solid var(--line)} .skp li:first-child{border-top:0}
|
|
591
593
|
.skp button{width:100%;text-align:left;padding:8px 4px;display:flex;flex-direction:column;gap:2px}
|
|
592
|
-
.skp .t{font-size:13px;font-weight:500;color:var(--ink)} .skp .r{font-size:12px;color:var(--ink-3)}
|
|
594
|
+
.skp .t{font-size:13px;font-weight:500;color:var(--ink);overflow-wrap:anywhere} .skp .r{font-size:12px;color:var(--ink-3)}
|
|
593
595
|
.gchart .gchart-head{display:flex;justify-content:space-between;align-items:flex-start;gap:12px}
|
|
594
596
|
.gchart .gchart-head h2{margin:0}
|
|
595
597
|
.gchart .gchart-sub{color:var(--ink-3);font-size:12px;margin-top:4px}
|
|
@@ -1300,17 +1302,43 @@ function errorSignature(msg){
|
|
|
1300
1302
|
}
|
|
1301
1303
|
function failureClusters(){
|
|
1302
1304
|
const m=new Map();
|
|
1303
|
-
for(const t of data.tests){ if(!isFail(t.outcome)) continue; const r=t.results[t.results.length-1]; const e=r&&r.errors[0]; const x=e&&e.explain; const
|
|
1305
|
+
for(const t of data.tests){ if(!isFail(t.outcome)) continue; const r=t.results[t.results.length-1]; const e=r&&r.errors[0]; const x=e&&e.explain; const msg=e?e.message:'(no error message)';
|
|
1306
|
+
// One cluster per root cause: the explain kind plus what it points at. An explanation that points at
|
|
1307
|
+
// nothing (a plain assertion) falls back to the message shape, so two different assertions stay apart.
|
|
1308
|
+
const sig=x&&!/^(thrown|script)$/.test(x.kind)? x.kind+'|'+(x.locator||x.url||'')+'|'+(x.matcher||x.action||'')+(x.locator||x.url?'':'|'+messageShape(msg)) : errorSignature(msg);
|
|
1309
|
+
const c=m.get(sig)||{sig,sample:msg.split('\n').map(l=>l.trim()).filter(Boolean).slice(0,3).join('\n'),why:x||null,tests:[]}; c.tests.push(t); m.set(sig,c); }
|
|
1304
1310
|
return [...m.values()].sort((a,b)=>b.tests.length-a.tests.length);
|
|
1305
1311
|
}
|
|
1312
|
+
// The message line shown under a cluster's explanation: the first line that says something the
|
|
1313
|
+
// explanation does not already say. Hamcrest's "Expected: is <200>" / "but: was <404>" is fully
|
|
1314
|
+
// covered by "The value was wrong: expected <200>, got <404>." so nothing is repeated.
|
|
1315
|
+
// The shape of a message: its first lines with numbers and quoted values blanked, so two assertions on
|
|
1316
|
+
// different values of the same kind cluster together, and assertions on different things stay apart.
|
|
1317
|
+
function messageShape(msg){ return String(msg||'').split('\n').map(l=>l.trim()).filter(Boolean).slice(0,3).map(errorSignature).join(' / '); }
|
|
1318
|
+
function cluToks(s){ const stop=/^(expected|expecting|but|is|was|were|received|actual|to|be|the|a|an|of|and|not|did|got|value|wrong|equal|have)$/; return new Set(String(s||'').toLowerCase().replace(/[^a-z0-9]+/g,' ').split(' ').filter(w=>w&&!stop.test(w))); }
|
|
1319
|
+
function clusterLine(c){
|
|
1320
|
+
const lines=c.sample.split('\n').filter(Boolean); if(!c.why) return lines[0]||c.sample;
|
|
1321
|
+
const have=cluToks(c.why.summary);
|
|
1322
|
+
// "Error: " / "AssertionError: " prefixes carry nothing; the expect(received).toBe(expected) hint line of
|
|
1323
|
+
// Jest-style matchers is a header, not a detail.
|
|
1324
|
+
const bare=l=>l.replace(/^[\w.$]*(Error|Exception|Failure|Throwable)(:\s*|$)/,'');
|
|
1325
|
+
return lines.find(l=>{ const b=bare(l); if(/^expect\(.*\)\.[\w.]+\(.*\)/.test(b)) return false; const t=[...cluToks(b)]; return t.length && t.some(w=>!have.has(w)); })||null;
|
|
1326
|
+
}
|
|
1327
|
+
// The tests of a cluster, six by name and "+N more" for the rest; the +N more button expands the list
|
|
1328
|
+
// in place and "show fewer" folds it again. Each name opens that test.
|
|
1329
|
+
function clusterWho(byTitle, all){
|
|
1330
|
+
const entries=[...byTitle.entries()], names=all?entries:entries.slice(0,6), extra=entries.length-names.length;
|
|
1331
|
+
const who=names.map(([title,ts])=>h('button',{onclick:()=>select(ts[0].id),title:ts.map(t=>t.project).join(', ')}, h('span',{class:'tri'},'›'), title, data.projects.length>1&&ts.length>1? h('small',{},' ×'+ts.length) : null));
|
|
1332
|
+
const swap=(ev,toAll)=>{ const d=ev.currentTarget.closest('.who'); d.replaceChildren(); for(const n of clusterWho(byTitle,toAll)) d.append(n); };
|
|
1333
|
+
if(extra>0) who.push(h('button',{class:'ex',onclick:ev=>swap(ev,true),title:'Show all '+entries.length+' tests'},'+'+extra+' more'));
|
|
1334
|
+
else if(all&&entries.length>6) who.push(h('button',{class:'ex',onclick:ev=>swap(ev,false)},'show fewer'));
|
|
1335
|
+
return who;
|
|
1336
|
+
}
|
|
1306
1337
|
function clustersView(cl){
|
|
1307
1338
|
return h('ol',{class:'clu2'}, cl.map(c=>{
|
|
1308
1339
|
const byTitle=new Map(); for(const t of c.tests){ const g=byTitle.get(t.title)||[]; g.push(t); byTitle.set(t.title,g); }
|
|
1309
|
-
const names=[...byTitle.entries()].slice(0,6), extra=byTitle.size-names.length;
|
|
1310
|
-
const who=[]; names.forEach(([title,ts],k)=>{ if(k) who.push(h('span',{class:'sep'},'·')); who.push(h('button',{onclick:()=>select(ts[0].id),title:ts.map(t=>t.project).join(', ')}, title, data.projects.length>1&&ts.length>1? h('small',{},' ×'+ts.length) : null)); });
|
|
1311
|
-
if(extra>0) who.push(h('span',{class:'sep'},'·'), h('span',{class:'ex'},'+'+extra+' more'));
|
|
1312
1340
|
return h('li',{}, h('span',{class:'n'}, c.tests.length, h('small',{},c.tests.length===1?'test':'tests')),
|
|
1313
|
-
h('div',{class:'b'}, c.why? h('div',{class:'why-c'}, h('span',{class:'why-k'},c.why.label), ' ', c.why.summary) : null, h('div',{class:'msg',title:c.sample}, c
|
|
1341
|
+
h('div',{class:'b'}, c.why? h('div',{class:'why-c'}, h('span',{class:'why-k'},c.why.label), ' ', c.why.summary) : null, (line=>line? h('div',{class:'msg',title:c.sample}, line):null)(clusterLine(c)), h('div',{class:'who'}, clusterWho(byTitle,false))));
|
|
1314
1342
|
}));
|
|
1315
1343
|
}
|
|
1316
1344
|
function trend(){
|
package/dist/template.js
CHANGED
|
@@ -237,7 +237,7 @@ a{color:var(--accent)}
|
|
|
237
237
|
.slow li{display:flex;gap:10px;align-items:center;padding:6px 0;border-top:1px solid var(--line);font-size:13px}
|
|
238
238
|
.slow li:first-child{border-top:0}
|
|
239
239
|
.slow .bar{height:6px;border-radius:3px;background:var(--accent);opacity:.7}
|
|
240
|
-
.slow .t{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
240
|
+
.slow .t{flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
241
241
|
.slow .d{font:12px var(--mono);color:var(--ink-3)}
|
|
242
242
|
.slow button{text-align:left;width:100%;display:flex;gap:10px;align-items:center}
|
|
243
243
|
.slow button:hover .t{color:var(--accent)}
|
|
@@ -273,7 +273,7 @@ a{color:var(--accent)}
|
|
|
273
273
|
/* ---------- detail ---------- */
|
|
274
274
|
.detail{background:var(--surface);border:1px solid var(--line);border-radius:var(--radius);padding:22px;min-width:0}
|
|
275
275
|
.crumb{font-size:12px;color:var(--ink-3)} .crumb span+span::before{content:" / ";color:var(--line)}
|
|
276
|
-
.detail h3{font-size:20px;font-weight:600;margin:6px 0 10px;letter-spacing:-.01em}
|
|
276
|
+
.detail h3{font-size:20px;font-weight:600;margin:6px 0 10px;letter-spacing:-.01em;overflow-wrap:anywhere;min-width:0}
|
|
277
277
|
.badges{display:flex;gap:6px;flex-wrap:wrap;align-items:center;margin-bottom:18px}
|
|
278
278
|
.badge{font-size:12px;padding:3px 9px;border-radius:999px;font-weight:500}
|
|
279
279
|
.badge.passed{background:var(--pass-bg);color:var(--pass)} .badge.failed,.badge.timedOut,.badge.interrupted{background:var(--fail-bg);color:var(--fail)}
|
|
@@ -371,8 +371,8 @@ li.collapsed>ul{display:none}
|
|
|
371
371
|
.tags .bar i{display:block;height:100%}
|
|
372
372
|
/* meta chips in detail */
|
|
373
373
|
.metas{display:flex;flex-wrap:wrap;gap:6px;margin:-8px 0 18px}
|
|
374
|
-
.meta{display:inline-flex;align-items:center;gap:6px;font-size:12px;padding:4px 9px;border:1px solid var(--line);border-radius:4px;background:var(--surface-2)}
|
|
375
|
-
.meta .k{color:var(--ink-3)} .meta .v{font-weight:500}
|
|
374
|
+
.meta{display:inline-flex;align-items:center;gap:6px;font-size:12px;padding:4px 9px;border:1px solid var(--line);border-radius:4px;background:var(--surface-2);max-width:100%;min-width:0}
|
|
375
|
+
.meta .k{color:var(--ink-3);flex:none} .meta .v{font-weight:500;overflow-wrap:anywhere;min-width:0}
|
|
376
376
|
.meta.priority .v,.meta.severity .v{color:var(--fail)}
|
|
377
377
|
.meta.priority.low .v,.meta.severity.low .v{color:var(--ink)}
|
|
378
378
|
|
|
@@ -554,7 +554,7 @@ html,body{-webkit-font-smoothing:antialiased}
|
|
|
554
554
|
.dim .row[aria-pressed=true]{background:var(--accent-tint);outline:0}
|
|
555
555
|
.legend-inline{font-size:12px;gap:14px;margin-top:12px} .legend-inline span::before{width:9px;height:9px;border-radius:3px;margin-right:6px}
|
|
556
556
|
.slow li{padding:9px 0} .slow button{display:grid;grid-template-columns:1fr auto;gap:10px;align-items:start}
|
|
557
|
-
.slow .t{white-space:normal}
|
|
557
|
+
.slow .t{white-space:normal;overflow-wrap:anywhere;min-width:0}
|
|
558
558
|
.slow .bar{display:block;height:5px;border-radius:999px;background:var(--accent-soft);opacity:1;margin-top:6px;position:relative;overflow:hidden;width:100%!important}
|
|
559
559
|
.slow .bar i{position:absolute;inset:0;width:var(--w);background:var(--accent);border-radius:999px}
|
|
560
560
|
.slow button:hover .t{color:var(--accent-2)}
|
|
@@ -613,15 +613,17 @@ html,body{-webkit-font-smoothing:antialiased}
|
|
|
613
613
|
.attn2 td:first-child{border-radius:6px 0 0 6px} .attn2 td:last-child{border-radius:0 6px 6px 0}
|
|
614
614
|
.attn2 .pr{width:96px;white-space:nowrap;font-size:12.5px;line-height:1.35} .attn2 .pr .dot{display:inline-block;width:8px;height:8px;border-radius:50%;margin-right:7px}
|
|
615
615
|
.attn2 .pr span{font-weight:600;color:var(--ink)} .attn2 .pr small{display:block;color:var(--ink-3);font-size:11.5px;padding-left:15px}
|
|
616
|
-
.attn2 .tt .n{font-weight:500;color:var(--ink);line-height:1.35} .attn2 .tt .s{font-size:12px;color:var(--ink-3);margin-top:2px} .attn2 .tt .s b.new{color:var(--fail);font-weight:600} .attn2 .tt .s .rsn{color:var(--ink-2)}
|
|
616
|
+
.attn2 .tt{min-width:0} .attn2 .tt .n{font-weight:500;color:var(--ink);line-height:1.35;overflow-wrap:anywhere} .attn2 .tt .s{font-size:12px;color:var(--ink-3);margin-top:2px;overflow-wrap:anywhere} .attn2 .tt .s b.new{color:var(--fail);font-weight:600} .attn2 .tt .s .rsn{color:var(--ink-2)}
|
|
617
617
|
.attn2 .ow{text-align:right;color:var(--ink-3);font-size:12.5px;white-space:nowrap;width:1%;padding-top:10px}
|
|
618
618
|
.attn-foot{margin-top:auto;padding-top:12px;border-top:1px solid var(--line)} .attn-foot button{font-size:12.5px;font-weight:500;color:var(--accent-2)} .attn-foot button:hover{text-decoration:underline}
|
|
619
619
|
.clu2{list-style:none;margin:0;padding:0}
|
|
620
620
|
.clu2 li{display:grid;grid-template-columns:44px 1fr;gap:12px;padding:11px 0;border-top:1px solid var(--line)} .clu2 li:first-child{border-top:0;padding-top:2px}
|
|
621
621
|
.clu2 .n{font-size:20px;font-weight:600;color:var(--fail);font-variant-numeric:tabular-nums;line-height:1.1} .clu2 .n small{display:block;font-size:10.5px;color:var(--ink-3);font-weight:500;letter-spacing:.04em;margin-top:1px}
|
|
622
622
|
.clu2 .b{min-width:0} .clu2 .msg{font:12.5px/1.45 var(--mono);color:var(--ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
623
|
-
.clu2 .who{font-size:12.5px;color:var(--ink-2);margin-top:
|
|
624
|
-
.clu2 .who
|
|
623
|
+
.clu2 .who{font-size:12.5px;color:var(--ink-2);margin-top:5px;line-height:1.45;display:flex;flex-direction:column;align-items:flex-start;gap:3px}
|
|
624
|
+
.clu2 .who button{color:var(--accent-2);font-weight:500;text-align:left;white-space:normal;overflow-wrap:anywhere;max-width:100%;padding:0 0 0 14px;text-indent:-14px} .clu2 .who button .tri{display:inline-block;width:14px;text-indent:0;color:var(--ink-3);font-weight:400}
|
|
625
|
+
.clu2 .who button:hover{text-decoration:underline} .clu2 .who button small{color:var(--ink-3);font-weight:400;font-size:11px}
|
|
626
|
+
.clu2 .who button.ex{color:var(--ink-3);font-weight:400;cursor:pointer;text-indent:0} .clu2 .who button.ex:hover{color:var(--accent-2);text-decoration:underline}
|
|
625
627
|
@media (max-width:640px){.attn2 .ow{display:none} .attn2 .pr{width:72px} .attn2 .pr small{display:none}}
|
|
626
628
|
/* ---------- run status, errors outside tests, snippets ---------- */
|
|
627
629
|
.att-err{padding:12px 14px;background:var(--fail-bg);color:var(--ink);border-radius:8px 8px 0 0;line-height:1.5} .att-err code{font:12px var(--mono);background:var(--surface);padding:1px 5px;border-radius:4px}
|
|
@@ -630,9 +632,9 @@ html,body{-webkit-font-smoothing:antialiased}
|
|
|
630
632
|
.badge.xfail{background:var(--flaky-bg);color:var(--flaky-ink)}
|
|
631
633
|
.errwrap+.errwrap{margin-top:14px}
|
|
632
634
|
.why{border:1px solid var(--line);border-radius:var(--radius);padding:10px 14px;margin-bottom:10px;background:var(--surface)}
|
|
633
|
-
.why-c{font-size:12.5px;color:var(--ink);margin-bottom:2px;line-height:1.4} .why-c .why-k{margin-right:2px}
|
|
635
|
+
.why-c{font-size:12.5px;color:var(--ink);margin-bottom:2px;line-height:1.4;overflow-wrap:anywhere} .why-c .why-k{margin-right:2px}
|
|
634
636
|
.why-l{display:flex;align-items:center;gap:8px;margin-bottom:4px} .why-k{font-size:12px;font-weight:600;color:var(--fail)} .why-a{font:11.5px var(--mono);color:var(--ink-3)}
|
|
635
|
-
.why-s{font-size:var(--fs);color:var(--ink);line-height:1.45} .why-h{font-size:12.5px;color:var(--ink-2);margin-top:4px;line-height:1.45}
|
|
637
|
+
.why-s{font-size:var(--fs);color:var(--ink);line-height:1.45;overflow-wrap:anywhere} .why-h{font-size:12.5px;color:var(--ink-2);margin-top:4px;line-height:1.45;overflow-wrap:anywhere}
|
|
636
638
|
.why.assertion .why-k,.why.visual .why-k{color:#9A6A00}
|
|
637
639
|
.why.script .why-k,.why.thrown .why-k,.why.file .why-k{color:var(--ink-2)}
|
|
638
640
|
.errloc{font-family:var(--mono);color:var(--ink-3);margin-bottom:6px} .errloc a{color:var(--accent-2);text-decoration:none} .errloc a:hover{text-decoration:underline}
|
|
@@ -649,18 +651,18 @@ html,body{-webkit-font-smoothing:antialiased}
|
|
|
649
651
|
.badges .dots{margin-left:2px;margin-right:4px}
|
|
650
652
|
.flk{list-style:none;margin:0;padding:0} .flk li{border-top:1px solid var(--line)} .flk li:first-child{border-top:0}
|
|
651
653
|
.flk button{width:100%;text-align:left;padding:9px 4px;display:grid;grid-template-columns:1fr auto;gap:4px 12px;align-items:center}
|
|
652
|
-
.flk .t{font-size:13px;font-weight:500;color:var(--ink)} .flk .m{grid-column:1/-1;font-size:12px;color:var(--ink-3)}
|
|
654
|
+
.flk .t{font-size:13px;font-weight:500;color:var(--ink);overflow-wrap:anywhere;min-width:0} .flk .m{grid-column:1/-1;font-size:12px;color:var(--ink-3)}
|
|
653
655
|
.own{list-style:none;margin:0;padding:0;display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:10px} .own li{border:1px solid var(--line);border-radius:10px;min-width:0}
|
|
654
656
|
.own button{width:100%;text-align:left;padding:10px 12px;display:grid;grid-template-columns:1fr auto;gap:2px 12px;align-items:center;border-radius:10px}
|
|
655
657
|
.own button:hover{background:var(--surface-2)}
|
|
656
|
-
.own .nm{font-weight:600} .own .cnts{font-size:12.5px;color:var(--ink-3);white-space:nowrap} .own .cnts b{font-weight:600}
|
|
658
|
+
.own .nm{font-weight:600;overflow-wrap:anywhere;min-width:0} .own .cnts{font-size:12.5px;color:var(--ink-3);white-space:nowrap} .own .cnts b{font-weight:600}
|
|
657
659
|
.own .cnts b.f{color:var(--fail)} .own .cnts b.k{color:var(--flaky-ink)}
|
|
658
660
|
.own .ts{grid-column:1/-1;font-size:12px;color:var(--ink-3);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
659
661
|
.fx-tools{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:-4px} .fx-tools .fx-sum{font-size:13px;color:var(--ink-2);font-weight:500} .fx-tools .spacer{flex:1}
|
|
660
662
|
a.btn{text-decoration:none}
|
|
661
663
|
.skp{list-style:none;margin:0;padding:0} .skp li{border-top:1px solid var(--line)} .skp li:first-child{border-top:0}
|
|
662
664
|
.skp button{width:100%;text-align:left;padding:8px 4px;display:flex;flex-direction:column;gap:2px}
|
|
663
|
-
.skp .t{font-size:13px;font-weight:500;color:var(--ink)} .skp .r{font-size:12px;color:var(--ink-3)}
|
|
665
|
+
.skp .t{font-size:13px;font-weight:500;color:var(--ink);overflow-wrap:anywhere} .skp .r{font-size:12px;color:var(--ink-3)}
|
|
664
666
|
.gchart .gchart-head{display:flex;justify-content:space-between;align-items:flex-start;gap:12px}
|
|
665
667
|
.gchart .gchart-head h2{margin:0}
|
|
666
668
|
.gchart .gchart-sub{color:var(--ink-3);font-size:12px;margin-top:4px}
|
|
@@ -1366,17 +1368,43 @@ function errorSignature(msg){
|
|
|
1366
1368
|
}
|
|
1367
1369
|
function failureClusters(){
|
|
1368
1370
|
const m=new Map();
|
|
1369
|
-
for(const t of data.tests){ if(!isFail(t.outcome)) continue; const r=t.results[t.results.length-1]; const e=r&&r.errors[0]; const x=e&&e.explain; const
|
|
1371
|
+
for(const t of data.tests){ if(!isFail(t.outcome)) continue; const r=t.results[t.results.length-1]; const e=r&&r.errors[0]; const x=e&&e.explain; const msg=e?e.message:'(no error message)';
|
|
1372
|
+
// One cluster per root cause: the explain kind plus what it points at. An explanation that points at
|
|
1373
|
+
// nothing (a plain assertion) falls back to the message shape, so two different assertions stay apart.
|
|
1374
|
+
const sig=x&&!/^(thrown|script)$/.test(x.kind)? x.kind+'|'+(x.locator||x.url||'')+'|'+(x.matcher||x.action||'')+(x.locator||x.url?'':'|'+messageShape(msg)) : errorSignature(msg);
|
|
1375
|
+
const c=m.get(sig)||{sig,sample:msg.split('\n').map(l=>l.trim()).filter(Boolean).slice(0,3).join('\n'),why:x||null,tests:[]}; c.tests.push(t); m.set(sig,c); }
|
|
1370
1376
|
return [...m.values()].sort((a,b)=>b.tests.length-a.tests.length);
|
|
1371
1377
|
}
|
|
1378
|
+
// The message line shown under a cluster's explanation: the first line that says something the
|
|
1379
|
+
// explanation does not already say. Hamcrest's "Expected: is <200>" / "but: was <404>" is fully
|
|
1380
|
+
// covered by "The value was wrong: expected <200>, got <404>." so nothing is repeated.
|
|
1381
|
+
// The shape of a message: its first lines with numbers and quoted values blanked, so two assertions on
|
|
1382
|
+
// different values of the same kind cluster together, and assertions on different things stay apart.
|
|
1383
|
+
function messageShape(msg){ return String(msg||'').split('\n').map(l=>l.trim()).filter(Boolean).slice(0,3).map(errorSignature).join(' / '); }
|
|
1384
|
+
function cluToks(s){ const stop=/^(expected|expecting|but|is|was|were|received|actual|to|be|the|a|an|of|and|not|did|got|value|wrong|equal|have)$/; return new Set(String(s||'').toLowerCase().replace(/[^a-z0-9]+/g,' ').split(' ').filter(w=>w&&!stop.test(w))); }
|
|
1385
|
+
function clusterLine(c){
|
|
1386
|
+
const lines=c.sample.split('\n').filter(Boolean); if(!c.why) return lines[0]||c.sample;
|
|
1387
|
+
const have=cluToks(c.why.summary);
|
|
1388
|
+
// "Error: " / "AssertionError: " prefixes carry nothing; the expect(received).toBe(expected) hint line of
|
|
1389
|
+
// Jest-style matchers is a header, not a detail.
|
|
1390
|
+
const bare=l=>l.replace(/^[\w.$]*(Error|Exception|Failure|Throwable)(:\s*|$)/,'');
|
|
1391
|
+
return lines.find(l=>{ const b=bare(l); if(/^expect\(.*\)\.[\w.]+\(.*\)/.test(b)) return false; const t=[...cluToks(b)]; return t.length && t.some(w=>!have.has(w)); })||null;
|
|
1392
|
+
}
|
|
1393
|
+
// The tests of a cluster, six by name and "+N more" for the rest; the +N more button expands the list
|
|
1394
|
+
// in place and "show fewer" folds it again. Each name opens that test.
|
|
1395
|
+
function clusterWho(byTitle, all){
|
|
1396
|
+
const entries=[...byTitle.entries()], names=all?entries:entries.slice(0,6), extra=entries.length-names.length;
|
|
1397
|
+
const who=names.map(([title,ts])=>h('button',{onclick:()=>select(ts[0].id),title:ts.map(t=>t.project).join(', ')}, h('span',{class:'tri'},'›'), title, data.projects.length>1&&ts.length>1? h('small',{},' ×'+ts.length) : null));
|
|
1398
|
+
const swap=(ev,toAll)=>{ const d=ev.currentTarget.closest('.who'); d.replaceChildren(); for(const n of clusterWho(byTitle,toAll)) d.append(n); };
|
|
1399
|
+
if(extra>0) who.push(h('button',{class:'ex',onclick:ev=>swap(ev,true),title:'Show all '+entries.length+' tests'},'+'+extra+' more'));
|
|
1400
|
+
else if(all&&entries.length>6) who.push(h('button',{class:'ex',onclick:ev=>swap(ev,false)},'show fewer'));
|
|
1401
|
+
return who;
|
|
1402
|
+
}
|
|
1372
1403
|
function clustersView(cl){
|
|
1373
1404
|
return h('ol',{class:'clu2'}, cl.map(c=>{
|
|
1374
1405
|
const byTitle=new Map(); for(const t of c.tests){ const g=byTitle.get(t.title)||[]; g.push(t); byTitle.set(t.title,g); }
|
|
1375
|
-
const names=[...byTitle.entries()].slice(0,6), extra=byTitle.size-names.length;
|
|
1376
|
-
const who=[]; names.forEach(([title,ts],k)=>{ if(k) who.push(h('span',{class:'sep'},'·')); who.push(h('button',{onclick:()=>select(ts[0].id),title:ts.map(t=>t.project).join(', ')}, title, data.projects.length>1&&ts.length>1? h('small',{},' ×'+ts.length) : null)); });
|
|
1377
|
-
if(extra>0) who.push(h('span',{class:'sep'},'·'), h('span',{class:'ex'},'+'+extra+' more'));
|
|
1378
1406
|
return h('li',{}, h('span',{class:'n'}, c.tests.length, h('small',{},c.tests.length===1?'test':'tests')),
|
|
1379
|
-
h('div',{class:'b'}, c.why? h('div',{class:'why-c'}, h('span',{class:'why-k'},c.why.label), ' ', c.why.summary) : null, h('div',{class:'msg',title:c.sample}, c
|
|
1407
|
+
h('div',{class:'b'}, c.why? h('div',{class:'why-c'}, h('span',{class:'why-k'},c.why.label), ' ', c.why.summary) : null, (line=>line? h('div',{class:'msg',title:c.sample}, line):null)(clusterLine(c)), h('div',{class:'who'}, clusterWho(byTitle,false))));
|
|
1380
1408
|
}));
|
|
1381
1409
|
}
|
|
1382
1410
|
function trend(){
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "reporting-labs",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.10",
|
|
4
4
|
"description": "reportingLabs – beautiful, customizable single-file HTML test reports. One line of config. Playwright adapter included, more runners on the way.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"test-report",
|