skopix 2.0.80 → 2.0.82

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.
@@ -406,6 +406,8 @@ export async function agentCommand(options) {
406
406
  }
407
407
  }
408
408
  if (!clicked) throw new Error('Could not click: ' + selectors.join(', '));
409
+ // Wait for any navigation triggered by the click
410
+ await page.waitForLoadState('domcontentloaded', { timeout: 5000 }).catch(() => {});
409
411
  await page.waitForTimeout(500);
410
412
 
411
413
  } else if (step.action === 'type') {
@@ -3288,6 +3288,7 @@ function startReplay(test, setupTest, activeRuns, reportsDir, currentUser, env)
3288
3288
  if (!clicked) throw new Error('Could not click element. Tried: ' + selectors.join(', '));
3289
3289
  const descLower = (step.description||'').toLowerCase(), selLower = selectors.join(' ').toLowerCase();
3290
3290
  const isSave = descLower.includes('save')||descLower.includes('submit')||selLower.includes('fa-save')||selLower.includes('fa-floppy');
3291
+ await page.waitForLoadState('domcontentloaded', { timeout: 5000 }).catch(() => {});
3291
3292
  await page.waitForTimeout(isSave ? 2000 : 500);
3292
3293
  broadcast({ type: 'stdout', text: ' \u2713 Clicked' + (isSave?' (waited for save)':'') });
3293
3294
 
package/core/llm.js CHANGED
@@ -547,13 +547,15 @@ export async function processRecording({ steps, testName, url, provider, apiKey,
547
547
  + '\n\nYour jobs:\n'
548
548
  + '1. For each step, write a STABLE SELECTOR. Priority order:\n'
549
549
  + ' - data-testid, data-test, data-cy, data-qa, pi-test-identifier attributes use [attr="value"]\n'
550
+ + ' - title attribute — if the element has a title, use a[title="..."] or button[title="..."] — this is VERY reliable\n'
550
551
  + ' - Unique meaningful id (NOT random/generated IDs) use #id\n'
551
552
  + ' - Semantic selector e.g. button:has-text("Login"), input[name="email"]\n'
552
553
  + ' - Role + text e.g. [role="button"]:has-text("Submit")\n'
553
554
  + ' - Class-based selector for well-named classes e.g. .chart-container, .save-btn\n'
554
555
  + ' - Fall back to the original selector if nothing better\n'
555
556
  + ' 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'
557
+ + ' - ONLY use attributes that ACTUALLY EXIST on the element from the captured data. NEVER invent aria-label, title, or other attributes that are not in the element info.\n'
558
+ + ' - For icon elements (<i>, <span> with fa/icon classes): use the PARENT title or pi-test-identifier as the anchor e.g. a[title="Create new chart"] or [pi-test-identifier="x"] i. Check parentTitle and parentTestId fields.\n'
557
559
  + ' - NEVER use IDs that look randomly generated (e.g. #highcharts-abc123-58, #ng-view-1, anything with random hex/numbers)\n'
558
560
  + ' - NEVER use :nth-child or :nth-of-type positional selectors\n'
559
561
  + ' - For chart/visualization containers: use class-based selectors like .highcharts-container, .chart-wrapper, [class*="chart"]\n'
package/core/recorder.js CHANGED
@@ -87,23 +87,32 @@ export class RecordingSession {
87
87
  }
88
88
 
89
89
  function getElementInfo(el) {
90
+ const piTestId = el.getAttribute('pi-test-identifier') || null;
91
+ const title = el.getAttribute('title') || null;
92
+ const ariaLabel = el.getAttribute('aria-label') || null;
93
+ const dataTestId = el.getAttribute('data-testid') || el.getAttribute('data-test-id') || null;
90
94
  const info = {
91
95
  tag: el.tagName.toLowerCase(),
92
96
  id: el.id || null,
93
97
  name: el.name || null,
94
98
  type: el.type || null,
95
- text: (el.innerText || el.value || el.placeholder || el.getAttribute('aria-label') || '').trim().slice(0, 80),
99
+ text: (el.innerText || el.value || el.placeholder || ariaLabel || title || '').trim().slice(0, 80),
96
100
  selector: getSelector(el),
97
101
  classes: el.className ? el.className.toString().trim().slice(0, 100) : null,
102
+ title,
103
+ ariaLabel,
104
+ piTestId,
105
+ dataTestId,
98
106
  };
99
107
  // 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;
108
+ const isIcon = ['i', 'span', 'svg'].includes(info.tag) && !info.id && !info.text && !piTestId;
101
109
  if (isIcon && el.parentElement) {
102
110
  const p = el.parentElement;
103
111
  info.parentTag = p.tagName.toLowerCase();
104
112
  info.parentClasses = p.className ? p.className.toString().trim().slice(0, 100) : null;
105
113
  info.parentSelector = getSelector(p);
106
114
  info.parentAriaLabel = p.getAttribute('aria-label') || null;
115
+ info.parentTitle = p.getAttribute('title') || null;
107
116
  info.parentTestId = p.getAttribute('pi-test-identifier') || p.getAttribute('data-testid') || null;
108
117
  }
109
118
  return info;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skopix",
3
- "version": "2.0.80",
3
+ "version": "2.0.82",
4
4
  "description": "Browser-based QA tool — record tests by using your app, replay them deterministically, generate Playwright code automatically",
5
5
  "main": "cli/index.js",
6
6
  "bin": {