@matware/e2e-runner 1.3.0 → 1.3.1
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/.claude-plugin/marketplace.json +37 -6
- package/.claude-plugin/plugin.json +17 -3
- package/LICENSE +190 -0
- package/README.md +61 -526
- package/bin/cli.js +5 -4
- package/commands/capture.md +45 -0
- package/package.json +1 -1
- package/src/actions.js +151 -0
- package/src/ai-generate.js +81 -0
- package/src/app-pool.js +339 -0
- package/src/config.js +125 -7
- package/src/dashboard.js +75 -8
- package/src/db.js +63 -7
- package/src/index.js +6 -4
- package/src/learner-sqlite.js +154 -0
- package/src/learner.js +70 -3
- package/src/mcp-tools.js +251 -32
- package/src/narrate.js +28 -0
- package/src/pool-manager.js +22 -16
- package/src/pool.js +301 -31
- package/src/reporter.js +4 -1
- package/src/runner.js +335 -55
- package/src/visual-diff.js +446 -0
- package/templates/dashboard/js/api.js +2 -0
- package/templates/dashboard/js/utils.js +20 -0
- package/templates/dashboard/js/view-live.js +40 -2
- package/templates/dashboard/js/view-runs.js +161 -57
- package/templates/dashboard/js/websocket.js +6 -0
- package/templates/dashboard/styles/components.css +7 -0
- package/templates/dashboard/styles/view-live.css +24 -1
- package/templates/dashboard/styles/view-runs.css +36 -0
- package/templates/dashboard/template.html +24 -9
- package/templates/dashboard.html +322 -310
|
@@ -88,7 +88,7 @@ function refreshRuns(){
|
|
|
88
88
|
var htr=document.createElement('tr');
|
|
89
89
|
var cols=[];
|
|
90
90
|
if(!S.project)cols.push('Project');
|
|
91
|
-
cols=cols.concat(['Suite','Source','Date','Total','Pass','Fail','Rate','Time']);
|
|
91
|
+
cols=cols.concat(['Suite','Driver','Source','Date','Total','Pass','Fail','Rate','Time']);
|
|
92
92
|
cols.forEach(function(c){htr.appendChild(el('th',null,c))});
|
|
93
93
|
head.textContent='';head.appendChild(htr);
|
|
94
94
|
var colSpan=cols.length;
|
|
@@ -107,6 +107,7 @@ function refreshRuns(){
|
|
|
107
107
|
if(r.id===S.selectedRun)tr.classList.add('expanded');
|
|
108
108
|
if(!S.project)tr.appendChild(el('td',{style:'font-weight:600'},r.project_name||'-'));
|
|
109
109
|
tr.appendChild(el('td',{style:'color:var(--accent)'},r.suite_name||'all'));
|
|
110
|
+
var driverTd=document.createElement('td');driverTd.appendChild(createDriverBadge(r.pool_driver));tr.appendChild(driverTd);
|
|
110
111
|
var srcTd=document.createElement('td');srcTd.appendChild(createTriggerBadge(r.triggered_by));tr.appendChild(srcTd);
|
|
111
112
|
tr.appendChild(el('td',null,fdate(r.generated_at)));
|
|
112
113
|
tr.appendChild(el('td',null,String(r.total||0)));
|
|
@@ -203,8 +204,10 @@ function loadDetailInline(id,detailTr){
|
|
|
203
204
|
])
|
|
204
205
|
]);
|
|
205
206
|
var srcBlock=el('div',null,[el('div',{className:'rd-s-label'},'Source'),el('div',{style:'margin-top:4px'},[createTriggerBadge(d.triggeredBy)])]);
|
|
207
|
+
var drvBlock=el('div',null,[el('div',{className:'rd-s-label'},'Driver'),el('div',{style:'margin-top:4px'},[createDriverBadge(d.poolDriver)])]);
|
|
206
208
|
var summ=el('div',{className:'rd-summary'},[
|
|
207
209
|
el('div',null,[el('div',{className:'rd-s-label'},'Suite'),el('div',{className:'rd-s-val',style:'font-size:14px;color:var(--accent)'},d.suiteName||'all')]),
|
|
210
|
+
drvBlock,
|
|
208
211
|
srcBlock,
|
|
209
212
|
el('div',null,[el('div',{className:'rd-s-label'},'Total'),el('div',{className:'rd-s-val'},String(d.summary.total))]),
|
|
210
213
|
el('div',null,[el('div',{className:'rd-s-label'},'Passed'),el('div',{className:'rd-s-val',style:'color:var(--green)'},String(d.summary.passed))]),
|
|
@@ -436,10 +439,8 @@ function refreshLearnings(){
|
|
|
436
439
|
fetch(url).then(function(r){return r.json()}).then(function(data){
|
|
437
440
|
if(!data||data.totalRuns===0){
|
|
438
441
|
$('#learningsEmpty').style.display='block';
|
|
439
|
-
$('#
|
|
440
|
-
$('#
|
|
441
|
-
$('#learningsPages').textContent='';$('#learningsApis').textContent='';
|
|
442
|
-
$('#learningsErrors').textContent='';
|
|
442
|
+
$('#learnHero').textContent='';$('#learnCards').textContent='';
|
|
443
|
+
$('#learnTrend').textContent='';$('#learnBottom').textContent='';
|
|
443
444
|
$('#badgeLearnings').textContent='-';
|
|
444
445
|
return;
|
|
445
446
|
}
|
|
@@ -464,48 +465,139 @@ function refreshLearnings(){
|
|
|
464
465
|
$('#badgeLearnings').textContent='\u2714';
|
|
465
466
|
$('#badgeLearnings').style.background='var(--green-dim)';$('#badgeLearnings').style.color='var(--green)';
|
|
466
467
|
}
|
|
467
|
-
|
|
468
|
+
renderLearnHero(data);
|
|
469
|
+
renderLearnCards(data);
|
|
468
470
|
renderLearnTrend(data.recentTrend||[]);
|
|
469
|
-
|
|
470
|
-
renderLearnSelectors(data.unstableSelectors||[]);
|
|
471
|
-
renderLearnPages(data.failingPages||[]);
|
|
472
|
-
renderLearnApis(data.apiIssues||[]);
|
|
473
|
-
renderLearnErrors(data.topErrors||[]);
|
|
471
|
+
renderLearnBottomRow(data);
|
|
474
472
|
}).catch(function(){$('#learningsEmpty').style.display='block'});
|
|
475
473
|
}
|
|
476
474
|
|
|
477
|
-
function
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
475
|
+
function rateColor(v){return v>=90?'var(--green)':v>=70?'var(--amber)':'var(--red)'}
|
|
476
|
+
function rateClass(v){return v>=90?'good':v>=70?'warn':'bad'}
|
|
477
|
+
function durFmt(ms){return ms<1000?Math.round(ms)+'ms':(ms/1000).toFixed(1)+'s'}
|
|
478
|
+
|
|
479
|
+
function renderLearnHero(d){
|
|
480
|
+
var c=$('#learnHero');c.textContent='';
|
|
481
|
+
var wrap=document.createElement('div');wrap.className='learn-hero';
|
|
482
|
+
var passRate=d.overallPassRate||0;
|
|
483
|
+
var ns='http://www.w3.org/2000/svg';
|
|
484
|
+
var ringWrap=document.createElement('div');ringWrap.className='learn-hero-ring';
|
|
485
|
+
var svg=document.createElementNS(ns,'svg');svg.setAttribute('viewBox','0 0 36 36');
|
|
486
|
+
var bgCircle=document.createElementNS(ns,'circle');bgCircle.setAttribute('cx','18');bgCircle.setAttribute('cy','18');bgCircle.setAttribute('r','15.9');bgCircle.className.baseVal='learn-hero-ring-bg';svg.appendChild(bgCircle);
|
|
487
|
+
var fgCircle=document.createElementNS(ns,'circle');fgCircle.setAttribute('cx','18');fgCircle.setAttribute('cy','18');fgCircle.setAttribute('r','15.9');fgCircle.className.baseVal='learn-hero-ring-fg';
|
|
488
|
+
var circ=2*Math.PI*15.9;fgCircle.setAttribute('stroke-dasharray',circ.toFixed(1));fgCircle.setAttribute('stroke-dashoffset',(circ*(1-passRate/100)).toFixed(1));fgCircle.setAttribute('stroke',rateColor(passRate));
|
|
489
|
+
svg.appendChild(fgCircle);ringWrap.appendChild(svg);
|
|
490
|
+
var pctEl=document.createElement('div');pctEl.className='learn-hero-pct';pctEl.style.color=rateColor(passRate);pctEl.textContent=passRate+'%';
|
|
491
|
+
ringWrap.appendChild(pctEl);wrap.appendChild(ringWrap);
|
|
492
|
+
|
|
493
|
+
var stats=document.createElement('div');stats.className='learn-hero-stats';
|
|
494
|
+
var badSels=d.unstableSelectors?d.unstableSelectors.length:0;
|
|
495
|
+
var slowTests=d.failingPages?d.failingPages.length:0;
|
|
496
|
+
var apiIssues=d.apiIssues?d.apiIssues.length:0;
|
|
497
|
+
var topErr=d.topErrors&&d.topErrors.length>0?d.topErrors[0].occurrence_count:0;
|
|
498
|
+
var flakyCount=d.flakyTests?d.flakyTests.length:0;
|
|
499
|
+
var items=[
|
|
500
|
+
{val:String(d.totalRuns),lbl:'Runs',color:'var(--accent)'},
|
|
501
|
+
{val:String(d.totalTests),lbl:'Tests',color:'var(--accent)'},
|
|
502
|
+
{val:durFmt(d.avgDurationMs||0),lbl:'Avg Duration',color:'var(--purple)'},
|
|
503
|
+
{val:String(flakyCount),lbl:'Flaky',color:flakyCount>0?'var(--amber)':'var(--green)'},
|
|
504
|
+
{val:String(badSels),lbl:'Bad Selectors',color:badSels>0?'var(--red)':'var(--green)'},
|
|
505
|
+
{val:String(slowTests),lbl:'Slow Pages',color:slowTests>0?'var(--amber)':'var(--green)'},
|
|
506
|
+
{val:String(apiIssues),lbl:'API Issues',color:apiIssues>0?'var(--red)':'var(--green)'},
|
|
507
|
+
{val:String(topErr),lbl:'Top Error Hits',color:topErr>0?'var(--red)':'var(--green)'}
|
|
508
|
+
];
|
|
509
|
+
items.forEach(function(it){
|
|
510
|
+
var statEl=document.createElement('div');statEl.className='learn-hero-stat';
|
|
511
|
+
var valEl=document.createElement('div');valEl.className='learn-hero-stat-val';valEl.style.color=it.color;valEl.textContent=it.val;
|
|
512
|
+
var lblEl=document.createElement('div');lblEl.className='learn-hero-stat-lbl';lblEl.textContent=it.lbl;
|
|
513
|
+
statEl.appendChild(valEl);statEl.appendChild(lblEl);stats.appendChild(statEl);
|
|
490
514
|
});
|
|
491
|
-
|
|
515
|
+
wrap.appendChild(stats);c.appendChild(wrap);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
function makeLearnItem(label,sub,pct,valText,color){
|
|
519
|
+
var item=document.createElement('div');item.className='learn-item';
|
|
520
|
+
var barWrap=document.createElement('div');barWrap.className='learn-item-bar';
|
|
521
|
+
var lblEl=document.createElement('div');lblEl.className='learn-item-label';
|
|
522
|
+
var codeEl=document.createElement('code');codeEl.textContent=label;lblEl.appendChild(codeEl);
|
|
523
|
+
barWrap.appendChild(lblEl);
|
|
524
|
+
if(sub){var subEl=document.createElement('div');subEl.className='learn-item-sub';subEl.textContent=sub;barWrap.appendChild(subEl)}
|
|
525
|
+
var bar=document.createElement('div');bar.className='learn-bar';
|
|
526
|
+
var fill=document.createElement('div');fill.className='learn-bar-fill';fill.style.width=Math.min(pct,100)+'%';fill.style.background=color;
|
|
527
|
+
bar.appendChild(fill);barWrap.appendChild(bar);
|
|
528
|
+
item.appendChild(barWrap);
|
|
529
|
+
var valEl=document.createElement('div');valEl.className='learn-item-val';valEl.style.color=color;valEl.textContent=valText;
|
|
530
|
+
item.appendChild(valEl);
|
|
531
|
+
return item;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
function makeLearnCard(icon,title,emptyMsg){
|
|
535
|
+
var card=document.createElement('div');card.className='learn-card';
|
|
536
|
+
var titleEl=document.createElement('div');titleEl.className='learn-card-title';
|
|
537
|
+
var iconEl=document.createElement('span');iconEl.className='lc-icon';iconEl.textContent=icon;
|
|
538
|
+
titleEl.appendChild(iconEl);titleEl.appendChild(document.createTextNode(title));
|
|
539
|
+
card.appendChild(titleEl);
|
|
540
|
+
card._empty=emptyMsg;
|
|
541
|
+
return card;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
function renderLearnCards(d){
|
|
545
|
+
var c=$('#learnCards');c.textContent='';
|
|
546
|
+
|
|
547
|
+
var selCard=makeLearnCard('\u26A0','Risky Selectors','No unstable selectors');
|
|
548
|
+
var sels=d.unstableSelectors||[];
|
|
549
|
+
if(!sels.length){var e1=document.createElement('div');e1.className='learn-card-empty';e1.textContent=selCard._empty;selCard.appendChild(e1)}
|
|
550
|
+
else{sels.slice(0,5).forEach(function(s){
|
|
551
|
+
var sel=s.selector.length>40?s.selector.slice(0,37)+'...':s.selector;
|
|
552
|
+
selCard.appendChild(makeLearnItem(sel,s.action_type+' \u00B7 '+s.total_uses+' uses',parseFloat(s.fail_rate),s.fail_rate+'%',parseFloat(s.fail_rate)>30?'var(--red)':'var(--amber)'));
|
|
553
|
+
})}
|
|
554
|
+
c.appendChild(selCard);
|
|
555
|
+
|
|
556
|
+
var pageCard=makeLearnCard('\u23F1','Problem Pages','No failing pages');
|
|
557
|
+
var pages=d.failingPages||[];
|
|
558
|
+
if(!pages.length){var e2=document.createElement('div');e2.className='learn-card-empty';e2.textContent=pageCard._empty;pageCard.appendChild(e2)}
|
|
559
|
+
else{pages.slice(0,5).forEach(function(p){
|
|
560
|
+
pageCard.appendChild(makeLearnItem(p.url_path,p.total_visits+' visits \u00B7 '+p.console_errors+' console errs',parseFloat(p.fail_rate),p.fail_rate+'%',parseFloat(p.fail_rate)>30?'var(--red)':'var(--amber)'));
|
|
561
|
+
})}
|
|
562
|
+
c.appendChild(pageCard);
|
|
563
|
+
|
|
564
|
+
var flakyCard=makeLearnCard('\u223C','Flaky Tests','No flaky tests detected');
|
|
565
|
+
var flaky=d.flakyTests||[];
|
|
566
|
+
if(!flaky.length){var e3=document.createElement('div');e3.className='learn-card-empty';e3.textContent=flakyCard._empty;flakyCard.appendChild(e3)}
|
|
567
|
+
else{flaky.slice(0,5).forEach(function(f){
|
|
568
|
+
flakyCard.appendChild(makeLearnItem(f.test_name,'Attempt avg '+f.avg_attempts+' \u00B7 '+f.total_runs+' runs',parseFloat(f.flaky_rate),f.flaky_rate+'%',parseFloat(f.flaky_rate)>30?'var(--red)':'var(--amber)'));
|
|
569
|
+
})}
|
|
570
|
+
c.appendChild(flakyCard);
|
|
571
|
+
|
|
572
|
+
var apiCard=makeLearnCard('\u21C4','API Issues','No API issues');
|
|
573
|
+
var apis=d.apiIssues||[];
|
|
574
|
+
if(!apis.length){var e4=document.createElement('div');e4.className='learn-card-empty';e4.textContent=apiCard._empty;apiCard.appendChild(e4)}
|
|
575
|
+
else{apis.slice(0,5).forEach(function(a){
|
|
576
|
+
var ep=a.endpoint.length>40?a.endpoint.slice(0,37)+'...':a.endpoint;
|
|
577
|
+
apiCard.appendChild(makeLearnItem(ep,a.total_calls+' calls \u00B7 '+durFmt(a.avg_duration_ms),parseFloat(a.error_rate),a.error_rate+'%',parseFloat(a.error_rate)>20?'var(--red)':'var(--amber)'));
|
|
578
|
+
})}
|
|
579
|
+
c.appendChild(apiCard);
|
|
492
580
|
}
|
|
493
581
|
|
|
494
582
|
function renderLearnTrend(trend){
|
|
495
|
-
var container=$('#
|
|
583
|
+
var container=$('#learnTrend');container.textContent='';
|
|
496
584
|
if(!trend.length)return;
|
|
497
|
-
var card=document.createElement('div');card.className='card';
|
|
498
|
-
var
|
|
499
|
-
var
|
|
585
|
+
var card=document.createElement('div');card.className='learn-card';
|
|
586
|
+
var titleEl=document.createElement('div');titleEl.className='learn-card-title';
|
|
587
|
+
var iconEl=document.createElement('span');iconEl.className='lc-icon';iconEl.textContent='\u2197';
|
|
588
|
+
titleEl.appendChild(iconEl);titleEl.appendChild(document.createTextNode('Pass Rate Trend'));
|
|
589
|
+
card.appendChild(titleEl);
|
|
590
|
+
var chartDiv=document.createElement('div');chartDiv.style.cssText='height:80px;width:100%';
|
|
500
591
|
var w=100/trend.length;var ns='http://www.w3.org/2000/svg';
|
|
501
|
-
var svg=document.createElementNS(ns,'svg');svg.setAttribute('viewBox','0 0 100 100');svg.setAttribute('preserveAspectRatio','none');
|
|
592
|
+
var svg=document.createElementNS(ns,'svg');svg.setAttribute('viewBox','0 0 100 100');svg.setAttribute('preserveAspectRatio','none');svg.style.cssText='width:100%;height:100%';
|
|
502
593
|
var bg=document.createElementNS(ns,'rect');bg.setAttribute('x','0');bg.setAttribute('y','0');bg.setAttribute('width','100');bg.setAttribute('height','100');bg.setAttribute('fill','var(--surface2)');bg.setAttribute('rx','2');svg.appendChild(bg);
|
|
503
594
|
var gridLine=document.createElementNS(ns,'line');gridLine.setAttribute('x1','0');gridLine.setAttribute('y1','50');gridLine.setAttribute('x2','100');gridLine.setAttribute('y2','50');gridLine.setAttribute('stroke','var(--border)');gridLine.setAttribute('stroke-width','0.3');gridLine.setAttribute('stroke-dasharray','2,2');svg.appendChild(gridLine);
|
|
504
595
|
var pts=trend.map(function(t,i){return(i*w+w/2)+','+(100-t.pass_rate)}).join(' ');
|
|
505
596
|
var poly=document.createElementNS(ns,'polygon');poly.setAttribute('points',(0*w+w/2)+',100 '+pts+' '+((trend.length-1)*w+w/2)+',100');poly.setAttribute('fill','var(--accent-dim)');svg.appendChild(poly);
|
|
506
597
|
var pl=document.createElementNS(ns,'polyline');pl.setAttribute('points',pts);pl.setAttribute('fill','none');pl.setAttribute('stroke','var(--accent)');pl.setAttribute('stroke-width','1.5');svg.appendChild(pl);
|
|
507
598
|
trend.forEach(function(t,i){
|
|
508
|
-
var
|
|
599
|
+
var color=rateColor(t.pass_rate);
|
|
600
|
+
var circle=document.createElementNS(ns,'circle');circle.setAttribute('cx',''+(i*w+w/2));circle.setAttribute('cy',''+(100-t.pass_rate));circle.setAttribute('r','2.5');circle.setAttribute('fill',color);
|
|
509
601
|
var title=document.createElementNS(ns,'title');title.textContent=t.date+': '+t.pass_rate+'% ('+t.total_tests+' tests)';circle.appendChild(title);svg.appendChild(circle);
|
|
510
602
|
});
|
|
511
603
|
chartDiv.appendChild(svg);card.appendChild(chartDiv);
|
|
@@ -514,35 +606,47 @@ function renderLearnTrend(trend){
|
|
|
514
606
|
card.appendChild(dates);container.appendChild(card);
|
|
515
607
|
}
|
|
516
608
|
|
|
517
|
-
function
|
|
518
|
-
var
|
|
519
|
-
|
|
520
|
-
var
|
|
521
|
-
var
|
|
522
|
-
var
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
var
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
609
|
+
function renderLearnBottomRow(d){
|
|
610
|
+
var c=$('#learnBottom');c.textContent='';
|
|
611
|
+
|
|
612
|
+
var errCard=makeLearnCard('\u2718','Most Common Errors','No errors recorded');
|
|
613
|
+
var errors=d.topErrors||[];
|
|
614
|
+
if(!errors.length){var e1=document.createElement('div');e1.className='learn-card-empty';e1.textContent=errCard._empty;errCard.appendChild(e1)}
|
|
615
|
+
else{errors.slice(0,5).forEach(function(e){
|
|
616
|
+
var pat=e.pattern.length>45?e.pattern.slice(0,42)+'...':e.pattern;
|
|
617
|
+
var maxCount=errors[0].occurrence_count||1;
|
|
618
|
+
var pct=(e.occurrence_count/maxCount)*100;
|
|
619
|
+
var verdictEl=document.createElement('div');verdictEl.className='learn-verdict '+rateClass(100-(pct));verdictEl.textContent=e.category.replace(/-/g,' ');
|
|
620
|
+
var item=makeLearnItem(pat,(e.last_seen||'').split('T')[0]+' \u00B7 '+e.occurrence_count+'x',pct,e.occurrence_count+'x','var(--red)');
|
|
621
|
+
item.insertBefore(verdictEl,item.lastChild);
|
|
622
|
+
errCard.appendChild(item);
|
|
623
|
+
})}
|
|
624
|
+
c.appendChild(errCard);
|
|
625
|
+
|
|
626
|
+
var slowCard=makeLearnCard('\u23F3','Slowest Tests','No slow test data');
|
|
627
|
+
var trend=d.recentTrend||[];
|
|
628
|
+
var slowTests=[];
|
|
629
|
+
if(d.flakyTests){
|
|
630
|
+
d.flakyTests.forEach(function(f){
|
|
631
|
+
if(f.avg_duration_ms&&f.avg_duration_ms>2000){slowTests.push({name:f.test_name,dur:f.avg_duration_ms})}
|
|
534
632
|
});
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
633
|
+
}
|
|
634
|
+
if(d.failingPages){
|
|
635
|
+
d.failingPages.forEach(function(p){
|
|
636
|
+
if(p.avg_load_time_ms&&p.avg_load_time_ms>3000){slowTests.push({name:p.url_path,dur:p.avg_load_time_ms})}
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
slowTests.sort(function(a,b){return b.dur-a.dur});
|
|
640
|
+
if(!slowTests.length){var e2=document.createElement('div');e2.className='learn-card-empty';e2.textContent=slowCard._empty;slowCard.appendChild(e2)}
|
|
641
|
+
else{
|
|
642
|
+
var maxDur=slowTests[0].dur;
|
|
643
|
+
slowTests.slice(0,5).forEach(function(t){
|
|
644
|
+
slowCard.appendChild(makeLearnItem(t.name,'','',durFmt(t.dur),(t.dur/maxDur)*100,t.dur>5000?'var(--red)':'var(--amber)'));
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
c.appendChild(slowCard);
|
|
538
648
|
}
|
|
539
649
|
|
|
540
|
-
function renderLearnFlaky(flaky){var c=$('#learningsFlaky');c.textContent='';if(!flaky.length)return;c.appendChild(buildLearnTable('Flaky Tests',['Test','Flaky Rate','Occurrences','Total Runs','Last Flaky','Avg Attempts'],flaky.map(function(f){return[{code:f.test_name},{badge:f.flaky_rate+'%',cls:f.flaky_rate>30?'fail':'flaky'},{text:f.flaky_count},{text:f.total_runs},{text:(f.last_flaky||'-').split('T')[0]},{text:f.avg_attempts}]})))}
|
|
541
|
-
function renderLearnSelectors(sels){var c=$('#learningsSelectors');c.textContent='';if(!sels.length)return;c.appendChild(buildLearnTable('Unstable Selectors',['Selector','Action','Fail Rate','Uses','Tests','Page'],sels.map(function(s){var sel=s.selector.length>45?s.selector.slice(0,42)+'...':s.selector;return[{code:sel},{text:s.action_type},{badge:s.fail_rate+'%',cls:s.fail_rate>30?'fail':'flaky'},{text:s.total_uses},{text:s.used_by_tests},{text:s.page_url||'-'}]})))}
|
|
542
|
-
function renderLearnPages(pages){var c=$('#learningsPages');c.textContent='';if(!pages.length)return;c.appendChild(buildLearnTable('Failing Pages',['Page','Fail Rate','Visits','Console Errors','Network Errors'],pages.map(function(p){return[{code:p.url_path},{badge:p.fail_rate+'%',cls:p.fail_rate>30?'fail':'flaky'},{text:p.total_visits},{text:p.console_errors},{text:p.network_errors}]})))}
|
|
543
|
-
function renderLearnApis(apis){var c=$('#learningsApis');c.textContent='';if(!apis.length)return;c.appendChild(buildLearnTable('API Issues',['Endpoint','Error Rate','Calls','Avg Duration','Status Codes'],apis.map(function(a){var ep=a.endpoint.length>45?a.endpoint.slice(0,42)+'...':a.endpoint;var d=a.avg_duration_ms<1000?Math.round(a.avg_duration_ms)+'ms':(a.avg_duration_ms/1000).toFixed(1)+'s';return[{code:ep},{badge:a.error_rate+'%',cls:a.error_rate>20?'fail':'flaky'},{text:a.total_calls},{text:d},{text:a.status_codes||'-'}]})))}
|
|
544
|
-
function renderLearnErrors(errors){var c=$('#learningsErrors');c.textContent='';if(!errors.length)return;c.appendChild(buildLearnTable('Error Patterns',['Pattern','Category','Count','First Seen','Last Seen','Example Test'],errors.map(function(e){var pat=e.pattern.length>50?e.pattern.slice(0,47)+'...':e.pattern;return[{text:pat},{badge:e.category,cls:'run'},{text:e.occurrence_count},{text:(e.first_seen||'-').split('T')[0]},{text:(e.last_seen||'-').split('T')[0]},{code:e.example_test||'-'}]})))}
|
|
545
|
-
|
|
546
650
|
$('#btnRefreshLearnings').addEventListener('click',refreshLearnings);
|
|
547
651
|
$('#learningsDays').addEventListener('change',refreshLearnings);
|
|
548
652
|
|
|
@@ -104,6 +104,12 @@ function handleWS(m){
|
|
|
104
104
|
var r7=getLiveRun(m);if(r7){r7.on=false;r7.done=true;r7.tests.__error={status:'failed',error:m.error}}
|
|
105
105
|
showToast('Run error: '+m.error,'error');
|
|
106
106
|
renderLive();break;
|
|
107
|
+
case 'test:frame':
|
|
108
|
+
if(S.screencastTest===m.name&&m.data){
|
|
109
|
+
var img=$('#screencastImg');
|
|
110
|
+
if(img)img.src='data:image/jpeg;base64,'+m.data;
|
|
111
|
+
}
|
|
112
|
+
break;
|
|
107
113
|
case 'db:updated':
|
|
108
114
|
refreshRuns();refreshProjects();refreshScreenshots();refreshLearnings();refreshWatch();break;
|
|
109
115
|
}
|
|
@@ -77,6 +77,13 @@ tbody tr.selected td{background:var(--accent-dim)}
|
|
|
77
77
|
.trigger-badge.src-unknown{background:rgba(70,75,98,.15);color:var(--text3)}
|
|
78
78
|
.trigger-badge .trig-icon{font-size:11px;line-height:1}
|
|
79
79
|
|
|
80
|
+
/* ── Driver Badges ── */
|
|
81
|
+
.driver-badge{display:inline-flex;align-items:center;gap:4px;padding:2px 8px;border-radius:10px;font-size:10px;font-weight:600;font-family:var(--mono);white-space:nowrap}
|
|
82
|
+
.driver-badge.drv-browserless{background:var(--accent-dim)}
|
|
83
|
+
.driver-badge.drv-cdp{background:var(--purple-dim)}
|
|
84
|
+
.driver-badge.drv-steel{background:var(--amber-dim)}
|
|
85
|
+
.driver-badge .drv-icon{font-size:11px;line-height:1}
|
|
86
|
+
|
|
80
87
|
/* ── Filter Bar ── */
|
|
81
88
|
.filter-bar{display:flex;align-items:center;gap:8px;margin-bottom:16px;flex-wrap:wrap}
|
|
82
89
|
.filter-btn{padding:5px 12px;border-radius:var(--r);border:1px solid var(--border);background:var(--surface2);color:var(--text2);font-family:var(--mono);font-size:11px;cursor:pointer;transition:all .15s}
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
.live-stats span strong{color:var(--text)}
|
|
12
12
|
.live-progress{height:3px;background:var(--surface3)}
|
|
13
13
|
.live-progress-fill{height:100%;background:var(--purple);transition:width .4s;border-radius:0 2px 2px 0}
|
|
14
|
-
.live-tests{padding:12px 16px;display:flex;flex-direction:column;gap:2px;
|
|
14
|
+
.live-tests{padding:12px 16px;display:flex;flex-direction:column;gap:2px;overflow-y:auto;min-height:0;flex:1}
|
|
15
15
|
.live-test{padding:10px 12px;border-radius:var(--r);border-left:3px solid var(--text3);background:var(--surface2);font-size:11px;transition:border-color .2s,padding .25s,max-height .35s cubic-bezier(.4,0,.2,1)}
|
|
16
16
|
.live-test.running{border-left-color:var(--purple)}
|
|
17
17
|
.live-test.passed{border-left-color:var(--green)}
|
|
@@ -68,6 +68,29 @@
|
|
|
68
68
|
.lr-dismiss{padding:2px 6px;font-size:9px;font-family:var(--mono);background:transparent;border:1px solid transparent;border-radius:4px;color:var(--text3);cursor:pointer;transition:all .15s;margin-left:auto}
|
|
69
69
|
.lr-dismiss:hover{color:var(--red);border-color:rgba(239,68,68,.3);background:var(--red-dim)}
|
|
70
70
|
|
|
71
|
+
/* ── Screencast Panel ── */
|
|
72
|
+
.live-body{display:flex;flex:1;min-height:0;overflow:hidden}
|
|
73
|
+
.live-body .live-tests{flex:1;min-width:0}
|
|
74
|
+
.screencast-panel{width:420px;flex-shrink:0;display:flex;flex-direction:column;border-left:1px solid var(--border);background:var(--surface2)}
|
|
75
|
+
.screencast-header{display:flex;align-items:center;gap:10px;padding:10px 14px;border-bottom:1px solid var(--border);background:var(--surface3)}
|
|
76
|
+
.screencast-label{font-size:11px;font-weight:600;color:var(--purple);white-space:nowrap}
|
|
77
|
+
.screencast-select{flex:1;padding:4px 8px;font-size:10px;font-family:var(--mono);background:var(--surface);border:1px solid var(--border);border-radius:4px;color:var(--text);outline:none;cursor:pointer}
|
|
78
|
+
.screencast-select:focus{border-color:var(--purple)}
|
|
79
|
+
.screencast-viewport{flex:1;display:flex;align-items:center;justify-content:center;overflow:hidden;background:#000;position:relative}
|
|
80
|
+
.screencast-viewport img{max-width:100%;max-height:100%;object-fit:contain;display:none}
|
|
81
|
+
.screencast-placeholder{display:flex;align-items:center;justify-content:center;width:100%;height:100%;color:var(--text3);font-size:12px;font-family:var(--mono)}
|
|
82
|
+
|
|
83
|
+
/* ── Screencast focus badge on test cards ── */
|
|
84
|
+
.sc-focus-badge{cursor:pointer;font-size:10px;padding:1px 4px;border-radius:3px;opacity:.4;transition:all .15s}
|
|
85
|
+
.sc-focus-badge:hover{opacity:.8}
|
|
86
|
+
.sc-focus-badge.active{opacity:1;background:var(--purple-dim);border-radius:3px}
|
|
87
|
+
|
|
88
|
+
/* ── Screencast toggle in Tests view ── */
|
|
89
|
+
.screencast-toggle-label{display:flex;align-items:center;gap:4px;cursor:pointer;font-size:14px;padding:4px 8px;border-radius:4px;border:1px solid var(--border);background:var(--surface2);transition:all .15s;user-select:none}
|
|
90
|
+
.screencast-toggle-label:hover{border-color:var(--purple);background:var(--surface3)}
|
|
91
|
+
.screencast-toggle-label input{display:none}
|
|
92
|
+
.screencast-toggle-label:has(input:checked){border-color:var(--purple);background:var(--purple-dim);color:var(--purple)}
|
|
93
|
+
|
|
71
94
|
.live-nav-dot{display:inline-block;width:8px;height:8px;border-radius:50%;background:var(--purple);animation:pulse 1.5s infinite}
|
|
72
95
|
.spinner{display:inline-block;width:12px;height:12px;border:2px solid var(--border);border-top-color:var(--purple);border-radius:50%;animation:spin .6s linear infinite;vertical-align:middle}
|
|
73
96
|
.spinner-small{display:inline-block;width:8px;height:8px;border:1.5px solid var(--border);border-top-color:var(--purple);border-radius:50%;animation:spin .6s linear infinite;vertical-align:middle}
|
|
@@ -200,6 +200,42 @@ tr.expanded td:first-child::before{content:'';position:absolute;left:0;top:0;bot
|
|
|
200
200
|
.learn-trend-chart{width:100%;height:100px;margin-bottom:20px}
|
|
201
201
|
.learn-trend-chart svg{width:100%;height:100%}
|
|
202
202
|
|
|
203
|
+
/* ── Learnings Dashboard (visual cards) ── */
|
|
204
|
+
.learn-hero{display:flex;align-items:center;gap:24px;margin-bottom:20px;padding:20px 24px;background:var(--surface);border:1px solid var(--border);border-radius:var(--r)}
|
|
205
|
+
.learn-hero-ring{position:relative;width:100px;height:100px;flex-shrink:0}
|
|
206
|
+
.learn-hero-ring svg{width:100%;height:100%;transform:rotate(-90deg)}
|
|
207
|
+
.learn-hero-ring-bg{fill:none;stroke:var(--surface3);stroke-width:8}
|
|
208
|
+
.learn-hero-ring-fg{fill:none;stroke-width:8;stroke-linecap:round;transition:stroke-dashoffset .6s ease}
|
|
209
|
+
.learn-hero-pct{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-size:22px;font-weight:700;font-family:var(--mono)}
|
|
210
|
+
.learn-hero-stats{flex:1;display:grid;grid-template-columns:repeat(4,1fr);gap:12px}
|
|
211
|
+
.learn-hero-stat{text-align:center}
|
|
212
|
+
.learn-hero-stat-val{font-size:18px;font-weight:700;font-family:var(--mono)}
|
|
213
|
+
.learn-hero-stat-lbl{font-size:9px;color:var(--text3);text-transform:uppercase;letter-spacing:.08em;margin-top:2px}
|
|
214
|
+
|
|
215
|
+
.learn-cols{display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-bottom:16px}
|
|
216
|
+
@media(max-width:900px){.learn-cols{grid-template-columns:1fr}}
|
|
217
|
+
|
|
218
|
+
.learn-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--r);padding:14px 16px}
|
|
219
|
+
.learn-card-title{font-size:11px;font-weight:600;color:var(--text2);text-transform:uppercase;letter-spacing:.08em;margin-bottom:10px;display:flex;align-items:center;gap:6px}
|
|
220
|
+
.learn-card-title .lc-icon{font-size:13px}
|
|
221
|
+
.learn-card-empty{font-size:11px;color:var(--text3);font-style:italic}
|
|
222
|
+
|
|
223
|
+
.learn-item{display:flex;align-items:center;gap:10px;padding:6px 0;border-bottom:1px solid var(--border)}
|
|
224
|
+
.learn-item:last-child{border-bottom:none}
|
|
225
|
+
.learn-item-bar{flex:1;min-width:0}
|
|
226
|
+
.learn-item-label{font-size:11px;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-bottom:3px}
|
|
227
|
+
.learn-item-label code{background:var(--surface3);padding:1px 4px;border-radius:3px;font-size:10px}
|
|
228
|
+
.learn-item-sub{font-size:9px;color:var(--text3)}
|
|
229
|
+
.learn-item-val{font-size:13px;font-weight:700;font-family:var(--mono);flex-shrink:0;min-width:44px;text-align:right}
|
|
230
|
+
|
|
231
|
+
.learn-bar{height:4px;border-radius:2px;background:var(--surface3);overflow:hidden;margin-top:3px}
|
|
232
|
+
.learn-bar-fill{height:100%;border-radius:2px;transition:width .4s ease}
|
|
233
|
+
|
|
234
|
+
.learn-verdict{display:inline-flex;align-items:center;gap:4px;padding:3px 8px;border-radius:10px;font-size:10px;font-weight:600}
|
|
235
|
+
.learn-verdict.good{background:var(--green-dim);color:var(--green)}
|
|
236
|
+
.learn-verdict.warn{background:var(--amber-dim);color:var(--amber)}
|
|
237
|
+
.learn-verdict.bad{background:var(--red-dim);color:var(--red)}
|
|
238
|
+
|
|
203
239
|
/* ── Pool Distribution ── */
|
|
204
240
|
.pool-dist{display:flex;align-items:stretch;gap:0;border-radius:6px;overflow:hidden;height:22px;margin:8px 0;font-size:10px;font-weight:600;font-family:var(--mono)}
|
|
205
241
|
.pool-dist-seg{display:flex;align-items:center;justify-content:center;gap:4px;padding:0 8px;color:#fff;white-space:nowrap;min-width:40px;transition:flex .3s}
|
|
@@ -79,7 +79,11 @@
|
|
|
79
79
|
<div class="view" id="view-tests">
|
|
80
80
|
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:16px">
|
|
81
81
|
<div style="font-family:var(--sans);font-size:16px;font-weight:600">Tests</div>
|
|
82
|
-
<div style="display:flex;gap:8px">
|
|
82
|
+
<div style="display:flex;gap:8px;align-items:center">
|
|
83
|
+
<label class="screencast-toggle-label" title="Enable live browser screencast during test runs">
|
|
84
|
+
<input type="checkbox" id="screencastToggle" />
|
|
85
|
+
<span>📹</span>
|
|
86
|
+
</label>
|
|
83
87
|
<button class="btn sm primary" id="btnRunAll">▷ Run All</button>
|
|
84
88
|
</div>
|
|
85
89
|
</div>
|
|
@@ -181,13 +185,12 @@
|
|
|
181
185
|
<button class="btn sm" id="btnRefreshLearnings">Refresh</button>
|
|
182
186
|
</div>
|
|
183
187
|
</div>
|
|
184
|
-
<div id="
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
<div id="learningsErrors"></div>
|
|
188
|
+
<div id="learnDash">
|
|
189
|
+
<div id="learnHero"></div>
|
|
190
|
+
<div id="learnCards" class="learn-cols"></div>
|
|
191
|
+
<div id="learnTrend"></div>
|
|
192
|
+
<div id="learnBottom" class="learn-cols"></div>
|
|
193
|
+
</div>
|
|
191
194
|
<div class="empty" id="learningsEmpty" style="display:none">
|
|
192
195
|
<div class="empty-icon">★</div>
|
|
193
196
|
<p>No learnings data yet. Run some tests to start building knowledge.</p>
|
|
@@ -214,7 +217,19 @@
|
|
|
214
217
|
<button class="live-clear-btn" id="liveClearBtn">Clear All</button>
|
|
215
218
|
</div>
|
|
216
219
|
<div class="live-progress"><div class="live-progress-fill" id="liveProgressFill" style="width:0"></div></div>
|
|
217
|
-
<div class="live-
|
|
220
|
+
<div class="live-body">
|
|
221
|
+
<div class="live-tests" id="liveTests"></div>
|
|
222
|
+
<div class="screencast-panel" id="screencastPanel" style="display:none">
|
|
223
|
+
<div class="screencast-header">
|
|
224
|
+
<span class="screencast-label">📹 Screencast</span>
|
|
225
|
+
<select id="screencastSelect" class="screencast-select"><option value="">Select test...</option></select>
|
|
226
|
+
</div>
|
|
227
|
+
<div class="screencast-viewport">
|
|
228
|
+
<img id="screencastImg" alt="Browser screencast" />
|
|
229
|
+
<div class="screencast-placeholder" id="screencastPlaceholder">Select a running test to watch</div>
|
|
230
|
+
</div>
|
|
231
|
+
</div>
|
|
232
|
+
</div>
|
|
218
233
|
</div>
|
|
219
234
|
<div class="empty" id="liveEmpty">
|
|
220
235
|
<div class="empty-icon" style="font-size:48px;opacity:.3">●</div>
|