@matware/e2e-runner 1.5.0 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +3 -3
- package/.claude-plugin/plugin.json +1 -1
- package/LICENSE +1 -1
- package/README.md +451 -274
- package/agents/test-improver.md +2 -1
- package/bin/cli.js +13 -2
- package/package.json +2 -2
- package/skills/e2e-testing/SKILL.md +2 -1
- package/skills/e2e-testing/references/action-types.md +17 -18
- package/skills/e2e-testing/references/troubleshooting.md +2 -26
- package/src/actions.js +12 -2
- package/src/dashboard.js +50 -5
- package/src/db.js +15 -0
- package/src/mcp-tools.js +238 -75
- package/src/narrate.js +19 -0
- package/src/runner.js +72 -14
- package/src/visual-diff.js +8 -7
- package/templates/dashboard/js/utils.js +23 -2
- package/templates/dashboard/js/view-runs.js +94 -9
- package/templates/dashboard/styles/components.css +17 -0
- package/templates/dashboard/styles/view-runs.css +51 -4
- package/templates/dashboard/template.html +2 -2
- package/templates/dashboard.html +187 -17
package/templates/dashboard.html
CHANGED
|
@@ -775,6 +775,23 @@ tbody tr.selected td{
|
|
|
775
775
|
cursor:default;
|
|
776
776
|
box-shadow:0 0 0 1px rgba(158,242,106,.12),0 40px 80px rgba(0,0,0,.6);
|
|
777
777
|
}
|
|
778
|
+
.modal pre{
|
|
779
|
+
max-width:min(900px,100%);max-height:90vh;overflow:auto;
|
|
780
|
+
margin:0;padding:20px 24px;
|
|
781
|
+
background:var(--bg-2);
|
|
782
|
+
border:1px solid var(--border);border-radius:var(--r);
|
|
783
|
+
font-family:var(--mono);font-size:12px;line-height:1.6;
|
|
784
|
+
color:var(--text);cursor:text;user-select:text;
|
|
785
|
+
box-shadow:0 0 0 1px rgba(158,242,106,.12),0 40px 80px rgba(0,0,0,.6);
|
|
786
|
+
}
|
|
787
|
+
.modal pre[hidden],.modal img[hidden]{display:none}
|
|
788
|
+
|
|
789
|
+
/* ── JSON syntax highlighting (modal + network body panels) ── */
|
|
790
|
+
.jn-key{color:var(--beacon)}
|
|
791
|
+
.jn-str{color:var(--amber)}
|
|
792
|
+
.jn-num{color:var(--phosphor)}
|
|
793
|
+
.jn-bool{color:var(--red);font-weight:600}
|
|
794
|
+
.jn-null{color:var(--red);opacity:.65;font-style:italic}
|
|
778
795
|
|
|
779
796
|
/* ── Toasts ── */
|
|
780
797
|
.toast-container{
|
|
@@ -2396,10 +2413,49 @@ tr.expanded td:first-child::before{
|
|
|
2396
2413
|
.hb-link span{font-size:10px;color:var(--beacon);font-weight:700;letter-spacing:.18em;text-transform:uppercase}
|
|
2397
2414
|
|
|
2398
2415
|
/* ═══════════════════ Screenshots ═══════════════════ */
|
|
2416
|
+
.gallery-group{
|
|
2417
|
+
border:1px solid var(--border);
|
|
2418
|
+
border-radius:var(--r);
|
|
2419
|
+
background:var(--surface);
|
|
2420
|
+
margin-bottom:10px;
|
|
2421
|
+
overflow:hidden;
|
|
2422
|
+
}
|
|
2423
|
+
.gallery-group-header{
|
|
2424
|
+
display:flex;align-items:center;gap:10px;
|
|
2425
|
+
padding:10px 14px;cursor:pointer;user-select:none;
|
|
2426
|
+
background:var(--bg-2);
|
|
2427
|
+
transition:background .15s;
|
|
2428
|
+
}
|
|
2429
|
+
.gallery-group-header:hover{background:var(--surface)}
|
|
2430
|
+
.gallery-group .gg-arrow{
|
|
2431
|
+
font-size:9px;color:var(--text2);
|
|
2432
|
+
transition:transform .15s;flex-shrink:0;
|
|
2433
|
+
}
|
|
2434
|
+
.gallery-group.open .gg-arrow{transform:rotate(90deg)}
|
|
2435
|
+
.gallery-group .gg-name{
|
|
2436
|
+
font-family:var(--mono);font-size:12px;font-weight:600;
|
|
2437
|
+
color:var(--text);
|
|
2438
|
+
overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;
|
|
2439
|
+
}
|
|
2440
|
+
.gallery-group .gg-count{
|
|
2441
|
+
font-family:var(--mono);font-size:10px;color:var(--text2);
|
|
2442
|
+
padding:2px 8px;border-radius:999px;
|
|
2443
|
+
background:var(--bg);border:1px solid var(--border);
|
|
2444
|
+
flex-shrink:0;margin-left:auto;
|
|
2445
|
+
}
|
|
2446
|
+
.gallery-group .gg-blank-badge{
|
|
2447
|
+
font-family:var(--mono);font-size:9px;font-weight:700;
|
|
2448
|
+
padding:2px 8px;border-radius:999px;
|
|
2449
|
+
background:var(--amber-dim);color:var(--amber);border:1px solid var(--amber);
|
|
2450
|
+
flex-shrink:0;
|
|
2451
|
+
}
|
|
2452
|
+
.gallery-group .gg-blank-badge[hidden]{display:none}
|
|
2453
|
+
.gallery-group-body{padding:12px}
|
|
2454
|
+
.gallery-group-body[hidden]{display:none}
|
|
2399
2455
|
.gallery{
|
|
2400
2456
|
display:grid;
|
|
2401
|
-
grid-template-columns:repeat(auto-fill,minmax(
|
|
2402
|
-
gap:
|
|
2457
|
+
grid-template-columns:repeat(auto-fill,minmax(150px,1fr));
|
|
2458
|
+
gap:10px;
|
|
2403
2459
|
}
|
|
2404
2460
|
.gallery-item{
|
|
2405
2461
|
background:var(--surface);
|
|
@@ -2413,11 +2469,11 @@ tr.expanded td:first-child::before{
|
|
|
2413
2469
|
content:'';position:absolute;top:0;left:0;width:18px;height:1px;
|
|
2414
2470
|
background:var(--phosphor);opacity:.4;z-index:1;
|
|
2415
2471
|
}
|
|
2416
|
-
|
|
2472
|
+
.gallery-item:hover{
|
|
2417
2473
|
border-color:var(--ui-accent);transform:translateY(-2px);
|
|
2418
2474
|
box-shadow:0 12px 28px rgba(0,0,0,.4);
|
|
2419
2475
|
}
|
|
2420
|
-
.gallery-item img{width:100%;height:
|
|
2476
|
+
.gallery-item img{width:100%;aspect-ratio:1/1;height:auto;object-fit:cover;object-position:top center;display:block}
|
|
2421
2477
|
.gallery-item .cap{
|
|
2422
2478
|
padding:8px 12px;font-size:10px;
|
|
2423
2479
|
color:var(--text2);display:flex;align-items:center;gap:6px;
|
|
@@ -2692,6 +2748,11 @@ tr.expanded td:first-child::before{
|
|
|
2692
2748
|
background:repeating-linear-gradient(45deg,var(--surface2),var(--surface2) 6px,var(--bg-2) 6px,var(--bg-2) 12px);
|
|
2693
2749
|
}
|
|
2694
2750
|
.sl-thumb-empty:hover{transform:none;box-shadow:none}
|
|
2751
|
+
.sl-thumb-json{
|
|
2752
|
+
display:flex;align-items:center;justify-content:center;
|
|
2753
|
+
color:var(--beacon);font-family:var(--mono);font-size:18px;font-weight:700;
|
|
2754
|
+
background:var(--bg-2);
|
|
2755
|
+
}
|
|
2695
2756
|
.sl-thumb-err{border-color:var(--crimson)}
|
|
2696
2757
|
|
|
2697
2758
|
.sl-info{display:flex;flex-direction:column;gap:6px;min-width:0}
|
|
@@ -2754,6 +2815,9 @@ tr.expanded td:first-child::before{
|
|
|
2754
2815
|
}
|
|
2755
2816
|
.sl-chip-v{color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:280px}
|
|
2756
2817
|
.sl-chip-sel .sl-chip-v{color:var(--beacon)}
|
|
2818
|
+
.sl-chip-json{cursor:pointer;border-color:var(--beacon)}
|
|
2819
|
+
.sl-chip-json .sl-chip-k{color:var(--beacon)}
|
|
2820
|
+
.sl-chip-json:hover{background:var(--beacon-dim)}
|
|
2757
2821
|
|
|
2758
2822
|
.sl-bar{
|
|
2759
2823
|
width:100%;height:3px;
|
|
@@ -3740,7 +3804,7 @@ tr.expanded td:first-child::before{
|
|
|
3740
3804
|
<button class="btn sm" id="ssBlankCancelBtn">Cancel</button>
|
|
3741
3805
|
</div>
|
|
3742
3806
|
</div>
|
|
3743
|
-
<div class="gallery" id="screenshotGallery"></div>
|
|
3807
|
+
<div class="gallery-groups" id="screenshotGallery"></div>
|
|
3744
3808
|
<div class="empty" id="screenshotsEmpty" style="display:none">
|
|
3745
3809
|
<div class="empty-icon">▣</div>
|
|
3746
3810
|
<p>Select a project to view screenshots.</p>
|
|
@@ -3986,7 +4050,7 @@ tr.expanded td:first-child::before{
|
|
|
3986
4050
|
</div>
|
|
3987
4051
|
</div>
|
|
3988
4052
|
</div>
|
|
3989
|
-
<div class="modal" id="modal"><img id="modalImg" src="" alt=""></div>
|
|
4053
|
+
<div class="modal" id="modal"><img id="modalImg" src="" alt=""><pre id="modalJson" hidden></pre></div>
|
|
3990
4054
|
|
|
3991
4055
|
<!-- ── Quick Search Palette ── -->
|
|
3992
4056
|
<div class="qs-modal" id="qsModal" aria-hidden="true">
|
|
@@ -4076,6 +4140,27 @@ function prettyJson(str){
|
|
|
4076
4140
|
try{return JSON.stringify(JSON.parse(str),null,2)}catch(e){return str}
|
|
4077
4141
|
}
|
|
4078
4142
|
|
|
4143
|
+
/* Lightweight JSON syntax highlighter. Escapes HTML, then wraps tokens in
|
|
4144
|
+
colored spans. Input should already be pretty-printed (prettyJson). */
|
|
4145
|
+
function highlightJson(text){
|
|
4146
|
+
var esc=String(text).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
|
4147
|
+
return esc.replace(/"(?:\\.|[^"\\])*"|\b(?:true|false|null)\b|-?\b\d+(?:\.\d+)?(?:[eE][+\-]?\d+)?\b/g,function(m,off,s){
|
|
4148
|
+
var cls;
|
|
4149
|
+
if(m[0]==='"'){cls=/^\s*:/.test(s.slice(off+m.length))?'jn-key':'jn-str'}
|
|
4150
|
+
else if(m==='true'||m==='false'){cls='jn-bool'}
|
|
4151
|
+
else if(m==='null'){cls='jn-null'}
|
|
4152
|
+
else{cls='jn-num'}
|
|
4153
|
+
return '<span class="'+cls+'">'+m+'</span>';
|
|
4154
|
+
});
|
|
4155
|
+
}
|
|
4156
|
+
|
|
4157
|
+
/* Builds a <pre> with syntax-highlighted JSON content. */
|
|
4158
|
+
function jsonPre(text){
|
|
4159
|
+
var p=document.createElement('pre');
|
|
4160
|
+
p.innerHTML=highlightJson(text);
|
|
4161
|
+
return p;
|
|
4162
|
+
}
|
|
4163
|
+
|
|
4079
4164
|
function fmtHeaders(h){
|
|
4080
4165
|
if(!h||typeof h!=='object')return '';
|
|
4081
4166
|
return Object.keys(h).map(function(k){return k+': '+h[k]}).join('\n');
|
|
@@ -4162,7 +4247,7 @@ function buildNetRow(n){
|
|
|
4162
4247
|
}
|
|
4163
4248
|
if(n.requestBody){
|
|
4164
4249
|
var rbText=prettyJson(n.requestBody);
|
|
4165
|
-
sections.push(buildNdSection('Request Body',
|
|
4250
|
+
sections.push(buildNdSection('Request Body',jsonPre(rbText),null,rbText));
|
|
4166
4251
|
}
|
|
4167
4252
|
if(n.responseHeaders){
|
|
4168
4253
|
var rhCount=Object.keys(n.responseHeaders).length;
|
|
@@ -4170,7 +4255,7 @@ function buildNetRow(n){
|
|
|
4170
4255
|
}
|
|
4171
4256
|
if(n.responseBody){
|
|
4172
4257
|
var respText=prettyJson(n.responseBody);
|
|
4173
|
-
sections.push(buildNdSection('Response Body',
|
|
4258
|
+
sections.push(buildNdSection('Response Body',jsonPre(respText),null,respText));
|
|
4174
4259
|
}
|
|
4175
4260
|
detail=el('div',{className:'rd-net-detail'},sections);
|
|
4176
4261
|
row.addEventListener('click',function(e){e.stopPropagation();row.classList.toggle('open')});
|
|
@@ -5676,7 +5761,8 @@ function loadDetailInline(id,detailTr){
|
|
|
5676
5761
|
var stateCls=a.success?'pass':'fail';
|
|
5677
5762
|
var icon=a.success?'\u2714':'\u2718';
|
|
5678
5763
|
|
|
5679
|
-
// Thumbnail: prefer autoScreenshot, fall back to action's own screenshot
|
|
5764
|
+
// Thumbnail: prefer autoScreenshot, fall back to action's own screenshot.
|
|
5765
|
+
// Data-capture steps (raw JSON saved instead of a screenshot) get a {} thumb.
|
|
5680
5766
|
var thumbPath=a.autoScreenshot||a.screenshot||null;
|
|
5681
5767
|
var thumb;
|
|
5682
5768
|
if(thumbPath){
|
|
@@ -5684,6 +5770,10 @@ function loadDetailInline(id,detailTr){
|
|
|
5684
5770
|
var img=document.createElement('img');
|
|
5685
5771
|
img.src=src;img.alt=label;img.loading='lazy';
|
|
5686
5772
|
thumb=el('div',{className:'sl-thumb',onclick:function(e){e.stopPropagation();openModal(src)}},[img]);
|
|
5773
|
+
}else if(a.dataCapture){
|
|
5774
|
+
(function(dc){
|
|
5775
|
+
thumb=el('div',{className:'sl-thumb sl-thumb-json',title:'Raw JSON response \u2014 click to view',onclick:function(e){e.stopPropagation();openJsonModal(dc)}},[el('span',null,'{\u2009}')]);
|
|
5776
|
+
})(a.dataCapture);
|
|
5687
5777
|
}else{
|
|
5688
5778
|
thumb=el('div',{className:'sl-thumb sl-thumb-empty',title:'No screenshot for this step'},[el('span',null,'\u25A1')]);
|
|
5689
5779
|
}
|
|
@@ -5693,6 +5783,12 @@ function loadDetailInline(id,detailTr){
|
|
|
5693
5783
|
if(a.selector)chips.appendChild(el('span',{className:'sl-chip sl-chip-sel'},[el('span',{className:'sl-chip-k'},'sel'),el('span',{className:'sl-chip-v'},a.selector)]));
|
|
5694
5784
|
if(a.text)chips.appendChild(el('span',{className:'sl-chip'},[el('span',{className:'sl-chip-k'},'text'),el('span',{className:'sl-chip-v'},String(a.text))]));
|
|
5695
5785
|
if(a.value!=null&&a.value!=='')chips.appendChild(el('span',{className:'sl-chip'},[el('span',{className:'sl-chip-k'},'val'),el('span',{className:'sl-chip-v'},String(a.value))]));
|
|
5786
|
+
if(a.dataCapture)(function(dc){
|
|
5787
|
+
chips.appendChild(el('span',{className:'sl-chip sl-chip-json',title:'Raw JSON response saved for this step \u2014 click to view',onclick:function(e){e.stopPropagation();openJsonModal(dc)}},[
|
|
5788
|
+
el('span',{className:'sl-chip-k'},'JSON'),
|
|
5789
|
+
el('span',{className:'sl-chip-v'},dc.split('/').pop())
|
|
5790
|
+
]));
|
|
5791
|
+
})(a.dataCapture);
|
|
5696
5792
|
|
|
5697
5793
|
var retryBadge=(a.actionRetries&&a.actionRetries>0)?el('span',{className:'sl-retry'},'\u21BB '+a.actionRetries):null;
|
|
5698
5794
|
|
|
@@ -5823,6 +5919,15 @@ function loadDetailInline(id,detailTr){
|
|
|
5823
5919
|
}
|
|
5824
5920
|
|
|
5825
5921
|
/* ── Screenshots ── */
|
|
5922
|
+
/* Session memory: which test groups the user expanded (survives refreshes, not reloads) */
|
|
5923
|
+
var SS_EXPANDED={};
|
|
5924
|
+
function buildGalleryItem(f){
|
|
5925
|
+
var src='/api/image?path='+encodeURIComponent(f.path);
|
|
5926
|
+
var img=document.createElement('img');img.src=src;img.alt=f.name;img.loading='lazy';
|
|
5927
|
+
var capEl=el('div',{className:'cap'},[el('span',{className:'cap-name'},f.name)]);
|
|
5928
|
+
(function(c,fp){ssHash(fp).then(function(h){c.appendChild(createHashBadge(h))})})(capEl,f.path);
|
|
5929
|
+
return el('div',{className:'gallery-item','data-path':f.path,onclick:function(){openModal(src)}},[img,capEl]);
|
|
5930
|
+
}
|
|
5826
5931
|
function refreshScreenshots(){
|
|
5827
5932
|
var gal=$('#screenshotGallery'),empty=$('#screenshotsEmpty');
|
|
5828
5933
|
gal.textContent='';
|
|
@@ -5831,12 +5936,50 @@ function refreshScreenshots(){
|
|
|
5831
5936
|
if(!Array.isArray(files)||!files.length){empty.style.display='block';empty.querySelector('p').textContent='No screenshots for this project.';$('#badgeScreenshots').textContent='0';return}
|
|
5832
5937
|
empty.style.display='none';
|
|
5833
5938
|
$('#badgeScreenshots').textContent=files.length;
|
|
5939
|
+
/* Group by test name; files without one go to a trailing "Other" bucket */
|
|
5940
|
+
var groups={},order=[];
|
|
5834
5941
|
files.forEach(function(f){
|
|
5835
|
-
var
|
|
5836
|
-
|
|
5837
|
-
|
|
5838
|
-
|
|
5839
|
-
|
|
5942
|
+
var key=f.testName||'__other__';
|
|
5943
|
+
if(!groups[key]){groups[key]=[];order.push(key)}
|
|
5944
|
+
groups[key].push(f);
|
|
5945
|
+
});
|
|
5946
|
+
function fileTs(f){var m=f.name.match(/(\d{10,})\.[a-z]+$/i);return m?parseInt(m[1],10):0}
|
|
5947
|
+
order.sort(function(a,b){
|
|
5948
|
+
if(a==='__other__')return 1;
|
|
5949
|
+
if(b==='__other__')return -1;
|
|
5950
|
+
var ma=0,mb=0;
|
|
5951
|
+
groups[a].forEach(function(f){var t=fileTs(f);if(t>ma)ma=t});
|
|
5952
|
+
groups[b].forEach(function(f){var t=fileTs(f);if(t>mb)mb=t});
|
|
5953
|
+
return mb-ma;
|
|
5954
|
+
});
|
|
5955
|
+
order.forEach(function(key){
|
|
5956
|
+
var items=groups[key],label=key==='__other__'?'Other':key;
|
|
5957
|
+
var grid=el('div',{className:'gallery'});
|
|
5958
|
+
var materialized=false;
|
|
5959
|
+
function materialize(){
|
|
5960
|
+
if(materialized)return;materialized=true;
|
|
5961
|
+
items.forEach(function(f){grid.appendChild(buildGalleryItem(f))});
|
|
5962
|
+
}
|
|
5963
|
+
var expanded=!!SS_EXPANDED[key];
|
|
5964
|
+
var body=el('div',{className:'gallery-group-body'},[grid]);
|
|
5965
|
+
var group=el('div',{className:'gallery-group'+(expanded?' open':'')},[]);
|
|
5966
|
+
var header=el('div',{className:'gallery-group-header',onclick:function(){
|
|
5967
|
+
expanded=!expanded;
|
|
5968
|
+
if(expanded){SS_EXPANDED[key]=true;materialize()}else{delete SS_EXPANDED[key]}
|
|
5969
|
+
group.classList.toggle('open',expanded);
|
|
5970
|
+
if(expanded)body.removeAttribute('hidden');else body.setAttribute('hidden','');
|
|
5971
|
+
}},[
|
|
5972
|
+
el('span',{className:'gg-arrow'},'▶'),
|
|
5973
|
+
el('span',{className:'gg-name'},label),
|
|
5974
|
+
el('span',{className:'gg-count'},items.length+' shot'+(items.length===1?'':'s')),
|
|
5975
|
+
el('span',{className:'gg-blank-badge',hidden:''},''),
|
|
5976
|
+
]);
|
|
5977
|
+
group.appendChild(header);
|
|
5978
|
+
group.appendChild(body);
|
|
5979
|
+
if(expanded){materialize()}else{body.setAttribute('hidden','')}
|
|
5980
|
+
/* Expose to the blank-scan so it can materialize collapsed groups on demand */
|
|
5981
|
+
group._ssItems=items;group._ssMaterialize=materialize;
|
|
5982
|
+
gal.appendChild(group);
|
|
5840
5983
|
});
|
|
5841
5984
|
resetBlankBar();
|
|
5842
5985
|
}).catch(function(){});
|
|
@@ -5846,6 +5989,7 @@ function refreshScreenshots(){
|
|
|
5846
5989
|
function resetBlankBar(){
|
|
5847
5990
|
var bar=$('#ssBlankBar');if(bar)bar.hidden=true;
|
|
5848
5991
|
$$('#screenshotGallery .gallery-item.blank-flagged').forEach(function(it){it.classList.remove('blank-flagged')});
|
|
5992
|
+
$$('#screenshotGallery .gg-blank-badge').forEach(function(b){b.textContent='';b.setAttribute('hidden','')});
|
|
5849
5993
|
S.blankPaths=null;
|
|
5850
5994
|
}
|
|
5851
5995
|
function scanBlankScreenshots(){
|
|
@@ -5861,10 +6005,18 @@ function scanBlankScreenshots(){
|
|
|
5861
6005
|
return;
|
|
5862
6006
|
}
|
|
5863
6007
|
S.blankPaths=blanks.map(function(b){return b.path});
|
|
5864
|
-
|
|
6008
|
+
/* Materialize collapsed groups that contain blanks, flag items, badge the headers */
|
|
6009
|
+
var blankSet={};S.blankPaths.forEach(function(p){blankSet[p]=true});
|
|
6010
|
+
$$('#screenshotGallery .gallery-group').forEach(function(g){
|
|
6011
|
+
var inGroup=(g._ssItems||[]).filter(function(it){return blankSet[it.path]}).length;
|
|
6012
|
+
if(!inGroup)return;
|
|
6013
|
+
if(g._ssMaterialize)g._ssMaterialize();
|
|
6014
|
+
var badge=g.querySelector('.gg-blank-badge');
|
|
6015
|
+
if(badge){badge.textContent=inGroup+' blank'+(inGroup===1?'':'s');badge.removeAttribute('hidden')}
|
|
6016
|
+
});
|
|
5865
6017
|
S.blankPaths.forEach(function(p){
|
|
5866
6018
|
var item=$('#screenshotGallery .gallery-item[data-path="'+(window.CSS&&CSS.escape?CSS.escape(p):p)+'"]');
|
|
5867
|
-
if(item)
|
|
6019
|
+
if(item)item.classList.add('blank-flagged');
|
|
5868
6020
|
});
|
|
5869
6021
|
$('#ssBlankMsg').textContent=blanks.length+' blank image'+(blanks.length===1?'':'s')+' of '+data.scanned+' scanned';
|
|
5870
6022
|
$('#ssBlankBar').hidden=false;
|
|
@@ -6159,8 +6311,26 @@ $('#btnExportLearnings').addEventListener('click',function(){
|
|
|
6159
6311
|
});
|
|
6160
6312
|
|
|
6161
6313
|
/* ── Modal ── */
|
|
6162
|
-
function openModal(src){
|
|
6314
|
+
function openModal(src){
|
|
6315
|
+
$('#modalImg').src=src;$('#modalImg').removeAttribute('hidden');
|
|
6316
|
+
$('#modalJson').setAttribute('hidden','');
|
|
6317
|
+
$('#modal').classList.add('open');
|
|
6318
|
+
}
|
|
6319
|
+
function openJsonModal(filePath){
|
|
6320
|
+
fetch('/api/image?path='+encodeURIComponent(filePath)).then(function(r){
|
|
6321
|
+
if(!r.ok)throw new Error('not found');
|
|
6322
|
+
return r.text();
|
|
6323
|
+
}).then(function(text){
|
|
6324
|
+
$('#modalImg').setAttribute('hidden','');
|
|
6325
|
+
var pre=$('#modalJson');
|
|
6326
|
+
pre.innerHTML=highlightJson(prettyJson(text));
|
|
6327
|
+
pre.removeAttribute('hidden');
|
|
6328
|
+
$('#modal').classList.add('open');
|
|
6329
|
+
}).catch(function(){showToast('Could not load JSON capture','error')});
|
|
6330
|
+
}
|
|
6163
6331
|
$('#modal').addEventListener('click',function(){$('#modal').classList.remove('open')});
|
|
6332
|
+
/* Allow selecting/copying JSON text without closing the modal */
|
|
6333
|
+
$('#modalJson').addEventListener('click',function(e){e.stopPropagation()});
|
|
6164
6334
|
|
|
6165
6335
|
/* ══════════════════════════════════════════════════════════════════
|
|
6166
6336
|
Screenshot Replay Player — plays a run's per-step screenshots
|