skopix 2.0.91 → 2.0.93
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 +49 -5
- package/package.json +1 -1
- package/web/app/index.html +42 -16
|
@@ -1496,6 +1496,15 @@ export async function dashboardCommand(options) {
|
|
|
1496
1496
|
const config = JSON.parse(body);
|
|
1497
1497
|
const userEnv = await resolveUserSecretsEnv(currentUser?.id, teamMode);
|
|
1498
1498
|
const runId = startRun(config, activeRuns, reportsDir, currentUser, userEnv);
|
|
1499
|
+
if (teamMode && currentUser) {
|
|
1500
|
+
teamMode.db.logAudit({
|
|
1501
|
+
userId: currentUser.id,
|
|
1502
|
+
action: 'test.run',
|
|
1503
|
+
targetType: 'test',
|
|
1504
|
+
targetId: config.testId || config.suiteId || 'unknown',
|
|
1505
|
+
metadata: { testName: config.testName || config.suiteName, scope: config.scope }
|
|
1506
|
+
});
|
|
1507
|
+
}
|
|
1499
1508
|
sendJSON(res, 200, { runId });
|
|
1500
1509
|
return;
|
|
1501
1510
|
}
|
|
@@ -1583,6 +1592,9 @@ export async function dashboardCommand(options) {
|
|
|
1583
1592
|
const result = await updateTest(suitesDir, scope, testId, data);
|
|
1584
1593
|
// Extract any new unmatched steps to pending review queue
|
|
1585
1594
|
extractStepsToLibrary(suitesDir, data.steps || [], data.name || testId).catch(() => {});
|
|
1595
|
+
if (teamMode && currentUser) {
|
|
1596
|
+
teamMode.db.logAudit({ userId: currentUser.id, action: 'test.updated', targetType: 'test', targetId: testId, metadata: { name: data.name, scope } });
|
|
1597
|
+
}
|
|
1586
1598
|
sendJSON(res, 200, result);
|
|
1587
1599
|
} catch (err) {
|
|
1588
1600
|
sendJSON(res, 400, { error: err.message });
|
|
@@ -1593,7 +1605,11 @@ export async function dashboardCommand(options) {
|
|
|
1593
1605
|
const parts = pathname.split('/');
|
|
1594
1606
|
const scope = decodeURIComponent(parts[3]);
|
|
1595
1607
|
const testId = decodeURIComponent(parts[4]);
|
|
1608
|
+
const testToDelete = await getTest(suitesDir, scope, testId).catch(() => null);
|
|
1596
1609
|
await deleteTest(suitesDir, scope, testId);
|
|
1610
|
+
if (teamMode && currentUser) {
|
|
1611
|
+
teamMode.db.logAudit({ userId: currentUser.id, action: 'test.deleted', targetType: 'test', targetId: testId, metadata: { name: testToDelete?.name, scope } });
|
|
1612
|
+
}
|
|
1597
1613
|
sendJSON(res, 200, { deleted: true });
|
|
1598
1614
|
return;
|
|
1599
1615
|
}
|
|
@@ -4334,25 +4350,37 @@ async function startStepTester(testerId, url, selector, mode, steps) {
|
|
|
4334
4350
|
if (mode === 'preview' && steps && steps.length > 0) {
|
|
4335
4351
|
// Register expose functions FIRST before addInitScript so they're available on DOMContentLoaded
|
|
4336
4352
|
await ctx.exposeFunction('__skopixPreviewRun', async ({ index }) => {
|
|
4353
|
+
const session = stepTesterSessions.get(testerId);
|
|
4354
|
+
if (!session) return;
|
|
4337
4355
|
const s = steps[index];
|
|
4338
4356
|
if (!s) return;
|
|
4339
4357
|
await page.evaluate(({ i, status }) => { if(window.__skopixUpdatePreview) window.__skopixUpdatePreview(i, null, status); }, { i: index, status: `Running step ${index+1}/${steps.length}...` }).catch(()=>{});
|
|
4340
4358
|
const result = await executeStepTesterAction(page, { selector: s.stableSelector||s.selector, action: s.action, value: s.value||'', assertType: s.assertType });
|
|
4341
4359
|
const msg = result.passed ? (index+1 >= steps.length ? `✓ All ${steps.length} steps passed!` : `✓ Step ${index+1} passed`) : `✗ Step ${index+1} failed`;
|
|
4342
|
-
|
|
4360
|
+
if (result.passed) session.currentStep = index + 1;
|
|
4361
|
+
if (!session.results) session.results = {};
|
|
4362
|
+
session.results[index] = { passed: result.passed, error: result.error||null };
|
|
4363
|
+
await page.evaluate(({ i, r, msg, currentStep }) => {
|
|
4343
4364
|
if(window.__skopixUpdatePreview) window.__skopixUpdatePreview(i, r, msg);
|
|
4344
|
-
|
|
4365
|
+
window.__skopixPreviewCurrentStep = currentStep;
|
|
4366
|
+
}, { i: index, r: { passed: result.passed, error: result.error||null }, msg, currentStep: session.currentStep || 0 }).catch(()=>{});
|
|
4345
4367
|
});
|
|
4346
4368
|
|
|
4347
4369
|
await ctx.exposeFunction('__skopixPreviewRunAll', async ({ fromIndex }) => {
|
|
4370
|
+
const session = stepTesterSessions.get(testerId);
|
|
4371
|
+
if (!session) return;
|
|
4348
4372
|
for (let i = fromIndex; i < steps.length; i++) {
|
|
4349
4373
|
const s = steps[i];
|
|
4350
4374
|
await page.evaluate(({ i, total }) => { if(window.__skopixUpdatePreview) window.__skopixUpdatePreview(i, null, `Running step ${i+1}/${total}...`); }, { i, total: steps.length }).catch(()=>{});
|
|
4351
4375
|
const result = await executeStepTesterAction(page, { selector: s.stableSelector||s.selector, action: s.action, value: s.value||'', assertType: s.assertType });
|
|
4376
|
+
if (result.passed) session.currentStep = i + 1;
|
|
4377
|
+
if (!session.results) session.results = {};
|
|
4378
|
+
session.results[i] = { passed: result.passed, error: result.error||null };
|
|
4352
4379
|
const msg = result.passed ? (i+1 >= steps.length ? `✓ All ${steps.length} steps passed!` : `Running...`) : `✗ Step ${i+1} failed — fix and retry`;
|
|
4353
|
-
await page.evaluate(({ i, r, msg }) => {
|
|
4380
|
+
await page.evaluate(({ i, r, msg, currentStep }) => {
|
|
4354
4381
|
if(window.__skopixUpdatePreview) window.__skopixUpdatePreview(i, r, msg);
|
|
4355
|
-
|
|
4382
|
+
window.__skopixPreviewCurrentStep = currentStep;
|
|
4383
|
+
}, { i, r: { passed: result.passed, error: result.error||null }, msg, currentStep: session.currentStep || 0 }).catch(()=>{});
|
|
4356
4384
|
if (!result.passed) break;
|
|
4357
4385
|
await new Promise(r => setTimeout(r, 400));
|
|
4358
4386
|
}
|
|
@@ -4363,6 +4391,11 @@ async function startStepTester(testerId, url, selector, mode, steps) {
|
|
|
4363
4391
|
stepTesterSessions.delete(testerId);
|
|
4364
4392
|
});
|
|
4365
4393
|
|
|
4394
|
+
await ctx.exposeFunction('__skopixGetState', async () => {
|
|
4395
|
+
const session = stepTesterSessions.get(testerId);
|
|
4396
|
+
return { currentStep: session?.currentStep || 0, results: session?.results || {} };
|
|
4397
|
+
});
|
|
4398
|
+
|
|
4366
4399
|
// THEN inject toolbar via addInitScript
|
|
4367
4400
|
// PREVIEW MODE — inject steps list toolbar
|
|
4368
4401
|
await ctx.addInitScript((stepsData) => {
|
|
@@ -4456,6 +4489,17 @@ async function startStepTester(testerId, url, selector, mode, steps) {
|
|
|
4456
4489
|
if (window.__skopixStopPreview) window.__skopixStopPreview({});
|
|
4457
4490
|
});
|
|
4458
4491
|
|
|
4492
|
+
// Restore state from server after navigation
|
|
4493
|
+
if (window.__skopixGetState) {
|
|
4494
|
+
window.__skopixGetState({}).then(state => {
|
|
4495
|
+
if (state && state.currentStep > 0) {
|
|
4496
|
+
window.__skopixPreviewCurrentStep = state.currentStep;
|
|
4497
|
+
window.__skopixPreviewResults = state.results || {};
|
|
4498
|
+
renderSteps(state.currentStep, state.results || {});
|
|
4499
|
+
}
|
|
4500
|
+
}).catch(() => {});
|
|
4501
|
+
}
|
|
4502
|
+
|
|
4459
4503
|
window.__skopixUpdatePreview = (index, result, status) => {
|
|
4460
4504
|
window.__skopixPreviewResults[index] = result;
|
|
4461
4505
|
if (result && result.passed) window.__skopixPreviewCurrentStep = index + 1;
|
|
@@ -4578,7 +4622,7 @@ async function startStepTester(testerId, url, selector, mode, steps) {
|
|
|
4578
4622
|
if (url) await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30000 }).catch(() => {});
|
|
4579
4623
|
// Ensure toolbar is injected on first page (DOMContentLoaded may have already fired)
|
|
4580
4624
|
await page.evaluate(() => { if (window.__skopixTesterSelector !== undefined && !document.getElementById('__skopix_tester') && !document.getElementById('__skopix_preview')) { document.dispatchEvent(new Event('DOMContentLoaded')); } }).catch(() => {});
|
|
4581
|
-
stepTesterSessions.set(testerId, { browser, ctx, page });
|
|
4625
|
+
stepTesterSessions.set(testerId, { browser, ctx, page, currentStep: 0, results: {} });
|
|
4582
4626
|
}
|
|
4583
4627
|
|
|
4584
4628
|
async function executeStepTesterAction(page, { selector, action, value }) {
|
package/package.json
CHANGED
package/web/app/index.html
CHANGED
|
@@ -2070,21 +2070,32 @@ body.viewer-mode .saved-test-row { cursor: default !important; }
|
|
|
2070
2070
|
<div style="display:flex;gap:10px;flex-wrap:wrap;align-items:center">
|
|
2071
2071
|
<select class="form-select" id="audit-action-filter" style="padding:8px 12px;font-size:12px;width:auto" onchange="renderAuditLog()">
|
|
2072
2072
|
<option value="">All actions</option>
|
|
2073
|
-
<
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
<
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2073
|
+
<optgroup label="Tests">
|
|
2074
|
+
<option value="test.created">Test created</option>
|
|
2075
|
+
<option value="test.updated">Test updated</option>
|
|
2076
|
+
<option value="test.deleted">Test deleted</option>
|
|
2077
|
+
<option value="test.duplicated">Test duplicated</option>
|
|
2078
|
+
<option value="test.run">Test run</option>
|
|
2079
|
+
</optgroup>
|
|
2080
|
+
<optgroup label="Users">
|
|
2081
|
+
<option value="user.login">Logins</option>
|
|
2082
|
+
<option value="user.logout">Logouts</option>
|
|
2083
|
+
<option value="user.created">User created</option>
|
|
2084
|
+
<option value="user.deleted">User deleted</option>
|
|
2085
|
+
<option value="user.role_changed">Role changed</option>
|
|
2086
|
+
<option value="user.disabled">User disabled</option>
|
|
2087
|
+
<option value="user.enabled">User enabled</option>
|
|
2088
|
+
<option value="user.sessions_revoked">Force logout</option>
|
|
2089
|
+
<option value="user.reset_generated">Password reset generated</option>
|
|
2090
|
+
<option value="user.password_reset">Password reset used</option>
|
|
2091
|
+
<option value="user.password_changed">Password changed</option>
|
|
2092
|
+
<option value="user.secret_set">Secret set</option>
|
|
2093
|
+
<option value="user.secret_deleted">Secret deleted</option>
|
|
2094
|
+
</optgroup>
|
|
2095
|
+
<optgroup label="Invites">
|
|
2096
|
+
<option value="invite.created">Invite created</option>
|
|
2097
|
+
<option value="invite.revoked">Invite revoked</option>
|
|
2098
|
+
</optgroup>
|
|
2088
2099
|
</select>
|
|
2089
2100
|
<span id="audit-count" style="font-family:var(--mono);font-size:11px;color:var(--muted)"></span>
|
|
2090
2101
|
</div>
|
|
@@ -5144,13 +5155,28 @@ function renderAuditLog() {
|
|
|
5144
5155
|
'user.secret_deleted': { label: 'Secret removed', badge: 'background:rgba(255,255,255,0.04);color:var(--muted)' },
|
|
5145
5156
|
'invite.created': { label: 'Invite created', badge: 'background:rgba(0,212,255,0.15);color:var(--cyan)' },
|
|
5146
5157
|
'invite.revoked': { label: 'Invite revoked', badge: 'background:rgba(255,255,255,0.04);color:var(--muted)' },
|
|
5158
|
+
'test.created': { label: 'Test created', badge: 'background:rgba(16,185,129,0.15);color:var(--green)' },
|
|
5159
|
+
'test.updated': { label: 'Test updated', badge: 'background:rgba(0,212,255,0.15);color:var(--cyan)' },
|
|
5160
|
+
'test.deleted': { label: 'Test deleted', badge: 'background:rgba(239,68,68,0.15);color:var(--red)' },
|
|
5161
|
+
'test.duplicated': { label: 'Test duplicated', badge: 'background:rgba(0,212,255,0.15);color:var(--cyan)' },
|
|
5162
|
+
'test.run': { label: 'Test run', badge: 'background:rgba(124,58,237,0.15);color:var(--purple)' },
|
|
5147
5163
|
};
|
|
5148
5164
|
|
|
5149
5165
|
list.innerHTML = _auditEntries.map(e => {
|
|
5150
5166
|
const meta = actionMeta[e.action] || { label: e.action, badge: 'background:rgba(255,255,255,0.04);color:var(--muted)' };
|
|
5151
5167
|
const when = formatDateTime(e.createdAt);
|
|
5152
5168
|
const actor = e.userName ? `${escapeHtml(e.userName)} <span style="color:var(--muted2)">·</span> <span style="color:var(--muted)">${escapeHtml(e.userEmail || '')}</span>` : '<span style="color:var(--muted2)">system</span>';
|
|
5153
|
-
|
|
5169
|
+
// Show metadata nicely — test name, scope etc
|
|
5170
|
+
let metaDisplay = '';
|
|
5171
|
+
if (e.metadata) {
|
|
5172
|
+
const m = e.metadata;
|
|
5173
|
+
const parts = [];
|
|
5174
|
+
if (m.name) parts.push(escapeHtml(m.name));
|
|
5175
|
+
else if (m.testName) parts.push(escapeHtml(m.testName));
|
|
5176
|
+
if (m.scope && m.scope !== 'saved') parts.push(escapeHtml(m.scope));
|
|
5177
|
+
if (m.type) parts.push(escapeHtml(m.type));
|
|
5178
|
+
metaDisplay = parts.length ? `<span style="color:var(--muted);font-size:11px;margin-left:8px">— ${parts.join(' · ')}</span>` : '';
|
|
5179
|
+
}
|
|
5154
5180
|
return `
|
|
5155
5181
|
<div class="saved-test-row" style="cursor:default;align-items:center;padding:14px 22px">
|
|
5156
5182
|
<div style="display:flex;align-items:center;gap:14px;flex:1;min-width:0">
|