skopix 2.0.16 → 2.0.18
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 +25 -0
- package/core/llm.js +1 -0
- package/core/recorder.js +12 -1
- package/package.json +1 -1
package/cli/commands/agent.js
CHANGED
|
@@ -384,7 +384,32 @@ export async function agentCommand(options) {
|
|
|
384
384
|
}
|
|
385
385
|
if (!clicked) { for (const s of selectors) { if (clicked) break; try { await page.locator(s).first().scrollIntoViewIfNeeded({ timeout: 3000 }).catch(() => {}); await page.locator(s).first().click({ timeout: 5000 }); clicked = true; } catch {} } }
|
|
386
386
|
if (!clicked) { for (const s of selectors) { if (clicked) break; try { await page.locator(s).first().click({ force: true, timeout: 5000 }); clicked = true; } catch {} } }
|
|
387
|
+
if (!clicked) {
|
|
388
|
+
// Last resort — JS dispatch for framework-managed elements
|
|
389
|
+
for (const s of selectors) {
|
|
390
|
+
if (clicked) break;
|
|
391
|
+
try {
|
|
392
|
+
await page.locator(s).first().evaluate(el => {
|
|
393
|
+
el.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, cancelable: true }));
|
|
394
|
+
el.dispatchEvent(new MouseEvent('mouseup', { bubbles: true, cancelable: true }));
|
|
395
|
+
el.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }));
|
|
396
|
+
});
|
|
397
|
+
clicked = true;
|
|
398
|
+
} catch {}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
387
401
|
if (!clicked) throw new Error('Could not click: ' + selectors.join(', '));
|
|
402
|
+
|
|
403
|
+
// Special handling for Bootstrap dropdowns — wait for menu to appear
|
|
404
|
+
try {
|
|
405
|
+
const el = await page.locator(selectors[0]).first();
|
|
406
|
+
const isDropdown = await el.evaluate(e => e.hasAttribute('data-toggle') && e.getAttribute('data-toggle') === 'dropdown').catch(() => false);
|
|
407
|
+
if (isDropdown) {
|
|
408
|
+
await page.waitForSelector('.dropdown-menu:visible, .dropdown.open .dropdown-menu', { timeout: 3000 }).catch(() => {});
|
|
409
|
+
await page.waitForTimeout(300);
|
|
410
|
+
}
|
|
411
|
+
} catch {}
|
|
412
|
+
|
|
388
413
|
await page.waitForTimeout(400);
|
|
389
414
|
|
|
390
415
|
} else if (step.action === 'type') {
|
package/core/llm.js
CHANGED
|
@@ -553,6 +553,7 @@ export async function processRecording({ steps, testName, url, provider, apiKey,
|
|
|
553
553
|
+ ' - Class-based selector for well-named classes e.g. .chart-container, .save-btn\n'
|
|
554
554
|
+ ' - Fall back to the original selector if nothing better\n'
|
|
555
555
|
+ ' CRITICAL RULES FOR SELECTORS:\n'
|
|
556
|
+
+ ' - For icon elements (<i>, <span> with fa/icon classes): use the parent element as the anchor if it has a meaningful class/attribute, e.g. .chart-panel__panel-heading__panel-button i, [aria-label="Options"] i. NEVER generate a bare i.fa-something if there are multiple on the page — always scope it to its parent container\n'
|
|
556
557
|
+ ' - NEVER use IDs that look randomly generated (e.g. #highcharts-abc123-58, #ng-view-1, anything with random hex/numbers)\n'
|
|
557
558
|
+ ' - NEVER use :nth-child or :nth-of-type positional selectors\n'
|
|
558
559
|
+ ' - For chart/visualization containers: use class-based selectors like .highcharts-container, .chart-wrapper, [class*="chart"]\n'
|
package/core/recorder.js
CHANGED
|
@@ -87,7 +87,7 @@ export class RecordingSession {
|
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
function getElementInfo(el) {
|
|
90
|
-
|
|
90
|
+
const info = {
|
|
91
91
|
tag: el.tagName.toLowerCase(),
|
|
92
92
|
id: el.id || null,
|
|
93
93
|
name: el.name || null,
|
|
@@ -96,6 +96,17 @@ export class RecordingSession {
|
|
|
96
96
|
selector: getSelector(el),
|
|
97
97
|
classes: el.className ? el.className.toString().trim().slice(0, 100) : null,
|
|
98
98
|
};
|
|
99
|
+
// For icon elements with no meaningful ID/text, capture parent context for better selector generation
|
|
100
|
+
const isIcon = ['i', 'span', 'svg'].includes(info.tag) && !info.id && !info.text;
|
|
101
|
+
if (isIcon && el.parentElement) {
|
|
102
|
+
const p = el.parentElement;
|
|
103
|
+
info.parentTag = p.tagName.toLowerCase();
|
|
104
|
+
info.parentClasses = p.className ? p.className.toString().trim().slice(0, 100) : null;
|
|
105
|
+
info.parentSelector = getSelector(p);
|
|
106
|
+
info.parentAriaLabel = p.getAttribute('aria-label') || null;
|
|
107
|
+
info.parentTestId = p.getAttribute('pi-test-identifier') || p.getAttribute('data-testid') || null;
|
|
108
|
+
}
|
|
109
|
+
return info;
|
|
99
110
|
}
|
|
100
111
|
|
|
101
112
|
// ─── Action listeners ─────────────────────────────────────────────────
|
package/package.json
CHANGED