@matware/e2e-runner 1.1.0 → 1.1.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/README.md +64 -6
- package/bin/cli.js +81 -1
- package/package.json +2 -2
- package/src/actions.js +11 -3
- package/src/ai-generate.js +35 -4
- package/src/config.js +30 -0
- package/src/dashboard.js +17 -4
- package/src/db.js +28 -7
- package/src/mcp-tools.js +131 -2
- package/src/reporter.js +13 -1
- package/src/runner.js +69 -3
- package/src/verify.js +16 -4
- package/templates/dashboard.html +248 -11
package/templates/dashboard.html
CHANGED
|
@@ -53,12 +53,13 @@ a{color:var(--accent);text-decoration:none}
|
|
|
53
53
|
.ws-dot{width:6px;height:6px;border-radius:50%;display:inline-block;margin-right:4px}
|
|
54
54
|
|
|
55
55
|
/* ── Main ── */
|
|
56
|
-
.main{margin-left:232px;flex:1;min-height:100vh}
|
|
56
|
+
.main{margin-left:232px;flex:1;min-height:100vh;display:flex;flex-direction:column}
|
|
57
57
|
.main-header{padding:16px 24px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:12px;background:var(--surface);position:sticky;top:0;z-index:40}
|
|
58
58
|
.main-header .title{font-family:var(--sans);font-size:16px;font-weight:600}
|
|
59
59
|
.main-header .actions{margin-left:auto;display:flex;gap:8px}
|
|
60
60
|
.view{display:none;padding:24px}
|
|
61
61
|
.view.active{display:block}
|
|
62
|
+
#view-live.active{display:flex;flex-direction:column;flex:1;min-height:calc(100vh - 0px);padding:16px}
|
|
62
63
|
|
|
63
64
|
/* ── Buttons ── */
|
|
64
65
|
.btn{display:inline-flex;align-items:center;gap:6px;padding:7px 14px;border-radius:var(--r);font-family:var(--mono);font-size:11px;font-weight:500;cursor:pointer;border:1px solid var(--border);background:var(--surface2);color:var(--text);transition:all .15s;white-space:nowrap}
|
|
@@ -116,8 +117,8 @@ tbody tr.selected td{background:var(--accent-dim)}
|
|
|
116
117
|
.suite-card-tests li::before{content:">";position:absolute;left:0;color:var(--text3)}
|
|
117
118
|
|
|
118
119
|
/* ── Live Execution ── */
|
|
119
|
-
.live-panel{display:none;background:var(--surface);border:1px solid var(--purple);border-radius:var(--r);overflow:hidden;animation:fadeSlide .3s ease}
|
|
120
|
-
.live-panel.active{display:
|
|
120
|
+
.live-panel{display:none;background:var(--surface);border:1px solid var(--purple);border-radius:var(--r);overflow:hidden;animation:fadeSlide .3s ease;flex-direction:column}
|
|
121
|
+
.live-panel.active{display:flex;flex:1;min-height:0}
|
|
121
122
|
@keyframes fadeSlide{from{opacity:0;transform:translateY(-8px)}to{opacity:1;transform:translateY(0)}}
|
|
122
123
|
.live-header{padding:14px 16px;display:flex;align-items:center;gap:16px;border-bottom:1px solid var(--border);background:var(--purple-dim)}
|
|
123
124
|
.live-header .label{font-weight:600;color:var(--purple);font-size:12px;display:flex;align-items:center;gap:8px}
|
|
@@ -128,7 +129,7 @@ tbody tr.selected td{background:var(--accent-dim)}
|
|
|
128
129
|
.live-stats span strong{color:var(--text)}
|
|
129
130
|
.live-progress{height:3px;background:var(--surface3)}
|
|
130
131
|
.live-progress-fill{height:100%;background:var(--purple);transition:width .4s;border-radius:0 2px 2px 0}
|
|
131
|
-
.live-tests{padding:12px 16px;display:flex;flex-direction:column;gap:2px;
|
|
132
|
+
.live-tests{padding:12px 16px;display:flex;flex-direction:column;gap:2px;flex:1;overflow-y:auto;min-height:0}
|
|
132
133
|
.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)}
|
|
133
134
|
.live-test.running{border-left-color:var(--purple)}
|
|
134
135
|
.live-test.passed{border-left-color:var(--green)}
|
|
@@ -175,6 +176,10 @@ tbody tr.selected td{background:var(--accent-dim)}
|
|
|
175
176
|
.live-done.has-failures{background:var(--red-dim);color:var(--red)}
|
|
176
177
|
.live-close{padding:4px 10px;font-size:10px;background:transparent;border:1px solid var(--border);border-radius:var(--r);color:var(--text2);cursor:pointer}
|
|
177
178
|
.live-close:hover{color:var(--text);border-color:var(--border-hi)}
|
|
179
|
+
.live-clear-btn{padding:5px 12px;font-size:10px;font-family:var(--mono);font-weight:500;background:var(--surface2);border:1px solid var(--border);border-radius:var(--r);color:var(--text2);cursor:pointer;display:none;transition:all .15s}
|
|
180
|
+
.live-clear-btn:hover{color:var(--text);border-color:var(--border-hi);background:var(--surface3)}
|
|
181
|
+
.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}
|
|
182
|
+
.lr-dismiss:hover{color:var(--red);border-color:rgba(239,68,68,.3);background:var(--red-dim)}
|
|
178
183
|
|
|
179
184
|
.live-nav-dot{display:inline-block;width:8px;height:8px;border-radius:50%;background:var(--purple);animation:pulse 1.5s infinite}
|
|
180
185
|
.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}
|
|
@@ -189,6 +194,16 @@ tbody tr.selected td{background:var(--accent-dim)}
|
|
|
189
194
|
.gallery-item .cap{padding:6px 10px;font-size:10px;color:var(--text2);display:flex;align-items:center;gap:4px}
|
|
190
195
|
.gallery-item .cap .cap-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0}
|
|
191
196
|
.gallery-item .cap .ss-hash{flex-shrink:0}
|
|
197
|
+
.ss-search{display:flex;gap:8px;margin-bottom:16px;align-items:center}
|
|
198
|
+
.ss-search input{flex:1;max-width:320px;padding:8px 12px;border-radius:var(--r);border:1px solid var(--border);background:var(--surface2);color:var(--text);font-family:var(--mono);font-size:12px}
|
|
199
|
+
.ss-search input:focus{outline:none;border-color:var(--accent)}
|
|
200
|
+
.ss-search input::placeholder{color:var(--text3)}
|
|
201
|
+
.ss-search button{padding:8px 16px;border-radius:var(--r);border:1px solid var(--border);background:var(--surface2);color:var(--text);font-family:var(--mono);font-size:12px;cursor:pointer;transition:background .15s,border-color .15s}
|
|
202
|
+
.ss-search button:hover{background:var(--surface3);border-color:var(--accent)}
|
|
203
|
+
.ss-search-result{margin-bottom:20px;padding:12px;background:var(--surface);border:1px solid var(--border);border-radius:var(--r)}
|
|
204
|
+
.ss-search-result img{max-width:100%;max-height:500px;border-radius:var(--r);cursor:pointer;display:block;margin-top:8px}
|
|
205
|
+
.ss-search-result .ss-result-label{font-size:11px;color:var(--text2);display:flex;align-items:center;gap:6px}
|
|
206
|
+
.ss-search-error{font-size:11px;color:var(--red);margin-bottom:12px}
|
|
192
207
|
|
|
193
208
|
/* ── Inline Run Detail ── */
|
|
194
209
|
.run-detail-row td{padding:0!important;border-bottom:2px solid var(--border-hi)}
|
|
@@ -217,6 +232,34 @@ tbody tr.selected td{background:var(--accent-dim)}
|
|
|
217
232
|
.rd-log-item{font-size:10px;padding:3px 8px;border-left:2px solid var(--border);margin-bottom:2px;color:var(--text2);word-break:break-all}
|
|
218
233
|
.rd-log-item.error{border-left-color:var(--red);color:var(--red)}
|
|
219
234
|
.rd-log-item.warning,.rd-log-item.warn{border-left-color:var(--amber);color:var(--amber)}
|
|
235
|
+
.rd-net-toggle{display:flex;align-items:center;gap:6px;cursor:pointer;font-size:10px;color:var(--text3);font-family:var(--mono);padding:2px 0;user-select:none;margin-top:10px}
|
|
236
|
+
.rd-net-toggle:hover{color:var(--text)}
|
|
237
|
+
.rd-net-toggle .net-arrow{transition:transform .2s;font-size:8px}
|
|
238
|
+
.rd-net-toggle.open .net-arrow{transform:rotate(90deg)}
|
|
239
|
+
.rd-net-list{display:none;margin-top:6px}
|
|
240
|
+
.rd-net-toggle.open+.rd-net-list{display:block}
|
|
241
|
+
.rd-net-row{display:flex;align-items:center;gap:8px;padding:3px 8px;font-size:10px;font-family:var(--mono);border-left:2px solid var(--border);margin-bottom:0;cursor:pointer;transition:background .15s}
|
|
242
|
+
.rd-net-row:hover{background:var(--surface2)}
|
|
243
|
+
.rd-net-row.has-error{border-left-color:var(--red)}
|
|
244
|
+
.rd-net-method{display:inline-block;padding:1px 5px;border-radius:3px;font-size:9px;font-weight:700;min-width:36px;text-align:center}
|
|
245
|
+
.rd-net-method.get{background:var(--accent-dim);color:var(--accent)}
|
|
246
|
+
.rd-net-method.post{background:var(--green-dim);color:var(--green)}
|
|
247
|
+
.rd-net-method.put,.rd-net-method.patch{background:var(--amber-dim);color:var(--amber)}
|
|
248
|
+
.rd-net-method.delete{background:var(--red-dim);color:var(--red)}
|
|
249
|
+
.rd-net-status{display:inline-block;padding:1px 5px;border-radius:3px;font-size:9px;font-weight:600;min-width:28px;text-align:center}
|
|
250
|
+
.rd-net-status.s2xx{background:var(--green-dim);color:var(--green)}
|
|
251
|
+
.rd-net-status.s3xx{background:var(--amber-dim);color:var(--amber)}
|
|
252
|
+
.rd-net-status.s4xx,.rd-net-status.s5xx{background:var(--red-dim);color:var(--red)}
|
|
253
|
+
.rd-net-url{color:var(--text2);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0}
|
|
254
|
+
.rd-net-dur{color:var(--text3);flex-shrink:0}
|
|
255
|
+
.rd-net-expand{font-size:8px;color:var(--text3);transition:transform .2s;flex-shrink:0}
|
|
256
|
+
.rd-net-row.open .rd-net-expand{transform:rotate(90deg)}
|
|
257
|
+
.rd-net-detail{display:none;margin:0 0 4px 0;padding:8px 12px;background:var(--surface);border:1px solid var(--border);border-radius:var(--r);font-size:10px;font-family:var(--mono);overflow:hidden}
|
|
258
|
+
.rd-net-row.open+.rd-net-detail{display:block}
|
|
259
|
+
.rd-net-detail-section{margin-bottom:8px}
|
|
260
|
+
.rd-net-detail-section:last-child{margin-bottom:0}
|
|
261
|
+
.rd-net-detail-title{font-size:9px;font-weight:700;color:var(--text3);text-transform:uppercase;letter-spacing:.08em;margin-bottom:4px}
|
|
262
|
+
.rd-net-detail-body{white-space:pre-wrap;word-break:break-all;color:var(--text2);max-height:300px;overflow-y:auto;line-height:1.5}
|
|
220
263
|
.rd-retries{font-size:10px;color:var(--amber);margin-bottom:6px}
|
|
221
264
|
.rd-summary{display:flex;gap:16px;margin-bottom:14px;padding:10px 14px;background:var(--surface);border:1px solid var(--border);border-radius:var(--r);font-size:11px;align-items:center}
|
|
222
265
|
.rd-summary .rd-s-label{color:var(--text3);font-size:9px;text-transform:uppercase;letter-spacing:.08em}
|
|
@@ -240,6 +283,14 @@ tr.expanded td:first-child::before{content:'';position:absolute;left:0;top:0;bot
|
|
|
240
283
|
.ss-hash.copied{border-color:var(--green);color:var(--green);background:var(--green-dim)}
|
|
241
284
|
.ss-hash .ss-icon{font-size:10px;line-height:1}
|
|
242
285
|
|
|
286
|
+
/* ── Trigger Source Badges ── */
|
|
287
|
+
.trigger-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}
|
|
288
|
+
.trigger-badge.src-dashboard{background:rgba(127,140,162,.10);color:var(--text2)}
|
|
289
|
+
.trigger-badge.src-mcp{background:var(--purple-dim);color:var(--purple)}
|
|
290
|
+
.trigger-badge.src-cli{background:var(--accent-dim);color:var(--accent)}
|
|
291
|
+
.trigger-badge.src-unknown{background:rgba(70,75,98,.15);color:var(--text3)}
|
|
292
|
+
.trigger-badge .trig-icon{font-size:11px;line-height:1}
|
|
293
|
+
|
|
243
294
|
/* ── Responsive ── */
|
|
244
295
|
@media(max-width:768px){
|
|
245
296
|
.sidebar{width:60px}.sidebar-logo h1,.sidebar-section-label,.nav-item span:not(.icon),.pool-info,.sidebar select,.sidebar-logo .ver{display:none}
|
|
@@ -312,6 +363,7 @@ tr.expanded td:first-child::before{content:'';position:absolute;left:0;top:0;bot
|
|
|
312
363
|
<span style="color:var(--red)">Fail: <strong id="liveFail">0</strong></span>
|
|
313
364
|
<span style="color:var(--purple)">Active: <strong id="liveActive">0</strong></span>
|
|
314
365
|
</div>
|
|
366
|
+
<button class="live-clear-btn" id="liveClearBtn">Clear All</button>
|
|
315
367
|
</div>
|
|
316
368
|
<div class="live-progress"><div class="live-progress-fill" id="liveProgressFill" style="width:0"></div></div>
|
|
317
369
|
<div class="live-tests" id="liveTests"></div>
|
|
@@ -360,6 +412,11 @@ tr.expanded td:first-child::before{content:'';position:absolute;left:0;top:0;bot
|
|
|
360
412
|
<!-- Screenshots View -->
|
|
361
413
|
<div class="view" id="view-screenshots">
|
|
362
414
|
<div style="font-family:var(--sans);font-size:16px;font-weight:600;margin-bottom:20px">Screenshots</div>
|
|
415
|
+
<div class="ss-search">
|
|
416
|
+
<input type="text" id="ssHashInput" placeholder="Search by hash (e.g. ss:a3f2b1c9)" spellcheck="false">
|
|
417
|
+
<button id="ssHashBtn">Search</button>
|
|
418
|
+
</div>
|
|
419
|
+
<div id="ssSearchResult"></div>
|
|
363
420
|
<div class="gallery" id="screenshotGallery"></div>
|
|
364
421
|
<div class="empty" id="screenshotsEmpty" style="display:none">
|
|
365
422
|
<div class="empty-icon">▣</div>
|
|
@@ -393,6 +450,60 @@ function css(n){return n.replace(/[^a-zA-Z0-9\-_]/g,'_')}
|
|
|
393
450
|
function dur(ms){return ms>=1000?(ms/1000).toFixed(1)+'s':ms+'ms'}
|
|
394
451
|
function fdate(iso){return iso?new Date(iso).toLocaleString():'--'}
|
|
395
452
|
|
|
453
|
+
/** Pretty-print a string if it's JSON, otherwise return as-is */
|
|
454
|
+
function prettyJson(str){
|
|
455
|
+
if(!str)return '';
|
|
456
|
+
try{return JSON.stringify(JSON.parse(str),null,2)}catch(e){return str}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/** Format headers object as readable string */
|
|
460
|
+
function fmtHeaders(h){
|
|
461
|
+
if(!h||typeof h!=='object')return '';
|
|
462
|
+
return Object.keys(h).map(function(k){return k+': '+h[k]}).join('\n');
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/** Build a clickable network row + expandable detail panel */
|
|
466
|
+
function buildNetRow(n){
|
|
467
|
+
var mCls='rd-net-method '+n.method.toLowerCase();
|
|
468
|
+
var sCls='rd-net-status '+(n.status<300?'s2xx':n.status<400?'s3xx':n.status<500?'s4xx':'s5xx');
|
|
469
|
+
var hasDetail=n.requestBody||n.responseBody||n.requestHeaders||n.responseHeaders;
|
|
470
|
+
var rowCls='rd-net-row'+(n.status>=400?' has-error':'');
|
|
471
|
+
var row=el('div',{className:rowCls},[
|
|
472
|
+
hasDetail?el('span',{className:'rd-net-expand'},'\u25B6'):null,
|
|
473
|
+
el('span',{className:mCls},n.method),
|
|
474
|
+
el('span',{className:sCls},String(n.status)+(n.statusText?' '+n.statusText:'')),
|
|
475
|
+
el('span',{className:'rd-net-url'},n.url),
|
|
476
|
+
el('span',{className:'rd-net-dur'},dur(n.duration))
|
|
477
|
+
]);
|
|
478
|
+
var detail=null;
|
|
479
|
+
if(hasDetail){
|
|
480
|
+
var sections=[];
|
|
481
|
+
if(n.requestHeaders){
|
|
482
|
+
var s=el('div',{className:'rd-net-detail-section'},[el('div',{className:'rd-net-detail-title'},'Request Headers')]);
|
|
483
|
+
s.appendChild(el('div',{className:'rd-net-detail-body'},fmtHeaders(n.requestHeaders)));
|
|
484
|
+
sections.push(s);
|
|
485
|
+
}
|
|
486
|
+
if(n.requestBody){
|
|
487
|
+
var s2=el('div',{className:'rd-net-detail-section'},[el('div',{className:'rd-net-detail-title'},'Request Body')]);
|
|
488
|
+
s2.appendChild(el('div',{className:'rd-net-detail-body'},prettyJson(n.requestBody)));
|
|
489
|
+
sections.push(s2);
|
|
490
|
+
}
|
|
491
|
+
if(n.responseHeaders){
|
|
492
|
+
var s3=el('div',{className:'rd-net-detail-section'},[el('div',{className:'rd-net-detail-title'},'Response Headers')]);
|
|
493
|
+
s3.appendChild(el('div',{className:'rd-net-detail-body'},fmtHeaders(n.responseHeaders)));
|
|
494
|
+
sections.push(s3);
|
|
495
|
+
}
|
|
496
|
+
if(n.responseBody){
|
|
497
|
+
var s4=el('div',{className:'rd-net-detail-section'},[el('div',{className:'rd-net-detail-title'},'Response Body')]);
|
|
498
|
+
s4.appendChild(el('div',{className:'rd-net-detail-body'},prettyJson(n.responseBody)));
|
|
499
|
+
sections.push(s4);
|
|
500
|
+
}
|
|
501
|
+
detail=el('div',{className:'rd-net-detail'},sections);
|
|
502
|
+
row.addEventListener('click',function(e){e.stopPropagation();row.classList.toggle('open')});
|
|
503
|
+
}
|
|
504
|
+
return {row:row,detail:detail};
|
|
505
|
+
}
|
|
506
|
+
|
|
396
507
|
/* ── Screenshot hash helpers ── */
|
|
397
508
|
var ssHashCache={};
|
|
398
509
|
async function ssHash(filePath){
|
|
@@ -419,6 +530,18 @@ function createHashBadge(hash){
|
|
|
419
530
|
return badge;
|
|
420
531
|
}
|
|
421
532
|
|
|
533
|
+
/* ── Trigger source badge helper ── */
|
|
534
|
+
function createTriggerBadge(source){
|
|
535
|
+
var s=source||'unknown';
|
|
536
|
+
var labels={dashboard:'Dashboard',mcp:'MCP',cli:'CLI',unknown:'--'};
|
|
537
|
+
var icons={dashboard:'\u{1F464}',mcp:'\u{1F916}',cli:'>_',unknown:'\u2022'};
|
|
538
|
+
var badge=el('span',{className:'trigger-badge src-'+s},[
|
|
539
|
+
el('span',{className:'trig-icon'},icons[s]||icons.unknown),
|
|
540
|
+
document.createTextNode(labels[s]||s)
|
|
541
|
+
]);
|
|
542
|
+
return badge;
|
|
543
|
+
}
|
|
544
|
+
|
|
422
545
|
/* ── State ── */
|
|
423
546
|
var S={
|
|
424
547
|
ws:null,project:null,view:'suites',selectedRun:null,
|
|
@@ -454,21 +577,30 @@ function connectWS(){
|
|
|
454
577
|
|
|
455
578
|
function getLiveRun(m){
|
|
456
579
|
var rid=m.runId;if(!rid)return null;
|
|
457
|
-
if(!S.liveRuns[rid])S.liveRuns[rid]={on:true,done:false,total:0,completed:0,passed:0,failed:0,active:0,tests:{},project:m.project||null,cwd:m.cwd||null,runId:rid,_lastEvent:Date.now()};
|
|
580
|
+
if(!S.liveRuns[rid])S.liveRuns[rid]={on:true,done:false,total:0,completed:0,passed:0,failed:0,active:0,tests:{},project:m.project||null,cwd:m.cwd||null,triggeredBy:m.triggeredBy||null,runId:rid,_lastEvent:Date.now()};
|
|
458
581
|
S.liveRuns[rid]._lastEvent=Date.now();
|
|
459
582
|
return S.liveRuns[rid];
|
|
460
583
|
}
|
|
461
584
|
function anyLiveRunning(){for(var k in S.liveRuns)if(S.liveRuns[k].on)return true;return false}
|
|
462
585
|
|
|
463
|
-
/* Staleness guard:
|
|
586
|
+
/* Staleness guard: auto-finish stuck runs, garbage-collect old finished runs */
|
|
464
587
|
setInterval(function(){
|
|
465
588
|
var changed=false;
|
|
466
589
|
for(var k in S.liveRuns){
|
|
467
590
|
var r=S.liveRuns[k];
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
591
|
+
var age=Date.now()-r._lastEvent;
|
|
592
|
+
/* Mark stuck runs as done */
|
|
593
|
+
if(r.on&&!r.done){
|
|
594
|
+
/* 0/0 runs (never received any tests) — mark stale after 10s */
|
|
595
|
+
if(r.total===0&&age>10000){r.on=false;r.done=true;r.stale=true;r.active=0;changed=true}
|
|
596
|
+
/* All tests completed but run:complete never arrived */
|
|
597
|
+
else if(r.completed>=r.total&&r.total>0&&age>15000){r.on=false;r.done=true;r.active=0;changed=true}
|
|
598
|
+
/* General staleness — no events for 30s */
|
|
599
|
+
else if(age>30000){r.on=false;r.done=true;r.stale=true;r.active=0;changed=true}
|
|
471
600
|
}
|
|
601
|
+
/* Auto-remove stale 0/0 runs after 15s, finished runs after 120s */
|
|
602
|
+
if(r.done&&r.stale&&r.total===0&&age>15000){delete S.liveRuns[k];changed=true}
|
|
603
|
+
else if(r.done&&age>120000){delete S.liveRuns[k];changed=true}
|
|
472
604
|
}
|
|
473
605
|
if(changed)renderLive();
|
|
474
606
|
},5000);
|
|
@@ -477,6 +609,8 @@ function handleWS(m){
|
|
|
477
609
|
switch(m.event){
|
|
478
610
|
case 'pool:status':renderPool(m.data);break;
|
|
479
611
|
case 'run:start':
|
|
612
|
+
/* Clear all finished/stale runs when a new one starts */
|
|
613
|
+
for(var dk in S.liveRuns){if(S.liveRuns[dk].done)delete S.liveRuns[dk]}
|
|
480
614
|
var r=getLiveRun(m);
|
|
481
615
|
r.total=m.total;r.on=true;r.done=false;
|
|
482
616
|
S.liveExpanded=new Set();S.liveSSOpen=new Set();
|
|
@@ -506,6 +640,7 @@ function handleWS(m){
|
|
|
506
640
|
r.tests[m.name].duration=m.duration;
|
|
507
641
|
if(m.screenshots&&m.screenshots.length)r.tests[m.name].screenshots=m.screenshots;
|
|
508
642
|
if(m.errorScreenshot)r.tests[m.name].errorScreenshot=m.errorScreenshot;
|
|
643
|
+
if(m.networkLogs&&m.networkLogs.length)r.tests[m.name].networkLogs=m.networkLogs;
|
|
509
644
|
}
|
|
510
645
|
r.active=Math.max(0,r.active-1);
|
|
511
646
|
renderLive();break;
|
|
@@ -619,7 +754,7 @@ function refreshRuns(){
|
|
|
619
754
|
var htr=document.createElement('tr');
|
|
620
755
|
var cols=[];
|
|
621
756
|
if(!S.project)cols.push('Project');
|
|
622
|
-
cols=cols.concat(['Suite','Date','Total','Pass','Fail','Rate','Time']);
|
|
757
|
+
cols=cols.concat(['Suite','Source','Date','Total','Pass','Fail','Rate','Time']);
|
|
623
758
|
cols.forEach(function(c){htr.appendChild(el('th',null,c))});
|
|
624
759
|
head.textContent='';head.appendChild(htr);
|
|
625
760
|
var colSpan=cols.length;
|
|
@@ -640,6 +775,7 @@ function refreshRuns(){
|
|
|
640
775
|
if(r.id===S.selectedRun)tr.classList.add('expanded');
|
|
641
776
|
if(!S.project)tr.appendChild(el('td',{style:'font-weight:600'},r.project_name||'-'));
|
|
642
777
|
tr.appendChild(el('td',{style:'color:var(--accent)'},r.suite_name||'all'));
|
|
778
|
+
var srcTd=document.createElement('td');srcTd.appendChild(createTriggerBadge(r.triggered_by));tr.appendChild(srcTd);
|
|
643
779
|
tr.appendChild(el('td',null,fdate(r.generated_at)));
|
|
644
780
|
tr.appendChild(el('td',null,String(r.total||0)));
|
|
645
781
|
tr.appendChild(el('td',{style:'color:var(--green)'},String(r.passed||0)));
|
|
@@ -726,8 +862,10 @@ function loadDetailInline(id,detailTr){
|
|
|
726
862
|
var results=d.results||[];
|
|
727
863
|
|
|
728
864
|
// Summary bar
|
|
865
|
+
var srcBlock=el('div',null,[el('div',{className:'rd-s-label'},'Source'),el('div',{style:'margin-top:4px'},[createTriggerBadge(d.triggeredBy)])]);
|
|
729
866
|
var summ=el('div',{className:'rd-summary'},[
|
|
730
867
|
el('div',null,[el('div',{className:'rd-s-label'},'Suite'),el('div',{className:'rd-s-val',style:'font-size:13px;color:var(--accent)'},d.suiteName||'all')]),
|
|
868
|
+
srcBlock,
|
|
731
869
|
el('div',null,[el('div',{className:'rd-s-label'},'Total'),el('div',{className:'rd-s-val'},String(d.summary.total))]),
|
|
732
870
|
el('div',null,[el('div',{className:'rd-s-label'},'Passed'),el('div',{className:'rd-s-val',style:'color:var(--green)'},String(d.summary.passed))]),
|
|
733
871
|
el('div',null,[el('div',{className:'rd-s-label'},'Failed'),el('div',{className:'rd-s-val',style:'color:var(--red)'},String(d.summary.failed))]),
|
|
@@ -812,6 +950,27 @@ function loadDetailInline(id,detailTr){
|
|
|
812
950
|
body.appendChild(netSec);
|
|
813
951
|
}
|
|
814
952
|
|
|
953
|
+
// Network API logs (collapsible, clickable rows)
|
|
954
|
+
if(r.networkLogs&&r.networkLogs.length){
|
|
955
|
+
var errCount=r.networkLogs.filter(function(n){return n.status>=400}).length;
|
|
956
|
+
var netLabel='Network ('+r.networkLogs.length+' request'+(r.networkLogs.length!==1?'s':'')+(errCount?', '+errCount+' error'+(errCount!==1?'s':''):'')+')';
|
|
957
|
+
var netToggle=el('div',{className:'rd-net-toggle'},[
|
|
958
|
+
el('span',{className:'net-arrow'},'\u25B6'),
|
|
959
|
+
el('span',{},netLabel)
|
|
960
|
+
]);
|
|
961
|
+
var netList=el('div',{className:'rd-net-list'});
|
|
962
|
+
r.networkLogs.forEach(function(n){
|
|
963
|
+
var built=buildNetRow(n);
|
|
964
|
+
netList.appendChild(built.row);
|
|
965
|
+
if(built.detail)netList.appendChild(built.detail);
|
|
966
|
+
});
|
|
967
|
+
netToggle.addEventListener('click',function(){
|
|
968
|
+
netToggle.classList.toggle('open');
|
|
969
|
+
});
|
|
970
|
+
body.appendChild(netToggle);
|
|
971
|
+
body.appendChild(netList);
|
|
972
|
+
}
|
|
973
|
+
|
|
815
974
|
var testCard=el('div',{className:'rd-test'},[head,body]);
|
|
816
975
|
inner.appendChild(testCard);
|
|
817
976
|
});
|
|
@@ -849,7 +1008,56 @@ function refreshScreenshots(){
|
|
|
849
1008
|
}).catch(function(){});
|
|
850
1009
|
}
|
|
851
1010
|
|
|
1011
|
+
/* ── Screenshot Hash Search ── */
|
|
1012
|
+
function searchByHash(){
|
|
1013
|
+
var container=$('#ssSearchResult');
|
|
1014
|
+
container.textContent='';
|
|
1015
|
+
var raw=$('#ssHashInput').value.trim();
|
|
1016
|
+
if(!raw)return;
|
|
1017
|
+
var hash=raw.replace(/^ss:/,'');
|
|
1018
|
+
if(!/^[a-f0-9]{1,8}$/i.test(hash)){
|
|
1019
|
+
container.appendChild(el('div',{className:'ss-search-error'},'Invalid hash format. Expected 8 hex characters (e.g. ss:a3f2b1c9).'));
|
|
1020
|
+
return;
|
|
1021
|
+
}
|
|
1022
|
+
fetch('/api/screenshot-hash/'+hash).then(function(res){
|
|
1023
|
+
if(!res.ok){
|
|
1024
|
+
container.appendChild(el('div',{className:'ss-search-error'},'Screenshot not found for hash: ss:'+hash));
|
|
1025
|
+
return;
|
|
1026
|
+
}
|
|
1027
|
+
return res.blob();
|
|
1028
|
+
}).then(function(blob){
|
|
1029
|
+
if(!blob)return;
|
|
1030
|
+
var url=URL.createObjectURL(blob);
|
|
1031
|
+
var wrap=el('div',{className:'ss-search-result'},[
|
|
1032
|
+
el('div',{className:'ss-result-label'},[
|
|
1033
|
+
createHashBadge(hash),
|
|
1034
|
+
el('span',{},'Found')
|
|
1035
|
+
])
|
|
1036
|
+
]);
|
|
1037
|
+
var img=document.createElement('img');
|
|
1038
|
+
img.src=url;
|
|
1039
|
+
img.alt='ss:'+hash;
|
|
1040
|
+
img.addEventListener('click',function(){openModal(url)});
|
|
1041
|
+
wrap.appendChild(img);
|
|
1042
|
+
container.appendChild(wrap);
|
|
1043
|
+
}).catch(function(){
|
|
1044
|
+
container.appendChild(el('div',{className:'ss-search-error'},'Error searching for screenshot.'));
|
|
1045
|
+
});
|
|
1046
|
+
}
|
|
1047
|
+
$('#ssHashBtn').addEventListener('click',searchByHash);
|
|
1048
|
+
$('#ssHashInput').addEventListener('keydown',function(e){if(e.key==='Enter')searchByHash()});
|
|
1049
|
+
|
|
852
1050
|
/* ── Live Execution ── */
|
|
1051
|
+
function clearFinishedLiveRuns(){
|
|
1052
|
+
for(var k in S.liveRuns){if(S.liveRuns[k].done||!S.liveRuns[k].on)delete S.liveRuns[k]}
|
|
1053
|
+
renderLive();
|
|
1054
|
+
}
|
|
1055
|
+
function dismissLiveRun(rid){
|
|
1056
|
+
delete S.liveRuns[rid];
|
|
1057
|
+
renderLive();
|
|
1058
|
+
}
|
|
1059
|
+
$('#liveClearBtn').addEventListener('click',clearFinishedLiveRuns);
|
|
1060
|
+
|
|
853
1061
|
function renderLive(){
|
|
854
1062
|
var panel=$('#livePanel'),grid=$('#liveTests'),navLive=$('#navLive'),liveEmpty=$('#liveEmpty');
|
|
855
1063
|
var runs=S.liveRuns;
|
|
@@ -859,6 +1067,7 @@ function renderLive(){
|
|
|
859
1067
|
panel.classList.remove('active');
|
|
860
1068
|
navLive.style.display='none';
|
|
861
1069
|
liveEmpty.style.display='block';
|
|
1070
|
+
$('#liveClearBtn').style.display='none';
|
|
862
1071
|
return;
|
|
863
1072
|
}
|
|
864
1073
|
|
|
@@ -889,6 +1098,10 @@ function renderLive(){
|
|
|
889
1098
|
// Hide single-project info (now shown per-section)
|
|
890
1099
|
$('#liveProject').style.display='none';
|
|
891
1100
|
|
|
1101
|
+
// Show "Clear All" when there are finished/stale runs
|
|
1102
|
+
var hasFinished=runIds.some(function(rid){return runs[rid].done||!runs[rid].on});
|
|
1103
|
+
$('#liveClearBtn').style.display=hasFinished?'inline-block':'none';
|
|
1104
|
+
|
|
892
1105
|
// Header state
|
|
893
1106
|
var lbl=panel.querySelector('.live-header .label');
|
|
894
1107
|
var anyStale=runIds.some(function(rid){return runs[rid].stale});
|
|
@@ -913,13 +1126,19 @@ function renderLive(){
|
|
|
913
1126
|
// Project section header
|
|
914
1127
|
var projLabel=L.project||(L.cwd?L.cwd.split('/').pop():'Run');
|
|
915
1128
|
var runStatus=L.done?(L.failed>0?'fail':'pass'):'running';
|
|
1129
|
+
var dismissBtn=null;
|
|
1130
|
+
if(L.done||!L.on){
|
|
1131
|
+
dismissBtn=el('button',{className:'lr-dismiss',onclick:function(e){e.stopPropagation();dismissLiveRun(rid)}},'\u2715');
|
|
1132
|
+
}
|
|
916
1133
|
var sectionHeader=el('div',{className:'lr-section-header '+runStatus},[
|
|
917
1134
|
el('span',{className:'lr-project-name'},projLabel),
|
|
1135
|
+
createTriggerBadge(L.triggeredBy),
|
|
918
1136
|
el('span',{className:'lr-section-stats'},[
|
|
919
1137
|
el('span',{},L.completed+'/'+L.total),
|
|
920
1138
|
L.failed>0?el('span',{style:'color:var(--red);margin-left:6px'},L.failed+' failed'):null,
|
|
921
1139
|
L.on?el('span',{className:'spinner-small',style:'margin-left:6px'}):null
|
|
922
|
-
])
|
|
1140
|
+
]),
|
|
1141
|
+
dismissBtn
|
|
923
1142
|
]);
|
|
924
1143
|
grid.appendChild(sectionHeader);
|
|
925
1144
|
|
|
@@ -1009,6 +1228,24 @@ function renderLive(){
|
|
|
1009
1228
|
stepsEl
|
|
1010
1229
|
]);
|
|
1011
1230
|
if(ssEl)card.appendChild(ssEl);
|
|
1231
|
+
// Network API logs in live view (clickable rows)
|
|
1232
|
+
if(t.networkLogs&&t.networkLogs.length&&!isCollapsed){
|
|
1233
|
+
var liveErrCount=t.networkLogs.filter(function(n){return n.status>=400}).length;
|
|
1234
|
+
var liveNetLabel='Network ('+t.networkLogs.length+(liveErrCount?', '+liveErrCount+' error'+(liveErrCount!==1?'s':'')+')':')');
|
|
1235
|
+
var liveNetToggle=el('div',{className:'rd-net-toggle',style:'margin:6px 0 0;padding-left:0'},[
|
|
1236
|
+
el('span',{className:'net-arrow'},'\u25B6'),
|
|
1237
|
+
el('span',{},liveNetLabel)
|
|
1238
|
+
]);
|
|
1239
|
+
var liveNetList=el('div',{className:'rd-net-list'});
|
|
1240
|
+
t.networkLogs.forEach(function(n){
|
|
1241
|
+
var built=buildNetRow(n);
|
|
1242
|
+
liveNetList.appendChild(built.row);
|
|
1243
|
+
if(built.detail)liveNetList.appendChild(built.detail);
|
|
1244
|
+
});
|
|
1245
|
+
liveNetToggle.addEventListener('click',function(e){e.stopPropagation();liveNetToggle.classList.toggle('open')});
|
|
1246
|
+
card.appendChild(liveNetToggle);
|
|
1247
|
+
card.appendChild(liveNetList);
|
|
1248
|
+
}
|
|
1012
1249
|
if(isFinished){
|
|
1013
1250
|
card.addEventListener('click',function(){
|
|
1014
1251
|
if(S.liveExpanded.has(testKey))S.liveExpanded.delete(testKey);
|