@matware/e2e-runner 1.1.1 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +21 -0
- package/.claude-plugin/plugin.json +9 -0
- package/.mcp.json +9 -0
- package/.opencode/commands/create-test.md +63 -0
- package/.opencode/commands/run.md +50 -0
- package/.opencode/commands/verify-issue.md +62 -0
- package/.opencode/skills/e2e-testing/SKILL.md +181 -0
- package/.opencode/skills/e2e-testing/references/action-types.md +143 -0
- package/.opencode/skills/e2e-testing/references/auth-strategies.md +91 -0
- package/.opencode/skills/e2e-testing/references/graphql.md +59 -0
- package/.opencode/skills/e2e-testing/references/issue-verification.md +59 -0
- package/.opencode/skills/e2e-testing/references/multi-pool.md +60 -0
- package/.opencode/skills/e2e-testing/references/network-debugging.md +62 -0
- package/.opencode/skills/e2e-testing/references/test-json-format.md +163 -0
- package/.opencode/skills/e2e-testing/references/troubleshooting.md +224 -0
- package/.opencode/skills/e2e-testing/references/variables.md +41 -0
- package/.opencode/skills/e2e-testing/references/visual-verification.md +89 -0
- package/OPENCODE.md +166 -0
- package/README.md +990 -296
- package/agents/test-analyzer.md +81 -0
- package/agents/test-creator.md +155 -0
- package/agents/test-improver.md +177 -0
- package/bin/cli.js +602 -22
- package/commands/create-test.md +65 -0
- package/commands/run.md +49 -0
- package/commands/verify-issue.md +63 -0
- package/opencode.json +11 -0
- package/package.json +15 -2
- package/scripts/setup-opencode.sh +113 -0
- package/skills/e2e-testing/SKILL.md +173 -0
- package/skills/e2e-testing/references/action-types.md +143 -0
- package/skills/e2e-testing/references/auth-strategies.md +91 -0
- package/skills/e2e-testing/references/graphql.md +59 -0
- package/skills/e2e-testing/references/issue-verification.md +59 -0
- package/skills/e2e-testing/references/multi-pool.md +60 -0
- package/skills/e2e-testing/references/network-debugging.md +62 -0
- package/skills/e2e-testing/references/test-json-format.md +163 -0
- package/skills/e2e-testing/references/troubleshooting.md +224 -0
- package/skills/e2e-testing/references/variables.md +41 -0
- package/skills/e2e-testing/references/visual-verification.md +89 -0
- package/src/actions.js +597 -20
- package/src/ai-generate.js +142 -12
- package/src/config.js +171 -0
- package/src/dashboard.js +299 -17
- package/src/db.js +335 -13
- package/src/index.js +15 -8
- package/src/learner-markdown.js +177 -0
- package/src/learner-neo4j.js +255 -0
- package/src/learner-sqlite.js +658 -0
- package/src/learner.js +418 -0
- package/src/mcp-tools.js +1558 -50
- package/src/module-resolver.js +310 -0
- package/src/narrate.js +262 -0
- package/src/neo4j-pool.js +124 -0
- package/src/pool-manager.js +223 -0
- package/src/reporter.js +117 -3
- package/src/runner.js +274 -71
- package/src/sync/auth.js +354 -0
- package/src/sync/client.js +572 -0
- package/src/sync/hub-routes.js +816 -0
- package/src/sync/index.js +68 -0
- package/src/sync/middleware.js +347 -0
- package/src/sync/queue.js +209 -0
- package/src/sync/schema.js +540 -0
- package/src/verify.js +14 -9
- package/src/watch.js +384 -0
- package/templates/build-dashboard.js +69 -0
- package/templates/dashboard/js/api.js +60 -0
- package/templates/dashboard/js/init.js +13 -0
- package/templates/dashboard/js/keyboard.js +46 -0
- package/templates/dashboard/js/state.js +40 -0
- package/templates/dashboard/js/toast.js +41 -0
- package/templates/dashboard/js/utils.js +196 -0
- package/templates/dashboard/js/view-live.js +143 -0
- package/templates/dashboard/js/view-runs.js +572 -0
- package/templates/dashboard/js/view-tests.js +294 -0
- package/templates/dashboard/js/view-watch.js +242 -0
- package/templates/dashboard/js/websocket.js +110 -0
- package/templates/dashboard/styles/base.css +69 -0
- package/templates/dashboard/styles/components.css +110 -0
- package/templates/dashboard/styles/view-live.css +74 -0
- package/templates/dashboard/styles/view-runs.css +207 -0
- package/templates/dashboard/styles/view-tests.css +96 -0
- package/templates/dashboard/styles/view-watch.css +53 -0
- package/templates/dashboard/template.html +267 -0
- package/templates/dashboard.html +2171 -530
- package/templates/docker-compose-neo4j.yml +19 -0
- package/templates/e2e.config.js +3 -0
- package/templates/sample-test.json +0 -8
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/* ── DOM Helpers ── */
|
|
2
|
+
var $=function(s){return document.querySelector(s)};
|
|
3
|
+
var $$=function(s){return document.querySelectorAll(s)};
|
|
4
|
+
|
|
5
|
+
function el(tag,a,ch){
|
|
6
|
+
var e=document.createElement(tag);
|
|
7
|
+
if(a)Object.keys(a).forEach(function(k){
|
|
8
|
+
if(k==='className')e.className=a[k];
|
|
9
|
+
else if(k==='style')e.style.cssText=a[k];
|
|
10
|
+
else if(k.indexOf('on')===0)e.addEventListener(k.slice(2),a[k]);
|
|
11
|
+
else e.setAttribute(k,a[k]);
|
|
12
|
+
});
|
|
13
|
+
if(typeof ch==='string')e.textContent=ch;
|
|
14
|
+
else if(Array.isArray(ch))ch.forEach(function(c){if(c)e.appendChild(c)});
|
|
15
|
+
return e;
|
|
16
|
+
}
|
|
17
|
+
function css(n){return n.replace(/[^a-zA-Z0-9\-_]/g,'_')}
|
|
18
|
+
function dur(ms){return ms>=1000?(ms/1000).toFixed(1)+'s':ms+'ms'}
|
|
19
|
+
function fdate(iso){return iso?new Date(iso).toLocaleString():'--'}
|
|
20
|
+
|
|
21
|
+
function prettyJson(str){
|
|
22
|
+
if(!str)return '';
|
|
23
|
+
try{return JSON.stringify(JSON.parse(str),null,2)}catch(e){return str}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function fmtHeaders(h){
|
|
27
|
+
if(!h||typeof h!=='object')return '';
|
|
28
|
+
return Object.keys(h).map(function(k){return k+': '+h[k]}).join('\n');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function buildHeaderKV(h){
|
|
32
|
+
if(!h||typeof h!=='object') return el('div',{className:'rd-nd-empty'},'No data');
|
|
33
|
+
var table=el('div',{className:'rd-hdr-table'});
|
|
34
|
+
Object.keys(h).forEach(function(k){
|
|
35
|
+
var row=el('div',{className:'rd-hdr-row'});
|
|
36
|
+
row.appendChild(el('span',{className:'rd-hdr-key'},k));
|
|
37
|
+
row.appendChild(el('span',{className:'rd-hdr-val'},String(h[k])));
|
|
38
|
+
table.appendChild(row);
|
|
39
|
+
});
|
|
40
|
+
return table;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function makeCopyBtn(getTextFn){
|
|
44
|
+
var btn=el('span',{className:'copy-btn',onclick:function(e){
|
|
45
|
+
e.stopPropagation();
|
|
46
|
+
var text=typeof getTextFn==='function'?getTextFn():String(getTextFn);
|
|
47
|
+
navigator.clipboard.writeText(text).then(function(){
|
|
48
|
+
btn.textContent='\u2713 Copied';
|
|
49
|
+
btn.classList.add('copied');
|
|
50
|
+
setTimeout(function(){btn.textContent='\u2398 Copy';btn.classList.remove('copied')},1200);
|
|
51
|
+
});
|
|
52
|
+
}},'\u2398 Copy');
|
|
53
|
+
return btn;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function buildNdSection(title,contentEl,count,copyText){
|
|
57
|
+
var toggle=el('div',{className:'rd-nd-toggle'},[
|
|
58
|
+
el('span',{className:'nd-arrow'},'\u25B6'),
|
|
59
|
+
el('span',null,title),
|
|
60
|
+
count?el('span',{className:'nd-count'},count+' entries'):null,
|
|
61
|
+
makeCopyBtn(copyText||function(){return contentWrap.textContent})
|
|
62
|
+
]);
|
|
63
|
+
var contentWrap=el('div',{className:'rd-nd-content'});
|
|
64
|
+
contentWrap.appendChild(contentEl);
|
|
65
|
+
toggle.addEventListener('click',function(e){
|
|
66
|
+
e.stopPropagation();
|
|
67
|
+
toggle.classList.toggle('open');
|
|
68
|
+
});
|
|
69
|
+
return el('div',{className:'rd-nd-section'},[toggle,contentWrap]);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function gqlOp(n){
|
|
73
|
+
if(n.requestBody){
|
|
74
|
+
try{
|
|
75
|
+
var b=JSON.parse(n.requestBody);
|
|
76
|
+
if(b.operationName)return b.operationName;
|
|
77
|
+
if(b.query){var m=b.query.match(/^(?:query|mutation|subscription)\s+([A-Za-z_]\w*)/);if(m)return m[1]}
|
|
78
|
+
}catch(e){}
|
|
79
|
+
}
|
|
80
|
+
if(n.url){
|
|
81
|
+
try{var u=new URL(n.url,location.href);var op=u.searchParams.get('operationName');if(op)return op}catch(e){}
|
|
82
|
+
}
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function buildNetRow(n){
|
|
87
|
+
var mCls='rd-net-method '+(n.method||'GET').toLowerCase();
|
|
88
|
+
var sCode=n.status||0;
|
|
89
|
+
var sCls='rd-net-status '+(sCode<300?'s2xx':sCode<400?'s3xx':sCode<500?'s4xx':'s5xx');
|
|
90
|
+
var hasDetail=n.requestBody||n.responseBody||n.requestHeaders||n.responseHeaders;
|
|
91
|
+
var rowCls='rd-net-row'+(sCode>=400?' has-error':'');
|
|
92
|
+
var opName=gqlOp(n);
|
|
93
|
+
var children=[
|
|
94
|
+
el('span',{className:'rd-net-expand'},hasDetail?'\u25B6':''),
|
|
95
|
+
el('span',{className:mCls},n.method||'GET'),
|
|
96
|
+
el('span',{className:sCls},String(sCode))
|
|
97
|
+
];
|
|
98
|
+
if(opName)children.push(el('span',{className:'rd-net-op'},opName));
|
|
99
|
+
children.push(el('span',{className:'rd-net-url'},n.url||''));
|
|
100
|
+
children.push(makeCopyBtn(n.url||''));
|
|
101
|
+
children.push(el('span',{className:'rd-net-dur'},dur(n.duration)));
|
|
102
|
+
var row=el('div',{className:rowCls},children);
|
|
103
|
+
var detail=null;
|
|
104
|
+
if(hasDetail){
|
|
105
|
+
var sections=[];
|
|
106
|
+
if(n.requestHeaders){
|
|
107
|
+
var hCount=Object.keys(n.requestHeaders).length;
|
|
108
|
+
sections.push(buildNdSection('Request Headers',buildHeaderKV(n.requestHeaders),hCount,fmtHeaders(n.requestHeaders)));
|
|
109
|
+
}
|
|
110
|
+
if(n.requestBody){
|
|
111
|
+
var rbText=prettyJson(n.requestBody);
|
|
112
|
+
sections.push(buildNdSection('Request Body',el('pre',null,rbText),null,rbText));
|
|
113
|
+
}
|
|
114
|
+
if(n.responseHeaders){
|
|
115
|
+
var rhCount=Object.keys(n.responseHeaders).length;
|
|
116
|
+
sections.push(buildNdSection('Response Headers',buildHeaderKV(n.responseHeaders),rhCount,fmtHeaders(n.responseHeaders)));
|
|
117
|
+
}
|
|
118
|
+
if(n.responseBody){
|
|
119
|
+
var respText=prettyJson(n.responseBody);
|
|
120
|
+
sections.push(buildNdSection('Response Body',el('pre',null,respText),null,respText));
|
|
121
|
+
}
|
|
122
|
+
detail=el('div',{className:'rd-net-detail'},sections);
|
|
123
|
+
row.addEventListener('click',function(e){e.stopPropagation();row.classList.toggle('open')});
|
|
124
|
+
}
|
|
125
|
+
return {row:row,detail:detail};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/* ── Screenshot hash helpers ── */
|
|
129
|
+
var ssHashCache={};
|
|
130
|
+
async function ssHash(filePath){
|
|
131
|
+
if(ssHashCache[filePath])return ssHashCache[filePath];
|
|
132
|
+
var data=new TextEncoder().encode(filePath);
|
|
133
|
+
var buf=await crypto.subtle.digest('SHA-256',data);
|
|
134
|
+
var hex=Array.from(new Uint8Array(buf)).map(function(b){return b.toString(16).padStart(2,'0')}).join('');
|
|
135
|
+
var h=hex.slice(0,8);
|
|
136
|
+
ssHashCache[filePath]=h;
|
|
137
|
+
return h;
|
|
138
|
+
}
|
|
139
|
+
function ssHashSync(filePath){return ssHashCache[filePath]||null}
|
|
140
|
+
function copyHash(hash,badge){
|
|
141
|
+
navigator.clipboard.writeText('ss:'+hash).then(function(){
|
|
142
|
+
badge.classList.add('copied');
|
|
143
|
+
setTimeout(function(){badge.classList.remove('copied')},1200);
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
function createHashBadge(hash){
|
|
147
|
+
var badge=el('span',{className:'ss-hash',onclick:function(e){e.stopPropagation();copyHash(hash,badge)}},[
|
|
148
|
+
el('span',{className:'ss-icon'},'\u2318'),
|
|
149
|
+
document.createTextNode('ss:'+hash)
|
|
150
|
+
]);
|
|
151
|
+
return badge;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function createTriggerBadge(source){
|
|
155
|
+
var s=source||'unknown';
|
|
156
|
+
var labels={dashboard:'Dashboard',mcp:'MCP',cli:'CLI',unknown:'--'};
|
|
157
|
+
var icons={dashboard:'\u{1F464}',mcp:'\u{1F916}',cli:'>_',unknown:'\u2022'};
|
|
158
|
+
var badge=el('span',{className:'trigger-badge src-'+s},[
|
|
159
|
+
el('span',{className:'trig-icon'},icons[s]||icons.unknown),
|
|
160
|
+
document.createTextNode(labels[s]||s)
|
|
161
|
+
]);
|
|
162
|
+
return badge;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/* ── Pool Distribution Summary ── */
|
|
166
|
+
var POOL_COLORS=['#6366f1','#22d3ee','#f59e0b','#10b981','#ef4444','#8b5cf6','#ec4899','#14b8a6'];
|
|
167
|
+
function buildPoolDistribution(tests){
|
|
168
|
+
var pools={};var total=0;
|
|
169
|
+
Object.keys(tests).forEach(function(n){
|
|
170
|
+
if(n==='__error')return;var t=tests[n];
|
|
171
|
+
if(!t.poolUrl)return;
|
|
172
|
+
var label=t.poolUrl.replace('ws://','').replace('wss://','');
|
|
173
|
+
if(!pools[label])pools[label]={count:0,passed:0,failed:0};
|
|
174
|
+
pools[label].count++;total++;
|
|
175
|
+
if(t.status==='passed'||t.success)pools[label].passed++;
|
|
176
|
+
if(t.status==='failed'||t.success===false)pools[label].failed++;
|
|
177
|
+
});
|
|
178
|
+
var keys=Object.keys(pools);
|
|
179
|
+
if(keys.length<2)return null;
|
|
180
|
+
var bar=el('div',{className:'pool-dist'});
|
|
181
|
+
var legend=el('div',{className:'pool-dist-legend'});
|
|
182
|
+
keys.forEach(function(k,i){
|
|
183
|
+
var pct=Math.round(pools[k].count/total*100);
|
|
184
|
+
var color=POOL_COLORS[i%POOL_COLORS.length];
|
|
185
|
+
var seg=el('div',{className:'pool-dist-seg'});
|
|
186
|
+
seg.style.flex=pools[k].count;seg.style.background=color;
|
|
187
|
+
seg.textContent=k+' ('+pools[k].count+')';
|
|
188
|
+
bar.appendChild(seg);
|
|
189
|
+
var lg=el('span',{},k+': '+pools[k].count+' tests ('+pct+'%)');
|
|
190
|
+
lg.style.cssText='display:inline-flex;align-items:center;gap:4px';
|
|
191
|
+
var dot=el('span',{});dot.style.cssText='width:8px;height:8px;border-radius:2px;background:'+color+';flex-shrink:0';
|
|
192
|
+
lg.insertBefore(dot,lg.firstChild);
|
|
193
|
+
legend.appendChild(lg);
|
|
194
|
+
});
|
|
195
|
+
return el('div',{style:'padding:4px 12px'},[bar,legend]);
|
|
196
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/* ══════════════════════════════════════════════════════════════════
|
|
2
|
+
Live Execution View
|
|
3
|
+
══════════════════════════════════════════════════════════════════ */
|
|
4
|
+
function clearFinishedLiveRuns(){for(var k in S.liveRuns){if(S.liveRuns[k].done||!S.liveRuns[k].on)delete S.liveRuns[k]}renderLive()}
|
|
5
|
+
function dismissLiveRun(rid){delete S.liveRuns[rid];renderLive()}
|
|
6
|
+
$('#liveClearBtn').addEventListener('click',clearFinishedLiveRuns);
|
|
7
|
+
|
|
8
|
+
function renderLive(){
|
|
9
|
+
var panel=$('#livePanel'),grid=$('#liveTests'),navLive=$('#navLive'),liveEmpty=$('#liveEmpty');
|
|
10
|
+
var runs=S.liveRuns;var runIds=Object.keys(runs);
|
|
11
|
+
|
|
12
|
+
if(runIds.length===0){panel.classList.remove('active');navLive.style.display='none';liveEmpty.style.display='block';$('#liveClearBtn').style.display='none';return}
|
|
13
|
+
|
|
14
|
+
navLive.style.display='';liveEmpty.style.display='none';panel.classList.add('active');
|
|
15
|
+
|
|
16
|
+
var gTotal=0,gCompleted=0,gPassed=0,gFailed=0,gActive=0,gRunning=false,gDone=true;
|
|
17
|
+
runIds.forEach(function(rid){var r=runs[rid];gTotal+=r.total;gCompleted+=r.completed;gPassed+=r.passed;gFailed+=r.failed;gActive+=r.active;if(r.on)gRunning=true;if(!r.done)gDone=false});
|
|
18
|
+
|
|
19
|
+
var badgeActive=0;
|
|
20
|
+
runIds.forEach(function(rid){var r=runs[rid];Object.keys(r.tests).forEach(function(n){if(n!=='__error'&&r.tests[n].status==='running')badgeActive++})});
|
|
21
|
+
$('#liveBadge').textContent=gRunning?badgeActive:gCompleted;
|
|
22
|
+
$('#liveBadge').style.background=gRunning?'var(--purple-dim)':gFailed>0?'var(--red-dim)':'var(--green-dim)';
|
|
23
|
+
$('#liveBadge').style.color=gRunning?'var(--purple)':gFailed>0?'var(--red)':'var(--green)';
|
|
24
|
+
|
|
25
|
+
$('#liveTotal').textContent=gTotal;$('#livePass').textContent=gPassed;$('#liveFail').textContent=gFailed;$('#liveActive').textContent=gActive;
|
|
26
|
+
$('#liveProgressFill').style.width=(gTotal>0?gCompleted/gTotal*100:0)+'%';
|
|
27
|
+
$('#liveProject').style.display='none';
|
|
28
|
+
|
|
29
|
+
var hasFinished=runIds.some(function(rid){return runs[rid].done||!runs[rid].on});
|
|
30
|
+
$('#liveClearBtn').style.display=hasFinished?'inline-block':'none';
|
|
31
|
+
|
|
32
|
+
var lbl=panel.querySelector('.live-header .label');
|
|
33
|
+
var anyStale=runIds.some(function(rid){return runs[rid].stale});
|
|
34
|
+
if(!gRunning&&gDone){
|
|
35
|
+
lbl.textContent=anyStale?'COMPLETED (connection lost)':gFailed>0?'COMPLETED WITH FAILURES':'ALL TESTS PASSED';
|
|
36
|
+
lbl.style.color=anyStale?'var(--yellow)':gFailed>0?'var(--red)':'var(--green)';
|
|
37
|
+
var dot=lbl.querySelector('.dot');if(dot)dot.remove();
|
|
38
|
+
$('#liveProgressFill').style.background=anyStale?'var(--yellow)':gFailed>0?'var(--red)':'var(--green)';
|
|
39
|
+
} else {
|
|
40
|
+
if(!lbl.querySelector('.dot')){lbl.textContent='';var d=el('span',{className:'dot'});lbl.appendChild(d);lbl.appendChild(document.createTextNode(' RUNNING'))}
|
|
41
|
+
lbl.style.color='var(--purple)';$('#liveProgressFill').style.background='var(--purple)';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
grid.textContent='';
|
|
45
|
+
runIds.forEach(function(rid){
|
|
46
|
+
var L=runs[rid];
|
|
47
|
+
var projLabel=L.project||(L.cwd?L.cwd.split('/').pop():'Run');
|
|
48
|
+
var runStatus=L.done?(L.failed>0?'fail':'pass'):'running';
|
|
49
|
+
var dismissBtn=null;
|
|
50
|
+
if(L.done||!L.on){dismissBtn=el('button',{className:'lr-dismiss',onclick:function(e){e.stopPropagation();dismissLiveRun(rid)}},'\u2715')}
|
|
51
|
+
grid.appendChild(el('div',{className:'lr-section-header '+runStatus},[
|
|
52
|
+
el('span',{className:'lr-project-name'},projLabel),createTriggerBadge(L.triggeredBy),
|
|
53
|
+
el('span',{className:'lr-section-stats'},[el('span',{},L.completed+'/'+L.total),L.failed>0?el('span',{style:'color:var(--red);margin-left:6px'},L.failed+' failed'):null,L.on?el('span',{className:'spinner-small',style:'margin-left:6px'}):null]),
|
|
54
|
+
dismissBtn
|
|
55
|
+
]));
|
|
56
|
+
|
|
57
|
+
var poolDist=buildPoolDistribution(L.tests);
|
|
58
|
+
if(poolDist)grid.appendChild(poolDist);
|
|
59
|
+
|
|
60
|
+
var testGrid=el('div',{className:'lr-test-grid'});
|
|
61
|
+
Object.keys(L.tests).forEach(function(name){
|
|
62
|
+
if(name==='__error')return;
|
|
63
|
+
var t=L.tests[name];var testKey=rid+'::'+name;
|
|
64
|
+
var iconText=t.status==='passed'?'\u2714':t.status==='failed'?'\u2718':'\u25CF';
|
|
65
|
+
var iconColor=t.status==='passed'?'color:var(--green)':t.status==='failed'?'color:var(--red)':'color:var(--purple)';
|
|
66
|
+
var meta='';
|
|
67
|
+
if(t.status==='running'){meta=t.actionType?('Step '+(t.actions||0)+'/'+(t.totalActions||'?')):'starting...';if(t.retry)meta='Retry '+t.retry}
|
|
68
|
+
else if(t.status==='passed'){meta=t.duration||'done'}
|
|
69
|
+
else if(t.status==='failed'){meta=t.error||'failed'}
|
|
70
|
+
|
|
71
|
+
var stepsEl=el('div',{className:'lt-actions'});
|
|
72
|
+
if(t.actionLog&&t.actionLog.length>0){
|
|
73
|
+
t.actionLog.forEach(function(a){
|
|
74
|
+
var detail=a.narrative||a.selector||a.value||a.text||'';
|
|
75
|
+
var durText=a.duration!=null?(a.duration<1000?a.duration+'ms':(a.duration/1000).toFixed(1)+'s'):'';
|
|
76
|
+
var retryBadge=null;
|
|
77
|
+
if(a.actionRetries&&a.actionRetries>0){retryBadge=el('span',{className:'badge flaky',style:'font-size:9px;padding:1px 5px;margin-left:4px'},'\u21BB x'+a.actionRetries)}
|
|
78
|
+
var stepCls='lt-step'+(a.isPoolLog?' pool-log':'');
|
|
79
|
+
stepsEl.appendChild(el('div',{className:stepCls},[
|
|
80
|
+
el('span',{className:'step-icon '+(a.isPoolLog?'':a.success?'ok':'fail')},a.isPoolLog?'\uD83D\uDD17':a.success?'\u2714':'\u2718'),
|
|
81
|
+
el('span',{className:'step-type'},a.isPoolLog?'pool':a.type),
|
|
82
|
+
el('span',{className:'step-detail'},a.isPoolLog?a.narrative:detail),
|
|
83
|
+
retryBadge,
|
|
84
|
+
a.isPoolLog?null:el('span',{className:'step-dur'},durText)
|
|
85
|
+
]));
|
|
86
|
+
});
|
|
87
|
+
if(t.status==='running'&&t.actions<t.totalActions){stepsEl.appendChild(el('div',{className:'lt-step'},[el('span',{className:'step-icon run spinner-small'}),el('span',{className:'step-type',style:'opacity:.6'},'waiting...')]))}
|
|
88
|
+
} else if(t.status==='running'){
|
|
89
|
+
stepsEl.appendChild(el('div',{className:'lt-step'},[el('span',{className:'step-icon run spinner-small'}),el('span',{className:'step-type',style:'opacity:.6'},'connecting...')]));
|
|
90
|
+
}
|
|
91
|
+
var isFinished=t.status==='passed'||t.status==='failed';
|
|
92
|
+
var isCollapsed=isFinished&&S.liveCollapsed.has(testKey);
|
|
93
|
+
var summaryEl=el('div',{className:'lt-summary'},[el('span',{className:'lt-dur'},t.duration||''),el('span',{className:'lt-expand'},isCollapsed?'\u25BC':'\u25B2')]);
|
|
94
|
+
|
|
95
|
+
var ssEl=null;
|
|
96
|
+
var allSS=(t.screenshots||[]).slice();
|
|
97
|
+
if(t.errorScreenshot)allSS.push(t.errorScreenshot);
|
|
98
|
+
if(allSS.length>0){
|
|
99
|
+
var ssOpen=S.liveSSOpen&&S.liveSSOpen.has(testKey);
|
|
100
|
+
var toggle=el('div',{className:'lt-screenshots-toggle'+(ssOpen?' open':'')},[el('span',{className:'ss-arrow'},'\u25B6'),el('span',{},'Screenshots ('+allSS.length+')')]);
|
|
101
|
+
var ssGridEl=el('div',{className:'lt-screenshots-grid'});
|
|
102
|
+
allSS.forEach(function(ssPath){
|
|
103
|
+
var fname=ssPath.split('/').pop();var isErr=t.errorScreenshot&&ssPath===t.errorScreenshot;
|
|
104
|
+
var thumb=el('div',{className:'lt-ss-thumb'});
|
|
105
|
+
var img=document.createElement('img');img.src='/api/image?path='+encodeURIComponent(ssPath);img.alt=fname;img.loading='lazy';
|
|
106
|
+
if(isErr)thumb.style.borderColor='var(--red)';
|
|
107
|
+
thumb.appendChild(img);
|
|
108
|
+
thumb.addEventListener('click',function(e){e.stopPropagation();openModal('/api/image?path='+encodeURIComponent(ssPath),fname)});
|
|
109
|
+
var labelEl=el('div',{className:'lt-ss-label'},[el('span',{style:'overflow:hidden;text-overflow:ellipsis;white-space:nowrap'},fname)]);
|
|
110
|
+
(function(lbl,sp){ssHash(sp).then(function(h){lbl.appendChild(createHashBadge(h))})})(labelEl,ssPath);
|
|
111
|
+
ssGridEl.appendChild(el('div',{},[thumb,labelEl]));
|
|
112
|
+
});
|
|
113
|
+
toggle.addEventListener('click',function(e){e.stopPropagation();if(S.liveSSOpen.has(testKey))S.liveSSOpen.delete(testKey);else S.liveSSOpen.add(testKey);toggle.classList.toggle('open');ssGridEl.style.display=ssGridEl.style.display==='grid'?'none':'grid'});
|
|
114
|
+
if(ssOpen)ssGridEl.style.display='grid';
|
|
115
|
+
ssEl=el('div',{className:'lt-screenshots'},[toggle,ssGridEl]);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
var serialBadge=t.serial?el('span',{className:'serial-badge'},'Serial'):null;
|
|
119
|
+
var poolBadge=t.poolUrl?el('span',{className:'pool-badge'},t.poolUrl.replace('ws://','').replace('wss://','')):null;
|
|
120
|
+
var card=el('div',{className:'live-test '+t.status+(isCollapsed?' collapsed':'')},[
|
|
121
|
+
el('div',{className:'lt-name'},[
|
|
122
|
+
t.status==='running'?el('span',{className:'spinner'}):el('span',{className:'lt-icon',style:iconColor},iconText),
|
|
123
|
+
document.createTextNode(' '+name),serialBadge,poolBadge,summaryEl
|
|
124
|
+
]),
|
|
125
|
+
el('div',{className:'lt-meta'},meta),stepsEl
|
|
126
|
+
]);
|
|
127
|
+
if(ssEl)card.appendChild(ssEl);
|
|
128
|
+
if(t.networkLogs&&t.networkLogs.length&&!isCollapsed){
|
|
129
|
+
var liveErrCount=t.networkLogs.filter(function(n){return n.status>=400}).length;
|
|
130
|
+
var liveNetHead=el('div',{className:'rd-net-head'},[el('span',{className:'net-arrow'},'\u25B6'),el('span',{className:'net-title'},'Network Requests'),el('div',{className:'net-stats'},[el('span',{className:'net-stat'},[document.createTextNode('Total: '),el('strong',null,String(t.networkLogs.length))]),liveErrCount?el('span',{className:'net-stat has-err'},[document.createTextNode('Errors: '),el('strong',null,String(liveErrCount))]):null])]);
|
|
131
|
+
var liveNetCols=el('div',{className:'rd-net-cols'},[el('span',{className:'col-e'},''),el('span',{className:'col-m'},'Method'),el('span',{className:'col-s'},'Status'),el('span',{className:'col-u'},'URL'),el('span',{className:'col-d'},'Time')]);
|
|
132
|
+
var liveNetBody=el('div',{className:'rd-net-body'},[liveNetCols]);
|
|
133
|
+
t.networkLogs.forEach(function(n){var built=buildNetRow(n);liveNetBody.appendChild(built.row);if(built.detail)liveNetBody.appendChild(built.detail)});
|
|
134
|
+
liveNetHead.addEventListener('click',function(e){e.stopPropagation();liveNetHead.classList.toggle('open')});
|
|
135
|
+
card.appendChild(el('div',{className:'rd-net-panel',style:'margin-top:6px'},[liveNetHead,liveNetBody]));
|
|
136
|
+
}
|
|
137
|
+
if(isFinished){card.addEventListener('click',function(e){if(window.getSelection().toString())return;if(S.liveCollapsed.has(testKey))S.liveCollapsed.delete(testKey);else S.liveCollapsed.add(testKey);renderLive()})}
|
|
138
|
+
testGrid.appendChild(card);
|
|
139
|
+
if(!isCollapsed)stepsEl.scrollTop=stepsEl.scrollHeight;
|
|
140
|
+
});
|
|
141
|
+
grid.appendChild(testGrid);
|
|
142
|
+
});
|
|
143
|
+
}
|