skopix 2.0.79 → 2.0.81

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
 
@@ -3901,28 +3902,78 @@ async function saveSavedUrls(urls) {
3901
3902
  }
3902
3903
  // Internal helper — sync selectors across all tests
3903
3904
  async function syncSelectorsInternal(suitesDir, fromSelectors, toSelector, toName) {
3904
- const allTests = await listAllTests(suitesDir);
3905
+ const files = await fs.readdir(suitesDir);
3906
+ const suiteFiles = files.filter(f => f.endsWith('.suite.yaml') || f.endsWith('.suite.yml'));
3905
3907
  let updated = 0;
3906
3908
  const fromLower = fromSelectors.map(s => s.toLowerCase());
3907
3909
  const allFromTokens = fromLower.flatMap(sel =>
3908
3910
  (sel.match(/\.[a-z][a-z0-9_-]+|\[[\w-]+=["'][^"']+["']\]|#[a-z][a-z0-9_-]+/g) || []).filter(t => t.length > 5)
3909
3911
  );
3910
- for (const test of allTests) {
3911
- if (!test.steps || !test.steps.length) continue;
3912
+
3913
+ function matchesSel(sSel) {
3914
+ if (!sSel) return false;
3915
+ const sLower = sSel.toLowerCase();
3916
+ if (fromLower.includes(sLower)) return true;
3917
+ if (allFromTokens.some(tok => sLower.includes(tok))) return true;
3918
+ return fromLower.some(fSel => stepSimilarity({ selector: sLower }, { selector: fSel }) >= 0.7);
3919
+ }
3920
+
3921
+ function syncSteps(steps) {
3922
+ if (!Array.isArray(steps)) return { steps, changed: false };
3912
3923
  let changed = false;
3913
- const newSteps = test.steps.map(step => {
3914
- const sSel = (step.stableSelector||step.selector||'').toLowerCase();
3915
- const isMatch = fromLower.includes(sSel) ||
3916
- allFromTokens.some(tok => sSel.includes(tok)) ||
3917
- fromLower.some(fSel => stepSimilarity({ selector: sSel }, { selector: fSel }) >= 0.7);
3918
- if (isMatch && sSel) {
3924
+ const newSteps = steps.map(step => {
3925
+ const sel = step.stableSelector || step.selector || '';
3926
+ if (matchesSel(sel)) {
3919
3927
  changed = true;
3920
3928
  updated++;
3921
3929
  return { ...step, stableSelector: toSelector, selector: toSelector, description: toName || step.description };
3922
3930
  }
3923
3931
  return step;
3924
3932
  });
3925
- if (changed) await updateTest(suitesDir, test.scope, test.id, { ...test, steps: newSteps });
3933
+ return { steps: newSteps, changed };
3934
+ }
3935
+
3936
+ for (const file of suiteFiles) {
3937
+ const filePath = path.join(suitesDir, file);
3938
+ try {
3939
+ const content = await fs.readFile(filePath, 'utf8');
3940
+ const data = yaml.parse(content);
3941
+ if (!data || !Array.isArray(data.tests)) continue;
3942
+ let fileChanged = false;
3943
+
3944
+ data.tests = data.tests.map(test => {
3945
+ let testChanged = false;
3946
+ // Sync structured steps
3947
+ if (Array.isArray(test.steps) && test.steps.length > 0) {
3948
+ const { steps: newSteps, changed } = syncSteps(test.steps);
3949
+ if (changed) { test = { ...test, steps: newSteps }; testChanged = true; }
3950
+ }
3951
+ // Sync playwrightJs code string
3952
+ if (test.playwrightJs && typeof test.playwrightJs === 'string') {
3953
+ let newJs = test.playwrightJs;
3954
+ for (const oldSel of fromSelectors) {
3955
+ if (newJs.includes(oldSel)) {
3956
+ newJs = newJs.split('"' + oldSel + '"').join('"' + toSelector + '"');
3957
+ newJs = newJs.split("'" + oldSel + "'").join("'" + toSelector + "'");
3958
+ // Update comment descriptions
3959
+ if (toName) {
3960
+ fromSelectors.forEach(s => {}); // placeholder — descriptions in comments are harder to match
3961
+ }
3962
+ testChanged = true;
3963
+ }
3964
+ }
3965
+ if (testChanged) test = { ...test, playwrightJs: newJs };
3966
+ }
3967
+ if (testChanged) fileChanged = true;
3968
+ return test;
3969
+ });
3970
+
3971
+ if (fileChanged) {
3972
+ await fs.writeFile(filePath, yaml.stringify(data));
3973
+ }
3974
+ } catch (err) {
3975
+ process.stderr.write('[sync] error processing ' + file + ': ' + err.message + '\n');
3976
+ }
3926
3977
  }
3927
3978
  return updated;
3928
3979
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skopix",
3
- "version": "2.0.79",
3
+ "version": "2.0.81",
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": {