skopix 2.0.31 → 2.0.32

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.
@@ -1800,14 +1800,14 @@ export async function dashboardCommand(options) {
1800
1800
  return;
1801
1801
  }
1802
1802
  if (pathname === '/api/step-library' && method === 'POST') {
1803
- const data = await readBody(req);
1803
+ const data = JSON.parse(await readBody(req));
1804
1804
  const step = await addLibraryStep(suitesDir, data);
1805
1805
  sendJSON(res, 200, step);
1806
1806
  return;
1807
1807
  }
1808
1808
  if (pathname.match(/^\/api\/step-library\/[^/]+$/) && method === 'PUT') {
1809
1809
  const id = decodeURIComponent(pathname.split('/')[3]);
1810
- const data = await readBody(req);
1810
+ const data = JSON.parse(await readBody(req));
1811
1811
  const step = await updateLibraryStep(suitesDir, id, data);
1812
1812
  if (!step) sendJSON(res, 404, { error: 'Not found' });
1813
1813
  else sendJSON(res, 200, step);
@@ -3650,11 +3650,12 @@ async function addLibraryStep(suitesDir, stepData) {
3650
3650
  const id = 'lib-' + Math.random().toString(36).slice(2, 10);
3651
3651
  const step = {
3652
3652
  id,
3653
- name: stepData.name || stepData.description || 'Unnamed step',
3654
- action: stepData.action,
3653
+ name: stepData.name || stepData.description || 'Unnamed element',
3655
3654
  selector: stepData.stableSelector || stepData.selector || null,
3656
3655
  stableSelector: stepData.stableSelector || stepData.selector || null,
3657
- value: stepData.value || null,
3656
+ // Store default action as hint but element is primary
3657
+ defaultAction: stepData.action || stepData.defaultAction || null,
3658
+ defaultValue: stepData.value || stepData.defaultValue || null,
3658
3659
  assertType: stepData.assertType || null,
3659
3660
  attribute: stepData.attribute || null,
3660
3661
  description: stepData.description || null,
@@ -3682,53 +3683,50 @@ async function deleteLibraryStep(suitesDir, id) {
3682
3683
  await saveLibrarySteps(filtered);
3683
3684
  }
3684
3685
 
3685
- // Similarity check — returns 0-1 score
3686
+ // Similarity check — element-based, matches on selector only
3686
3687
  function stepSimilarity(a, b) {
3687
- if (a.action !== b.action) return 0;
3688
3688
  const selA = (a.stableSelector || a.selector || '').toLowerCase();
3689
3689
  const selB = (b.stableSelector || b.selector || '').toLowerCase();
3690
- if (selA && selB) {
3691
- if (selA === selB) return 1;
3692
- // Check if one contains the other
3693
- if (selA.includes(selB) || selB.includes(selA)) return 0.85;
3694
- // Check shared tokens
3695
- const tokA = selA.split(/[\s>+~.,#\[\]()=]/).filter(Boolean);
3696
- const tokB = selB.split(/[\s>+~.,#\[\]()=]/).filter(Boolean);
3697
- const shared = tokA.filter(t => tokB.includes(t) && t.length > 2);
3698
- if (shared.length > 0) return Math.min(0.8, shared.length / Math.max(tokA.length, tokB.length));
3699
- }
3700
- const descA = (a.description || '').toLowerCase();
3701
- const descB = (b.description || '').toLowerCase();
3702
- if (descA && descB && descA === descB) return 0.9;
3690
+ if (!selA || !selB) return 0;
3691
+ if (selA === selB) return 1;
3692
+ if (selA.includes(selB) || selB.includes(selA)) return 0.85;
3693
+ const tokA = selA.split(/[\s>+~.,#\[\]()=]/).filter(Boolean);
3694
+ const tokB = selB.split(/[\s>+~.,#\[\]()=]/).filter(Boolean);
3695
+ const shared = tokA.filter(t => tokB.includes(t) && t.length > 2);
3696
+ if (shared.length > 0) return Math.min(0.8, shared.length / Math.max(tokA.length, tokB.length));
3703
3697
  return 0;
3704
3698
  }
3705
3699
 
3706
- // Extract unique steps from a test and add to library (skip duplicates)
3700
+ // Extract unique elements from a test and add to library (skip duplicates)
3707
3701
  async function extractStepsToLibrary(suitesDir, steps, testName) {
3708
3702
  if (!steps || steps.length === 0) return;
3709
3703
  const existing = await listLibrarySteps(suitesDir);
3710
3704
  const toAdd = [];
3705
+ const seenSelectors = new Set(existing.map(e => (e.stableSelector || e.selector || '').toLowerCase()));
3711
3706
 
3712
3707
  for (const step of steps) {
3713
- if (!step.action || step.action === 'navigate') continue;
3714
- // Skip steps with no selector (e.g. scroll)
3708
+ if (!step.action || step.action === 'navigate' || step.action === 'scroll') continue;
3715
3709
  const sel = step.stableSelector || step.selector;
3716
- if (!sel && step.action !== 'type') continue;
3717
- // Check if similar step already exists
3718
- const similar = existing.find(e => stepSimilarity(e, step) >= 0.9);
3719
- if (similar) {
3720
- // Increment usage count
3721
- similar.usageCount = (similar.usageCount || 0) + 1;
3710
+ if (!sel) continue;
3711
+ const selLower = sel.toLowerCase();
3712
+ // Skip click steps if we already have this selector from a type step (deduplicate click/type pairs)
3713
+ if (seenSelectors.has(selLower)) {
3714
+ const existing2 = existing.find(e => (e.stableSelector||e.selector||'').toLowerCase() === selLower) ||
3715
+ toAdd.find(e => (e.stableSelector||e.selector||'').toLowerCase() === selLower);
3716
+ if (existing2) { existing2.usageCount = (existing2.usageCount || 0) + 1; }
3722
3717
  continue;
3723
3718
  }
3724
- // New unique step
3719
+ seenSelectors.add(selLower);
3720
+ // Generate clean name — strip action prefix
3721
+ let name = step.description || '';
3722
+ name = name.replace(/^(click|type|check|assert|select)\s+/i, '').trim() || sel.slice(0, 50);
3725
3723
  toAdd.push({
3726
3724
  id: 'lib-' + Math.random().toString(36).slice(2, 10),
3727
- name: step.description || (step.action + ' ' + (sel || '').slice(0, 40)),
3728
- action: step.action,
3729
- selector: sel || null,
3730
- stableSelector: step.stableSelector || sel || null,
3731
- value: step.value || null,
3725
+ name,
3726
+ selector: sel,
3727
+ stableSelector: step.stableSelector || sel,
3728
+ defaultAction: step.action,
3729
+ defaultValue: step.value || null,
3732
3730
  assertType: step.assertType || null,
3733
3731
  attribute: step.attribute || null,
3734
3732
  description: step.description || null,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skopix",
3
- "version": "2.0.31",
3
+ "version": "2.0.32",
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": {