@matware/e2e-runner 1.3.0 → 1.5.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 +37 -6
- package/.claude-plugin/plugin.json +17 -3
- package/LICENSE +190 -0
- package/README.md +151 -527
- package/agents/test-creator.md +4 -2
- package/agents/test-improver.md +5 -3
- package/bin/cli.js +84 -20
- package/commands/capture.md +45 -0
- package/package.json +3 -2
- package/skills/e2e-testing/SKILL.md +3 -2
- package/skills/e2e-testing/references/action-types.md +22 -4
- package/skills/e2e-testing/references/test-json-format.md +23 -0
- package/src/actions.js +321 -14
- package/src/ai-generate.js +81 -0
- package/src/app-pool.js +339 -0
- package/src/config.js +131 -7
- package/src/dashboard.js +209 -11
- package/src/db.js +74 -7
- package/src/index.js +6 -4
- package/src/learner-sqlite.js +154 -0
- package/src/learner.js +70 -3
- package/src/mcp-tools.js +259 -34
- package/src/module-analysis.js +247 -0
- package/src/module-resolver.js +35 -2
- package/src/narrate.js +42 -1
- package/src/pool-manager.js +68 -17
- package/src/pool.js +464 -37
- package/src/reporter.js +4 -1
- package/src/runner.js +410 -63
- package/src/visual-diff.js +515 -0
- package/src/websocket.js +14 -3
- package/src/wizard.js +184 -0
- package/templates/build-dashboard.js +3 -0
- package/templates/dashboard/js/api.js +62 -3
- package/templates/dashboard/js/init.js +46 -0
- package/templates/dashboard/js/keyboard.js +8 -7
- package/templates/dashboard/js/quicksearch.js +277 -0
- package/templates/dashboard/js/state.js +61 -7
- package/templates/dashboard/js/toast.js +1 -1
- package/templates/dashboard/js/utils.js +20 -0
- package/templates/dashboard/js/view-live.js +240 -9
- package/templates/dashboard/js/view-runs.js +540 -94
- package/templates/dashboard/js/view-tests.js +157 -16
- package/templates/dashboard/js/view-tools.js +234 -0
- package/templates/dashboard/js/view-watch.js +2 -2
- package/templates/dashboard/js/websocket.js +36 -0
- package/templates/dashboard/styles/base.css +489 -53
- package/templates/dashboard/styles/components.css +719 -77
- package/templates/dashboard/styles/view-live.css +463 -59
- package/templates/dashboard/styles/view-runs.css +793 -155
- package/templates/dashboard/styles/view-tests.css +440 -77
- package/templates/dashboard/styles/view-tools.css +206 -0
- package/templates/dashboard/styles/view-watch.css +198 -41
- package/templates/dashboard/template.html +369 -56
- package/templates/dashboard.html +5375 -901
- package/templates/docker-compose-lightpanda.yml +7 -0
|
@@ -4,43 +4,67 @@
|
|
|
4
4
|
function refreshSuites(){
|
|
5
5
|
var grid=$('#suiteGrid'),empty=$('#suitesEmpty'),accordion=$('#suiteAccordionContainer');
|
|
6
6
|
grid.textContent='';
|
|
7
|
+
accordion.textContent='';
|
|
7
8
|
var moduleSection=$('#moduleSection');
|
|
8
9
|
moduleSection.textContent='';
|
|
10
|
+
var toolbar=$('#suitesToolbar');
|
|
9
11
|
|
|
10
12
|
if(S.project){
|
|
13
|
+
// Keep the toolbar visible so users can still search suites within a
|
|
14
|
+
// single project — only the expand/collapse buttons are dropped since
|
|
15
|
+
// there are no project accordions to expand in single-project view.
|
|
16
|
+
if(toolbar){
|
|
17
|
+
toolbar.style.display='';
|
|
18
|
+
toolbar.classList.add('single-project');
|
|
19
|
+
}
|
|
11
20
|
api('/api/db/projects/'+S.project+'/suites').then(function(suites){
|
|
12
21
|
if(!Array.isArray(suites)||suites.length===0){empty.style.display='block';empty.querySelector('p').textContent='No test suites found for this project.';return}
|
|
13
22
|
empty.style.display='none';
|
|
14
23
|
$('#badgeSuites').textContent=suites.length;
|
|
15
24
|
renderSuiteCards(grid,suites,S.project);
|
|
25
|
+
applyTestsSearch();
|
|
16
26
|
}).catch(function(){});
|
|
17
27
|
api('/api/db/projects/'+S.project+'/modules').then(function(modules){
|
|
18
28
|
renderModules(moduleSection,modules);
|
|
29
|
+
applyTestsSearch();
|
|
19
30
|
}).catch(function(){});
|
|
20
31
|
} else {
|
|
32
|
+
if(toolbar){toolbar.style.display='';toolbar.classList.remove('single-project')}
|
|
21
33
|
api('/api/db/projects').then(function(projects){
|
|
22
34
|
if(!Array.isArray(projects)||projects.length===0){empty.style.display='block';empty.querySelector('p').textContent='No projects registered yet.';return}
|
|
23
|
-
var
|
|
24
|
-
|
|
35
|
+
var sorted=projects.slice().sort(function(a,b){return (a.name||'').localeCompare(b.name||'')});
|
|
36
|
+
var pending=sorted.length,results=[];
|
|
37
|
+
sorted.forEach(function(p,idx){
|
|
25
38
|
api('/api/db/projects/'+p.id+'/suites').then(function(suites){
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
if(loaded===projects.length){
|
|
34
|
-
$('#badgeSuites').textContent=totalSuites;
|
|
35
|
-
if(!hasAny){empty.style.display='block';empty.querySelector('p').textContent='No test suites found.'}
|
|
36
|
-
}
|
|
37
|
-
}).catch(function(){loaded++;});
|
|
39
|
+
results[idx]={project:p,suites:Array.isArray(suites)?suites:[]};
|
|
40
|
+
}).catch(function(){
|
|
41
|
+
results[idx]={project:p,suites:[]};
|
|
42
|
+
}).then(function(){
|
|
43
|
+
pending--;
|
|
44
|
+
if(pending===0)renderAllProjectAccordions(results);
|
|
45
|
+
});
|
|
38
46
|
});
|
|
39
47
|
}).catch(function(){});
|
|
40
48
|
}
|
|
41
49
|
}
|
|
42
50
|
|
|
43
|
-
function
|
|
51
|
+
function renderAllProjectAccordions(results){
|
|
52
|
+
var container=$('#suiteAccordionContainer');
|
|
53
|
+
var empty=$('#suitesEmpty');
|
|
54
|
+
container.textContent='';
|
|
55
|
+
var withSuites=results.filter(function(r){return r.suites.length>0});
|
|
56
|
+
var totalSuites=withSuites.reduce(function(s,r){return s+r.suites.length},0);
|
|
57
|
+
$('#badgeSuites').textContent=totalSuites;
|
|
58
|
+
if(withSuites.length===0){empty.style.display='block';empty.querySelector('p').textContent='No test suites found.';return}
|
|
59
|
+
empty.style.display='none';
|
|
60
|
+
var autoExpand=withSuites.length===1;
|
|
61
|
+
withSuites.forEach(function(r){
|
|
62
|
+
renderProjectAccordion(container,r.project,r.suites,autoExpand||S.testsExpanded.has(r.project.id));
|
|
63
|
+
});
|
|
64
|
+
applyTestsSearch();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function renderProjectAccordion(container,project,suites,startOpen){
|
|
44
68
|
var totalTests=suites.reduce(function(sum,s){return sum+(s.testCount||0)},0);
|
|
45
69
|
var body=el('div',{className:'project-accordion-body'});
|
|
46
70
|
var innerGrid=el('div',{className:'suite-grid'});
|
|
@@ -57,10 +81,104 @@ function renderProjectAccordion(container,project,suites){
|
|
|
57
81
|
]);
|
|
58
82
|
|
|
59
83
|
var wrapper=el('div',{className:'project-accordion'},[header,body]);
|
|
60
|
-
|
|
84
|
+
wrapper.dataset.projectId=String(project.id);
|
|
85
|
+
wrapper.dataset.projectName=(project.name||'').toLowerCase();
|
|
86
|
+
if(startOpen)wrapper.classList.add('open');
|
|
87
|
+
header.addEventListener('click',function(){
|
|
88
|
+
wrapper.classList.toggle('open');
|
|
89
|
+
if(wrapper.classList.contains('open'))S.testsExpanded.add(project.id);
|
|
90
|
+
else S.testsExpanded.delete(project.id);
|
|
91
|
+
});
|
|
61
92
|
container.appendChild(wrapper);
|
|
62
93
|
}
|
|
63
94
|
|
|
95
|
+
/* ── Search / filter ── */
|
|
96
|
+
function applyTestsSearch(){
|
|
97
|
+
var q=(S.testsSearch||'').trim().toLowerCase();
|
|
98
|
+
// Single-project mode: filter suite cards in #suiteGrid + module cards
|
|
99
|
+
// in #moduleSection. The toolbar count reflects both.
|
|
100
|
+
if(S.project){
|
|
101
|
+
var visSuites=0,visModules=0;
|
|
102
|
+
$$('#suiteGrid .suite-card').forEach(function(card){
|
|
103
|
+
var sname=(card.dataset.suiteName||'').toLowerCase();
|
|
104
|
+
var tests=card.querySelectorAll('.suite-card-tests li');
|
|
105
|
+
var testHit=false;
|
|
106
|
+
tests.forEach(function(li){
|
|
107
|
+
var raw=(li.firstChild&&li.firstChild.nodeType===3?li.firstChild.nodeValue:li.textContent)||'';
|
|
108
|
+
var tname=raw.toLowerCase();
|
|
109
|
+
var matches=!q||sname.indexOf(q)>=0||tname.indexOf(q)>=0;
|
|
110
|
+
li.style.display=matches?'':'none';
|
|
111
|
+
if(q&&tname.indexOf(q)>=0)testHit=true;
|
|
112
|
+
});
|
|
113
|
+
var show=!q||sname.indexOf(q)>=0||testHit;
|
|
114
|
+
card.style.display=show?'':'none';
|
|
115
|
+
if(show)visSuites++;
|
|
116
|
+
});
|
|
117
|
+
$$('#moduleSection .module-card').forEach(function(card){
|
|
118
|
+
var nm=(card.querySelector('.module-card-name')?.textContent||'').toLowerCase();
|
|
119
|
+
var desc=(card.querySelector('.module-card-desc')?.textContent||'').toLowerCase();
|
|
120
|
+
var show=!q||nm.indexOf(q)>=0||desc.indexOf(q)>=0;
|
|
121
|
+
card.style.display=show?'':'none';
|
|
122
|
+
if(show)visModules++;
|
|
123
|
+
});
|
|
124
|
+
var t=$('#module-section-title')||document.querySelector('.module-section-title');
|
|
125
|
+
var countEl=$('#suitesToolbarCount');
|
|
126
|
+
if(countEl){
|
|
127
|
+
if(q)countEl.textContent=visSuites+' suites · '+visModules+' modules';
|
|
128
|
+
else countEl.textContent='';
|
|
129
|
+
}
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
// Multi-project (All Projects) mode: filter accordions and their children
|
|
133
|
+
var accordions=$$('#suiteAccordionContainer .project-accordion');
|
|
134
|
+
var visibleProjects=0,visibleSuites=0;
|
|
135
|
+
|
|
136
|
+
accordions.forEach(function(acc){
|
|
137
|
+
var pname=acc.dataset.projectName||'';
|
|
138
|
+
var projectMatches=q&&pname.indexOf(q)>=0;
|
|
139
|
+
var anySuiteVisible=false;
|
|
140
|
+
var cards=acc.querySelectorAll('.suite-card');
|
|
141
|
+
cards.forEach(function(card){
|
|
142
|
+
var sname=(card.dataset.suiteName||'').toLowerCase();
|
|
143
|
+
var tests=card.querySelectorAll('.suite-card-tests li');
|
|
144
|
+
var testMatches=0;
|
|
145
|
+
tests.forEach(function(li){
|
|
146
|
+
var raw=(li.firstChild&&li.firstChild.nodeType===3?li.firstChild.nodeValue:li.textContent)||'';
|
|
147
|
+
var tname=raw.toLowerCase();
|
|
148
|
+
var matches=!q||projectMatches||sname.indexOf(q)>=0||tname.indexOf(q)>=0;
|
|
149
|
+
li.style.display=matches?'':'none';
|
|
150
|
+
if(matches&&q&&tname.indexOf(q)>=0)testMatches++;
|
|
151
|
+
});
|
|
152
|
+
var suiteVisible=!q||projectMatches||sname.indexOf(q)>=0||testMatches>0;
|
|
153
|
+
card.style.display=suiteVisible?'':'none';
|
|
154
|
+
if(suiteVisible){anySuiteVisible=true;visibleSuites++}
|
|
155
|
+
});
|
|
156
|
+
var projectVisible=!q||projectMatches||anySuiteVisible;
|
|
157
|
+
acc.style.display=projectVisible?'':'none';
|
|
158
|
+
if(projectVisible)visibleProjects++;
|
|
159
|
+
if(q&&projectVisible&&anySuiteVisible)acc.classList.add('open');
|
|
160
|
+
else if(q&&!projectVisible)acc.classList.remove('open');
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
var countEl=$('#suitesToolbarCount');
|
|
164
|
+
if(countEl){
|
|
165
|
+
if(q)countEl.textContent=visibleSuites+' suites · '+visibleProjects+' projects';
|
|
166
|
+
else countEl.textContent='';
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function setSuiteAccordionsOpen(open){
|
|
171
|
+
$$('#suiteAccordionContainer .project-accordion').forEach(function(acc){
|
|
172
|
+
if(acc.style.display==='none')return;
|
|
173
|
+
acc.classList.toggle('open',!!open);
|
|
174
|
+
var pid=parseInt(acc.dataset.projectId,10);
|
|
175
|
+
if(!isNaN(pid)){
|
|
176
|
+
if(open)S.testsExpanded.add(pid);
|
|
177
|
+
else S.testsExpanded.delete(pid);
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
64
182
|
/* ── Suite Modal ── */
|
|
65
183
|
var _suiteCache={};
|
|
66
184
|
|
|
@@ -195,6 +313,7 @@ function renderSuiteCards(container,suites,projectId){
|
|
|
195
313
|
el('button',{className:'btn sm primary',onclick:function(){triggerRun(s.name,pid)}},'Run Suite')
|
|
196
314
|
])
|
|
197
315
|
]);
|
|
316
|
+
card.dataset.suiteName=s.name;
|
|
198
317
|
container.appendChild(card);
|
|
199
318
|
});
|
|
200
319
|
}
|
|
@@ -292,3 +411,25 @@ $('#btnAddVar').addEventListener('click',function(){
|
|
|
292
411
|
});
|
|
293
412
|
|
|
294
413
|
$('#btnRunAll').addEventListener('click',function(){triggerRun()});
|
|
414
|
+
|
|
415
|
+
/* ── Tests toolbar (search + expand/collapse all) ── */
|
|
416
|
+
(function(){
|
|
417
|
+
var input=$('#suitesSearchInput');
|
|
418
|
+
if(input){
|
|
419
|
+
var debounce;
|
|
420
|
+
input.addEventListener('input',function(){
|
|
421
|
+
clearTimeout(debounce);
|
|
422
|
+
debounce=setTimeout(function(){
|
|
423
|
+
S.testsSearch=input.value||'';
|
|
424
|
+
applyTestsSearch();
|
|
425
|
+
},90);
|
|
426
|
+
});
|
|
427
|
+
input.addEventListener('keydown',function(e){
|
|
428
|
+
if(e.key==='Escape'){input.value='';S.testsSearch='';applyTestsSearch()}
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
var bExp=$('#btnExpandAllSuites');
|
|
432
|
+
if(bExp)bExp.addEventListener('click',function(){setSuiteAccordionsOpen(true)});
|
|
433
|
+
var bCol=$('#btnCollapseAllSuites');
|
|
434
|
+
if(bCol)bCol.addEventListener('click',function(){setSuiteAccordionsOpen(false)});
|
|
435
|
+
})();
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/* ══════════════════════════════════════════════════════════════════
|
|
2
|
+
Tools View — Module Analysis, Capture, Analyze, Verify, Agent prompts
|
|
3
|
+
══════════════════════════════════════════════════════════════════ */
|
|
4
|
+
|
|
5
|
+
/* ── Module Analysis ─────────────────────────────────────────── */
|
|
6
|
+
var MA_LAST = null;
|
|
7
|
+
|
|
8
|
+
function refreshModuleAnalysis(){
|
|
9
|
+
var body=$('#modAnalysisBody');var btnCopy=$('#btnCopyModulePrompt');
|
|
10
|
+
if(!body)return;
|
|
11
|
+
if(!S.project){
|
|
12
|
+
body.innerHTML='';
|
|
13
|
+
body.appendChild(el('div',{className:'tool-empty'},'Pick a project from the sidebar, then click Run analysis.'));
|
|
14
|
+
if(btnCopy)btnCopy.disabled=true;
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
body.innerHTML='';
|
|
18
|
+
body.appendChild(el('div',{className:'tool-empty'},'Running analysis…'));
|
|
19
|
+
api('/api/tools/module-analysis/'+S.project).then(function(data){
|
|
20
|
+
if(data&&data.error){
|
|
21
|
+
body.innerHTML='';
|
|
22
|
+
body.appendChild(el('div',{className:'tool-result is-error'},'Error: '+data.error));
|
|
23
|
+
if(btnCopy)btnCopy.disabled=true;
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
MA_LAST=data;
|
|
27
|
+
if(btnCopy)btnCopy.disabled=!data.agentPrompt;
|
|
28
|
+
renderModuleAnalysis(body,data);
|
|
29
|
+
}).catch(function(e){
|
|
30
|
+
body.innerHTML='';
|
|
31
|
+
body.appendChild(el('div',{className:'tool-result is-error'},'Request failed: '+(e&&e.message||'unknown')));
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function renderModuleAnalysis(body,data){
|
|
36
|
+
body.innerHTML='';
|
|
37
|
+
var s=data.summary||{};
|
|
38
|
+
var summary=el('div',{className:'mod-summary'},[
|
|
39
|
+
summaryCell('Tests',s.testCount),
|
|
40
|
+
summaryCell('Modules',s.moduleCount),
|
|
41
|
+
summaryCell('Candidates',s.candidateCount,'signal'),
|
|
42
|
+
summaryCell('Unused',s.unusedModules,s.unusedModules>0?'warn':''),
|
|
43
|
+
]);
|
|
44
|
+
body.appendChild(summary);
|
|
45
|
+
|
|
46
|
+
// Extraction candidates
|
|
47
|
+
if(data.candidates&&data.candidates.length){
|
|
48
|
+
body.appendChild(el('div',{className:'mod-section-title'},[
|
|
49
|
+
document.createTextNode('Extraction candidates'),
|
|
50
|
+
el('span',{className:'count'},String(data.candidates.length))
|
|
51
|
+
]));
|
|
52
|
+
data.candidates.forEach(function(c){
|
|
53
|
+
body.appendChild(renderCandidateRow(c));
|
|
54
|
+
});
|
|
55
|
+
}else{
|
|
56
|
+
body.appendChild(el('div',{className:'mod-section-title'},[
|
|
57
|
+
document.createTextNode('Extraction candidates'),
|
|
58
|
+
el('span',{className:'count'},'0')
|
|
59
|
+
]));
|
|
60
|
+
body.appendChild(el('div',{className:'tool-empty'},'No duplicated 3-8-action sequences found across tests. Your suite is well factored — or maybe under-modularized?'));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Existing modules
|
|
64
|
+
if(data.modules&&data.modules.length){
|
|
65
|
+
body.appendChild(el('div',{className:'mod-section-title'},[
|
|
66
|
+
document.createTextNode('Existing modules'),
|
|
67
|
+
el('span',{className:'count'},String(data.modules.length))
|
|
68
|
+
]));
|
|
69
|
+
var sorted=data.modules.slice().sort(function(a,b){return (b.usageCount||0)-(a.usageCount||0)});
|
|
70
|
+
sorted.forEach(function(m){body.appendChild(renderModuleRow(m))});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function summaryCell(label,value,cls){
|
|
75
|
+
return el('div',{className:'mod-summary-cell'+(cls?' '+cls:'')},[
|
|
76
|
+
el('div',{className:'mod-summary-cell-lbl'},label),
|
|
77
|
+
el('div',{className:'mod-summary-cell-val'},String(value!=null?value:'—'))
|
|
78
|
+
]);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function renderModuleRow(m){
|
|
82
|
+
var cls='mod-row'+(m.usageCount===0?' unused':'');
|
|
83
|
+
var metaItems=[
|
|
84
|
+
el('span',null,(m.actionCount||0)+' actions'),
|
|
85
|
+
el('span',null,(m.params&&m.params.length||0)+' params'),
|
|
86
|
+
];
|
|
87
|
+
if(m.usedBy&&m.usedBy.length)metaItems.push(el('span',null,'used by: '+m.usedBy.slice(0,3).join(', ')+(m.usedBy.length>3?' +'+(m.usedBy.length-3):'')));
|
|
88
|
+
return el('div',{className:cls},[
|
|
89
|
+
el('div',{className:'mod-row-main'},[
|
|
90
|
+
el('div',{className:'mod-row-name'},m.name),
|
|
91
|
+
m.description?el('div',{className:'mod-row-desc'},m.description):null,
|
|
92
|
+
el('div',{className:'mod-row-meta'},metaItems)
|
|
93
|
+
]),
|
|
94
|
+
el('div',{className:'mod-row-usage'},(m.usageCount||0)+'×')
|
|
95
|
+
]);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function renderCandidateRow(c){
|
|
99
|
+
var preview=(c.sample||[]).map(function(a,i){
|
|
100
|
+
var bits=[String(i+1).padStart(2,'0')+'.',a.type||'?'];
|
|
101
|
+
if(a.selector)bits.push('@'+a.selector);
|
|
102
|
+
if(a.text!=null)bits.push('"'+String(a.text).slice(0,40)+'"');
|
|
103
|
+
if(a.value!=null&&!a.text)bits.push('= '+String(a.value).slice(0,40));
|
|
104
|
+
return bits.join(' ');
|
|
105
|
+
}).join('\n');
|
|
106
|
+
var usedBy=(c.usedBy||[]).map(function(u){return u.suite+' › '+u.test+(u.occurrences>1?' (×'+u.occurrences+')':'')}).join(', ');
|
|
107
|
+
return el('div',{className:'cand-row'},[
|
|
108
|
+
el('div',{className:'cand-row-head'},[
|
|
109
|
+
el('div',{className:'cand-name'},'Suggested: '+(c.suggestedName||'module')),
|
|
110
|
+
el('div',{className:'cand-stats'},[
|
|
111
|
+
document.createTextNode((c.length||0)+' actions · '),
|
|
112
|
+
el('strong',null,(c.testCount||0)+' tests'),
|
|
113
|
+
document.createTextNode(' · '),
|
|
114
|
+
el('strong',null,(c.occurrenceCount||0)+' occurrences')
|
|
115
|
+
])
|
|
116
|
+
]),
|
|
117
|
+
el('div',{className:'cand-actions-preview'},preview),
|
|
118
|
+
el('div',{className:'cand-used-by'},[el('strong',null,'used by: '),document.createTextNode(usedBy)])
|
|
119
|
+
]);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/* ── Capture URL ─────────────────────────────────────────── */
|
|
123
|
+
function runCapture(){
|
|
124
|
+
var url=$('#captureUrl').value.trim();var out=$('#captureResult');
|
|
125
|
+
if(!url){out.textContent='URL required';out.classList.add('is-error');return}
|
|
126
|
+
out.classList.remove('is-error');out.textContent='Capturing…';
|
|
127
|
+
var body={url:url,fullPage:$('#captureFullPage').checked};
|
|
128
|
+
if(S.project)body.projectId=S.project;
|
|
129
|
+
fetch('/api/tool/capture',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)})
|
|
130
|
+
.then(function(r){return r.json()})
|
|
131
|
+
.then(function(d){
|
|
132
|
+
if(d.error){out.classList.add('is-error');out.textContent='Error: '+d.error;return}
|
|
133
|
+
out.textContent='';
|
|
134
|
+
if(d.hash)out.appendChild(el('div',null,'Hash: '+d.hash));
|
|
135
|
+
if(d.path){
|
|
136
|
+
var img=document.createElement('img');img.src='/api/image?path='+encodeURIComponent(d.path);img.alt='capture';
|
|
137
|
+
out.appendChild(img);
|
|
138
|
+
}
|
|
139
|
+
})
|
|
140
|
+
.catch(function(e){out.classList.add('is-error');out.textContent='Request failed: '+(e&&e.message||'unknown')});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/* ── Analyze Page ────────────────────────────────────────── */
|
|
144
|
+
function runAnalyze(){
|
|
145
|
+
var url=$('#analyzeUrl').value.trim();var out=$('#analyzeResult');
|
|
146
|
+
if(!url){out.textContent='URL required';out.classList.add('is-error');return}
|
|
147
|
+
out.classList.remove('is-error');out.textContent='Analyzing…';
|
|
148
|
+
var body={url:url};if(S.project)body.projectId=S.project;
|
|
149
|
+
fetch('/api/tool/analyze',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)})
|
|
150
|
+
.then(function(r){return r.json()})
|
|
151
|
+
.then(function(d){
|
|
152
|
+
if(d.error){out.classList.add('is-error');out.textContent='Error: '+d.error;return}
|
|
153
|
+
out.textContent='';
|
|
154
|
+
var pre=el('pre',null,JSON.stringify(d,null,2));
|
|
155
|
+
out.appendChild(pre);
|
|
156
|
+
})
|
|
157
|
+
.catch(function(e){out.classList.add('is-error');out.textContent='Request failed: '+(e&&e.message||'unknown')});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/* ── Verify Issue ────────────────────────────────────────── */
|
|
161
|
+
function copyIssuePrompt(){
|
|
162
|
+
var url=$('#issueUrl').value.trim();
|
|
163
|
+
if(!url){showToast&&showToast('Issue URL required','warn');return}
|
|
164
|
+
var prompt='Use the e2e-runner test-creator agent to verify this issue end-to-end:\n\n'+
|
|
165
|
+
'1. Call e2e_issue with url="'+url+'" to fetch the issue details and the suggested test prompt.\n'+
|
|
166
|
+
'2. Generate the test JSON based on the issue requirements.\n'+
|
|
167
|
+
'3. Save it via e2e_create_test.\n'+
|
|
168
|
+
'4. Run it via e2e_run and report pass/fail with screenshots.';
|
|
169
|
+
copyToClipboard(prompt);
|
|
170
|
+
}
|
|
171
|
+
function runIssueVerify(){
|
|
172
|
+
var url=$('#issueUrl').value.trim();var out=$('#issueResult');
|
|
173
|
+
if(!url){out.textContent='URL required';out.classList.add('is-error');return}
|
|
174
|
+
out.classList.remove('is-error');out.textContent='Calling Anthropic API... this can take a minute.';
|
|
175
|
+
fetch('/api/tool/issue-verify',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({url:url,projectId:S.project||null})})
|
|
176
|
+
.then(function(r){return r.json()})
|
|
177
|
+
.then(function(d){
|
|
178
|
+
if(d.error){out.classList.add('is-error');out.textContent='Error: '+d.error;return}
|
|
179
|
+
out.textContent='';
|
|
180
|
+
out.appendChild(el('pre',null,JSON.stringify(d,null,2)));
|
|
181
|
+
})
|
|
182
|
+
.catch(function(e){out.classList.add('is-error');out.textContent='Request failed: '+(e&&e.message||'unknown')});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/* ── Agent prompt copy buttons ───────────────────────────── */
|
|
186
|
+
var AGENT_PROMPTS={
|
|
187
|
+
improver:'Run the test-improver agent on this project. Tasks:\n'+
|
|
188
|
+
'1. Use e2e_list to enumerate suites + modules.\n'+
|
|
189
|
+
'2. Identify duplicated 3+ action sequences across tests (canonical $use candidates).\n'+
|
|
190
|
+
'3. For each candidate, call e2e_create_module with sensible parameters, then Edit the test files to replace the inline sequence with {"$use":"<module-name>","params":{...}}.\n'+
|
|
191
|
+
'4. Replace any verbose evaluate blocks with built-in actions where possible.\n'+
|
|
192
|
+
'5. Run the affected suites via e2e_run and confirm no regressions.\n'+
|
|
193
|
+
'6. Report a summary: modules created, sequences replaced, tests touched.',
|
|
194
|
+
creator:'Run the test-creator agent on this project. Tasks:\n'+
|
|
195
|
+
'1. Ask me which feature/page you should write a new test for.\n'+
|
|
196
|
+
'2. Use e2e_analyze on the target URL to map interactive elements.\n'+
|
|
197
|
+
'3. Design a clear action sequence (goto, asserts, click, type, etc.).\n'+
|
|
198
|
+
'4. Save via e2e_create_test and run it via e2e_run.\n'+
|
|
199
|
+
'5. If it passes, show me the JSON + screenshot. If it fails, debug and fix.',
|
|
200
|
+
analyzer:'Run the test-analyzer agent on this project. Tasks:\n'+
|
|
201
|
+
'1. Use e2e_learnings query="summary" to get the current stability state.\n'+
|
|
202
|
+
'2. Use e2e_learnings query="flaky" and "errors" to drill into problems.\n'+
|
|
203
|
+
'3. For each top issue, check e2e_network_logs for the relevant runDbIds.\n'+
|
|
204
|
+
'4. Recommend concrete fixes: stabilization (waits, retries), selector hardening, or root-cause investigations.',
|
|
205
|
+
};
|
|
206
|
+
function copyAgentPrompt(name){
|
|
207
|
+
var p=AGENT_PROMPTS[name];if(!p)return;
|
|
208
|
+
copyToClipboard(p);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function copyToClipboard(text){
|
|
212
|
+
try{
|
|
213
|
+
if(navigator.clipboard){navigator.clipboard.writeText(text).then(function(){showToast&&showToast('Copied to clipboard','success')});return}
|
|
214
|
+
}catch(e){}
|
|
215
|
+
var ta=document.createElement('textarea');ta.value=text;ta.style.position='fixed';ta.style.opacity='0';
|
|
216
|
+
document.body.appendChild(ta);ta.select();
|
|
217
|
+
try{document.execCommand('copy');showToast&&showToast('Copied to clipboard','success')}catch(e){showToast&&showToast('Could not copy','error')}
|
|
218
|
+
document.body.removeChild(ta);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/* ── Wire up buttons ─────────────────────────────────────── */
|
|
222
|
+
(function(){
|
|
223
|
+
var b1=$('#btnRunModuleAnalysis');if(b1)b1.addEventListener('click',refreshModuleAnalysis);
|
|
224
|
+
var b2=$('#btnCopyModulePrompt');if(b2)b2.addEventListener('click',function(){
|
|
225
|
+
if(MA_LAST&&MA_LAST.agentPrompt)copyToClipboard(MA_LAST.agentPrompt);
|
|
226
|
+
});
|
|
227
|
+
var c1=$('#btnRunCapture');if(c1)c1.addEventListener('click',runCapture);
|
|
228
|
+
var a1=$('#btnRunAnalyze');if(a1)a1.addEventListener('click',runAnalyze);
|
|
229
|
+
var i1=$('#btnIssuePrompt');if(i1)i1.addEventListener('click',copyIssuePrompt);
|
|
230
|
+
var i2=$('#btnIssueVerify');if(i2)i2.addEventListener('click',runIssueVerify);
|
|
231
|
+
document.querySelectorAll('[data-prompt]').forEach(function(btn){
|
|
232
|
+
btn.addEventListener('click',function(){copyAgentPrompt(btn.dataset.prompt)});
|
|
233
|
+
});
|
|
234
|
+
})();
|
|
@@ -65,7 +65,7 @@ function renderWatchCards(projects){
|
|
|
65
65
|
var detailBtn=el('button',{className:'btn sm',onclick:function(e){
|
|
66
66
|
e.stopPropagation();
|
|
67
67
|
S.project=p.id;$('#projectSelect').value=p.id;
|
|
68
|
-
showView('
|
|
68
|
+
showView('investigate');
|
|
69
69
|
refreshRuns();refreshSuites();
|
|
70
70
|
}},'\uD83D\uDD0D');
|
|
71
71
|
|
|
@@ -222,7 +222,7 @@ function renderEventLog(runs){
|
|
|
222
222
|
(function(run){
|
|
223
223
|
row.addEventListener('click',function(){
|
|
224
224
|
S.project=run.project_id;$('#projectSelect').value=run.project_id;
|
|
225
|
-
showView('
|
|
225
|
+
showView('investigate');
|
|
226
226
|
refreshRuns();
|
|
227
227
|
});
|
|
228
228
|
})(r);
|
|
@@ -104,6 +104,42 @@ function handleWS(m){
|
|
|
104
104
|
var r7=getLiveRun(m);if(r7){r7.on=false;r7.done=true;r7.tests.__error={status:'failed',error:m.error}}
|
|
105
105
|
showToast('Run error: '+m.error,'error');
|
|
106
106
|
renderLive();break;
|
|
107
|
+
case 'test:frame':
|
|
108
|
+
if(m.data){
|
|
109
|
+
var pinned=S.screencastSel;
|
|
110
|
+
var showThis;
|
|
111
|
+
if(pinned){
|
|
112
|
+
// Pinned: only the watched test's frames.
|
|
113
|
+
showThis=pinned.runId===m.runId&&pinned.name===m.name;
|
|
114
|
+
}else if(S.screencastAuto!==false){
|
|
115
|
+
// Auto: sticky — stay on the current test until it stops running,
|
|
116
|
+
// then adopt the next one. Never interleave two tests' frames.
|
|
117
|
+
var cur=S.screencastLast;
|
|
118
|
+
var curRun=cur&&S.liveRuns[cur.runId];
|
|
119
|
+
var curT=curRun&&curRun.tests?curRun.tests[cur.name]:null;
|
|
120
|
+
var curRunning=curT&&curT.status==='running';
|
|
121
|
+
showThis=!cur||!curRunning||(cur.runId===m.runId&&cur.name===m.name);
|
|
122
|
+
}else{showThis=false}
|
|
123
|
+
if(showThis){
|
|
124
|
+
var frameSrc='data:image/jpeg;base64,'+m.data;
|
|
125
|
+
// Switching to a different test? Clear the strip so they don't pile up.
|
|
126
|
+
var changed=!pinned&&(!S.screencastLast||S.screencastLast.runId!==m.runId||S.screencastLast.name!==m.name);
|
|
127
|
+
if(changed&&S.screencastLast&&typeof resetFilmstrip==='function')resetFilmstrip();
|
|
128
|
+
var img=$('#screencastImg');
|
|
129
|
+
if(img){
|
|
130
|
+
img.src=frameSrc;
|
|
131
|
+
img.style.display='block';
|
|
132
|
+
var vp=img.closest('.screencast-viewport');if(vp)vp.classList.add('has-frame');
|
|
133
|
+
var ph2=$('#screencastPlaceholder');if(ph2)ph2.style.display='none';
|
|
134
|
+
}
|
|
135
|
+
if(!pinned){
|
|
136
|
+
S.screencastLast={runId:m.runId,name:m.name};
|
|
137
|
+
if(changed&&typeof updateScreencastUI==='function')updateScreencastUI();
|
|
138
|
+
}
|
|
139
|
+
if(typeof pushFilmFrame==='function')pushFilmFrame(frameSrc,m.name,m.runId);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
break;
|
|
107
143
|
case 'db:updated':
|
|
108
144
|
refreshRuns();refreshProjects();refreshScreenshots();refreshLearnings();refreshWatch();break;
|
|
109
145
|
}
|