skopix 2.0.3 → 2.0.5
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/agent.js +9 -1
- package/cli/commands/dashboard.js +5 -1
- package/core/llm.js +1 -1
- package/package.json +1 -1
- package/web/app/index.html +2 -1
package/cli/commands/agent.js
CHANGED
|
@@ -383,7 +383,15 @@ export async function agentCommand(options) {
|
|
|
383
383
|
} else if (step.action === 'assert') {
|
|
384
384
|
const assertSel = sanitiseSelector(step.stableSelector || step.selector);
|
|
385
385
|
switch (step.assertType) {
|
|
386
|
-
case 'visible':
|
|
386
|
+
case 'visible': {
|
|
387
|
+
try {
|
|
388
|
+
await page.locator(assertSel).first().waitFor({ state: 'visible', timeout: 10000 });
|
|
389
|
+
} catch {
|
|
390
|
+
// Fall back to checking element exists in DOM (some elements are technically hidden but visually present via opacity/transform)
|
|
391
|
+
await page.locator(assertSel).first().waitFor({ state: 'attached', timeout: 5000 });
|
|
392
|
+
}
|
|
393
|
+
break;
|
|
394
|
+
}
|
|
387
395
|
case 'text_contains': { const txt = await page.locator(assertSel).first().textContent({ timeout: 10000 }); if (!txt || !txt.includes(step.value || '')) throw new Error('Expected to contain "' + step.value + '"'); break; }
|
|
388
396
|
case 'text_equals': { const txt = await page.locator(assertSel).first().textContent({ timeout: 10000 }); if ((txt || '').trim() !== (step.value || '').trim()) throw new Error('Expected "' + step.value + '"'); break; }
|
|
389
397
|
case 'url_contains': if (!page.url().includes(step.value || '')) throw new Error('URL does not contain "' + step.value + '"'); break;
|
|
@@ -2898,7 +2898,11 @@ function startReplay(test, setupTest, activeRuns, reportsDir, currentUser, env)
|
|
|
2898
2898
|
const assertSel = sanitiseSelector(step.stableSelector || step.selector);
|
|
2899
2899
|
switch (step.assertType) {
|
|
2900
2900
|
case 'visible':
|
|
2901
|
-
|
|
2901
|
+
try {
|
|
2902
|
+
await page.locator(assertSel).first().waitFor({ state: 'visible', timeout: 10000 });
|
|
2903
|
+
} catch {
|
|
2904
|
+
await page.locator(assertSel).first().waitFor({ state: 'attached', timeout: 5000 });
|
|
2905
|
+
}
|
|
2902
2906
|
broadcast({ type: 'stdout', text: ' \u2713 Visible: ' + assertSel });
|
|
2903
2907
|
break;
|
|
2904
2908
|
case 'text_contains': {
|
package/core/llm.js
CHANGED
|
@@ -581,7 +581,7 @@ export async function processRecording({ steps, testName, url, provider, apiKey,
|
|
|
581
581
|
+ ' - For scroll actions: if selector is window use await page.evaluate(() => window.scrollTo(x, y)), otherwise use document.querySelector(sel).scrollTo(x, y)\n'
|
|
582
582
|
+ ' - Add await page.waitForLoadState("networkidle") after navigation actions\n'
|
|
583
583
|
+ ' - For assert steps: generate correct Playwright expect() calls:\n'
|
|
584
|
-
+ ' visible -> await expect(page.locator(selector)).
|
|
584
|
+
+ ' visible -> await expect(page.locator(selector)).toBeAttached(); // use toBeAttached not toBeVisible — elements may be in DOM but hidden via CSS\n'
|
|
585
585
|
+ ' text_contains -> await expect(page.locator(selector)).toContainText("value");\n'
|
|
586
586
|
+ ' text_equals -> await expect(page.locator(selector)).toHaveText("value");\n'
|
|
587
587
|
+ ' url_contains -> await expect(page).toHaveURL(/value/);\n'
|
package/package.json
CHANGED
package/web/app/index.html
CHANGED
|
@@ -5535,9 +5535,10 @@ function renderReSteps() {
|
|
|
5535
5535
|
+ '<button class="btn-icon" style="width:22px;height:22px;font-size:11px;color:#ef4444" title="Delete step" onclick="reStepDelete(' + i + ')">✕</button>'
|
|
5536
5536
|
+ '</div></div>'
|
|
5537
5537
|
+ (isAssert
|
|
5538
|
-
? '<div style="display:grid;grid-template-columns:1fr' + (s.assertType !== 'visible' ? ' 1fr' : '') + ';gap:6px">'
|
|
5538
|
+
? '<div style="display:grid;grid-template-columns:1fr' + (s.assertType !== 'visible' ? ' 1fr' : '') + (s.assertType === 'attribute_contains' ? ' 1fr' : '') + ';gap:6px">'
|
|
5539
5539
|
+ '<div><div style="font-family:var(--mono);font-size:9px;color:var(--muted);margin-bottom:3px">SELECTOR (assertType: ' + escapeHtml(s.assertType||'') + ')</div>'
|
|
5540
5540
|
+ '<input class="form-input" type="text" value="' + sel + '" placeholder="CSS selector..." style="padding:4px 8px;font-size:11px;height:auto;font-family:var(--mono)" onchange="reEditorState.steps[' + i + '].stableSelector=this.value;reEditorState.steps[' + i + '].selector=this.value"></div>'
|
|
5541
|
+
+ (s.assertType === 'attribute_contains' ? '<div><div style="font-family:var(--mono);font-size:9px;color:var(--muted);margin-bottom:3px">ATTRIBUTE</div><input class="form-input" type="text" value="' + escapeHtml(s.attribute||'title') + '" placeholder="e.g. fill, style, aria-label" style="padding:4px 8px;font-size:11px;height:auto;font-family:var(--mono)" onchange="reEditorState.steps[' + i + '].attribute=this.value"></div>' : '')
|
|
5541
5542
|
+ (s.assertType !== 'visible' ? '<div><div style="font-family:var(--mono);font-size:9px;color:var(--muted);margin-bottom:3px">EXPECTED</div><input class="form-input" type="text" value="' + val + '" style="padding:4px 8px;font-size:11px;height:auto" onchange="reEditorState.steps[' + i + '].value=this.value"></div>' : '')
|
|
5542
5543
|
+ '</div>'
|
|
5543
5544
|
: '<div style="display:grid;grid-template-columns:1fr' + (s.action === 'type' || s.action === 'select' ? ' 1fr' : '') + ';gap:6px">'
|