skopix 2.0.20 → 2.0.21

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.
@@ -361,10 +361,11 @@ export async function agentCommand(options) {
361
361
  await page.waitForTimeout(800);
362
362
 
363
363
  } else if (step.action === 'click') {
364
+ await page.waitForTimeout(300);
364
365
  let clicked = false;
365
366
  const selectors = [step.stableSelector, step.selector].filter(Boolean).map(sanitiseSelector);
366
- // Try position-matched click first (for elements with coordinates)
367
- if (step.elementX || step.clickX) {
367
+ // Strategy 1: coordinate-based (best for duplicate elements)
368
+ if (!clicked && (step.elementX || step.clickX)) {
368
369
  const tx = step.elementX || step.clickX, ty = step.elementY || step.clickY;
369
370
  for (const s of selectors) {
370
371
  if (clicked) break;
@@ -375,17 +376,25 @@ export async function agentCommand(options) {
375
376
  for (let i = 0; i < count; i++) { try { const box = await page.locator(s).nth(i).boundingBox({ timeout: 2000 }); if (!box) continue; const d = Math.sqrt(Math.pow(box.x+box.width/2-tx,2)+Math.pow(box.y+box.height/2-ty,2)); if (d < bd) { bd=d; bi=i; } } catch {} }
376
377
  await page.locator(s).nth(bi).click({ timeout: 5000 }); clicked = true;
377
378
  } else if (count === 1) {
379
+ await page.locator(s).first().waitFor({ state: 'visible', timeout: 3000 });
378
380
  await page.locator(s).first().click({ timeout: 5000 }); clicked = true;
379
381
  }
380
382
  } catch {}
381
383
  }
382
384
  }
383
- // Simple click fallback
384
- if (!clicked) { for (const s of selectors) { if (clicked) break; try { await page.locator(s).first().click({ timeout: 5000 }); clicked = true; } catch {} } }
385
- // Force click (for off-screen elements)
385
+ // Strategy 2: normal click
386
+ if (!clicked) { for (const s of selectors) { if (clicked) break; try { await page.locator(s).first().waitFor({ state: 'visible', timeout: 3000 }); await page.locator(s).first().click({ timeout: 5000 }); clicked = true; } catch {} } }
387
+ // Strategy 3: force click
386
388
  if (!clicked) { for (const s of selectors) { if (clicked) break; try { await page.locator(s).first().click({ force: true, timeout: 5000 }); clicked = true; } catch {} } }
389
+ // Strategy 4: click clickable ancestor (for icons/SVGs)
390
+ if (!clicked && step.element) {
391
+ const tag = (step.element.tag || '').toLowerCase();
392
+ if (['i', 'svg', 'path', 'span', 'img'].includes(tag)) {
393
+ for (const s of selectors) { if (clicked) break; try { await page.locator(s).first().locator('xpath=ancestor-or-self::*[self::a or self::button or @role="button"][1]').first().click({ timeout: 5000 }); clicked = true; } catch {} }
394
+ }
395
+ }
387
396
  if (!clicked) throw new Error('Could not click: ' + selectors.join(', '));
388
- await page.waitForTimeout(200);
397
+ await page.waitForTimeout(500);
389
398
 
390
399
  } else if (step.action === 'type') {
391
400
  await page.locator(sel).first().click({ timeout: 5000 });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skopix",
3
- "version": "2.0.20",
3
+ "version": "2.0.21",
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": {