@ia-qa/self-healing 1.5.8 → 1.6.2

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.
Files changed (49) hide show
  1. package/README.md +80 -6
  2. package/dist/checks.d.ts +152 -0
  3. package/dist/checks.js +0 -0
  4. package/dist/checks.js.map +1 -0
  5. package/dist/cli/args.js +2 -0
  6. package/dist/cli/args.js.map +1 -1
  7. package/dist/cli/audit.d.ts +63 -0
  8. package/dist/cli/audit.js +333 -0
  9. package/dist/cli/audit.js.map +1 -0
  10. package/dist/cli/check.d.ts +1 -0
  11. package/dist/cli/check.js +334 -0
  12. package/dist/cli/check.js.map +1 -0
  13. package/dist/cli/diff.d.ts +28 -1
  14. package/dist/cli/diff.js +112 -17
  15. package/dist/cli/diff.js.map +1 -1
  16. package/dist/cli/fix.js +53 -7
  17. package/dist/cli/fix.js.map +1 -1
  18. package/dist/cli/index.js +30 -3
  19. package/dist/cli/index.js.map +1 -1
  20. package/dist/cli/ingest.js +26 -9
  21. package/dist/cli/ingest.js.map +1 -1
  22. package/dist/cli/run.js +11 -3
  23. package/dist/cli/run.js.map +1 -1
  24. package/dist/cli/summary.d.ts +13 -0
  25. package/dist/cli/summary.js +18 -0
  26. package/dist/cli/summary.js.map +1 -1
  27. package/dist/cli-ai/index.js +13 -1
  28. package/dist/cli-ai/index.js.map +1 -1
  29. package/dist/config.d.ts +7 -0
  30. package/dist/config.js +15 -0
  31. package/dist/config.js.map +1 -1
  32. package/dist/fixEngine.d.ts +41 -2
  33. package/dist/fixEngine.js +65 -5
  34. package/dist/fixEngine.js.map +1 -1
  35. package/dist/htmlReport.d.ts +19 -0
  36. package/dist/htmlReport.js +86 -9
  37. package/dist/htmlReport.js.map +1 -1
  38. package/dist/ingest.d.ts +15 -1
  39. package/dist/ingest.js +0 -0
  40. package/dist/ingest.js.map +1 -1
  41. package/dist/mcp/server.js +22 -1
  42. package/dist/mcp/server.js.map +1 -1
  43. package/dist/nameDrift.d.ts +85 -0
  44. package/dist/nameDrift.js +177 -0
  45. package/dist/nameDrift.js.map +1 -0
  46. package/dist/nameLocators.d.ts +103 -0
  47. package/dist/nameLocators.js +235 -0
  48. package/dist/nameLocators.js.map +1 -0
  49. package/package.json +1 -1
@@ -0,0 +1,177 @@
1
+ "use strict";
2
+ /**
3
+ * Name drift — the rename your suite actually depends on.
4
+ *
5
+ * `diffMappings` classifies a relabelled element as `renamed`: the selector still
6
+ * resolves, the click still lands on the right thing, so the verdict stays PASS
7
+ * and a human is asked to eyeball the copy change. That reading is correct for a
8
+ * suite written in CSS. It is exactly wrong for one written with
9
+ * `getByRole('button', { name: 'Save' })`, where the label *is* the locator: the
10
+ * gate goes green while the suite goes red.
11
+ *
12
+ * This module is the arbiter between the two readings, and it needs one fact the
13
+ * contract cannot hold — what the tests reference. That fact comes from
14
+ * `.ia-qa/usage.json` (`ia-qa-heal ingest`), read from disk, never from the
15
+ * network.
16
+ *
17
+ * It deliberately lives **outside** `src/browser/match.js`. That file is shipped
18
+ * twice (npm + the zero-install web flow) and must stay dependency-free, so the
19
+ * classification it computes is left untouched and this pass only *escalates* on
20
+ * top of it. The web tool, which has no usage.json, keeps behaving exactly as
21
+ * before.
22
+ */
23
+ Object.defineProperty(exports, "__esModule", { value: true });
24
+ exports.REASON_LABEL = void 0;
25
+ exports.assessNameDrift = assessNameDrift;
26
+ exports.escalateVerdict = escalateVerdict;
27
+ exports.formatFinding = formatFinding;
28
+ exports.mergeFindings = mergeFindings;
29
+ const nameLocators_1 = require("./nameLocators");
30
+ const norm = nameLocators_1.normalizeName;
31
+ /**
32
+ * Which recorded test occurrences could be pointing at this element?
33
+ *
34
+ * A `getByRole('button', …)` site only speaks about buttons; `getByText` and
35
+ * `cy.contains` constrain nothing, so they are kept for any role. Filtering here
36
+ * rather than at scan time keeps the inventory framework-neutral.
37
+ */
38
+ function sitesFor(usage, name, role) {
39
+ const all = usage.names?.[norm(name)] ?? [];
40
+ return all.filter((s) => {
41
+ const roles = (0, nameLocators_1.candidateRoles)(s);
42
+ return roles === null || roles.indexOf(role) !== -1;
43
+ });
44
+ }
45
+ /**
46
+ * Assess every `renamed` row against what the suite references.
47
+ *
48
+ * Returns one finding per rename the tests actually depend on. A rename nobody
49
+ * tests produces nothing — which is why adopting this costs no new noise.
50
+ */
51
+ function assessNameDrift(rows, afterElements, usage) {
52
+ if (!usage || !usage.names)
53
+ return [];
54
+ const findings = [];
55
+ for (const row of rows) {
56
+ if (row.status !== 'renamed' || !row.newName || !row.role)
57
+ continue;
58
+ const oldName = row.name ?? '';
59
+ if (oldName === '')
60
+ continue;
61
+ const sites = sitesFor(usage, oldName, row.role);
62
+ if (sites.length === 0)
63
+ continue;
64
+ // A site is rewritable only when its call constrains the role and that role
65
+ // is the drifted element's. Everything else names a bare string, and no
66
+ // contract can say which node wears it.
67
+ const rewritable = sites.filter((s) => {
68
+ const roles = (0, nameLocators_1.candidateRoles)(s);
69
+ return roles !== null && roles.indexOf(row.role) !== -1;
70
+ });
71
+ const unattributable = sites.filter((s) => rewritable.indexOf(s) === -1);
72
+ // Does the OLD label still exist on something these calls could reach? Then
73
+ // the locator has not stopped matching — it has started matching something
74
+ // *else*. Rewriting it would silently retarget a passing test, so this is
75
+ // the name-space twin of `rebound` and it belongs to a human.
76
+ //
77
+ // The role set is the one the CALLS can reach, not the drifted element's
78
+ // alone: `getByLabel` resolves against any form control, so a renamed
79
+ // checkbox does not license rewriting a label a textbox still carries.
80
+ const reachable = new Set([row.role]);
81
+ for (const s of rewritable)
82
+ for (const r of (0, nameLocators_1.candidateRoles)(s) ?? [])
83
+ reachable.add(r);
84
+ const shadowed = afterElements.some((e) => reachable.has(e.role) && norm(e.name) === norm(oldName));
85
+ // Symmetrically: the NEW label must be unique among the roles the calls can
86
+ // reach, not merely among the drifted element's own role. Scoping this to
87
+ // `row.role` would let a renamed checkbox emit `getByLabel('Email')` while a
88
+ // textbox already answers to it — a locator matching two elements, which is
89
+ // a Playwright strict-mode failure rather than a heal.
90
+ const targets = afterElements.filter((e) => reachable.has(e.role) && norm(e.name) === norm(row.newName));
91
+ const reason = shadowed
92
+ ? 'shadowed'
93
+ : targets.length === 0
94
+ ? 'no-target'
95
+ : targets.length > 1
96
+ ? 'ambiguous-target'
97
+ : rewritable.length === 0
98
+ ? 'text-anchored'
99
+ : 'fixable';
100
+ findings.push({
101
+ role: row.role,
102
+ oldName,
103
+ newName: row.newName,
104
+ selector: row.selector ?? '',
105
+ sites,
106
+ rewritable,
107
+ unattributable,
108
+ fixable: reason === 'fixable',
109
+ reason,
110
+ });
111
+ }
112
+ return findings;
113
+ }
114
+ /**
115
+ * Raise a verdict to account for name drift.
116
+ *
117
+ * A rename the suite depends on and that we can rewrite is a FIX — same class as
118
+ * a healable selector. One we cannot rewrite without guessing is a BLOCK, for the
119
+ * same reason `ambiguous` is: a FIX exits 0, and exiting 0 on a suite that is
120
+ * about to fail is the bug this whole module exists to close. A verdict is only
121
+ * ever raised, never lowered.
122
+ */
123
+ function escalateVerdict(current, findings) {
124
+ if (findings.length === 0)
125
+ return current;
126
+ if (current === 'BLOCK')
127
+ return 'BLOCK';
128
+ // An occurrence we refuse to attribute is not a smaller problem than one we
129
+ // cannot resolve — in both cases a human has to look, and FIX exits 0.
130
+ if (findings.some((f) => !f.fixable || f.unattributable.length > 0))
131
+ return 'BLOCK';
132
+ return current === 'PASS' ? 'FIX' : current;
133
+ }
134
+ exports.REASON_LABEL = {
135
+ fixable: 'rewritable — one element carries the new label',
136
+ shadowed: 'the old label still exists on another element: your locator now targets it',
137
+ 'ambiguous-target': 'several elements carry the new label — pick one by hand',
138
+ 'no-target': 'the new label matches no element in this contract',
139
+ 'text-anchored': 'only text-anchored references — the contract cannot say what they target',
140
+ };
141
+ const sitesLine = (sites) => {
142
+ const where = sites.slice(0, 3).map((s) => `${s.file}:${s.line}`).join(', ');
143
+ return where + (sites.length > 3 ? ` +${sites.length - 3} more` : '');
144
+ };
145
+ /** One block per finding, shared by `diff`, `fix` and `run`. */
146
+ function formatFinding(f) {
147
+ const mark = f.fixable && f.unattributable.length === 0 ? '✏️ ' : '⛔';
148
+ const lines = [` ${mark} ${f.role} "${f.oldName}" → "${f.newName}"`];
149
+ if (f.rewritable.length > 0) {
150
+ lines.push(` ${f.rewritable.length} role-anchored reference${f.rewritable.length === 1 ? '' : 's'}` +
151
+ `${f.fixable ? ' — rewritten' : ''}: ${sitesLine(f.rewritable)}`);
152
+ }
153
+ if (f.unattributable.length > 0) {
154
+ lines.push(` ${f.unattributable.length} text-anchored reference${f.unattributable.length === 1 ? '' : 's'}` +
155
+ ` — NOT rewritten, confirm by hand: ${sitesLine(f.unattributable)}`);
156
+ lines.push(` (getByText/contains name a string, not an element; the contract maps interactive`);
157
+ lines.push(` elements only, so it cannot prove they meant this one.)`);
158
+ }
159
+ lines.push(` ${exports.REASON_LABEL[f.reason]}`);
160
+ return lines.join('\n');
161
+ }
162
+ /** De-dupe findings across pages: a shared header renames once, not 40 times. */
163
+ function mergeFindings(groups) {
164
+ const byKey = new Map();
165
+ for (const group of groups) {
166
+ for (const f of group) {
167
+ const key = `${f.role}${norm(f.oldName)}${norm(f.newName)}`;
168
+ const existing = byKey.get(key);
169
+ // Keep the strictest reading: one page proving the rewrite unsafe is enough.
170
+ const safety = (x) => (x.fixable ? 1 : 0) + (x.unattributable.length === 0 ? 1 : 0);
171
+ if (!existing || safety(f) < safety(existing))
172
+ byKey.set(key, f);
173
+ }
174
+ }
175
+ return Array.from(byKey.values());
176
+ }
177
+ //# sourceMappingURL=nameDrift.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nameDrift.js","sourceRoot":"","sources":["../src/nameDrift.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Static inventory of **name-anchored** locators — the other half of `ingest`.
3
+ *
4
+ * `usage.selectors` answers "which CSS selectors does the suite use?". That was
5
+ * only ever half the question: a suite written the modern way does not hold CSS
6
+ * at all. `page.getByRole('button', { name: 'Save' })`, `cy.contains('Save')` and
7
+ * `By.linkText('Save')` are anchored on the **accessible name**, so they survive
8
+ * every selector rewrite this tool can perform and break on the one kind of drift
9
+ * it used to classify as harmless: a rename.
10
+ *
11
+ * Same discipline as `ingest`: quoted literals in known call shapes, no
12
+ * execution, no AST, no network. A name built by concatenation or holding `${…}`
13
+ * is invisible here — and invisible to the rewrite below too, which is why it is
14
+ * skipped rather than guessed at.
15
+ *
16
+ * Framework parity is the point (guardrail: Playwright / Cypress / Selenium must
17
+ * all be first-class). Every shape below carries what the contract needs to check
18
+ * it: a name, and a role when the call states one.
19
+ */
20
+ export type NameShape = 'playwright-role' | 'playwright-text' | 'playwright-label' | 'playwright-placeholder' | 'playwright-title' | 'playwright-alt' | 'cypress-contains' | 'selenium-linktext';
21
+ /** Where a name-anchored locator was found. Serialized into `.ia-qa/usage.json`. */
22
+ export interface NameSite {
23
+ file: string;
24
+ line: number;
25
+ shape: NameShape;
26
+ /** The ARIA role the call states, when it states one (`getByRole` only). */
27
+ role?: string;
28
+ /**
29
+ * The name exactly as the source writes it. The inventory is keyed by the
30
+ * normalized form so lookups are case- and whitespace-insensitive, but every
31
+ * message a human reads must quote what is actually in their file — telling
32
+ * someone to look for `"save"` when the line says `'Save'` costs them a grep.
33
+ */
34
+ text: string;
35
+ }
36
+ /** A single occurrence, with everything needed to rewrite it in place. */
37
+ export interface NameHit {
38
+ /** The literal name as written in the source. */
39
+ name: string;
40
+ role?: string;
41
+ shape: NameShape;
42
+ site: NameSite;
43
+ /** The exact source substring of the call, used as a rewrite needle. */
44
+ call: string;
45
+ /** The same substring with the name literal replaced. */
46
+ rename: (newName: string) => string;
47
+ }
48
+ export declare const normalizeName: (s?: string) => string;
49
+ /**
50
+ * The roles a name-anchored call can be checked against.
51
+ *
52
+ * `getByLabel` / `getByPlaceholder` do not name a role, but `extractInPage`
53
+ * derives an accessible name from `<label>`, then `placeholder`, then `name` —
54
+ * so both resolve against form-control roles and nothing else.
55
+ */
56
+ export declare const SHAPE_ROLES: Partial<Record<NameShape, string[]>>;
57
+ /**
58
+ * Every role `extractInPage` can emit — the complete vocabulary of a contract.
59
+ *
60
+ * A locator asking for anything else is asking about a part of the page the
61
+ * mapping never claimed to hold. `getByRole('heading', …)` is the common case:
62
+ * headings are not interactive, so no contract has ever contained one, and
63
+ * "absent from the contract" would mean "absent from the app" only if the two
64
+ * were the same thing. They are not, and a checker that forgets it reports every
65
+ * heading in the suite as broken.
66
+ */
67
+ export declare const CONTRACT_ROLES: Set<string>;
68
+ /**
69
+ * Shapes whose miss is *evidence* that a test targets nothing.
70
+ *
71
+ * The contract maps interactive elements only. `getByText('Terms of service')`
72
+ * may well point at a paragraph the mapping never saw, so its absence proves
73
+ * nothing — reporting it would be a false positive, and one false positive costs
74
+ * more than ten honest "inconclusive". A stated role, a label or a placeholder,
75
+ * on the other hand, can designate something the contract holds — provided the
76
+ * role is one the contract speaks (see `judgeable` below).
77
+ */
78
+ export declare const AUTHORITATIVE_SHAPES: Set<NameShape>;
79
+ /**
80
+ * Can this occurrence's absence from a contract be read as a finding?
81
+ *
82
+ * Both halves must hold: a shape that names an element the contract enumerates,
83
+ * AND a role the contract can actually contain.
84
+ */
85
+ export declare function judgeable(site: {
86
+ shape: NameShape;
87
+ role?: string;
88
+ }): boolean;
89
+ /** Wrap `name` in a quote it does not contain; escape as a last resort. */
90
+ export declare function quoteLiteral(name: string): string;
91
+ /**
92
+ * Find every name-anchored locator in `text`.
93
+ *
94
+ * Overlapping shapes are resolved by source position: the first definition that
95
+ * claims a character range wins, which is why the two-argument `cy.contains` is
96
+ * declared before the one-argument form.
97
+ */
98
+ export declare function scanNameLocators(text: string, file: string): NameHit[];
99
+ /** Roles a hit can legitimately resolve to, or `null` when it constrains nothing. */
100
+ export declare function candidateRoles(hit: {
101
+ role?: string;
102
+ shape: NameShape;
103
+ }): string[] | null;
@@ -0,0 +1,235 @@
1
+ "use strict";
2
+ /**
3
+ * Static inventory of **name-anchored** locators — the other half of `ingest`.
4
+ *
5
+ * `usage.selectors` answers "which CSS selectors does the suite use?". That was
6
+ * only ever half the question: a suite written the modern way does not hold CSS
7
+ * at all. `page.getByRole('button', { name: 'Save' })`, `cy.contains('Save')` and
8
+ * `By.linkText('Save')` are anchored on the **accessible name**, so they survive
9
+ * every selector rewrite this tool can perform and break on the one kind of drift
10
+ * it used to classify as harmless: a rename.
11
+ *
12
+ * Same discipline as `ingest`: quoted literals in known call shapes, no
13
+ * execution, no AST, no network. A name built by concatenation or holding `${…}`
14
+ * is invisible here — and invisible to the rewrite below too, which is why it is
15
+ * skipped rather than guessed at.
16
+ *
17
+ * Framework parity is the point (guardrail: Playwright / Cypress / Selenium must
18
+ * all be first-class). Every shape below carries what the contract needs to check
19
+ * it: a name, and a role when the call states one.
20
+ */
21
+ Object.defineProperty(exports, "__esModule", { value: true });
22
+ exports.AUTHORITATIVE_SHAPES = exports.CONTRACT_ROLES = exports.SHAPE_ROLES = exports.normalizeName = void 0;
23
+ exports.judgeable = judgeable;
24
+ exports.quoteLiteral = quoteLiteral;
25
+ exports.scanNameLocators = scanNameLocators;
26
+ exports.candidateRoles = candidateRoles;
27
+ const normalizeName = (s) => (s == null ? '' : String(s)).toLowerCase().trim().replace(/\s+/g, ' ');
28
+ exports.normalizeName = normalizeName;
29
+ /**
30
+ * The roles a name-anchored call can be checked against.
31
+ *
32
+ * `getByLabel` / `getByPlaceholder` do not name a role, but `extractInPage`
33
+ * derives an accessible name from `<label>`, then `placeholder`, then `name` —
34
+ * so both resolve against form-control roles and nothing else.
35
+ */
36
+ exports.SHAPE_ROLES = {
37
+ 'playwright-label': ['textbox', 'combobox', 'checkbox', 'radio', 'slider'],
38
+ 'playwright-placeholder': ['textbox', 'combobox'],
39
+ 'selenium-linktext': ['link'],
40
+ };
41
+ /**
42
+ * Every role `extractInPage` can emit — the complete vocabulary of a contract.
43
+ *
44
+ * A locator asking for anything else is asking about a part of the page the
45
+ * mapping never claimed to hold. `getByRole('heading', …)` is the common case:
46
+ * headings are not interactive, so no contract has ever contained one, and
47
+ * "absent from the contract" would mean "absent from the app" only if the two
48
+ * were the same thing. They are not, and a checker that forgets it reports every
49
+ * heading in the suite as broken.
50
+ */
51
+ exports.CONTRACT_ROLES = new Set([
52
+ 'button', 'link', 'textbox', 'combobox', 'checkbox', 'radio', 'menuitem', 'tab', 'slider', 'generic',
53
+ ]);
54
+ /**
55
+ * Shapes whose miss is *evidence* that a test targets nothing.
56
+ *
57
+ * The contract maps interactive elements only. `getByText('Terms of service')`
58
+ * may well point at a paragraph the mapping never saw, so its absence proves
59
+ * nothing — reporting it would be a false positive, and one false positive costs
60
+ * more than ten honest "inconclusive". A stated role, a label or a placeholder,
61
+ * on the other hand, can designate something the contract holds — provided the
62
+ * role is one the contract speaks (see `judgeable` below).
63
+ */
64
+ exports.AUTHORITATIVE_SHAPES = new Set([
65
+ 'playwright-role',
66
+ 'playwright-label',
67
+ 'playwright-placeholder',
68
+ 'selenium-linktext',
69
+ ]);
70
+ /**
71
+ * Can this occurrence's absence from a contract be read as a finding?
72
+ *
73
+ * Both halves must hold: a shape that names an element the contract enumerates,
74
+ * AND a role the contract can actually contain.
75
+ */
76
+ function judgeable(site) {
77
+ if (!exports.AUTHORITATIVE_SHAPES.has(site.shape))
78
+ return false;
79
+ const roles = candidateRoles(site);
80
+ return roles !== null && roles.some((r) => exports.CONTRACT_ROLES.has(r));
81
+ }
82
+ const Q = "(['\"`])";
83
+ /** Body of a quoted literal: no unescaped closing quote, no interpolation. */
84
+ const BODY = '((?:(?!\\1)[^\\\\]|\\\\.)*?)';
85
+ /**
86
+ * A literal we can read *and* rewrite. A template hole (`${…}`) makes the runtime
87
+ * value unknowable, and a regex argument (`{ name: /save/i }`) is not a literal at
88
+ * all — both are left to the human rather than guessed at.
89
+ */
90
+ function literalName(raw) {
91
+ if (raw == null)
92
+ return null;
93
+ if (raw.includes('${'))
94
+ return null;
95
+ return raw.replace(/\\(['"`\\])/g, '$1');
96
+ }
97
+ /** Wrap `name` in a quote it does not contain; escape as a last resort. */
98
+ function quoteLiteral(name) {
99
+ for (const q of ["'", '"', '`']) {
100
+ if (name.indexOf(q) === -1 && !(q === '`' && name.includes('${')))
101
+ return q + name + q;
102
+ }
103
+ return "'" + name.replace(/(['\\])/g, '\\$1') + "'";
104
+ }
105
+ /** Replace the `name: '…'` value inside an already-matched `getByRole(…)` call. */
106
+ function renameRoleCall(call, newName) {
107
+ return call.replace(/(\bname\s*:\s*)(['"`])((?:(?!\2)[^\\]|\\.)*?)\2/, (_m, key) => key + quoteLiteral(newName));
108
+ }
109
+ /** Replace the Nth quoted literal of an already-matched call. */
110
+ function renameNthLiteral(call, newName, nth) {
111
+ let seen = 0;
112
+ return call.replace(/(['"`])((?:(?!\1)[^\\]|\\.)*?)\1/g, (m, _q, _body) => {
113
+ seen++;
114
+ return seen === nth ? quoteLiteral(newName) : m;
115
+ });
116
+ }
117
+ /**
118
+ * Playwright and Testing Library are the same call under two spellings, and a
119
+ * Cypress suite reaches role-anchored locating only through the second
120
+ * (`@testing-library/cypress` → `cy.findByRole`). Matching the whole family
121
+ * rather than Playwright's spelling alone is what keeps the three frameworks at
122
+ * parity here — otherwise a Cypress suite would have no rewritable locator at
123
+ * all, and "parity" would mean "we read your file and did nothing".
124
+ *
125
+ * `getBy` / `findBy` / `queryBy`, with or without `All`.
126
+ */
127
+ const V = '(?:get|find|query)(?:All)?By';
128
+ /**
129
+ * Every needle starts at the `.` so the single-pass rewriter in `fixEngine` — which
130
+ * only probes at `.` and quote characters — can match it. That covers
131
+ * `page.getByRole`, `screen.findByRole`, `cy.findByRole`, `cy.contains`,
132
+ * `By.linkText` and any chained form. A bare destructured `getByText('…')` (React
133
+ * Testing Library) is inventoried but not rewritten: an accepted, reported miss.
134
+ */
135
+ const SHAPES = [
136
+ {
137
+ shape: 'playwright-role',
138
+ re: new RegExp(`\\.${V}Role\\(\\s*${Q}([a-z]+)\\1\\s*,\\s*\\{[^{}]*?\\bname\\s*:\\s*(['"\`])((?:(?!\\3)[^\\\\]|\\\\.)*?)\\3[^{}]*\\}\\s*\\)`, 'g'),
139
+ nameGroup: 4,
140
+ roleGroup: 2,
141
+ literalIndex: 2,
142
+ role: true,
143
+ },
144
+ { shape: 'playwright-label', re: new RegExp(`\\.${V}Label(?:Text)?\\(\\s*${Q}${BODY}\\1`, 'g'), nameGroup: 2, literalIndex: 1 },
145
+ { shape: 'playwright-placeholder', re: new RegExp(`\\.${V}Placeholder(?:Text)?\\(\\s*${Q}${BODY}\\1`, 'g'), nameGroup: 2, literalIndex: 1 },
146
+ { shape: 'playwright-alt', re: new RegExp(`\\.${V}AltText\\(\\s*${Q}${BODY}\\1`, 'g'), nameGroup: 2, literalIndex: 1 },
147
+ { shape: 'playwright-title', re: new RegExp(`\\.${V}Title\\(\\s*${Q}${BODY}\\1`, 'g'), nameGroup: 2, literalIndex: 1 },
148
+ // `By` + `Text(` exactly, so `.getByLabelText(` and `.getByAltText(` are not
149
+ // candidates for it and the shapes above stay mutually exclusive.
150
+ { shape: 'playwright-text', re: new RegExp(`\\.${V}Text\\(\\s*${Q}${BODY}\\1`, 'g'), nameGroup: 2, literalIndex: 1 },
151
+ // `cy.contains(selector, text)` — the text is the SECOND literal. Checked before
152
+ // the one-argument form so the two-argument call is never read as `contains(text)`.
153
+ {
154
+ shape: 'cypress-contains',
155
+ re: new RegExp(`\\.contains\\(\\s*${Q}${BODY}\\1\\s*,\\s*(['"\`])((?:(?!\\3)[^\\\\]|\\\\.)*?)\\3`, 'g'),
156
+ nameGroup: 4,
157
+ literalIndex: 2,
158
+ },
159
+ { shape: 'cypress-contains', re: new RegExp(`\\.contains\\(\\s*${Q}${BODY}\\1\\s*\\)`, 'g'), nameGroup: 2, literalIndex: 1 },
160
+ { shape: 'selenium-linktext', re: new RegExp(`\\.(?:linkText|partialLinkText)\\(\\s*${Q}${BODY}\\1`, 'g'), nameGroup: 2, literalIndex: 1 },
161
+ // Selenium's constant form, shared by the Java / Python / C# bindings:
162
+ // `find_element(By.LINK_TEXT, "Save")`.
163
+ {
164
+ shape: 'selenium-linktext',
165
+ re: new RegExp(`\\.(?:LINK_TEXT|PARTIAL_LINK_TEXT)\\s*,\\s*${Q}${BODY}\\1`, 'g'),
166
+ nameGroup: 2,
167
+ literalIndex: 1,
168
+ },
169
+ ];
170
+ /**
171
+ * Find every name-anchored locator in `text`.
172
+ *
173
+ * Overlapping shapes are resolved by source position: the first definition that
174
+ * claims a character range wins, which is why the two-argument `cy.contains` is
175
+ * declared before the one-argument form.
176
+ */
177
+ function scanNameLocators(text, file) {
178
+ const lines = text.split('\n');
179
+ const lineStart = [];
180
+ let acc = 0;
181
+ for (const l of lines) {
182
+ lineStart.push(acc);
183
+ acc += l.length + 1;
184
+ }
185
+ const lineOf = (index) => {
186
+ let lo = 0;
187
+ let hi = lineStart.length - 1;
188
+ while (lo < hi) {
189
+ const mid = (lo + hi + 1) >> 1;
190
+ if (lineStart[mid] <= index)
191
+ lo = mid;
192
+ else
193
+ hi = mid - 1;
194
+ }
195
+ return lo + 1;
196
+ };
197
+ const claimed = [];
198
+ const out = [];
199
+ for (const def of SHAPES) {
200
+ def.re.lastIndex = 0;
201
+ let m;
202
+ while ((m = def.re.exec(text)) !== null) {
203
+ const start = m.index;
204
+ const end = start + m[0].length;
205
+ if (claimed.some(([s, e]) => start < e && end > s))
206
+ continue;
207
+ const name = literalName(m[def.nameGroup]);
208
+ if (name == null || name.trim() === '')
209
+ continue;
210
+ claimed.push([start, end]);
211
+ const call = m[0];
212
+ const shape = def.shape;
213
+ const role = def.roleGroup ? m[def.roleGroup] : undefined;
214
+ const literalIndex = def.literalIndex;
215
+ out.push({
216
+ name,
217
+ role,
218
+ shape,
219
+ site: { file, line: lineOf(start), shape, ...(role ? { role } : {}), text: name },
220
+ call,
221
+ rename: (newName) => shape === 'playwright-role'
222
+ ? renameRoleCall(call, newName)
223
+ : renameNthLiteral(call, newName, literalIndex),
224
+ });
225
+ }
226
+ }
227
+ return out.sort((a, b) => a.site.line - b.site.line);
228
+ }
229
+ /** Roles a hit can legitimately resolve to, or `null` when it constrains nothing. */
230
+ function candidateRoles(hit) {
231
+ if (hit.role)
232
+ return [hit.role];
233
+ return exports.SHAPE_ROLES[hit.shape] ?? null;
234
+ }
235
+ //# sourceMappingURL=nameLocators.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nameLocators.js","sourceRoot":"","sources":["../src/nameLocators.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ia-qa/self-healing",
3
- "version": "1.5.8",
3
+ "version": "1.6.2",
4
4
  "description": "Local-first self-healing for UI tests: a local MCP server + CLI that map your app's pages to a role/name/selector contract, diff selector drift (PASS/FIX/BLOCK), and apply deterministic fixes to Cypress/Playwright/Selenium tests. Runs on your machine — nothing leaves it.",
5
5
  "keywords": [
6
6
  "self-healing",