skopix 2.0.40 → 2.0.42
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/cli/commands/dashboard.js +193 -76
- package/package.json +1 -1
- package/web/app/index.html +18 -195
|
@@ -1796,10 +1796,10 @@ export async function dashboardCommand(options) {
|
|
|
1796
1796
|
|
|
1797
1797
|
// ─── STEP TESTER ───────────────────────────────────────────────────
|
|
1798
1798
|
if (pathname === '/api/step-tester/start' && method === 'POST') {
|
|
1799
|
-
const { url, selector } = JSON.parse(await readBody(req));
|
|
1799
|
+
const { url, selector, mode, steps } = JSON.parse(await readBody(req));
|
|
1800
1800
|
try {
|
|
1801
1801
|
const testerId = Math.random().toString(36).slice(2, 10);
|
|
1802
|
-
await startStepTester(testerId, url, selector);
|
|
1802
|
+
await startStepTester(testerId, url, selector, mode || 'test', steps);
|
|
1803
1803
|
sendJSON(res, 200, { testerId });
|
|
1804
1804
|
} catch (err) { sendJSON(res, 500, { error: err.message }); }
|
|
1805
1805
|
return;
|
|
@@ -3945,91 +3945,208 @@ async function syncTestsToLibrary(suitesDir) {
|
|
|
3945
3945
|
|
|
3946
3946
|
const stepTesterSessions = new Map(); // testerId -> { browser, ctx, page }
|
|
3947
3947
|
|
|
3948
|
-
async function startStepTester(testerId, url, selector) {
|
|
3948
|
+
async function startStepTester(testerId, url, selector, mode, steps) {
|
|
3949
3949
|
const { chromium } = await import('playwright');
|
|
3950
3950
|
const browser = await chromium.launch({ headless: false, args: ['--no-sandbox'] });
|
|
3951
3951
|
const ctx = await browser.newContext({ viewport: { width: 1280, height: 800 } });
|
|
3952
3952
|
const page = await ctx.newPage();
|
|
3953
3953
|
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
3954
|
+
if (mode === 'preview' && steps && steps.length > 0) {
|
|
3955
|
+
// PREVIEW MODE — inject steps list toolbar
|
|
3956
|
+
await ctx.addInitScript((stepsData) => {
|
|
3957
|
+
window.__skopixPreviewSteps = stepsData;
|
|
3958
|
+
window.__skopixPreviewResults = {};
|
|
3959
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
3960
|
+
if (document.getElementById('__skopix_preview')) return;
|
|
3961
|
+
const tb = document.createElement('div');
|
|
3962
|
+
tb.id = '__skopix_preview';
|
|
3963
|
+
tb.style.cssText = [
|
|
3964
|
+
'position:fixed', 'top:0', 'right:0', 'bottom:0', 'width:320px',
|
|
3965
|
+
'z-index:2147483647', 'background:#0d0d1a', 'border-left:1px solid rgba(245,158,11,0.4)',
|
|
3966
|
+
'display:flex', 'flex-direction:column', 'font-family:monospace', 'font-size:12px',
|
|
3967
|
+
'box-shadow:-4px 0 24px rgba(0,0,0,0.5)',
|
|
3968
|
+
].join(';');
|
|
3969
|
+
tb.innerHTML = `
|
|
3970
|
+
<div style="padding:12px 16px;border-bottom:1px solid rgba(245,158,11,0.2);background:rgba(245,158,11,0.06);flex-shrink:0">
|
|
3971
|
+
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:8px">
|
|
3972
|
+
<div style="display:flex;align-items:center;gap:8px">
|
|
3973
|
+
<span style="color:#f59e0b">⚡</span>
|
|
3974
|
+
<span style="color:#f59e0b;font-size:10px;letter-spacing:0.1em">PREVIEW</span>
|
|
3975
|
+
</div>
|
|
3976
|
+
<div style="display:flex;gap:6px">
|
|
3977
|
+
<button id="__skopix_run_next" style="background:transparent;border:1px solid rgba(245,158,11,0.4);color:#f59e0b;
|
|
3978
|
+
border-radius:6px;padding:4px 10px;cursor:pointer;font-size:11px;font-family:monospace">▶ Next</button>
|
|
3979
|
+
<button id="__skopix_run_all" style="background:#f59e0b;border:none;color:#000;
|
|
3980
|
+
border-radius:6px;padding:4px 10px;cursor:pointer;font-size:11px;font-family:monospace;font-weight:700">▶▶ All</button>
|
|
3981
|
+
</div>
|
|
3982
|
+
</div>
|
|
3983
|
+
<div id="__skopix_preview_status" style="color:#9ca3af;font-size:11px">Navigate to start, then click Run</div>
|
|
3984
|
+
</div>
|
|
3985
|
+
<div id="__skopix_steps" style="overflow-y:auto;flex:1;padding:0"></div>
|
|
3986
|
+
<div id="__skopix_preview_summary" style="display:none;padding:12px 16px;border-top:1px solid rgba(255,255,255,0.08);font-size:12px"></div>
|
|
3987
|
+
`;
|
|
3988
|
+
document.body.appendChild(tb);
|
|
3989
|
+
|
|
3990
|
+
function renderSteps(currentIdx, results) {
|
|
3991
|
+
const actionColors = { click:'#22d3ee', type:'#a78bfa', check:'#34d399', assert:'#fb923c', select:'#f472b6', scroll:'#6b7280' };
|
|
3992
|
+
document.getElementById('__skopix_steps').innerHTML = stepsData.map((s, i) => {
|
|
3993
|
+
const r = results[i];
|
|
3994
|
+
const isCurrent = i === currentIdx && !r;
|
|
3995
|
+
const status = r ? (r.passed ? '✓' : '✗') : (isCurrent ? '▶' : '○');
|
|
3996
|
+
const statusColor = r ? (r.passed ? '#34d399' : '#ef4444') : (isCurrent ? '#f59e0b' : '#374151');
|
|
3997
|
+
const bg = isCurrent ? 'rgba(245,158,11,0.08)' : r && !r.passed ? 'rgba(239,68,68,0.06)' : '';
|
|
3998
|
+
return `<div style="padding:8px 14px;border-bottom:1px solid rgba(255,255,255,0.05);display:flex;gap:10px;align-items:flex-start;background:${bg}">
|
|
3999
|
+
<span style="color:${statusColor};font-size:13px;margin-top:1px;flex-shrink:0">${status}</span>
|
|
4000
|
+
<div style="min-width:0">
|
|
4001
|
+
<div style="display:flex;align-items:center;gap:6px;margin-bottom:2px">
|
|
4002
|
+
<span style="color:${actionColors[s.action]||'#6b7280'};font-size:10px">${s.action}</span>
|
|
4003
|
+
<span style="color:#e5e7eb;font-size:11px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:180px">${s.description||''}</span>
|
|
4004
|
+
</div>
|
|
4005
|
+
<code style="color:#4b5563;font-size:10px;display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis">${s.selector||''}</code>
|
|
4006
|
+
${r && !r.passed ? `<div style="color:#ef4444;font-size:10px;margin-top:3px">${(r.error||'Failed').slice(0,80)}</div>` : ''}
|
|
4007
|
+
</div>
|
|
4008
|
+
</div>`;
|
|
4009
|
+
}).join('');
|
|
4010
|
+
}
|
|
4011
|
+
|
|
4012
|
+
window.__skopixPreviewCurrentStep = 0;
|
|
4013
|
+
window.__skopixPreviewResults = {};
|
|
4014
|
+
renderSteps(0, {});
|
|
4015
|
+
|
|
4016
|
+
document.getElementById('__skopix_run_next').addEventListener('click', (e) => {
|
|
4017
|
+
e.stopPropagation();
|
|
4018
|
+
if (window.__skopixPreviewRun) window.__skopixPreviewRun({ index: window.__skopixPreviewCurrentStep });
|
|
4019
|
+
});
|
|
4020
|
+
|
|
4021
|
+
document.getElementById('__skopix_run_all').addEventListener('click', (e) => {
|
|
4022
|
+
e.stopPropagation();
|
|
4023
|
+
if (window.__skopixPreviewRunAll) window.__skopixPreviewRunAll({ fromIndex: window.__skopixPreviewCurrentStep });
|
|
4024
|
+
});
|
|
4025
|
+
|
|
4026
|
+
window.__skopixUpdatePreview = (index, result, status) => {
|
|
4027
|
+
window.__skopixPreviewResults[index] = result;
|
|
4028
|
+
if (result && result.passed) window.__skopixPreviewCurrentStep = index + 1;
|
|
4029
|
+
document.getElementById('__skopix_preview_status').textContent = status || '';
|
|
4030
|
+
renderSteps(window.__skopixPreviewCurrentStep, window.__skopixPreviewResults);
|
|
4031
|
+
// Highlight element
|
|
4032
|
+
if (result) {
|
|
4033
|
+
try {
|
|
4034
|
+
const target = document.querySelector(stepsData[index]?.selector || '');
|
|
4035
|
+
if (target) {
|
|
4036
|
+
const orig = target.style.outline;
|
|
4037
|
+
target.style.outline = result.passed ? '3px solid #34d399' : '3px solid #ef4444';
|
|
4038
|
+
setTimeout(() => { target.style.outline = orig; }, 1500);
|
|
4039
|
+
}
|
|
4040
|
+
} catch {}
|
|
4041
|
+
}
|
|
4042
|
+
// Update summary
|
|
4043
|
+
const total = Object.keys(window.__skopixPreviewResults).length;
|
|
4044
|
+
const passed = Object.values(window.__skopixPreviewResults).filter(r=>r&&r.passed).length;
|
|
4045
|
+
const failed = total - passed;
|
|
4046
|
+
if (total > 0) {
|
|
4047
|
+
const summary = document.getElementById('__skopix_preview_summary');
|
|
4048
|
+
summary.style.display = '';
|
|
4049
|
+
summary.innerHTML = `<span style="color:#34d399">✓ ${passed}</span><span style="color:#4b5563"> / </span><span style="color:${failed>0?'#ef4444':'#4b5563'}">✗ ${failed}</span><span style="color:#4b5563"> of ${stepsData.length}</span>`;
|
|
4050
|
+
}
|
|
4051
|
+
};
|
|
3996
4052
|
});
|
|
4053
|
+
}, steps.map(s => ({ action: s.action, selector: s.stableSelector||s.selector||'', description: s.description||s.action, value: s.value||'' })));
|
|
4054
|
+
|
|
4055
|
+
// Expose run functions
|
|
4056
|
+
await ctx.exposeFunction('__skopixPreviewRun', async ({ index }) => {
|
|
4057
|
+
const s = steps[index];
|
|
4058
|
+
if (!s) return;
|
|
4059
|
+
await page.evaluate((i, status) => { if(window.__skopixUpdatePreview) window.__skopixUpdatePreview(i, null, status); }, index, `Running step ${index+1}/${steps.length}...`);
|
|
4060
|
+
const result = await executeStepTesterAction(page, { selector: s.stableSelector||s.selector, action: s.action, value: s.value||'', assertType: s.assertType });
|
|
4061
|
+
await page.evaluate((i, r, total) => {
|
|
4062
|
+
if(window.__skopixUpdatePreview) window.__skopixUpdatePreview(i, r, r.passed ? (i+1 >= total ? `✓ All ${total} steps passed!` : `✓ Step ${i+1} passed`) : `✗ Step ${i+1} failed`);
|
|
4063
|
+
}, index, result, steps.length);
|
|
4064
|
+
});
|
|
3997
4065
|
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
const
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
if (
|
|
4007
|
-
|
|
4008
|
-
|
|
4066
|
+
await ctx.exposeFunction('__skopixPreviewRunAll', async ({ fromIndex }) => {
|
|
4067
|
+
for (let i = fromIndex; i < steps.length; i++) {
|
|
4068
|
+
const s = steps[i];
|
|
4069
|
+
await page.evaluate((i, total) => { if(window.__skopixUpdatePreview) window.__skopixUpdatePreview(i, null, `Running step ${i+1}/${total}...`); }, i, steps.length);
|
|
4070
|
+
const result = await executeStepTesterAction(page, { selector: s.stableSelector||s.selector, action: s.action, value: s.value||'', assertType: s.assertType });
|
|
4071
|
+
await page.evaluate((i, r, total) => {
|
|
4072
|
+
if(window.__skopixUpdatePreview) window.__skopixUpdatePreview(i, r, r.passed ? (i+1 >= total ? `✓ All ${total} steps passed!` : `Running...`) : `✗ Step ${i+1} failed — fix and retry`);
|
|
4073
|
+
}, i, result, steps.length);
|
|
4074
|
+
if (!result.passed) break;
|
|
4075
|
+
await new Promise(r => setTimeout(r, 400));
|
|
4076
|
+
}
|
|
4077
|
+
});
|
|
4078
|
+
|
|
4079
|
+
} else {
|
|
4080
|
+
// STEP TESTER MODE — single step toolbar
|
|
4081
|
+
await ctx.addInitScript((sel) => {
|
|
4082
|
+
window.__skopixTesterSelector = sel;
|
|
4083
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
4084
|
+
if (document.getElementById('__skopix_tester')) return;
|
|
4085
|
+
const tb = document.createElement('div');
|
|
4086
|
+
tb.id = '__skopix_tester';
|
|
4087
|
+
tb.style.cssText = [
|
|
4088
|
+
'position:fixed', 'bottom:20px', 'left:50%', 'transform:translateX(-50%)',
|
|
4089
|
+
'z-index:2147483647', 'background:#0f1117', 'border:1px solid #f59e0b',
|
|
4090
|
+
'border-radius:12px', 'padding:12px 16px', 'display:flex', 'align-items:center',
|
|
4091
|
+
'gap:10px', 'font-family:monospace', 'font-size:12px', 'color:#e5e7eb',
|
|
4092
|
+
'box-shadow:0 4px 24px rgba(0,0,0,0.7)', 'min-width:400px',
|
|
4093
|
+
].join(';');
|
|
4094
|
+
tb.innerHTML = `
|
|
4095
|
+
<span style="color:#f59e0b;font-size:14px">⚡</span>
|
|
4096
|
+
<span style="color:#9ca3af;font-size:11px">STEP TESTER</span>
|
|
4097
|
+
<input id="__skopix_sel" value="${(sel||'').replace(/"/g,'"')}" placeholder="selector..."
|
|
4098
|
+
style="flex:1;background:#1a1d2e;border:1px solid #374151;border-radius:6px;padding:4px 8px;
|
|
4099
|
+
color:#e5e7eb;font-family:monospace;font-size:11px;min-width:160px">
|
|
4100
|
+
<select id="__skopix_action" style="background:#1a1d2e;border:1px solid #374151;border-radius:6px;
|
|
4101
|
+
padding:4px 8px;color:#e5e7eb;font-family:monospace;font-size:11px">
|
|
4102
|
+
<option value="click">click</option>
|
|
4103
|
+
<option value="type">type</option>
|
|
4104
|
+
<option value="check">check</option>
|
|
4105
|
+
<option value="assert">assert (visible)</option>
|
|
4106
|
+
<option value="assert_text">assert (text)</option>
|
|
4107
|
+
</select>
|
|
4108
|
+
<input id="__skopix_value" placeholder="value..." style="width:100px;background:#1a1d2e;
|
|
4109
|
+
border:1px solid #374151;border-radius:6px;padding:4px 8px;color:#e5e7eb;
|
|
4110
|
+
font-family:monospace;font-size:11px;display:none">
|
|
4111
|
+
<button id="__skopix_run" style="background:#f59e0b;border:none;color:#000;
|
|
4112
|
+
border-radius:6px;padding:5px 14px;cursor:pointer;font-size:11px;font-weight:700;
|
|
4113
|
+
font-family:monospace">▶ Run</button>
|
|
4114
|
+
<span id="__skopix_result" style="font-size:13px;min-width:20px"></span>
|
|
4115
|
+
`;
|
|
4116
|
+
document.body.appendChild(tb);
|
|
4117
|
+
document.getElementById('__skopix_action').addEventListener('change', function() {
|
|
4118
|
+
const needsValue = ['type','assert_text'].includes(this.value);
|
|
4119
|
+
document.getElementById('__skopix_value').style.display = needsValue ? '' : 'none';
|
|
4120
|
+
});
|
|
4121
|
+
document.getElementById('__skopix_run').addEventListener('click', function(e) {
|
|
4122
|
+
e.stopPropagation();
|
|
4123
|
+
const sel = document.getElementById('__skopix_sel').value.trim();
|
|
4124
|
+
const action = document.getElementById('__skopix_action').value;
|
|
4125
|
+
const value = document.getElementById('__skopix_value').value.trim();
|
|
4126
|
+
document.getElementById('__skopix_result').textContent = '⏳';
|
|
4127
|
+
if (window.__skopixTesterRun) window.__skopixTesterRun({ sel, action, value });
|
|
4128
|
+
});
|
|
4009
4129
|
});
|
|
4130
|
+
}, selector || '');
|
|
4131
|
+
|
|
4132
|
+
await ctx.exposeFunction('__skopixTesterRun', async ({ sel, action, value }) => {
|
|
4133
|
+
const result = await executeStepTesterAction(page, { selector: sel, action, value });
|
|
4134
|
+
await page.evaluate((r, sel) => {
|
|
4135
|
+
const el = document.getElementById('__skopix_result');
|
|
4136
|
+
if (el) el.textContent = r.passed ? '✓' : '✗';
|
|
4137
|
+
try {
|
|
4138
|
+
const target = document.querySelector(sel);
|
|
4139
|
+
if (target) {
|
|
4140
|
+
const orig = target.style.outline;
|
|
4141
|
+
target.style.outline = r.passed ? '3px solid #34d399' : '3px solid #ef4444';
|
|
4142
|
+
setTimeout(() => { target.style.outline = orig; }, 1500);
|
|
4143
|
+
}
|
|
4144
|
+
} catch {}
|
|
4145
|
+
}, result, sel);
|
|
4010
4146
|
});
|
|
4011
|
-
}
|
|
4012
|
-
|
|
4013
|
-
// Expose run function back to page
|
|
4014
|
-
await ctx.exposeFunction('__skopixTesterRun', async ({ sel, action, value }) => {
|
|
4015
|
-
const result = await executeStepTesterAction(page, { selector: sel, action, value });
|
|
4016
|
-
await page.evaluate((r) => {
|
|
4017
|
-
const el = document.getElementById('__skopix_result');
|
|
4018
|
-
if (el) el.textContent = r.passed ? '✓' : '✗';
|
|
4019
|
-
// Highlight element briefly
|
|
4020
|
-
try {
|
|
4021
|
-
const target = document.querySelector(r.selector);
|
|
4022
|
-
if (target) {
|
|
4023
|
-
const orig = target.style.outline;
|
|
4024
|
-
target.style.outline = r.passed ? '3px solid #34d399' : '3px solid #ef4444';
|
|
4025
|
-
setTimeout(() => { target.style.outline = orig; }, 1500);
|
|
4026
|
-
}
|
|
4027
|
-
} catch {}
|
|
4028
|
-
}, { ...result, selector: sel });
|
|
4029
|
-
});
|
|
4147
|
+
}
|
|
4030
4148
|
|
|
4031
4149
|
if (url) await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30000 }).catch(() => {});
|
|
4032
|
-
|
|
4033
4150
|
stepTesterSessions.set(testerId, { browser, ctx, page });
|
|
4034
4151
|
}
|
|
4035
4152
|
|
package/package.json
CHANGED
package/web/app/index.html
CHANGED
|
@@ -1708,7 +1708,7 @@ body.viewer-mode .saved-test-row { cursor: default !important; }
|
|
|
1708
1708
|
</div>
|
|
1709
1709
|
|
|
1710
1710
|
<!-- RIGHT: Test steps being built -->
|
|
1711
|
-
<div style="display:flex;flex-direction:column;overflow:hidden
|
|
1711
|
+
<div style="display:flex;flex-direction:column;overflow:hidden">
|
|
1712
1712
|
<div style="padding:14px 20px;border-bottom:1px solid var(--border);flex-shrink:0;display:flex;align-items:center;justify-content:space-between">
|
|
1713
1713
|
<div>
|
|
1714
1714
|
<div style="font-family:var(--mono);font-size:10px;color:var(--muted);letter-spacing:0.1em">TEST STEPS</div>
|
|
@@ -1725,35 +1725,6 @@ body.viewer-mode .saved-test-row { cursor: default !important; }
|
|
|
1725
1725
|
</div>
|
|
1726
1726
|
</div>
|
|
1727
1727
|
|
|
1728
|
-
<!-- PREVIEW OVERLAY -->
|
|
1729
|
-
<div id="builder-preview-overlay" style="display:none;position:absolute;inset:0;background:var(--bg);flex-direction:column;z-index:10">
|
|
1730
|
-
<div style="padding:14px 20px;border-bottom:1px solid var(--border);flex-shrink:0;display:flex;align-items:center;justify-content:space-between;background:rgba(245,158,11,0.06)">
|
|
1731
|
-
<div style="display:flex;align-items:center;gap:10px">
|
|
1732
|
-
<span style="color:#f59e0b;font-size:14px" id="preview-status-dot">⏸</span>
|
|
1733
|
-
<div>
|
|
1734
|
-
<div style="font-family:var(--mono);font-size:10px;color:#f59e0b;letter-spacing:0.1em">PREVIEW MODE</div>
|
|
1735
|
-
<div style="font-family:var(--mono);font-size:11px;color:var(--muted);margin-top:2px" id="preview-status-text">Browser open — ready to run</div>
|
|
1736
|
-
</div>
|
|
1737
|
-
</div>
|
|
1738
|
-
<div style="display:flex;gap:8px">
|
|
1739
|
-
<button class="btn btn-ghost" id="btn-preview-run-all" onclick="previewRunAll()" style="font-size:11px;padding:4px 12px;color:#f59e0b;border-color:rgba(245,158,11,0.3)">
|
|
1740
|
-
▶▶ Run all
|
|
1741
|
-
</button>
|
|
1742
|
-
<button class="btn btn-ghost" id="btn-preview-run-next" onclick="previewRunNext()" style="font-size:11px;padding:4px 12px;color:#f59e0b;border-color:rgba(245,158,11,0.3)">
|
|
1743
|
-
▶ Run next
|
|
1744
|
-
</button>
|
|
1745
|
-
<button class="btn btn-ghost" onclick="stopBuilderPreview()" style="font-size:11px;padding:4px 10px">
|
|
1746
|
-
✕ Stop preview
|
|
1747
|
-
</button>
|
|
1748
|
-
</div>
|
|
1749
|
-
</div>
|
|
1750
|
-
<div id="preview-steps-list" style="overflow-y:auto;flex:1;padding:8px 0"></div>
|
|
1751
|
-
<div style="padding:12px 20px;border-top:1px solid var(--border);flex-shrink:0;display:flex;align-items:center;gap:10px" id="preview-summary" style="display:none">
|
|
1752
|
-
<span id="preview-passed" style="font-family:var(--mono);font-size:12px;color:#34d399"></span>
|
|
1753
|
-
<span id="preview-failed" style="font-family:var(--mono);font-size:12px;color:#ef4444"></span>
|
|
1754
|
-
</div>
|
|
1755
|
-
</div>
|
|
1756
|
-
|
|
1757
1728
|
</div>
|
|
1758
1729
|
|
|
1759
1730
|
</div>
|
|
@@ -6122,7 +6093,6 @@ function closeTestBuilder() {
|
|
|
6122
6093
|
fetch(API_BASE + '/api/step-tester/' + previewState.testerId + '/stop', { method: 'POST' }).catch(() => {});
|
|
6123
6094
|
previewState.testerId = null;
|
|
6124
6095
|
}
|
|
6125
|
-
document.getElementById('builder-preview-overlay').style.display = 'none';
|
|
6126
6096
|
const builder = document.getElementById('view-test-builder');
|
|
6127
6097
|
if (builder) builder.style.display = 'none';
|
|
6128
6098
|
document.querySelectorAll('.view').forEach(v => {
|
|
@@ -6422,47 +6392,7 @@ function harvestAnother() {
|
|
|
6422
6392
|
}
|
|
6423
6393
|
|
|
6424
6394
|
// ── TEST BUILDER PREVIEW ─────────────────────────────────────────────────────
|
|
6425
|
-
let previewState = { testerId: null
|
|
6426
|
-
|
|
6427
|
-
function renderPreviewSteps() {
|
|
6428
|
-
const container = document.getElementById('preview-steps-list');
|
|
6429
|
-
if (!container) return;
|
|
6430
|
-
const actionColors = { click:'#22d3ee', type:'#a78bfa', check:'#34d399', assert:'#fb923c', select:'#f472b6', scroll:'#6b7280' };
|
|
6431
|
-
|
|
6432
|
-
container.innerHTML = builderSteps.map((s, i) => {
|
|
6433
|
-
const result = previewState.results[i];
|
|
6434
|
-
const isCurrent = i === previewState.currentStep && !result;
|
|
6435
|
-
const status = result ? (result.passed ? '✓' : '✗') : (isCurrent ? '▶' : '○');
|
|
6436
|
-
const statusColor = result ? (result.passed ? '#34d399' : '#ef4444') : (isCurrent ? '#f59e0b' : 'var(--muted2)');
|
|
6437
|
-
const bg = isCurrent ? 'rgba(245,158,11,0.06)' : result && !result.passed ? 'rgba(239,68,68,0.04)' : '';
|
|
6438
|
-
return `<div style="display:flex;align-items:flex-start;gap:12px;padding:10px 20px;border-bottom:1px solid var(--border);background:${bg}">
|
|
6439
|
-
<span style="font-family:var(--mono);font-size:13px;color:${statusColor};min-width:20px;margin-top:1px">${status}</span>
|
|
6440
|
-
<div style="flex:1;min-width:0">
|
|
6441
|
-
<div style="display:flex;align-items:center;gap:8px">
|
|
6442
|
-
<span style="font-family:var(--mono);font-size:10px;color:${actionColors[s.action]||'var(--muted)'}">${s.action}</span>
|
|
6443
|
-
<span style="font-size:12px;color:var(--text)">${escapeHtml(s.description||'')}</span>
|
|
6444
|
-
</div>
|
|
6445
|
-
<code style="font-size:10px;color:var(--muted2)">${escapeHtml(s.stableSelector||s.selector||'')}</code>
|
|
6446
|
-
${result && !result.passed ? `<div style="font-family:var(--mono);font-size:11px;color:#ef4444;margin-top:4px">✗ ${escapeHtml(result.error||'Failed')}</div>` : ''}
|
|
6447
|
-
${result && result.screenshot ? `<img src="data:image/jpeg;base64,${result.screenshot}" style="width:100%;max-width:300px;border-radius:6px;margin-top:8px;border:1px solid var(--border)" loading="lazy">` : ''}
|
|
6448
|
-
</div>
|
|
6449
|
-
</div>`;
|
|
6450
|
-
}).join('');
|
|
6451
|
-
|
|
6452
|
-
// Scroll current step into view
|
|
6453
|
-
const items = container.querySelectorAll('div[style*="border-bottom"]');
|
|
6454
|
-
if (items[previewState.currentStep]) items[previewState.currentStep].scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
6455
|
-
}
|
|
6456
|
-
|
|
6457
|
-
function updatePreviewSummary() {
|
|
6458
|
-
const passed = previewState.results.filter(r => r && r.passed).length;
|
|
6459
|
-
const failed = previewState.results.filter(r => r && !r.passed).length;
|
|
6460
|
-
const total = previewState.results.filter(r => r).length;
|
|
6461
|
-
if (total === 0) return;
|
|
6462
|
-
document.getElementById('preview-passed').textContent = `✓ ${passed} passed`;
|
|
6463
|
-
document.getElementById('preview-failed').textContent = failed > 0 ? `✗ ${failed} failed` : '';
|
|
6464
|
-
document.getElementById('preview-summary').style.display = 'flex';
|
|
6465
|
-
}
|
|
6395
|
+
let previewState = { testerId: null };
|
|
6466
6396
|
|
|
6467
6397
|
async function startBuilderPreview() {
|
|
6468
6398
|
const url = document.getElementById('builder-url')?.value?.trim();
|
|
@@ -6477,19 +6407,25 @@ async function startBuilderPreview() {
|
|
|
6477
6407
|
const res = await fetch(API_BASE + '/api/step-tester/start', {
|
|
6478
6408
|
method: 'POST',
|
|
6479
6409
|
headers: { 'Content-Type': 'application/json' },
|
|
6480
|
-
body: JSON.stringify({
|
|
6410
|
+
body: JSON.stringify({
|
|
6411
|
+
url,
|
|
6412
|
+
mode: 'preview',
|
|
6413
|
+
steps: builderSteps.map(s => ({
|
|
6414
|
+
action: s.action,
|
|
6415
|
+
stableSelector: s.stableSelector || s.selector,
|
|
6416
|
+
selector: s.selector || s.stableSelector,
|
|
6417
|
+
value: s.value || '',
|
|
6418
|
+
assertType: s.assertType || null,
|
|
6419
|
+
description: s.description || s.action,
|
|
6420
|
+
})),
|
|
6421
|
+
}),
|
|
6481
6422
|
});
|
|
6482
6423
|
const data = await res.json();
|
|
6483
6424
|
if (!res.ok) throw new Error(data.error || 'Failed');
|
|
6484
|
-
|
|
6485
|
-
previewState = { testerId: data.testerId, currentStep: 0, results: [], running: false };
|
|
6486
|
-
|
|
6487
|
-
// Show overlay
|
|
6488
|
-
document.getElementById('builder-preview-overlay').style.display = 'flex';
|
|
6489
|
-
document.getElementById('preview-status-text').textContent = 'Browser open — navigate to start, then click Run next or Run all';
|
|
6490
|
-
document.getElementById('preview-summary').style.display = 'none';
|
|
6491
|
-
renderPreviewSteps();
|
|
6425
|
+
previewState.testerId = data.testerId;
|
|
6492
6426
|
btn.textContent = '⏸ Previewing';
|
|
6427
|
+
btn.onclick = stopBuilderPreview;
|
|
6428
|
+
showToast('Browser open — use the toolbar in the browser to run steps');
|
|
6493
6429
|
} catch (err) {
|
|
6494
6430
|
showToast('Error: ' + err.message);
|
|
6495
6431
|
btn.disabled = false;
|
|
@@ -6502,121 +6438,8 @@ async function stopBuilderPreview() {
|
|
|
6502
6438
|
await fetch(API_BASE + '/api/step-tester/' + previewState.testerId + '/stop', { method: 'POST' }).catch(() => {});
|
|
6503
6439
|
previewState.testerId = null;
|
|
6504
6440
|
}
|
|
6505
|
-
document.getElementById('builder-preview-overlay').style.display = 'none';
|
|
6506
6441
|
const btn = document.getElementById('btn-preview-test');
|
|
6507
|
-
btn.disabled = false;
|
|
6508
|
-
btn.textContent = 'Preview';
|
|
6509
|
-
}
|
|
6510
|
-
|
|
6511
|
-
async function previewRunNext() {
|
|
6512
|
-
if (!previewState.testerId || previewState.running) return;
|
|
6513
|
-
if (previewState.currentStep >= builderSteps.length) return;
|
|
6514
|
-
|
|
6515
|
-
previewState.running = true;
|
|
6516
|
-
const step = builderSteps[previewState.currentStep];
|
|
6517
|
-
document.getElementById('preview-status-text').textContent = `Running step ${previewState.currentStep + 1}/${builderSteps.length}...`;
|
|
6518
|
-
document.getElementById('btn-preview-run-next').disabled = true;
|
|
6519
|
-
document.getElementById('btn-preview-run-all').disabled = true;
|
|
6520
|
-
renderPreviewSteps();
|
|
6521
|
-
|
|
6522
|
-
try {
|
|
6523
|
-
const res = await fetch(API_BASE + '/api/step-tester/' + previewState.testerId + '/run', {
|
|
6524
|
-
method: 'POST',
|
|
6525
|
-
headers: { 'Content-Type': 'application/json' },
|
|
6526
|
-
body: JSON.stringify({ selector: step.stableSelector || step.selector, action: step.action, value: step.value || '', assertType: step.assertType }),
|
|
6527
|
-
});
|
|
6528
|
-
const result = await res.json();
|
|
6529
|
-
previewState.results[previewState.currentStep] = result;
|
|
6530
|
-
|
|
6531
|
-
if (result.passed) {
|
|
6532
|
-
previewState.currentStep++;
|
|
6533
|
-
document.getElementById('preview-status-text').textContent =
|
|
6534
|
-
previewState.currentStep >= builderSteps.length
|
|
6535
|
-
? `✓ All ${builderSteps.length} steps passed!`
|
|
6536
|
-
: `✓ Step ${previewState.currentStep} passed — ready for next`;
|
|
6537
|
-
document.getElementById('preview-status-dot').textContent = previewState.currentStep >= builderSteps.length ? '✓' : '⏸';
|
|
6538
|
-
document.getElementById('preview-status-dot').style.color = previewState.currentStep >= builderSteps.length ? '#34d399' : '#f59e0b';
|
|
6539
|
-
} else {
|
|
6540
|
-
document.getElementById('preview-status-text').textContent = `✗ Step ${previewState.currentStep + 1} failed — fix the step and try again`;
|
|
6541
|
-
document.getElementById('preview-status-dot').textContent = '✗';
|
|
6542
|
-
document.getElementById('preview-status-dot').style.color = '#ef4444';
|
|
6543
|
-
}
|
|
6544
|
-
} catch (err) {
|
|
6545
|
-
previewState.results[previewState.currentStep] = { passed: false, error: err.message };
|
|
6546
|
-
}
|
|
6547
|
-
|
|
6548
|
-
previewState.running = false;
|
|
6549
|
-
document.getElementById('btn-preview-run-next').disabled = previewState.currentStep >= builderSteps.length;
|
|
6550
|
-
document.getElementById('btn-preview-run-all').disabled = false;
|
|
6551
|
-
renderPreviewSteps();
|
|
6552
|
-
updatePreviewSummary();
|
|
6553
|
-
}
|
|
6554
|
-
|
|
6555
|
-
async function previewRunAll() {
|
|
6556
|
-
if (!previewState.testerId || previewState.running) return;
|
|
6557
|
-
previewState.running = true;
|
|
6558
|
-
previewState.results = [];
|
|
6559
|
-
previewState.currentStep = 0;
|
|
6560
|
-
document.getElementById('btn-preview-run-all').disabled = true;
|
|
6561
|
-
document.getElementById('btn-preview-run-next').disabled = true;
|
|
6562
|
-
document.getElementById('preview-summary').style.display = 'none';
|
|
6563
|
-
|
|
6564
|
-
const steps = builderSteps.map(s => ({
|
|
6565
|
-
selector: s.stableSelector || s.selector,
|
|
6566
|
-
action: s.action,
|
|
6567
|
-
value: s.value || '',
|
|
6568
|
-
assertType: s.assertType,
|
|
6569
|
-
}));
|
|
6570
|
-
|
|
6571
|
-
try {
|
|
6572
|
-
const es = new EventSource(API_BASE + '/api/step-tester/' + previewState.testerId + '/run-sequence?' + new URLSearchParams({ steps: JSON.stringify(steps) }));
|
|
6573
|
-
|
|
6574
|
-
// Use POST with EventSource workaround — send steps via a separate POST then listen
|
|
6575
|
-
const res = await fetch(API_BASE + '/api/step-tester/' + previewState.testerId + '/run-sequence', {
|
|
6576
|
-
method: 'POST',
|
|
6577
|
-
headers: { 'Content-Type': 'application/json' },
|
|
6578
|
-
body: JSON.stringify({ steps }),
|
|
6579
|
-
});
|
|
6580
|
-
|
|
6581
|
-
const reader = res.body.getReader();
|
|
6582
|
-
const decoder = new TextDecoder();
|
|
6583
|
-
let buffer = '';
|
|
6584
|
-
|
|
6585
|
-
while (true) {
|
|
6586
|
-
const { done, value } = await reader.read();
|
|
6587
|
-
if (done) break;
|
|
6588
|
-
buffer += decoder.decode(value, { stream: true });
|
|
6589
|
-
const lines = buffer.split('\n');
|
|
6590
|
-
buffer = lines.pop();
|
|
6591
|
-
for (const line of lines) {
|
|
6592
|
-
if (!line.startsWith('data: ')) continue;
|
|
6593
|
-
try {
|
|
6594
|
-
const msg = JSON.parse(line.slice(6));
|
|
6595
|
-
if (msg.type === 'step-start') {
|
|
6596
|
-
previewState.currentStep = msg.index;
|
|
6597
|
-
document.getElementById('preview-status-text').textContent = `Running step ${msg.index + 1}/${builderSteps.length}...`;
|
|
6598
|
-
renderPreviewSteps();
|
|
6599
|
-
} else if (msg.type === 'step-result') {
|
|
6600
|
-
previewState.results[msg.index] = { passed: msg.passed, error: msg.error, screenshot: msg.screenshot };
|
|
6601
|
-
previewState.currentStep = msg.passed ? msg.index + 1 : msg.index;
|
|
6602
|
-
renderPreviewSteps();
|
|
6603
|
-
updatePreviewSummary();
|
|
6604
|
-
} else if (msg.type === 'done') {
|
|
6605
|
-
const allPassed = previewState.results.every(r => r && r.passed);
|
|
6606
|
-
document.getElementById('preview-status-text').textContent = allPassed ? `✓ All ${builderSteps.length} steps passed!` : `✗ Preview complete — some steps failed`;
|
|
6607
|
-
document.getElementById('preview-status-dot').textContent = allPassed ? '✓' : '✗';
|
|
6608
|
-
document.getElementById('preview-status-dot').style.color = allPassed ? '#34d399' : '#ef4444';
|
|
6609
|
-
}
|
|
6610
|
-
} catch {}
|
|
6611
|
-
}
|
|
6612
|
-
}
|
|
6613
|
-
} catch (err) {
|
|
6614
|
-
document.getElementById('preview-status-text').textContent = 'Error: ' + err.message;
|
|
6615
|
-
}
|
|
6616
|
-
|
|
6617
|
-
previewState.running = false;
|
|
6618
|
-
document.getElementById('btn-preview-run-all').disabled = false;
|
|
6619
|
-
document.getElementById('btn-preview-run-next').disabled = previewState.currentStep >= builderSteps.length;
|
|
6442
|
+
if (btn) { btn.disabled = false; btn.textContent = 'Preview'; btn.onclick = startBuilderPreview; }
|
|
6620
6443
|
}
|
|
6621
6444
|
|
|
6622
6445
|
// ── SUITE TEST PICKER ────────────────────────────────────────────────────────
|