skopix 2.0.20 → 2.0.22

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.
@@ -254,6 +254,14 @@ export async function agentCommand(options) {
254
254
  deviceScaleFactor: 1,
255
255
  ...(wantReport ? { recordVideo: { dir: sessionDir, size: { width: 1280, height: 800 } } } : {}),
256
256
  });
257
+ // Set zoom via initScript so it persists across page loads and route changes
258
+ if (browserZoom !== 1) {
259
+ await ctx.addInitScript((zoom) => {
260
+ const applyZ = () => { if (document.documentElement) document.documentElement.style.zoom = zoom; };
261
+ applyZ();
262
+ document.addEventListener('DOMContentLoaded', applyZ);
263
+ }, String(browserZoom));
264
+ }
257
265
  const page = await ctx.newPage();
258
266
  const applyZoom = async () => {
259
267
  if (browserZoom !== 1) {
@@ -272,6 +280,9 @@ export async function agentCommand(options) {
272
280
  // Apply zoom now if no setup test (otherwise applied at setup→main transition)
273
281
  if (!setupTest) await applyZoom();
274
282
 
283
+ // Listen for future navigations and re-apply zoom
284
+ page.on('load', () => { applyZoom().catch(() => {}); });
285
+
275
286
  send({ type: 'stdout', text: '◆ Replaying ' + allSteps.length + ' steps on ' + os.hostname() });
276
287
 
277
288
  for (const step of allSteps) {
@@ -358,13 +369,15 @@ export async function agentCommand(options) {
358
369
  try { const ro = new URL(navUrl).origin; const to = new URL(test.url).origin; if (ro !== to) navUrl = navUrl.replace(ro, to); } catch {}
359
370
  }
360
371
  await page.goto(navUrl, { waitUntil: 'domcontentloaded', timeout: 15000 });
372
+ await applyZoom();
361
373
  await page.waitForTimeout(800);
362
374
 
363
375
  } else if (step.action === 'click') {
376
+ await page.waitForTimeout(300);
364
377
  let clicked = false;
365
378
  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) {
379
+ // Strategy 1: coordinate-based (best for duplicate elements)
380
+ if (!clicked && (step.elementX || step.clickX)) {
368
381
  const tx = step.elementX || step.clickX, ty = step.elementY || step.clickY;
369
382
  for (const s of selectors) {
370
383
  if (clicked) break;
@@ -375,17 +388,25 @@ export async function agentCommand(options) {
375
388
  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
389
  await page.locator(s).nth(bi).click({ timeout: 5000 }); clicked = true;
377
390
  } else if (count === 1) {
391
+ await page.locator(s).first().waitFor({ state: 'visible', timeout: 3000 });
378
392
  await page.locator(s).first().click({ timeout: 5000 }); clicked = true;
379
393
  }
380
394
  } catch {}
381
395
  }
382
396
  }
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)
397
+ // Strategy 2: normal click
398
+ 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 {} } }
399
+ // Strategy 3: force click
386
400
  if (!clicked) { for (const s of selectors) { if (clicked) break; try { await page.locator(s).first().click({ force: true, timeout: 5000 }); clicked = true; } catch {} } }
401
+ // Strategy 4: click clickable ancestor (for icons/SVGs)
402
+ if (!clicked && step.element) {
403
+ const tag = (step.element.tag || '').toLowerCase();
404
+ if (['i', 'svg', 'path', 'span', 'img'].includes(tag)) {
405
+ 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 {} }
406
+ }
407
+ }
387
408
  if (!clicked) throw new Error('Could not click: ' + selectors.join(', '));
388
- await page.waitForTimeout(200);
409
+ await page.waitForTimeout(500);
389
410
 
390
411
  } else if (step.action === 'type') {
391
412
  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.22",
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": {