skopix 2.0.78 → 2.0.80

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