skopix 2.0.90 → 2.0.92
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 +39 -5
- package/package.json +1 -1
- package/web/app/index.html +47 -7
|
@@ -1896,6 +1896,12 @@ export async function dashboardCommand(options) {
|
|
|
1896
1896
|
sendJSON(res, 200, folders);
|
|
1897
1897
|
return;
|
|
1898
1898
|
}
|
|
1899
|
+
if (pathname === '/api/folders' && method === 'PUT') {
|
|
1900
|
+
const folders = JSON.parse(await readBody(req));
|
|
1901
|
+
await saveFolders(folders);
|
|
1902
|
+
sendJSON(res, 200, folders);
|
|
1903
|
+
return;
|
|
1904
|
+
}
|
|
1899
1905
|
if (pathname === '/api/folders' && method === 'DELETE') {
|
|
1900
1906
|
const { name, type } = JSON.parse(await readBody(req));
|
|
1901
1907
|
const folders = await getFolders();
|
|
@@ -4328,25 +4334,37 @@ async function startStepTester(testerId, url, selector, mode, steps) {
|
|
|
4328
4334
|
if (mode === 'preview' && steps && steps.length > 0) {
|
|
4329
4335
|
// Register expose functions FIRST before addInitScript so they're available on DOMContentLoaded
|
|
4330
4336
|
await ctx.exposeFunction('__skopixPreviewRun', async ({ index }) => {
|
|
4337
|
+
const session = stepTesterSessions.get(testerId);
|
|
4338
|
+
if (!session) return;
|
|
4331
4339
|
const s = steps[index];
|
|
4332
4340
|
if (!s) return;
|
|
4333
4341
|
await page.evaluate(({ i, status }) => { if(window.__skopixUpdatePreview) window.__skopixUpdatePreview(i, null, status); }, { i: index, status: `Running step ${index+1}/${steps.length}...` }).catch(()=>{});
|
|
4334
4342
|
const result = await executeStepTesterAction(page, { selector: s.stableSelector||s.selector, action: s.action, value: s.value||'', assertType: s.assertType });
|
|
4335
4343
|
const msg = result.passed ? (index+1 >= steps.length ? `✓ All ${steps.length} steps passed!` : `✓ Step ${index+1} passed`) : `✗ Step ${index+1} failed`;
|
|
4336
|
-
|
|
4344
|
+
if (result.passed) session.currentStep = index + 1;
|
|
4345
|
+
if (!session.results) session.results = {};
|
|
4346
|
+
session.results[index] = { passed: result.passed, error: result.error||null };
|
|
4347
|
+
await page.evaluate(({ i, r, msg, currentStep }) => {
|
|
4337
4348
|
if(window.__skopixUpdatePreview) window.__skopixUpdatePreview(i, r, msg);
|
|
4338
|
-
|
|
4349
|
+
window.__skopixPreviewCurrentStep = currentStep;
|
|
4350
|
+
}, { i: index, r: { passed: result.passed, error: result.error||null }, msg, currentStep: session.currentStep || 0 }).catch(()=>{});
|
|
4339
4351
|
});
|
|
4340
4352
|
|
|
4341
4353
|
await ctx.exposeFunction('__skopixPreviewRunAll', async ({ fromIndex }) => {
|
|
4354
|
+
const session = stepTesterSessions.get(testerId);
|
|
4355
|
+
if (!session) return;
|
|
4342
4356
|
for (let i = fromIndex; i < steps.length; i++) {
|
|
4343
4357
|
const s = steps[i];
|
|
4344
4358
|
await page.evaluate(({ i, total }) => { if(window.__skopixUpdatePreview) window.__skopixUpdatePreview(i, null, `Running step ${i+1}/${total}...`); }, { i, total: steps.length }).catch(()=>{});
|
|
4345
4359
|
const result = await executeStepTesterAction(page, { selector: s.stableSelector||s.selector, action: s.action, value: s.value||'', assertType: s.assertType });
|
|
4360
|
+
if (result.passed) session.currentStep = i + 1;
|
|
4361
|
+
if (!session.results) session.results = {};
|
|
4362
|
+
session.results[i] = { passed: result.passed, error: result.error||null };
|
|
4346
4363
|
const msg = result.passed ? (i+1 >= steps.length ? `✓ All ${steps.length} steps passed!` : `Running...`) : `✗ Step ${i+1} failed — fix and retry`;
|
|
4347
|
-
await page.evaluate(({ i, r, msg }) => {
|
|
4364
|
+
await page.evaluate(({ i, r, msg, currentStep }) => {
|
|
4348
4365
|
if(window.__skopixUpdatePreview) window.__skopixUpdatePreview(i, r, msg);
|
|
4349
|
-
|
|
4366
|
+
window.__skopixPreviewCurrentStep = currentStep;
|
|
4367
|
+
}, { i, r: { passed: result.passed, error: result.error||null }, msg, currentStep: session.currentStep || 0 }).catch(()=>{});
|
|
4350
4368
|
if (!result.passed) break;
|
|
4351
4369
|
await new Promise(r => setTimeout(r, 400));
|
|
4352
4370
|
}
|
|
@@ -4357,6 +4375,11 @@ async function startStepTester(testerId, url, selector, mode, steps) {
|
|
|
4357
4375
|
stepTesterSessions.delete(testerId);
|
|
4358
4376
|
});
|
|
4359
4377
|
|
|
4378
|
+
await ctx.exposeFunction('__skopixGetState', async () => {
|
|
4379
|
+
const session = stepTesterSessions.get(testerId);
|
|
4380
|
+
return { currentStep: session?.currentStep || 0, results: session?.results || {} };
|
|
4381
|
+
});
|
|
4382
|
+
|
|
4360
4383
|
// THEN inject toolbar via addInitScript
|
|
4361
4384
|
// PREVIEW MODE — inject steps list toolbar
|
|
4362
4385
|
await ctx.addInitScript((stepsData) => {
|
|
@@ -4450,6 +4473,17 @@ async function startStepTester(testerId, url, selector, mode, steps) {
|
|
|
4450
4473
|
if (window.__skopixStopPreview) window.__skopixStopPreview({});
|
|
4451
4474
|
});
|
|
4452
4475
|
|
|
4476
|
+
// Restore state from server after navigation
|
|
4477
|
+
if (window.__skopixGetState) {
|
|
4478
|
+
window.__skopixGetState({}).then(state => {
|
|
4479
|
+
if (state && state.currentStep > 0) {
|
|
4480
|
+
window.__skopixPreviewCurrentStep = state.currentStep;
|
|
4481
|
+
window.__skopixPreviewResults = state.results || {};
|
|
4482
|
+
renderSteps(state.currentStep, state.results || {});
|
|
4483
|
+
}
|
|
4484
|
+
}).catch(() => {});
|
|
4485
|
+
}
|
|
4486
|
+
|
|
4453
4487
|
window.__skopixUpdatePreview = (index, result, status) => {
|
|
4454
4488
|
window.__skopixPreviewResults[index] = result;
|
|
4455
4489
|
if (result && result.passed) window.__skopixPreviewCurrentStep = index + 1;
|
|
@@ -4572,7 +4606,7 @@ async function startStepTester(testerId, url, selector, mode, steps) {
|
|
|
4572
4606
|
if (url) await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30000 }).catch(() => {});
|
|
4573
4607
|
// Ensure toolbar is injected on first page (DOMContentLoaded may have already fired)
|
|
4574
4608
|
await page.evaluate(() => { if (window.__skopixTesterSelector !== undefined && !document.getElementById('__skopix_tester') && !document.getElementById('__skopix_preview')) { document.dispatchEvent(new Event('DOMContentLoaded')); } }).catch(() => {});
|
|
4575
|
-
stepTesterSessions.set(testerId, { browser, ctx, page });
|
|
4609
|
+
stepTesterSessions.set(testerId, { browser, ctx, page, currentStep: 0, results: {} });
|
|
4576
4610
|
}
|
|
4577
4611
|
|
|
4578
4612
|
async function executeStepTesterAction(page, { selector, action, value }) {
|
package/package.json
CHANGED
package/web/app/index.html
CHANGED
|
@@ -5738,13 +5738,16 @@ function renderFolderTree(containerId, type, selectedFolder, onSelect) {
|
|
|
5738
5738
|
const isSelected = selectedFolder === node.fullPath;
|
|
5739
5739
|
return `<div>
|
|
5740
5740
|
<div class="folder-item ${isSelected ? 'folder-item--active' : ''}"
|
|
5741
|
-
|
|
5741
|
+
draggable="true"
|
|
5742
|
+
ondragstart="onFolderReorderStart(event,'${escapeAttr(node.fullPath)}','${type}')"
|
|
5743
|
+
ondragover="event.preventDefault();event.stopPropagation();this.style.outline="2px solid var(--cyan)""
|
|
5744
|
+
ondragleave="this.style.outline="""
|
|
5745
|
+
ondrop="onFolderReorderDrop(event,'${escapeAttr(node.fullPath)}','${type}')"
|
|
5746
|
+
style="padding:7px 14px 7px ${14 + depth * 16}px;cursor:pointer;display:flex;align-items:center;gap:8px;font-size:12px;color:${isSelected ? 'var(--cyan)' : 'var(--text)'};background:${isSelected ? 'rgba(34,211,238,0.08)' : ''};transition:background 0.1s;border-top:2px solid transparent"
|
|
5742
5747
|
data-folder="${escapeAttr(node.fullPath)}" data-type="${type}"
|
|
5743
5748
|
onmouseover="if(!this.classList.contains('folder-item--active'))this.style.background='var(--surface2)'"
|
|
5744
|
-
onmouseout="if(!this.classList.contains('folder-item--active'))this.style.background=''"
|
|
5745
|
-
|
|
5746
|
-
ondragleave="this.style.background=''"
|
|
5747
|
-
ondrop="onFolderDrop(event,'${escapeAttr(node.fullPath)}','${type}')">
|
|
5749
|
+
onmouseout="if(!this.classList.contains('folder-item--active'))this.style.background=''">
|
|
5750
|
+
<span style="color:var(--muted2);cursor:grab;font-size:12px;flex-shrink:0" onmousedown="event.stopPropagation()">⠿</span>
|
|
5748
5751
|
<svg width="13" height="13" viewBox="0 0 24 24" fill="${isSelected ? 'rgba(34,211,238,0.3)' : 'none'}" stroke="currentColor" stroke-width="2"><path d="M22 19a2 2 0 01-2 2H4a2 2 0 01-2-2V5a2 2 0 012-2h5l2 3h9a2 2 0 012 2z"/></svg>
|
|
5749
5752
|
<span style="flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis">${escapeHtml(node.name)}</span>
|
|
5750
5753
|
<span class="folder-actions" style="display:none;gap:4px">
|
|
@@ -5763,7 +5766,7 @@ function renderFolderTree(containerId, type, selectedFolder, onSelect) {
|
|
|
5763
5766
|
onmouseover="if(${!allActive})this.style.background='var(--surface2)'" onmouseout="if(${!allActive})this.style.background=''"
|
|
5764
5767
|
ondragover="event.preventDefault();this.style.background='rgba(34,211,238,0.15)'"
|
|
5765
5768
|
ondragleave="this.style.background=''"
|
|
5766
|
-
ondrop="onFolderDrop(event,'','${type}')">
|
|
5769
|
+
ondrop="if(!event.dataTransfer.getData('folder-reorder'))onFolderDrop(event,'','${type}')">
|
|
5767
5770
|
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18"/></svg>
|
|
5768
5771
|
All
|
|
5769
5772
|
</div>
|
|
@@ -5805,7 +5808,44 @@ document.addEventListener('click', (e) => {
|
|
|
5805
5808
|
}
|
|
5806
5809
|
});
|
|
5807
5810
|
|
|
5808
|
-
//
|
|
5811
|
+
// Folder reordering
|
|
5812
|
+
let folderDragName = null;
|
|
5813
|
+
|
|
5814
|
+
function onFolderReorderStart(e, folderName, type) {
|
|
5815
|
+
folderDragName = folderName;
|
|
5816
|
+
e.dataTransfer.setData('folder-reorder', JSON.stringify({ name: folderName, type }));
|
|
5817
|
+
e.dataTransfer.effectAllowed = 'move';
|
|
5818
|
+
e.stopPropagation();
|
|
5819
|
+
}
|
|
5820
|
+
|
|
5821
|
+
async function onFolderReorderDrop(e, targetName, type) {
|
|
5822
|
+
e.preventDefault();
|
|
5823
|
+
e.stopPropagation();
|
|
5824
|
+
e.currentTarget.style.outline = '';
|
|
5825
|
+
const data = e.dataTransfer.getData('folder-reorder');
|
|
5826
|
+
if (!data) return;
|
|
5827
|
+
const { name: draggedName } = JSON.parse(data);
|
|
5828
|
+
if (draggedName === targetName) return;
|
|
5829
|
+
// Reorder in foldersCache
|
|
5830
|
+
const typeFolders = foldersCache.filter(f => f.type === type);
|
|
5831
|
+
const others = foldersCache.filter(f => f.type !== type);
|
|
5832
|
+
const dragIdx = typeFolders.findIndex(f => f.name === draggedName);
|
|
5833
|
+
const targetIdx = typeFolders.findIndex(f => f.name === targetName);
|
|
5834
|
+
if (dragIdx === -1 || targetIdx === -1) return;
|
|
5835
|
+
const [dragged] = typeFolders.splice(dragIdx, 1);
|
|
5836
|
+
typeFolders.splice(targetIdx, 0, dragged);
|
|
5837
|
+
foldersCache = [...others, ...typeFolders];
|
|
5838
|
+
// Save new order
|
|
5839
|
+
await fetch(API_BASE + '/api/folders', {
|
|
5840
|
+
method: 'PUT',
|
|
5841
|
+
headers: { 'Content-Type': 'application/json' },
|
|
5842
|
+
body: JSON.stringify(foldersCache),
|
|
5843
|
+
});
|
|
5844
|
+
if (type === 'library') renderFolderTree('library-folder-tree', 'library', selectedLibraryFolder);
|
|
5845
|
+
else renderFolderTree('tests-folder-tree', 'tests', selectedTestsFolder);
|
|
5846
|
+
}
|
|
5847
|
+
|
|
5848
|
+
// Drag items into folders
|
|
5809
5849
|
function onItemDragStart(e, id, type) {
|
|
5810
5850
|
e.dataTransfer.setData('text/plain', JSON.stringify({ id, type }));
|
|
5811
5851
|
e.dataTransfer.effectAllowed = 'move';
|