skopix 2.0.84 → 2.0.86
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/dashboard.js +38 -14
- package/package.json +1 -1
|
@@ -1036,11 +1036,13 @@ export async function dashboardCommand(options) {
|
|
|
1036
1036
|
const { scope, name, url, steps, playwrightJs, playwrightTs, tags } = JSON.parse(body);
|
|
1037
1037
|
if (!name) { sendJSON(res, 400, { error: 'name is required' }); return; }
|
|
1038
1038
|
try {
|
|
1039
|
-
|
|
1039
|
+
// Match steps against library before saving
|
|
1040
|
+
const matched = await matchStepsToLibrary(suitesDir, steps || []);
|
|
1041
|
+
const testData = { name, type: 'recorded', url: url || '', steps: matched.steps, playwrightJs: playwrightJs || '', playwrightTs: playwrightTs || '', tags: tags || [] };
|
|
1040
1042
|
const result = await createTest(suitesDir, scope || 'saved', testData);
|
|
1041
1043
|
if (teamMode && currentUser) { teamMode.db.logAudit({ userId: currentUser.id, action: 'test.created', targetType: 'test', targetId: result.id, metadata: { scope: scope || 'saved', type: 'recorded' } }); }
|
|
1042
|
-
// Auto-extract steps to
|
|
1043
|
-
extractStepsToLibrary(suitesDir, steps
|
|
1044
|
+
// Auto-extract unmatched steps to pending review
|
|
1045
|
+
extractStepsToLibrary(suitesDir, matched.steps, name).catch(() => {});
|
|
1044
1046
|
sendJSON(res, 200, result);
|
|
1045
1047
|
} catch (err) { sendJSON(res, 400, { error: err.message }); }
|
|
1046
1048
|
return;
|
|
@@ -1575,8 +1577,11 @@ export async function dashboardCommand(options) {
|
|
|
1575
1577
|
const body = await readBody(req);
|
|
1576
1578
|
const data = JSON.parse(body);
|
|
1577
1579
|
try {
|
|
1580
|
+
// Match steps against library before saving
|
|
1581
|
+
const matched = await matchStepsToLibrary(suitesDir, data.steps || []);
|
|
1582
|
+
data.steps = matched.steps;
|
|
1578
1583
|
const result = await updateTest(suitesDir, scope, testId, data);
|
|
1579
|
-
// Extract any new steps to pending review queue
|
|
1584
|
+
// Extract any new unmatched steps to pending review queue
|
|
1580
1585
|
extractStepsToLibrary(suitesDir, data.steps || [], data.name || testId).catch(() => {});
|
|
1581
1586
|
sendJSON(res, 200, result);
|
|
1582
1587
|
} catch (err) {
|
|
@@ -3878,21 +3883,40 @@ async function matchStepsToLibrary(suitesDir, steps) {
|
|
|
3878
3883
|
|
|
3879
3884
|
let count = 0;
|
|
3880
3885
|
const matched = steps.map(step => {
|
|
3881
|
-
const sSel = (step.stableSelector || step.selector || '').toLowerCase();
|
|
3886
|
+
const sSel = (step.stableSelector || step.selector || '').toLowerCase().trim();
|
|
3882
3887
|
if (!sSel) return step;
|
|
3883
3888
|
|
|
3884
|
-
// Find matching library element
|
|
3885
3889
|
const match = library.find(lib => {
|
|
3886
|
-
const lSel = (lib.stableSelector || lib.selector || '').toLowerCase();
|
|
3890
|
+
const lSel = (lib.stableSelector || lib.selector || '').toLowerCase().trim();
|
|
3887
3891
|
if (!lSel) return false;
|
|
3888
|
-
|
|
3892
|
+
|
|
3893
|
+
// 1. Exact match
|
|
3889
3894
|
if (sSel === lSel) return true;
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
const
|
|
3894
|
-
|
|
3895
|
-
|
|
3895
|
+
|
|
3896
|
+
// 2. Extract pi-test-identifier values from both and compare
|
|
3897
|
+
const piFrom = (sSel.match(/pi-test-identifier[*^]?=["']([^"']+)["']/) || [])[1];
|
|
3898
|
+
const piLib = (lSel.match(/pi-test-identifier[*^]?=["']([^"']+)["']/) || [])[1];
|
|
3899
|
+
if (piFrom && piLib) {
|
|
3900
|
+
if (piFrom === piLib) return true;
|
|
3901
|
+
// One starts with the other (handles *= vs =)
|
|
3902
|
+
if (piFrom.startsWith(piLib) || piLib.startsWith(piFrom)) return true;
|
|
3903
|
+
}
|
|
3904
|
+
|
|
3905
|
+
// 3. Both use same has-text value (e.g. :has-text("Mariadb"))
|
|
3906
|
+
const textFrom = (sSel.match(/has-text\(["']([^"']+)["']\)/) || [])[1];
|
|
3907
|
+
const textLib = (lSel.match(/has-text\(["']([^"']+)["']\)/) || [])[1];
|
|
3908
|
+
if (textFrom && textLib && textFrom.toLowerCase() === textLib.toLowerCase()) {
|
|
3909
|
+
// Only match if same element type or one contains the other
|
|
3910
|
+
const tagFrom = sSel.split(':')[0].split(' ').pop();
|
|
3911
|
+
const tagLib = lSel.split(':')[0].split(' ').pop();
|
|
3912
|
+
if (tagFrom === tagLib || sSel.includes(lSel) || lSel.includes(sSel)) return true;
|
|
3913
|
+
}
|
|
3914
|
+
|
|
3915
|
+
// 4. Step selector is a child/descendant of library selector
|
|
3916
|
+
// e.g. "a[title='x'] i.fa-plus" should match "a[title='x']"
|
|
3917
|
+
if (lSel.length > 10 && sSel.includes(lSel)) return true;
|
|
3918
|
+
if (sSel.length > 10 && lSel.includes(sSel)) return true;
|
|
3919
|
+
|
|
3896
3920
|
return false;
|
|
3897
3921
|
});
|
|
3898
3922
|
|
package/package.json
CHANGED