skopix 2.0.38 → 2.0.39
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 +19 -0
- package/package.json +1 -1
- package/web/app/index.html +239 -2
|
@@ -1811,6 +1811,25 @@ export async function dashboardCommand(options) {
|
|
|
1811
1811
|
sendJSON(res, 200, result);
|
|
1812
1812
|
return;
|
|
1813
1813
|
}
|
|
1814
|
+
if (pathname.match(/^\/api\/step-tester\/[^/]+\/run-sequence$/) && method === 'POST') {
|
|
1815
|
+
const testerId = pathname.split('/')[3];
|
|
1816
|
+
const { steps } = JSON.parse(await readBody(req));
|
|
1817
|
+
const session = stepTesterSessions.get(testerId);
|
|
1818
|
+
if (!session) { sendJSON(res, 404, { error: 'Session not found' }); return; }
|
|
1819
|
+
// Run steps one at a time, streaming results via SSE
|
|
1820
|
+
res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive', 'Access-Control-Allow-Origin': '*' });
|
|
1821
|
+
for (let i = 0; i < steps.length; i++) {
|
|
1822
|
+
const step = steps[i];
|
|
1823
|
+
res.write(`data: ${JSON.stringify({ type: 'step-start', index: i })}\n\n`);
|
|
1824
|
+
const result = await executeStepTesterAction(session.page, step);
|
|
1825
|
+
res.write(`data: ${JSON.stringify({ type: 'step-result', index: i, ...result })}\n\n`);
|
|
1826
|
+
if (!result.passed) break; // stop on first failure
|
|
1827
|
+
await new Promise(r => setTimeout(r, 300)); // small gap between steps
|
|
1828
|
+
}
|
|
1829
|
+
res.write(`data: ${JSON.stringify({ type: 'done' })}\n\n`);
|
|
1830
|
+
res.end();
|
|
1831
|
+
return;
|
|
1832
|
+
}
|
|
1814
1833
|
if (pathname.match(/^\/api\/step-tester\/[^/]+\/stop$/) && method === 'POST') {
|
|
1815
1834
|
const testerId = pathname.split('/')[3];
|
|
1816
1835
|
await stopStepTester(testerId);
|
package/package.json
CHANGED
package/web/app/index.html
CHANGED
|
@@ -1681,6 +1681,10 @@ body.viewer-mode .saved-test-row { cursor: default !important; }
|
|
|
1681
1681
|
</div>
|
|
1682
1682
|
<div style="display:flex;gap:8px;align-items:center">
|
|
1683
1683
|
<input class="form-input" id="builder-test-name" type="text" placeholder="Test name..." style="width:220px">
|
|
1684
|
+
<button class="btn btn-ghost" id="btn-preview-test" onclick="startBuilderPreview()" style="color:#f59e0b;border-color:rgba(245,158,11,0.3)">
|
|
1685
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="5 3 19 12 5 21 5 3"/></svg>
|
|
1686
|
+
Preview
|
|
1687
|
+
</button>
|
|
1684
1688
|
<button class="btn btn-primary" onclick="saveBuiltTest()">Save test</button>
|
|
1685
1689
|
</div>
|
|
1686
1690
|
</div>
|
|
@@ -1704,7 +1708,7 @@ body.viewer-mode .saved-test-row { cursor: default !important; }
|
|
|
1704
1708
|
</div>
|
|
1705
1709
|
|
|
1706
1710
|
<!-- RIGHT: Test steps being built -->
|
|
1707
|
-
<div style="display:flex;flex-direction:column;overflow:hidden">
|
|
1711
|
+
<div style="display:flex;flex-direction:column;overflow:hidden;position:relative">
|
|
1708
1712
|
<div style="padding:14px 20px;border-bottom:1px solid var(--border);flex-shrink:0;display:flex;align-items:center;justify-content:space-between">
|
|
1709
1713
|
<div>
|
|
1710
1714
|
<div style="font-family:var(--mono);font-size:10px;color:var(--muted);letter-spacing:0.1em">TEST STEPS</div>
|
|
@@ -1720,6 +1724,36 @@ body.viewer-mode .saved-test-row { cursor: default !important; }
|
|
|
1720
1724
|
← Click steps from the library to add them here
|
|
1721
1725
|
</div>
|
|
1722
1726
|
</div>
|
|
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
|
+
|
|
1723
1757
|
</div>
|
|
1724
1758
|
|
|
1725
1759
|
</div>
|
|
@@ -6083,9 +6117,14 @@ function openTestBuilder() {
|
|
|
6083
6117
|
}
|
|
6084
6118
|
|
|
6085
6119
|
function closeTestBuilder() {
|
|
6120
|
+
// Stop preview if active
|
|
6121
|
+
if (previewState.testerId) {
|
|
6122
|
+
fetch(API_BASE + '/api/step-tester/' + previewState.testerId + '/stop', { method: 'POST' }).catch(() => {});
|
|
6123
|
+
previewState.testerId = null;
|
|
6124
|
+
}
|
|
6125
|
+
document.getElementById('builder-preview-overlay').style.display = 'none';
|
|
6086
6126
|
const builder = document.getElementById('view-test-builder');
|
|
6087
6127
|
if (builder) builder.style.display = 'none';
|
|
6088
|
-
// Restore all views to their default display state before switching
|
|
6089
6128
|
document.querySelectorAll('.view').forEach(v => {
|
|
6090
6129
|
if (v.id !== 'view-test-builder') v.style.display = '';
|
|
6091
6130
|
});
|
|
@@ -6382,6 +6421,204 @@ function harvestAnother() {
|
|
|
6382
6421
|
document.getElementById('harvester-status').textContent = 'Navigate to the next element, then click Capture.';
|
|
6383
6422
|
}
|
|
6384
6423
|
|
|
6424
|
+
// ── TEST BUILDER PREVIEW ─────────────────────────────────────────────────────
|
|
6425
|
+
let previewState = { testerId: null, currentStep: 0, results: [], running: false };
|
|
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
|
+
}
|
|
6466
|
+
|
|
6467
|
+
async function startBuilderPreview() {
|
|
6468
|
+
const url = document.getElementById('builder-url')?.value?.trim();
|
|
6469
|
+
if (builderSteps.length === 0) { showToast('Add steps first'); return; }
|
|
6470
|
+
if (!url) { showToast('Set a start URL first'); document.getElementById('builder-url').focus(); return; }
|
|
6471
|
+
|
|
6472
|
+
const btn = document.getElementById('btn-preview-test');
|
|
6473
|
+
btn.disabled = true;
|
|
6474
|
+
btn.textContent = 'Opening...';
|
|
6475
|
+
|
|
6476
|
+
try {
|
|
6477
|
+
const res = await fetch(API_BASE + '/api/step-tester/start', {
|
|
6478
|
+
method: 'POST',
|
|
6479
|
+
headers: { 'Content-Type': 'application/json' },
|
|
6480
|
+
body: JSON.stringify({ url, selector: builderSteps[0]?.stableSelector || '' }),
|
|
6481
|
+
});
|
|
6482
|
+
const data = await res.json();
|
|
6483
|
+
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();
|
|
6492
|
+
btn.textContent = '⏸ Previewing';
|
|
6493
|
+
} catch (err) {
|
|
6494
|
+
showToast('Error: ' + err.message);
|
|
6495
|
+
btn.disabled = false;
|
|
6496
|
+
btn.textContent = 'Preview';
|
|
6497
|
+
}
|
|
6498
|
+
}
|
|
6499
|
+
|
|
6500
|
+
async function stopBuilderPreview() {
|
|
6501
|
+
if (previewState.testerId) {
|
|
6502
|
+
await fetch(API_BASE + '/api/step-tester/' + previewState.testerId + '/stop', { method: 'POST' }).catch(() => {});
|
|
6503
|
+
previewState.testerId = null;
|
|
6504
|
+
}
|
|
6505
|
+
document.getElementById('builder-preview-overlay').style.display = 'none';
|
|
6506
|
+
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;
|
|
6620
|
+
}
|
|
6621
|
+
|
|
6385
6622
|
// ── SUITE TEST PICKER ────────────────────────────────────────────────────────
|
|
6386
6623
|
function openSuiteTestPicker() {
|
|
6387
6624
|
const list = document.getElementById('stp-test-list');
|