@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,572 @@
|
|
|
1
|
+
/* ══════════════════════════════════════════════════════════════════
|
|
2
|
+
Runs View — History + Screenshots + Learnings (inner tabs)
|
|
3
|
+
══════════════════════════════════════════════════════════════════ */
|
|
4
|
+
|
|
5
|
+
/* ── Filters ── */
|
|
6
|
+
$$('.filter-btn').forEach(function(btn){
|
|
7
|
+
btn.addEventListener('click',function(){
|
|
8
|
+
$$('.filter-btn').forEach(function(b){b.classList.remove('active')});
|
|
9
|
+
btn.classList.add('active');
|
|
10
|
+
S.runFilter.status=btn.dataset.filter;
|
|
11
|
+
applyRunFilters();
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
$('#runSearchInput').addEventListener('input',function(){
|
|
15
|
+
S.runFilter.search=this.value.trim().toLowerCase();
|
|
16
|
+
applyRunFilters();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
var _allRunRows=[];
|
|
20
|
+
function applyRunFilters(){
|
|
21
|
+
_allRunRows.forEach(function(item){
|
|
22
|
+
var show=true;
|
|
23
|
+
var r=item.data;
|
|
24
|
+
if(S.runFilter.status!=='all'){
|
|
25
|
+
var total=r.total||0;var passed=r.passed||0;var failed=r.failed||0;
|
|
26
|
+
if(S.runFilter.status==='pass'&&(failed>0||total===0))show=false;
|
|
27
|
+
if(S.runFilter.status==='fail'&&failed===0)show=false;
|
|
28
|
+
if(S.runFilter.status==='mixed'&&(failed===0||passed===0))show=false;
|
|
29
|
+
}
|
|
30
|
+
if(show&&S.runFilter.search){
|
|
31
|
+
var suite=(r.suite_name||'all').toLowerCase();
|
|
32
|
+
var proj=(r.project_name||'').toLowerCase();
|
|
33
|
+
if(suite.indexOf(S.runFilter.search)===-1&&proj.indexOf(S.runFilter.search)===-1)show=false;
|
|
34
|
+
}
|
|
35
|
+
item.tr.style.display=show?'':'none';
|
|
36
|
+
if(item.detailTr)item.detailTr.style.display=show?'':'none';
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function renderRunsHealthBanner(){
|
|
41
|
+
var banner=$('#runsHealthBanner');
|
|
42
|
+
banner.textContent='';
|
|
43
|
+
var url=S.project?'/api/db/projects/'+S.project+'/health':'/api/db/health';
|
|
44
|
+
fetch(url).then(function(r){return r.json()}).then(function(h){
|
|
45
|
+
if(!h||!h.passRate)return;
|
|
46
|
+
var rateColor=h.passRate>=90?'green':h.passRate>=70?'amber':'red';
|
|
47
|
+
var trendIcon=h.passRateTrend==='improving'?'\u25B2':h.passRateTrend==='declining'?'\u25BC':'=';
|
|
48
|
+
var trendCls=h.passRateTrend==='improving'?'green':h.passRateTrend==='declining'?'red':'dim';
|
|
49
|
+
var deltaStr=h.trendDelta!==0?(h.trendDelta>0?'+':'')+h.trendDelta+'%':'';
|
|
50
|
+
|
|
51
|
+
banner.appendChild(el('div',{className:'hb-item'},[
|
|
52
|
+
el('div',{className:'hb-val '+rateColor},h.passRate+'%'),
|
|
53
|
+
el('div',{className:'hb-lbl'},'Pass Rate'),
|
|
54
|
+
el('div',{className:'hb-trend '+trendCls},trendIcon+' '+h.passRateTrend+(deltaStr?' ('+deltaStr+')':''))
|
|
55
|
+
]));
|
|
56
|
+
if(h.flakyCount>0){
|
|
57
|
+
banner.appendChild(el('div',{className:'hb-item'},[
|
|
58
|
+
el('div',{className:'hb-val amber'},String(h.flakyCount)),
|
|
59
|
+
el('div',{className:'hb-lbl'},'Flaky Tests')
|
|
60
|
+
]));
|
|
61
|
+
}
|
|
62
|
+
if(h.topErrorPattern){
|
|
63
|
+
var cat=h.topErrorPattern.category||h.topErrorPattern.pattern||'unknown';
|
|
64
|
+
var pat=cat.replace(/-/g,' ').replace(/\b\w/g,function(c){return c.toUpperCase()});
|
|
65
|
+
banner.appendChild(el('div',{className:'hb-item'},[
|
|
66
|
+
el('div',{className:'hb-val red',style:'font-size:13px'},pat),
|
|
67
|
+
el('div',{className:'hb-lbl'},'Top Error ('+h.topErrorPattern.count+'x)')
|
|
68
|
+
]));
|
|
69
|
+
}
|
|
70
|
+
banner.appendChild(el('div',{className:'hb-link',onclick:function(){var lb=$('#runsTabLearnings');if(lb)lb.click()}},[
|
|
71
|
+
el('span',null,'\u2192 View Learnings')
|
|
72
|
+
]));
|
|
73
|
+
}).catch(function(){});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function refreshRuns(){
|
|
77
|
+
renderRunsHealthBanner();
|
|
78
|
+
var url=S.project?'/api/db/projects/'+S.project+'/runs':'/api/db/runs';
|
|
79
|
+
api(url).then(function(rows){
|
|
80
|
+
var chart=$('#trendChart'),body=$('#runsBody'),empty=$('#runsEmpty'),head=$('#runsHead');
|
|
81
|
+
chart.textContent='';body.textContent='';
|
|
82
|
+
_allRunRows=[];
|
|
83
|
+
S.highlightedRunIdx=-1;
|
|
84
|
+
if(!Array.isArray(rows)||rows.length===0){empty.style.display='block';head.parentNode.parentNode.style.display='none';$('#badgeRuns').textContent='0';return}
|
|
85
|
+
empty.style.display='none';head.parentNode.parentNode.style.display='';
|
|
86
|
+
$('#badgeRuns').textContent=rows.length;
|
|
87
|
+
|
|
88
|
+
var htr=document.createElement('tr');
|
|
89
|
+
var cols=[];
|
|
90
|
+
if(!S.project)cols.push('Project');
|
|
91
|
+
cols=cols.concat(['Suite','Source','Date','Total','Pass','Fail','Rate','Time']);
|
|
92
|
+
cols.forEach(function(c){htr.appendChild(el('th',null,c))});
|
|
93
|
+
head.textContent='';head.appendChild(htr);
|
|
94
|
+
var colSpan=cols.length;
|
|
95
|
+
|
|
96
|
+
rows.slice(0,40).slice().reverse().forEach(function(r){
|
|
97
|
+
var rate=parseFloat(r.pass_rate)||0;
|
|
98
|
+
var color=rate>=90?'var(--green)':rate>=70?'var(--amber)':'var(--red)';
|
|
99
|
+
var bar=el('div',{className:'chart-bar',style:'height:'+Math.max(rate,4)+'%;background:'+color});
|
|
100
|
+
bar.appendChild(el('div',{className:'tip'},(r.project_name||'')+(r.suite_name?' / '+r.suite_name:'')+': '+r.pass_rate));
|
|
101
|
+
chart.appendChild(bar);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
rows.forEach(function(r){
|
|
105
|
+
var tr=document.createElement('tr');
|
|
106
|
+
tr.dataset.runId=r.id;
|
|
107
|
+
if(r.id===S.selectedRun)tr.classList.add('expanded');
|
|
108
|
+
if(!S.project)tr.appendChild(el('td',{style:'font-weight:600'},r.project_name||'-'));
|
|
109
|
+
tr.appendChild(el('td',{style:'color:var(--accent)'},r.suite_name||'all'));
|
|
110
|
+
var srcTd=document.createElement('td');srcTd.appendChild(createTriggerBadge(r.triggered_by));tr.appendChild(srcTd);
|
|
111
|
+
tr.appendChild(el('td',null,fdate(r.generated_at)));
|
|
112
|
+
tr.appendChild(el('td',null,String(r.total||0)));
|
|
113
|
+
tr.appendChild(el('td',{style:'color:var(--green)'},String(r.passed||0)));
|
|
114
|
+
tr.appendChild(el('td',{style:'color:var(--red)'},String(r.failed||0)));
|
|
115
|
+
var rv=parseFloat(r.pass_rate)||0;
|
|
116
|
+
tr.appendChild(el('td',{style:'font-weight:600;color:'+(rv>=90?'var(--green)':rv>=70?'var(--amber)':'var(--red)')},r.pass_rate||'-'));
|
|
117
|
+
tr.appendChild(el('td',{style:'color:var(--text2)'},r.duration||'-'));
|
|
118
|
+
tr.addEventListener('click',function(){toggleDetail(r.id,tr,colSpan)});
|
|
119
|
+
body.appendChild(tr);
|
|
120
|
+
|
|
121
|
+
var item={tr:tr,data:r,detailTr:null};
|
|
122
|
+
if(r.id===S.selectedRun){
|
|
123
|
+
var detailTr=createDetailRow(colSpan);
|
|
124
|
+
body.appendChild(detailTr);
|
|
125
|
+
loadDetailInline(r.id,detailTr);
|
|
126
|
+
item.detailTr=detailTr;
|
|
127
|
+
}
|
|
128
|
+
_allRunRows.push(item);
|
|
129
|
+
});
|
|
130
|
+
}).catch(function(){});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function createDetailRow(colSpan){
|
|
134
|
+
var detailTr=document.createElement('tr');
|
|
135
|
+
detailTr.className='run-detail-row';
|
|
136
|
+
var td=document.createElement('td');
|
|
137
|
+
td.setAttribute('colspan',colSpan);
|
|
138
|
+
var wrap=el('div',{className:'rd-wrap'});
|
|
139
|
+
var inner=el('div',{className:'rd-inner'},[
|
|
140
|
+
el('div',{style:'color:var(--text3);font-size:11px'},[
|
|
141
|
+
el('span',{className:'spinner-small'}),
|
|
142
|
+
document.createTextNode(' Loading...')
|
|
143
|
+
])
|
|
144
|
+
]);
|
|
145
|
+
wrap.appendChild(inner);
|
|
146
|
+
td.appendChild(wrap);
|
|
147
|
+
detailTr.appendChild(td);
|
|
148
|
+
return detailTr;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function toggleDetail(id,clickedTr,colSpan){
|
|
152
|
+
if(S.selectedRun===id){
|
|
153
|
+
var existing=clickedTr.nextElementSibling;
|
|
154
|
+
if(existing&&existing.classList.contains('run-detail-row')){
|
|
155
|
+
var w=existing.querySelector('.rd-wrap');
|
|
156
|
+
if(w)w.classList.remove('open');
|
|
157
|
+
clickedTr.classList.remove('expanded');
|
|
158
|
+
setTimeout(function(){if(existing.parentNode)existing.parentNode.removeChild(existing)},350);
|
|
159
|
+
}
|
|
160
|
+
S.selectedRun=null;
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
var prevTr=document.querySelector('#runsBody tr.expanded');
|
|
165
|
+
if(prevTr){
|
|
166
|
+
prevTr.classList.remove('expanded');
|
|
167
|
+
var prevDetail=prevTr.nextElementSibling;
|
|
168
|
+
if(prevDetail&&prevDetail.classList.contains('run-detail-row')){
|
|
169
|
+
var pw=prevDetail.querySelector('.rd-wrap');
|
|
170
|
+
if(pw)pw.classList.remove('open');
|
|
171
|
+
setTimeout(function(){if(prevDetail.parentNode)prevDetail.parentNode.removeChild(prevDetail)},350);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
S.selectedRun=id;
|
|
176
|
+
clickedTr.classList.add('expanded');
|
|
177
|
+
var detailTr=createDetailRow(colSpan);
|
|
178
|
+
clickedTr.parentNode.insertBefore(detailTr,clickedTr.nextSibling);
|
|
179
|
+
requestAnimationFrame(function(){
|
|
180
|
+
requestAnimationFrame(function(){
|
|
181
|
+
var w2=detailTr.querySelector('.rd-wrap');
|
|
182
|
+
if(w2)w2.classList.add('open');
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
loadDetailInline(id,detailTr);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/* ── Run Detail ── */
|
|
189
|
+
function loadDetailInline(id,detailTr){
|
|
190
|
+
api('/api/db/runs/'+id).then(function(d){
|
|
191
|
+
if(d.error)return;
|
|
192
|
+
var inner=detailTr.querySelector('.rd-inner');
|
|
193
|
+
inner.textContent='';
|
|
194
|
+
var results=d.results||[];
|
|
195
|
+
|
|
196
|
+
var exportBtn=el('div',null,[
|
|
197
|
+
el('div',{className:'rd-s-label'},'Export'),
|
|
198
|
+
el('div',{style:'margin-top:4px'},[
|
|
199
|
+
el('button',{className:'btn sm',onclick:function(e){
|
|
200
|
+
e.stopPropagation();
|
|
201
|
+
downloadFile('run-'+id+'.json',JSON.stringify(d,null,2),'application/json');
|
|
202
|
+
}},'JSON')
|
|
203
|
+
])
|
|
204
|
+
]);
|
|
205
|
+
var srcBlock=el('div',null,[el('div',{className:'rd-s-label'},'Source'),el('div',{style:'margin-top:4px'},[createTriggerBadge(d.triggeredBy)])]);
|
|
206
|
+
var summ=el('div',{className:'rd-summary'},[
|
|
207
|
+
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')]),
|
|
208
|
+
srcBlock,
|
|
209
|
+
el('div',null,[el('div',{className:'rd-s-label'},'Total'),el('div',{className:'rd-s-val'},String(d.summary.total))]),
|
|
210
|
+
el('div',null,[el('div',{className:'rd-s-label'},'Passed'),el('div',{className:'rd-s-val',style:'color:var(--green)'},String(d.summary.passed))]),
|
|
211
|
+
el('div',null,[el('div',{className:'rd-s-label'},'Failed'),el('div',{className:'rd-s-val',style:'color:'+(d.summary.failed>0?'var(--red)':'var(--text3)')},String(d.summary.failed))]),
|
|
212
|
+
el('div',null,[el('div',{className:'rd-s-label'},'Duration'),el('div',{className:'rd-s-val',style:'font-size:14px;color:var(--text2)'},d.summary.duration||'-')]),
|
|
213
|
+
exportBtn
|
|
214
|
+
]);
|
|
215
|
+
inner.appendChild(summ);
|
|
216
|
+
|
|
217
|
+
// Insights
|
|
218
|
+
var insightsContainer=el('div',{className:'rd-insights'});
|
|
219
|
+
inner.appendChild(insightsContainer);
|
|
220
|
+
fetch('/api/db/runs/'+id+'/insights').then(function(r){return r.json()}).then(function(ins){
|
|
221
|
+
if(!ins||ins.error)return;
|
|
222
|
+
var items=[];
|
|
223
|
+
var h=ins.health;
|
|
224
|
+
if(h){
|
|
225
|
+
var rateColor=h.passRate>=90?'green':h.passRate>=70?'amber':'red';
|
|
226
|
+
var trendIcon=h.passRateTrend==='improving'?'\u25B2':h.passRateTrend==='declining'?'\u25BC':'=';
|
|
227
|
+
var trendCls=h.passRateTrend==='improving'?'green':h.passRateTrend==='declining'?'red':'';
|
|
228
|
+
items.push(el('div',{className:'rd-ins-health'},[
|
|
229
|
+
el('span',{className:'rd-ins-rate '+rateColor},h.passRate+'%'),
|
|
230
|
+
el('span',{className:'rd-ins-trend '+trendCls},trendIcon+' '+h.passRateTrend),
|
|
231
|
+
h.flakyCount>0?el('span',{className:'rd-ins-tag amber'},h.flakyCount+' flaky'):null,
|
|
232
|
+
h.unstableSelectorCount>0?el('span',{className:'rd-ins-tag red'},h.unstableSelectorCount+' unstable sel.'):null
|
|
233
|
+
]));
|
|
234
|
+
}
|
|
235
|
+
var insights=ins.insights||[];
|
|
236
|
+
insights.forEach(function(i){
|
|
237
|
+
var icon=i.type==='new-failure'?'\u2718':i.type==='recovered'?'\u2714':i.type==='flaky'?'\u223C':'!';
|
|
238
|
+
var cls=i.type==='new-failure'?'red':i.type==='recovered'?'green':i.type==='flaky'?'amber':'';
|
|
239
|
+
items.push(el('div',{className:'rd-ins-item '+cls},[
|
|
240
|
+
el('span',{className:'rd-ins-icon'},icon),
|
|
241
|
+
el('span',null,i.message)
|
|
242
|
+
]));
|
|
243
|
+
});
|
|
244
|
+
if(items.length>0){items.forEach(function(it){insightsContainer.appendChild(it)})}
|
|
245
|
+
else{insightsContainer.style.display='none'}
|
|
246
|
+
}).catch(function(){insightsContainer.style.display='none'});
|
|
247
|
+
|
|
248
|
+
// Pool distribution bar
|
|
249
|
+
var histPoolTests={};
|
|
250
|
+
results.forEach(function(r){if(!r.poolUrl)return;histPoolTests[r.name]={poolUrl:r.poolUrl,success:r.success}});
|
|
251
|
+
var histPoolDist=buildPoolDistribution(histPoolTests);
|
|
252
|
+
if(histPoolDist)inner.appendChild(histPoolDist);
|
|
253
|
+
|
|
254
|
+
results.forEach(function(r){
|
|
255
|
+
var d2=r.durationMs?dur(r.durationMs):r.endTime&&r.startTime?dur(new Date(r.endTime)-new Date(r.startTime)):'-';
|
|
256
|
+
var flaky=r.success&&r.attempt>1;
|
|
257
|
+
var state=flaky?'flaky':(r.success?'pass':'fail');
|
|
258
|
+
|
|
259
|
+
var badges=el('div',{style:'display:flex;gap:6px;align-items:center;flex-shrink:0'});
|
|
260
|
+
badges.appendChild(el('span',{className:'badge '+(r.success?'pass':'fail')},r.success?'PASS':'FAIL'));
|
|
261
|
+
if(flaky)badges.appendChild(el('span',{className:'badge flaky'},'FLAKY'));
|
|
262
|
+
|
|
263
|
+
var poolEl=r.poolUrl?el('span',{className:'pool-badge'},r.poolUrl.replace('ws://','').replace('wss://','')) :null;
|
|
264
|
+
var head=el('div',{className:'rd-test-head'},[badges,el('div',{className:'rd-test-name'},[document.createTextNode(r.name),poolEl]),el('div',{className:'rd-test-dur'},d2)]);
|
|
265
|
+
var body=el('div',{className:'rd-test-body'});
|
|
266
|
+
|
|
267
|
+
if(r.maxAttempts>1){body.appendChild(el('div',{className:'rd-retries'},'Attempt '+r.attempt+' of '+r.maxAttempts))}
|
|
268
|
+
if(r.error){
|
|
269
|
+
var errDiv=el('div',{className:'rd-error-msg'});
|
|
270
|
+
errDiv.appendChild(document.createTextNode(r.error));
|
|
271
|
+
errDiv.appendChild(makeCopyBtn(r.error));
|
|
272
|
+
body.appendChild(errDiv);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Actions panel
|
|
276
|
+
if(r.actions&&r.actions.length){
|
|
277
|
+
var passCount=r.actions.filter(function(a){return a.success}).length;
|
|
278
|
+
var failCount=r.actions.length-passCount;
|
|
279
|
+
var actHead=el('div',{className:'rd-net-head'},[
|
|
280
|
+
el('span',{className:'net-arrow'},'\u25B6'),
|
|
281
|
+
el('span',{className:'net-title'},'Actions'),
|
|
282
|
+
el('div',{className:'net-stats'},[
|
|
283
|
+
el('span',{className:'net-stat'},[document.createTextNode('Steps: '),el('strong',null,String(r.actions.length))]),
|
|
284
|
+
failCount?el('span',{className:'net-stat has-err'},[document.createTextNode('Failed: '),el('strong',null,String(failCount))]):null
|
|
285
|
+
])
|
|
286
|
+
]);
|
|
287
|
+
var actBody=el('div',{className:'rd-net-body',style:'padding:8px 14px'});
|
|
288
|
+
r.actions.forEach(function(a){
|
|
289
|
+
var label=a.narrative||a.type;
|
|
290
|
+
var durText=a.duration!=null?dur(a.duration):'';
|
|
291
|
+
var retryBadge=null;
|
|
292
|
+
if(a.actionRetries&&a.actionRetries>0){
|
|
293
|
+
retryBadge=el('span',{className:'badge flaky',style:'font-size:9px;padding:1px 5px'},'\u21BB x'+a.actionRetries);
|
|
294
|
+
}
|
|
295
|
+
actBody.appendChild(el('div',{className:'lt-step'},[
|
|
296
|
+
el('span',{className:'step-icon '+(a.success?'ok':'fail')},a.success?'\u2714':'\u2718'),
|
|
297
|
+
el('span',{className:'step-detail',style:'flex:1'},label),
|
|
298
|
+
retryBadge,
|
|
299
|
+
el('span',{className:'step-dur'},durText)
|
|
300
|
+
]));
|
|
301
|
+
});
|
|
302
|
+
actHead.addEventListener('click',function(){actHead.classList.toggle('open')});
|
|
303
|
+
body.appendChild(el('div',{className:'rd-net-panel'},[actHead,actBody]));
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Screenshots
|
|
307
|
+
var shots=[];
|
|
308
|
+
var hashes=r.screenshotHashes||{};
|
|
309
|
+
(r.screenshots||[]).forEach(function(p){shots.push({path:p,label:p.split('/').pop(),type:'screenshot',hash:hashes[p]||null})});
|
|
310
|
+
if(r.errorScreenshot){shots.push({path:r.errorScreenshot,label:r.errorScreenshot.split('/').pop(),type:'error',hash:hashes[r.errorScreenshot]||null})}
|
|
311
|
+
if(shots.length){
|
|
312
|
+
var shotsWrap=el('div',{className:'rd-shots'});
|
|
313
|
+
shots.forEach(function(s){
|
|
314
|
+
var src='/api/image?path='+encodeURIComponent(s.path);
|
|
315
|
+
var img=document.createElement('img');img.src=src;img.alt=s.label;img.loading='lazy';
|
|
316
|
+
var capEl=el('div',{className:'rd-shot-cap'},[el('span',{className:'cap-name'},s.label)]);
|
|
317
|
+
if(s.hash){capEl.appendChild(createHashBadge(s.hash))}
|
|
318
|
+
else{(function(c,fp){ssHash(fp).then(function(h){c.appendChild(createHashBadge(h))})})(capEl,s.path)}
|
|
319
|
+
shotsWrap.appendChild(el('div',{className:'rd-shot'+(s.type==='error'?' err-shot':''),onclick:function(e){e.stopPropagation();openModal(src)}},[img,capEl]));
|
|
320
|
+
});
|
|
321
|
+
body.appendChild(shotsWrap);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Console logs
|
|
325
|
+
var cIssues=(r.consoleLogs||[]).filter(function(l){return l.type==='error'||l.type==='warn'||l.type==='warning'});
|
|
326
|
+
if(cIssues.length){
|
|
327
|
+
var cErrors=cIssues.filter(function(l){return l.type==='error'}).length;
|
|
328
|
+
var cWarns=cIssues.length-cErrors;
|
|
329
|
+
var conHead=el('div',{className:'rd-net-head'},[
|
|
330
|
+
el('span',{className:'net-arrow'},'\u25B6'),
|
|
331
|
+
el('span',{className:'net-title'},'Console'),
|
|
332
|
+
el('div',{className:'net-stats'},[
|
|
333
|
+
cErrors?el('span',{className:'net-stat has-err'},[document.createTextNode('Errors: '),el('strong',null,String(cErrors))]):null,
|
|
334
|
+
cWarns?el('span',{className:'net-stat'},[document.createTextNode('Warnings: '),el('strong',null,String(cWarns))]):null
|
|
335
|
+
]),
|
|
336
|
+
makeCopyBtn(function(){return cIssues.map(function(l){return '['+l.type+'] '+l.text}).join('\n')})
|
|
337
|
+
]);
|
|
338
|
+
var conBody=el('div',{className:'rd-net-body'});
|
|
339
|
+
cIssues.forEach(function(l){conBody.appendChild(el('div',{className:'rd-log-item '+l.type},'['+l.type+'] '+l.text))});
|
|
340
|
+
conHead.addEventListener('click',function(){conHead.classList.toggle('open')});
|
|
341
|
+
body.appendChild(el('div',{className:'rd-net-panel'},[conHead,conBody]));
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Network errors
|
|
345
|
+
if(r.networkErrors&&r.networkErrors.length){
|
|
346
|
+
var neHead=el('div',{className:'rd-net-head'},[
|
|
347
|
+
el('span',{className:'net-arrow'},'\u25B6'),
|
|
348
|
+
el('span',{className:'net-title'},'Network Errors'),
|
|
349
|
+
el('div',{className:'net-stats'},[el('span',{className:'net-stat has-err'},[document.createTextNode('Errors: '),el('strong',null,String(r.networkErrors.length))])]),
|
|
350
|
+
makeCopyBtn(function(){return r.networkErrors.map(function(ne){return '['+ne.error+'] '+ne.url}).join('\n')})
|
|
351
|
+
]);
|
|
352
|
+
var neBody=el('div',{className:'rd-net-body'});
|
|
353
|
+
r.networkErrors.forEach(function(ne){neBody.appendChild(el('div',{className:'rd-log-item error'},'['+ne.error+'] '+ne.url))});
|
|
354
|
+
neHead.addEventListener('click',function(){neHead.classList.toggle('open')});
|
|
355
|
+
body.appendChild(el('div',{className:'rd-net-panel'},[neHead,neBody]));
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Network panel
|
|
359
|
+
if(r.networkLogs&&r.networkLogs.length){
|
|
360
|
+
var errCount=r.networkLogs.filter(function(n){return n.status>=400}).length;
|
|
361
|
+
var netHead=el('div',{className:'rd-net-head'},[
|
|
362
|
+
el('span',{className:'net-arrow'},'\u25B6'),
|
|
363
|
+
el('span',{className:'net-title'},'Network Requests'),
|
|
364
|
+
el('div',{className:'net-stats'},[
|
|
365
|
+
el('span',{className:'net-stat'},[document.createTextNode('Total: '),el('strong',null,String(r.networkLogs.length))]),
|
|
366
|
+
errCount?el('span',{className:'net-stat has-err'},[document.createTextNode('Errors: '),el('strong',null,String(errCount))]):null
|
|
367
|
+
])
|
|
368
|
+
]);
|
|
369
|
+
var netCols=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')]);
|
|
370
|
+
var netBody=el('div',{className:'rd-net-body'},[netCols]);
|
|
371
|
+
r.networkLogs.forEach(function(n){var built=buildNetRow(n);netBody.appendChild(built.row);if(built.detail)netBody.appendChild(built.detail)});
|
|
372
|
+
netHead.addEventListener('click',function(){netHead.classList.toggle('open')});
|
|
373
|
+
body.appendChild(el('div',{className:'rd-net-panel'},[netHead,netBody]));
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
inner.appendChild(el('div',{className:'rd-test '+state},[head,body]));
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
var w=detailTr.querySelector('.rd-wrap');
|
|
380
|
+
if(w&&!w.classList.contains('open')){requestAnimationFrame(function(){w.classList.add('open')})}
|
|
381
|
+
}).catch(function(){
|
|
382
|
+
var inner=detailTr.querySelector('.rd-inner');
|
|
383
|
+
if(inner)inner.textContent='Failed to load run detail';
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/* ── Screenshots ── */
|
|
388
|
+
function refreshScreenshots(){
|
|
389
|
+
var gal=$('#screenshotGallery'),empty=$('#screenshotsEmpty');
|
|
390
|
+
gal.textContent='';
|
|
391
|
+
if(!S.project){empty.style.display='block';empty.querySelector('p').textContent='Select a project to view screenshots.';$('#badgeScreenshots').textContent='-';return}
|
|
392
|
+
api('/api/db/projects/'+S.project+'/screenshots').then(function(files){
|
|
393
|
+
if(!Array.isArray(files)||!files.length){empty.style.display='block';empty.querySelector('p').textContent='No screenshots for this project.';$('#badgeScreenshots').textContent='0';return}
|
|
394
|
+
empty.style.display='none';
|
|
395
|
+
$('#badgeScreenshots').textContent=files.length;
|
|
396
|
+
files.forEach(function(f){
|
|
397
|
+
var src='/api/image?path='+encodeURIComponent(f.path);
|
|
398
|
+
var img=document.createElement('img');img.src=src;img.alt=f.name;img.loading='lazy';
|
|
399
|
+
var capEl=el('div',{className:'cap'},[el('span',{className:'cap-name'},f.name)]);
|
|
400
|
+
(function(c,fp){ssHash(fp).then(function(h){c.appendChild(createHashBadge(h))})})(capEl,f.path);
|
|
401
|
+
gal.appendChild(el('div',{className:'gallery-item',onclick:function(){openModal(src)}},[img,capEl]));
|
|
402
|
+
});
|
|
403
|
+
}).catch(function(){});
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function searchByHash(){
|
|
407
|
+
var container=$('#ssSearchResult');
|
|
408
|
+
container.textContent='';
|
|
409
|
+
var raw=$('#ssHashInput').value.trim();
|
|
410
|
+
if(!raw)return;
|
|
411
|
+
var hash=raw.replace(/^ss:/,'');
|
|
412
|
+
if(!/^[a-f0-9]{1,8}$/i.test(hash)){
|
|
413
|
+
container.appendChild(el('div',{className:'ss-search-error'},'Invalid hash format. Expected 8 hex characters (e.g. ss:a3f2b1c9).'));
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
fetch('/api/screenshot-hash/'+hash).then(function(res){
|
|
417
|
+
if(!res.ok){container.appendChild(el('div',{className:'ss-search-error'},'Screenshot not found for hash: ss:'+hash));return}
|
|
418
|
+
return res.blob();
|
|
419
|
+
}).then(function(blob){
|
|
420
|
+
if(!blob)return;
|
|
421
|
+
var url=URL.createObjectURL(blob);
|
|
422
|
+
var wrap=el('div',{className:'ss-search-result'},[el('div',{className:'ss-result-label'},[createHashBadge(hash),el('span',{},'Found')])]);
|
|
423
|
+
var img=document.createElement('img');img.src=url;img.alt='ss:'+hash;
|
|
424
|
+
img.addEventListener('click',function(){openModal(url)});
|
|
425
|
+
wrap.appendChild(img);
|
|
426
|
+
container.appendChild(wrap);
|
|
427
|
+
}).catch(function(){container.appendChild(el('div',{className:'ss-search-error'},'Error searching for screenshot.'))});
|
|
428
|
+
}
|
|
429
|
+
$('#ssHashBtn').addEventListener('click',searchByHash);
|
|
430
|
+
$('#ssHashInput').addEventListener('keydown',function(e){if(e.key==='Enter')searchByHash()});
|
|
431
|
+
|
|
432
|
+
/* ── Learnings ── */
|
|
433
|
+
function refreshLearnings(){
|
|
434
|
+
var days=$('#learningsDays').value||30;
|
|
435
|
+
var url=S.project?'/api/db/projects/'+S.project+'/learnings?days='+days:'/api/db/learnings?days='+days;
|
|
436
|
+
fetch(url).then(function(r){return r.json()}).then(function(data){
|
|
437
|
+
if(!data||data.totalRuns===0){
|
|
438
|
+
$('#learningsEmpty').style.display='block';
|
|
439
|
+
$('#learningsOverview').textContent='';$('#learningsTrend').textContent='';
|
|
440
|
+
$('#learningsFlaky').textContent='';$('#learningsSelectors').textContent='';
|
|
441
|
+
$('#learningsPages').textContent='';$('#learningsApis').textContent='';
|
|
442
|
+
$('#learningsErrors').textContent='';
|
|
443
|
+
$('#badgeLearnings').textContent='-';
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
$('#learningsEmpty').style.display='none';
|
|
447
|
+
S.lastLearningsData=data;
|
|
448
|
+
var flakyCount=data.flakyTests?data.flakyTests.length:0;
|
|
449
|
+
var passRate=data.overallPassRate||0;
|
|
450
|
+
var declining=data.recentTrend&&Array.isArray(data.recentTrend.data||data.recentTrend)&&(function(){
|
|
451
|
+
var td=data.recentTrend.data||data.recentTrend;
|
|
452
|
+
if(td.length<2)return false;
|
|
453
|
+
var last=td[td.length-1].pass_rate;
|
|
454
|
+
var prior=td.slice(0,-1).reduce(function(s,t){return s+t.pass_rate},0)/(td.length-1);
|
|
455
|
+
return last-prior<-2;
|
|
456
|
+
})();
|
|
457
|
+
if(passRate<70){
|
|
458
|
+
$('#badgeLearnings').textContent='\u26A0';
|
|
459
|
+
$('#badgeLearnings').style.background='var(--red-dim)';$('#badgeLearnings').style.color='var(--red)';
|
|
460
|
+
} else if(flakyCount>0||declining){
|
|
461
|
+
$('#badgeLearnings').textContent=flakyCount>0?flakyCount:(declining?'\u25BC':'\u2714');
|
|
462
|
+
$('#badgeLearnings').style.background='var(--amber-dim)';$('#badgeLearnings').style.color='var(--amber)';
|
|
463
|
+
} else {
|
|
464
|
+
$('#badgeLearnings').textContent='\u2714';
|
|
465
|
+
$('#badgeLearnings').style.background='var(--green-dim)';$('#badgeLearnings').style.color='var(--green)';
|
|
466
|
+
}
|
|
467
|
+
renderLearnOverview(data);
|
|
468
|
+
renderLearnTrend(data.recentTrend||[]);
|
|
469
|
+
renderLearnFlaky(data.flakyTests||[]);
|
|
470
|
+
renderLearnSelectors(data.unstableSelectors||[]);
|
|
471
|
+
renderLearnPages(data.failingPages||[]);
|
|
472
|
+
renderLearnApis(data.apiIssues||[]);
|
|
473
|
+
renderLearnErrors(data.topErrors||[]);
|
|
474
|
+
}).catch(function(){$('#learningsEmpty').style.display='block'});
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function renderLearnOverview(d){
|
|
478
|
+
var container=$('#learningsOverview');container.textContent='';
|
|
479
|
+
var grid=document.createElement('div');grid.className='learn-grid';
|
|
480
|
+
[{val:d.totalRuns,lbl:'Runs',cls:'accent'},{val:d.totalTests,lbl:'Tests',cls:'accent'},
|
|
481
|
+
{val:d.overallPassRate+'%',lbl:'Pass Rate',cls:d.overallPassRate>=90?'green':d.overallPassRate>=70?'':'red'},
|
|
482
|
+
{val:d.avgDurationMs<1000?d.avgDurationMs+'ms':(d.avgDurationMs/1000).toFixed(1)+'s',lbl:'Avg Duration',cls:'purple'},
|
|
483
|
+
{val:(d.flakyTests?d.flakyTests.length:0),lbl:'Flaky Tests',cls:d.flakyTests&&d.flakyTests.length>0?'red':'green'},
|
|
484
|
+
{val:(d.unstableSelectors?d.unstableSelectors.length:0),lbl:'Unstable Selectors',cls:d.unstableSelectors&&d.unstableSelectors.length>0?'red':'green'}
|
|
485
|
+
].forEach(function(item){
|
|
486
|
+
var stat=document.createElement('div');stat.className='learn-stat';
|
|
487
|
+
var valEl=document.createElement('div');valEl.className='learn-stat-val '+item.cls;valEl.textContent=item.val;
|
|
488
|
+
var lblEl=document.createElement('div');lblEl.className='learn-stat-lbl';lblEl.textContent=item.lbl;
|
|
489
|
+
stat.appendChild(valEl);stat.appendChild(lblEl);grid.appendChild(stat);
|
|
490
|
+
});
|
|
491
|
+
container.appendChild(grid);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
function renderLearnTrend(trend){
|
|
495
|
+
var container=$('#learningsTrend');container.textContent='';
|
|
496
|
+
if(!trend.length)return;
|
|
497
|
+
var card=document.createElement('div');card.className='card';
|
|
498
|
+
var label=document.createElement('div');label.className='card-label';label.textContent='Pass Rate Trend (7 days)';card.appendChild(label);
|
|
499
|
+
var chartDiv=document.createElement('div');chartDiv.className='learn-trend-chart';
|
|
500
|
+
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');
|
|
502
|
+
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
|
+
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
|
+
var pts=trend.map(function(t,i){return(i*w+w/2)+','+(100-t.pass_rate)}).join(' ');
|
|
505
|
+
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
|
+
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
|
+
trend.forEach(function(t,i){
|
|
508
|
+
var circle=document.createElementNS(ns,'circle');circle.setAttribute('cx',''+(i*w+w/2));circle.setAttribute('cy',''+(100-t.pass_rate));circle.setAttribute('r','2');circle.setAttribute('fill','var(--accent)');
|
|
509
|
+
var title=document.createElementNS(ns,'title');title.textContent=t.date+': '+t.pass_rate+'% ('+t.total_tests+' tests)';circle.appendChild(title);svg.appendChild(circle);
|
|
510
|
+
});
|
|
511
|
+
chartDiv.appendChild(svg);card.appendChild(chartDiv);
|
|
512
|
+
var dates=document.createElement('div');dates.style.cssText='display:flex;justify-content:space-between;font-size:10px;color:var(--text3);margin-top:4px';
|
|
513
|
+
dates.appendChild(el('span',null,trend[0].date));dates.appendChild(el('span',null,trend[trend.length-1].date));
|
|
514
|
+
card.appendChild(dates);container.appendChild(card);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
function buildLearnTable(title,headers,rows){
|
|
518
|
+
var card=document.createElement('div');card.className='card learn-section';
|
|
519
|
+
var h=document.createElement('div');h.className='learn-section-title';h.textContent=title;card.appendChild(h);
|
|
520
|
+
var wrap=document.createElement('div');wrap.className='tbl-wrap';
|
|
521
|
+
var tbl=document.createElement('table');tbl.className='learn-table';
|
|
522
|
+
var thead=document.createElement('thead');var hr=document.createElement('tr');
|
|
523
|
+
headers.forEach(function(hdr){var th=document.createElement('th');th.textContent=hdr;hr.appendChild(th)});
|
|
524
|
+
thead.appendChild(hr);tbl.appendChild(thead);
|
|
525
|
+
var tbody=document.createElement('tbody');
|
|
526
|
+
rows.forEach(function(cells){
|
|
527
|
+
var tr=document.createElement('tr');
|
|
528
|
+
cells.forEach(function(cell){
|
|
529
|
+
var td=document.createElement('td');
|
|
530
|
+
if(cell.code){var code=document.createElement('code');code.textContent=cell.code;td.appendChild(code)}
|
|
531
|
+
else if(cell.badge){var span=document.createElement('span');span.className='badge '+cell.cls;span.textContent=cell.badge;td.appendChild(span)}
|
|
532
|
+
else{td.textContent=cell.text!==undefined&&cell.text!==null?cell.text:(typeof cell==='object'?'-':cell)}
|
|
533
|
+
tr.appendChild(td);
|
|
534
|
+
});
|
|
535
|
+
tbody.appendChild(tr);
|
|
536
|
+
});
|
|
537
|
+
tbl.appendChild(tbody);wrap.appendChild(tbl);card.appendChild(wrap);return card;
|
|
538
|
+
}
|
|
539
|
+
|
|
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
|
+
$('#btnRefreshLearnings').addEventListener('click',refreshLearnings);
|
|
547
|
+
$('#learningsDays').addEventListener('change',refreshLearnings);
|
|
548
|
+
|
|
549
|
+
$('#btnExportLearnings').addEventListener('click',function(){
|
|
550
|
+
var data=S.lastLearningsData;
|
|
551
|
+
if(!data){showToast('No learnings data to export','error');return}
|
|
552
|
+
var md='# E2E Learnings Report\n\n';
|
|
553
|
+
md+='| Metric | Value |\n|--------|-------|\n';
|
|
554
|
+
md+='| Total Runs | '+data.totalRuns+' |\n';
|
|
555
|
+
md+='| Total Tests | '+data.totalTests+' |\n';
|
|
556
|
+
md+='| Pass Rate | '+data.overallPassRate+'% |\n';
|
|
557
|
+
md+='| Avg Duration | '+dur(data.avgDurationMs)+' |\n\n';
|
|
558
|
+
if(data.flakyTests&&data.flakyTests.length){
|
|
559
|
+
md+='## Flaky Tests\n\n| Test | Flaky Rate | Occurrences |\n|------|-----------|-------------|\n';
|
|
560
|
+
data.flakyTests.forEach(function(f){md+='| '+f.test_name+' | '+f.flaky_rate+'% | '+f.flaky_count+' |\n'});md+='\n';
|
|
561
|
+
}
|
|
562
|
+
if(data.unstableSelectors&&data.unstableSelectors.length){
|
|
563
|
+
md+='## Unstable Selectors\n\n| Selector | Action | Fail Rate |\n|----------|--------|-----------|\n';
|
|
564
|
+
data.unstableSelectors.forEach(function(s){md+='| `'+s.selector+'` | '+s.action_type+' | '+s.fail_rate+'% |\n'});md+='\n';
|
|
565
|
+
}
|
|
566
|
+
downloadFile('learnings-report.md',md,'text/markdown');
|
|
567
|
+
showToast('Learnings exported','success');
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
/* ── Modal ── */
|
|
571
|
+
function openModal(src){$('#modalImg').src=src;$('#modal').classList.add('open')}
|
|
572
|
+
$('#modal').addEventListener('click',function(){$('#modal').classList.remove('open')});
|