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.
- package/cli/commands/agent.js +2 -0
- package/cli/commands/dashboard.js +61 -10
- package/package.json +1 -1
package/cli/commands/agent.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
3911
|
-
|
|
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 =
|
|
3914
|
-
const
|
|
3915
|
-
|
|
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
|
-
|
|
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