@ia-qa/self-healing 0.1.0
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/README.md +91 -0
- package/TUTORIAL.md +399 -0
- package/dist/aom.d.ts +22 -0
- package/dist/aom.js +58 -0
- package/dist/aom.js.map +1 -0
- package/dist/browser/contract.d.ts +1 -0
- package/dist/browser/contract.js +107 -0
- package/dist/browser/contract.js.map +1 -0
- package/dist/browser/extract.d.ts +25 -0
- package/dist/browser/extract.js +194 -0
- package/dist/browser/extract.js.map +1 -0
- package/dist/browser/match.d.ts +88 -0
- package/dist/browser/match.js +179 -0
- package/dist/browser/match.js.map +1 -0
- package/dist/cli/diff.d.ts +13 -0
- package/dist/cli/diff.js +143 -0
- package/dist/cli/diff.js.map +1 -0
- package/dist/cli/fix.d.ts +17 -0
- package/dist/cli/fix.js +144 -0
- package/dist/cli/fix.js.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +50 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/init.d.ts +8 -0
- package/dist/cli/init.js +185 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/map.d.ts +9 -0
- package/dist/cli/map.js +118 -0
- package/dist/cli/map.js.map +1 -0
- package/dist/config.d.ts +55 -0
- package/dist/config.js +111 -0
- package/dist/config.js.map +1 -0
- package/dist/fixEngine.d.ts +49 -0
- package/dist/fixEngine.js +154 -0
- package/dist/fixEngine.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -0
- package/dist/launcher.d.ts +38 -0
- package/dist/launcher.js +38 -0
- package/dist/launcher.js.map +1 -0
- package/dist/mapUrl.d.ts +34 -0
- package/dist/mapUrl.js +90 -0
- package/dist/mapUrl.js.map +1 -0
- package/dist/markdown.d.ts +10 -0
- package/dist/markdown.js +54 -0
- package/dist/markdown.js.map +1 -0
- package/dist/mcp/server.d.ts +2 -0
- package/dist/mcp/server.js +251 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/playwright/healer.d.ts +39 -0
- package/dist/playwright/healer.js +84 -0
- package/dist/playwright/healer.js.map +1 -0
- package/package.json +54 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Page contract as Markdown — the framework- and language-agnostic artifact an
|
|
4
|
+
* LLM reads to auto-heal a broken UI test.
|
|
5
|
+
*
|
|
6
|
+
* Pure, dependency-free JS (no imports, no fs) so both the CLI and ia-qa.com's
|
|
7
|
+
* web tool render byte-identical contracts from the same mapping. The CLI writes
|
|
8
|
+
* the result to `<page>.md`; the browser offers it as a download.
|
|
9
|
+
*
|
|
10
|
+
* The workflow it enables: `map` emits `<page>.json` (for tooling / diff) AND
|
|
11
|
+
* `<page>.md` (for a model). When a Cypress/Playwright/Selenium test breaks in
|
|
12
|
+
* CI, you hand the failing test file + this `.md` to an LLM: it finds the element
|
|
13
|
+
* by role + accessible name, reads the current selector, and rewrites the test in
|
|
14
|
+
* whatever framework/language it is written in. The self-describing header tells
|
|
15
|
+
* the model how to use the file.
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.renderPageContract = renderPageContract;
|
|
19
|
+
const ROLE_ORDER = [
|
|
20
|
+
'textbox',
|
|
21
|
+
'combobox',
|
|
22
|
+
'checkbox',
|
|
23
|
+
'radio',
|
|
24
|
+
'slider',
|
|
25
|
+
'button',
|
|
26
|
+
'link',
|
|
27
|
+
'tab',
|
|
28
|
+
'menuitem',
|
|
29
|
+
];
|
|
30
|
+
function escapeCell(s) {
|
|
31
|
+
return (s || '').replace(/\|/g, '\\|').replace(/\n/g, ' ');
|
|
32
|
+
}
|
|
33
|
+
function groupByRole(elements) {
|
|
34
|
+
const groups = new Map();
|
|
35
|
+
for (const el of elements) {
|
|
36
|
+
const list = groups.get(el.role);
|
|
37
|
+
if (list)
|
|
38
|
+
list.push(el);
|
|
39
|
+
else
|
|
40
|
+
groups.set(el.role, [el]);
|
|
41
|
+
}
|
|
42
|
+
const roles = Array.from(groups.keys()).sort((a, b) => {
|
|
43
|
+
const ia = ROLE_ORDER.indexOf(a);
|
|
44
|
+
const ib = ROLE_ORDER.indexOf(b);
|
|
45
|
+
if (ia === -1 && ib === -1)
|
|
46
|
+
return a.localeCompare(b);
|
|
47
|
+
if (ia === -1)
|
|
48
|
+
return 1;
|
|
49
|
+
if (ib === -1)
|
|
50
|
+
return -1;
|
|
51
|
+
return ia - ib;
|
|
52
|
+
});
|
|
53
|
+
const ordered = new Map();
|
|
54
|
+
for (const role of roles)
|
|
55
|
+
ordered.set(role, groups.get(role));
|
|
56
|
+
return ordered;
|
|
57
|
+
}
|
|
58
|
+
function renderPageContract(mapping) {
|
|
59
|
+
const page = mapping.page || 'page';
|
|
60
|
+
const url = mapping.url || '';
|
|
61
|
+
const capturedAt = mapping.capturedAt || '';
|
|
62
|
+
const elements = (mapping && mapping.elements) || [];
|
|
63
|
+
const groups = groupByRole(elements);
|
|
64
|
+
const lines = [];
|
|
65
|
+
lines.push('# Page contract — ' + page);
|
|
66
|
+
lines.push('');
|
|
67
|
+
lines.push('- **URL:** `' + url + '`');
|
|
68
|
+
lines.push('- **Captured:** ' + capturedAt);
|
|
69
|
+
lines.push('- **Interactive elements:** ' + elements.length);
|
|
70
|
+
lines.push('');
|
|
71
|
+
lines.push('> Generated by `@ia-qa/self-healing`. This file is the **current source of truth** for the');
|
|
72
|
+
lines.push('> selectors on this page. **To heal a broken UI test** (any framework — Cypress, Playwright,');
|
|
73
|
+
lines.push('> Selenium… — any language): find the element below by its **role** and **accessible name**,');
|
|
74
|
+
lines.push('> then replace the stale selector in the test with the current `selector`. If an element from');
|
|
75
|
+
lines.push('> the test is absent here, it was removed or renamed — flag it instead of guessing.');
|
|
76
|
+
lines.push('');
|
|
77
|
+
if (elements.length === 0) {
|
|
78
|
+
lines.push('_No visible interactive elements were captured on this page._');
|
|
79
|
+
lines.push('');
|
|
80
|
+
return lines.join('\n');
|
|
81
|
+
}
|
|
82
|
+
const anyContext = elements.some((el) => el.context);
|
|
83
|
+
for (const [role, els] of groups) {
|
|
84
|
+
lines.push('## ' + role + ' (' + els.length + ')');
|
|
85
|
+
lines.push('');
|
|
86
|
+
if (anyContext) {
|
|
87
|
+
lines.push('| Accessible name | Context | Selector |');
|
|
88
|
+
lines.push('| --- | --- | --- |');
|
|
89
|
+
for (const el of els) {
|
|
90
|
+
const name = el.name ? escapeCell(el.name) : '_(no accessible name)_';
|
|
91
|
+
const ctx = el.context ? escapeCell(el.context) : '—';
|
|
92
|
+
lines.push('| ' + name + ' | ' + ctx + ' | `' + escapeCell(el.selector) + '` |');
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
lines.push('| Accessible name | Selector |');
|
|
97
|
+
lines.push('| --- | --- |');
|
|
98
|
+
for (const el of els) {
|
|
99
|
+
const name = el.name ? escapeCell(el.name) : '_(no accessible name)_';
|
|
100
|
+
lines.push('| ' + name + ' | `' + escapeCell(el.selector) + '` |');
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
lines.push('');
|
|
104
|
+
}
|
|
105
|
+
return lines.join('\n');
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=contract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract.js","sourceRoot":"","sources":["../../src/browser/contract.js"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;GAcG;;AAsCH,gDAoDC;AAxFD,MAAM,UAAU,GAAG;IACjB,SAAS;IACT,UAAU;IACV,UAAU;IACV,OAAO;IACP,QAAQ;IACR,QAAQ;IACR,MAAM;IACN,KAAK;IACL,UAAU;CACX,CAAC;AAEF,SAAS,UAAU,CAAC,CAAC;IACnB,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;AAC7D,CAAC;AAED,SAAS,WAAW,CAAC,QAAQ;IAC3B,MAAM,MAAM,GAAG,IAAI,GAAG,EAAE,CAAC;IACzB,KAAK,MAAM,EAAE,IAAI,QAAQ,EAAE,CAAC;QAC1B,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;QACjC,IAAI,IAAI;YAAE,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;;YACnB,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IACjC,CAAC;IACD,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QACpD,MAAM,EAAE,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QACjC,MAAM,EAAE,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QACjC,IAAI,EAAE,KAAK,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;YAAE,OAAO,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;QACtD,IAAI,EAAE,KAAK,CAAC,CAAC;YAAE,OAAO,CAAC,CAAC;QACxB,IAAI,EAAE,KAAK,CAAC,CAAC;YAAE,OAAO,CAAC,CAAC,CAAC;QACzB,OAAO,EAAE,GAAG,EAAE,CAAC;IACjB,CAAC,CAAC,CAAC;IACH,MAAM,OAAO,GAAG,IAAI,GAAG,EAAE,CAAC;IAC1B,KAAK,MAAM,IAAI,IAAI,KAAK;QAAE,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;IAC9D,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,SAAgB,kBAAkB,CAAC,OAAO;IACxC,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,MAAM,CAAC;IACpC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,EAAE,CAAC;IAC9B,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,EAAE,CAAC;IAC5C,MAAM,QAAQ,GAAG,CAAC,OAAO,IAAI,OAAO,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;IACrD,MAAM,MAAM,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC;IAErC,MAAM,KAAK,GAAG,EAAE,CAAC;IACjB,KAAK,CAAC,IAAI,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAAC;IACxC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACf,KAAK,CAAC,IAAI,CAAC,cAAc,GAAG,GAAG,GAAG,GAAG,CAAC,CAAC;IACvC,KAAK,CAAC,IAAI,CAAC,kBAAkB,GAAG,UAAU,CAAC,CAAC;IAC5C,KAAK,CAAC,IAAI,CAAC,8BAA8B,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC7D,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACf,KAAK,CAAC,IAAI,CAAC,4FAA4F,CAAC,CAAC;IACzG,KAAK,CAAC,IAAI,CAAC,8FAA8F,CAAC,CAAC;IAC3G,KAAK,CAAC,IAAI,CAAC,8FAA8F,CAAC,CAAC;IAC3G,KAAK,CAAC,IAAI,CAAC,+FAA+F,CAAC,CAAC;IAC5G,KAAK,CAAC,IAAI,CAAC,qFAAqF,CAAC,CAAC;IAClG,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAEf,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,KAAK,CAAC,IAAI,CAAC,+DAA+D,CAAC,CAAC;QAC5E,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACf,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IAED,MAAM,UAAU,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC;IAErD,KAAK,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,MAAM,EAAE,CAAC;QACjC,KAAK,CAAC,IAAI,CAAC,KAAK,GAAG,IAAI,GAAG,IAAI,GAAG,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC;QACnD,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACf,IAAI,UAAU,EAAE,CAAC;YACf,KAAK,CAAC,IAAI,CAAC,0CAA0C,CAAC,CAAC;YACvD,KAAK,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;YAClC,KAAK,MAAM,EAAE,IAAI,GAAG,EAAE,CAAC;gBACrB,MAAM,IAAI,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,wBAAwB,CAAC;gBACtE,MAAM,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;gBACtD,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,IAAI,GAAG,KAAK,GAAG,GAAG,GAAG,MAAM,GAAG,UAAU,CAAC,EAAE,CAAC,QAAQ,CAAC,GAAG,KAAK,CAAC,CAAC;YACnF,CAAC;QACH,CAAC;aAAM,CAAC;YACN,KAAK,CAAC,IAAI,CAAC,gCAAgC,CAAC,CAAC;YAC7C,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;YAC5B,KAAK,MAAM,EAAE,IAAI,GAAG,EAAE,CAAC;gBACrB,MAAM,IAAI,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,wBAAwB,CAAC;gBACtE,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,IAAI,GAAG,MAAM,GAAG,UAAU,CAAC,EAAE,CAAC,QAAQ,CAAC,GAAG,KAAK,CAAC,CAAC;YACrE,CAAC;QACH,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjB,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Accessibility-oriented DOM extraction — the single source of truth.
|
|
3
|
+
*
|
|
4
|
+
* This function is used three ways and MUST stay self-contained (no imports, no
|
|
5
|
+
* closures over module scope, no TypeScript syntax):
|
|
6
|
+
* 1. `ia-qa-heal map` passes it to Playwright's `page.evaluate()`, which
|
|
7
|
+
* serializes it via `toString()` and injects it into the page.
|
|
8
|
+
* 2. The healer re-runs it on the live page to find healing candidates.
|
|
9
|
+
* 3. ia-qa.com imports this file with `?raw` and shows it as a copy-paste
|
|
10
|
+
* DevTools snippet, so the zero-install flow produces byte-identical
|
|
11
|
+
* mappings to the CLI.
|
|
12
|
+
*
|
|
13
|
+
* Any dependency added here silently breaks (1) and (3).
|
|
14
|
+
*
|
|
15
|
+
* `page.accessibility.snapshot()` is deprecated and returns nodes *without any
|
|
16
|
+
* selector*, which makes it useless for a mapping meant to drive clicks. We walk
|
|
17
|
+
* the DOM ourselves and compute, per visible interactive element: its ARIA role,
|
|
18
|
+
* its accessible name (accname priority order), and a stable selector.
|
|
19
|
+
*/
|
|
20
|
+
export function extractInPage(): {
|
|
21
|
+
role: any;
|
|
22
|
+
name: string;
|
|
23
|
+
selector: string;
|
|
24
|
+
context: string;
|
|
25
|
+
}[];
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.extractInPage = extractInPage;
|
|
4
|
+
/**
|
|
5
|
+
* Accessibility-oriented DOM extraction — the single source of truth.
|
|
6
|
+
*
|
|
7
|
+
* This function is used three ways and MUST stay self-contained (no imports, no
|
|
8
|
+
* closures over module scope, no TypeScript syntax):
|
|
9
|
+
* 1. `ia-qa-heal map` passes it to Playwright's `page.evaluate()`, which
|
|
10
|
+
* serializes it via `toString()` and injects it into the page.
|
|
11
|
+
* 2. The healer re-runs it on the live page to find healing candidates.
|
|
12
|
+
* 3. ia-qa.com imports this file with `?raw` and shows it as a copy-paste
|
|
13
|
+
* DevTools snippet, so the zero-install flow produces byte-identical
|
|
14
|
+
* mappings to the CLI.
|
|
15
|
+
*
|
|
16
|
+
* Any dependency added here silently breaks (1) and (3).
|
|
17
|
+
*
|
|
18
|
+
* `page.accessibility.snapshot()` is deprecated and returns nodes *without any
|
|
19
|
+
* selector*, which makes it useless for a mapping meant to drive clicks. We walk
|
|
20
|
+
* the DOM ourselves and compute, per visible interactive element: its ARIA role,
|
|
21
|
+
* its accessible name (accname priority order), and a stable selector.
|
|
22
|
+
*/
|
|
23
|
+
function extractInPage() {
|
|
24
|
+
const INTERACTIVE_SELECTOR = [
|
|
25
|
+
'button',
|
|
26
|
+
'a[href]',
|
|
27
|
+
'input:not([type="hidden"])',
|
|
28
|
+
'select',
|
|
29
|
+
'textarea',
|
|
30
|
+
'[role="button"]',
|
|
31
|
+
'[role="link"]',
|
|
32
|
+
'[role="textbox"]',
|
|
33
|
+
'[role="combobox"]',
|
|
34
|
+
'[role="checkbox"]',
|
|
35
|
+
'[role="radio"]',
|
|
36
|
+
'[role="menuitem"]',
|
|
37
|
+
'[role="tab"]',
|
|
38
|
+
'[onclick]',
|
|
39
|
+
].join(', ');
|
|
40
|
+
function isVisible(el) {
|
|
41
|
+
const rect = el.getBoundingClientRect();
|
|
42
|
+
if (rect.width === 0 || rect.height === 0)
|
|
43
|
+
return false;
|
|
44
|
+
const style = window.getComputedStyle(el);
|
|
45
|
+
return style.visibility !== 'hidden' && style.display !== 'none' && style.opacity !== '0';
|
|
46
|
+
}
|
|
47
|
+
function role(el) {
|
|
48
|
+
const explicit = el.getAttribute('role');
|
|
49
|
+
if (explicit)
|
|
50
|
+
return explicit;
|
|
51
|
+
const tag = el.tagName.toLowerCase();
|
|
52
|
+
if (tag === 'a')
|
|
53
|
+
return 'link';
|
|
54
|
+
if (tag === 'button')
|
|
55
|
+
return 'button';
|
|
56
|
+
if (tag === 'select')
|
|
57
|
+
return 'combobox';
|
|
58
|
+
if (tag === 'textarea')
|
|
59
|
+
return 'textbox';
|
|
60
|
+
if (tag === 'input') {
|
|
61
|
+
const type = el.type;
|
|
62
|
+
if (type === 'checkbox')
|
|
63
|
+
return 'checkbox';
|
|
64
|
+
if (type === 'radio')
|
|
65
|
+
return 'radio';
|
|
66
|
+
if (type === 'submit' || type === 'button' || type === 'reset')
|
|
67
|
+
return 'button';
|
|
68
|
+
if (type === 'range')
|
|
69
|
+
return 'slider';
|
|
70
|
+
return 'textbox';
|
|
71
|
+
}
|
|
72
|
+
return 'generic';
|
|
73
|
+
}
|
|
74
|
+
function accessibleName(el) {
|
|
75
|
+
const clean = (s) => (s == null ? '' : String(s)).trim().replace(/\s+/g, ' ').slice(0, 120);
|
|
76
|
+
const ariaLabel = el.getAttribute('aria-label');
|
|
77
|
+
if (ariaLabel)
|
|
78
|
+
return clean(ariaLabel);
|
|
79
|
+
const labelledBy = el.getAttribute('aria-labelledby');
|
|
80
|
+
if (labelledBy) {
|
|
81
|
+
const joined = labelledBy
|
|
82
|
+
.split(/\s+/)
|
|
83
|
+
.map((id) => {
|
|
84
|
+
const node = document.getElementById(id);
|
|
85
|
+
return node ? node.textContent : '';
|
|
86
|
+
})
|
|
87
|
+
.join(' ');
|
|
88
|
+
if (clean(joined))
|
|
89
|
+
return clean(joined);
|
|
90
|
+
}
|
|
91
|
+
const tag = el.tagName.toLowerCase();
|
|
92
|
+
if (tag === 'input' || tag === 'textarea' || tag === 'select') {
|
|
93
|
+
const label = el.labels && el.labels.length > 0 ? el.labels[0].textContent : '';
|
|
94
|
+
if (clean(label))
|
|
95
|
+
return clean(label);
|
|
96
|
+
if (tag === 'input' && ['submit', 'button', 'reset'].indexOf(el.type) !== -1 && el.value) {
|
|
97
|
+
return clean(el.value);
|
|
98
|
+
}
|
|
99
|
+
const placeholder = el.getAttribute('placeholder');
|
|
100
|
+
if (placeholder)
|
|
101
|
+
return clean(placeholder);
|
|
102
|
+
const nameAttr = el.getAttribute('name');
|
|
103
|
+
if (nameAttr)
|
|
104
|
+
return clean(nameAttr);
|
|
105
|
+
}
|
|
106
|
+
const title = el.getAttribute('title');
|
|
107
|
+
const text = clean(el.textContent);
|
|
108
|
+
return text || clean(title);
|
|
109
|
+
}
|
|
110
|
+
function stableSelector(el) {
|
|
111
|
+
const testId = el.getAttribute('data-testid') || el.getAttribute('data-test-id');
|
|
112
|
+
if (testId)
|
|
113
|
+
return '[data-testid="' + testId + '"]';
|
|
114
|
+
if (el.id)
|
|
115
|
+
return el.tagName.toLowerCase() + '#' + CSS.escape(el.id);
|
|
116
|
+
const nameAttr = el.getAttribute('name');
|
|
117
|
+
if (nameAttr)
|
|
118
|
+
return el.tagName.toLowerCase() + '[name="' + nameAttr + '"]';
|
|
119
|
+
const parts = [];
|
|
120
|
+
let node = el;
|
|
121
|
+
while (node && node !== document.body && parts.length < 5) {
|
|
122
|
+
if (node.id) {
|
|
123
|
+
parts.unshift('#' + CSS.escape(node.id));
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
let part = node.tagName.toLowerCase();
|
|
127
|
+
const parent = node.parentElement;
|
|
128
|
+
if (parent) {
|
|
129
|
+
const current = node;
|
|
130
|
+
const sameTag = Array.prototype.filter.call(parent.children, (c) => c.tagName === current.tagName);
|
|
131
|
+
if (sameTag.length > 1)
|
|
132
|
+
part += ':nth-of-type(' + (sameTag.indexOf(current) + 1) + ')';
|
|
133
|
+
}
|
|
134
|
+
parts.unshift(part);
|
|
135
|
+
node = parent;
|
|
136
|
+
}
|
|
137
|
+
return parts.join(' > ');
|
|
138
|
+
}
|
|
139
|
+
const LANDMARK_TAGS = { MAIN: 'main', NAV: 'nav', HEADER: 'banner', FOOTER: 'contentinfo', ASIDE: 'complementary', FORM: 'form' };
|
|
140
|
+
const LANDMARK_ROLES = ['main', 'navigation', 'banner', 'contentinfo', 'complementary', 'form', 'search', 'region', 'dialog'];
|
|
141
|
+
function clean(s) {
|
|
142
|
+
return (s == null ? '' : String(s)).trim().replace(/\s+/g, ' ').slice(0, 80);
|
|
143
|
+
}
|
|
144
|
+
// Disambiguating context: what container this element lives in, so two elements
|
|
145
|
+
// with the same role + accessible name (e.g. many "Delete" buttons) stay
|
|
146
|
+
// distinguishable. We surface the nearest landmark and the nearest section
|
|
147
|
+
// heading — both human-meaningful and stable against class/id churn.
|
|
148
|
+
function context(el) {
|
|
149
|
+
let landmark = '';
|
|
150
|
+
let node = el.parentElement;
|
|
151
|
+
while (node && node !== document.body) {
|
|
152
|
+
const roleAttr = node.getAttribute('role');
|
|
153
|
+
if (roleAttr && LANDMARK_ROLES.indexOf(roleAttr) !== -1) {
|
|
154
|
+
landmark = clean(node.getAttribute('aria-label')) || roleAttr;
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
if (LANDMARK_TAGS[node.tagName]) {
|
|
158
|
+
landmark = clean(node.getAttribute('aria-label')) || LANDMARK_TAGS[node.tagName];
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
node = node.parentElement;
|
|
162
|
+
}
|
|
163
|
+
// Nearest heading that precedes the element in document order.
|
|
164
|
+
let heading = '';
|
|
165
|
+
const headings = document.querySelectorAll('h1, h2, h3, h4, h5, h6, [role="heading"]');
|
|
166
|
+
for (let i = headings.length - 1; i >= 0; i--) {
|
|
167
|
+
const h = headings[i];
|
|
168
|
+
const pos = h.compareDocumentPosition(el);
|
|
169
|
+
if (pos & Node.DOCUMENT_POSITION_FOLLOWING) {
|
|
170
|
+
heading = clean(h.textContent);
|
|
171
|
+
if (heading)
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (landmark && heading)
|
|
176
|
+
return landmark + ' › ' + heading;
|
|
177
|
+
return landmark || heading || '';
|
|
178
|
+
}
|
|
179
|
+
const seen = {};
|
|
180
|
+
const elements = [];
|
|
181
|
+
const found = document.querySelectorAll(INTERACTIVE_SELECTOR);
|
|
182
|
+
for (let i = 0; i < found.length; i++) {
|
|
183
|
+
const el = found[i];
|
|
184
|
+
if (!isVisible(el))
|
|
185
|
+
continue;
|
|
186
|
+
const selector = stableSelector(el);
|
|
187
|
+
if (seen[selector])
|
|
188
|
+
continue;
|
|
189
|
+
seen[selector] = true;
|
|
190
|
+
elements.push({ role: role(el), name: accessibleName(el), selector: selector, context: context(el) });
|
|
191
|
+
}
|
|
192
|
+
return elements;
|
|
193
|
+
}
|
|
194
|
+
//# sourceMappingURL=extract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extract.js","sourceRoot":"","sources":["../../src/browser/extract.js"],"names":[],"mappings":";;AAmBA,sCAqKC;AAxLD;;;;;;;;;;;;;;;;;;GAkBG;AACH,SAAgB,aAAa;IAC3B,MAAM,oBAAoB,GAAG;QAC3B,QAAQ;QACR,SAAS;QACT,4BAA4B;QAC5B,QAAQ;QACR,UAAU;QACV,iBAAiB;QACjB,eAAe;QACf,kBAAkB;QAClB,mBAAmB;QACnB,mBAAmB;QACnB,gBAAgB;QAChB,mBAAmB;QACnB,cAAc;QACd,WAAW;KACZ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAEb,SAAS,SAAS,CAAC,EAAE;QACnB,MAAM,IAAI,GAAG,EAAE,CAAC,qBAAqB,EAAE,CAAC;QACxC,IAAI,IAAI,CAAC,KAAK,KAAK,CAAC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,KAAK,CAAC;QACxD,MAAM,KAAK,GAAG,MAAM,CAAC,gBAAgB,CAAC,EAAE,CAAC,CAAC;QAC1C,OAAO,KAAK,CAAC,UAAU,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,KAAK,MAAM,IAAI,KAAK,CAAC,OAAO,KAAK,GAAG,CAAC;IAC5F,CAAC;IAED,SAAS,IAAI,CAAC,EAAE;QACd,MAAM,QAAQ,GAAG,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;QACzC,IAAI,QAAQ;YAAE,OAAO,QAAQ,CAAC;QAC9B,MAAM,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC;QACrC,IAAI,GAAG,KAAK,GAAG;YAAE,OAAO,MAAM,CAAC;QAC/B,IAAI,GAAG,KAAK,QAAQ;YAAE,OAAO,QAAQ,CAAC;QACtC,IAAI,GAAG,KAAK,QAAQ;YAAE,OAAO,UAAU,CAAC;QACxC,IAAI,GAAG,KAAK,UAAU;YAAE,OAAO,SAAS,CAAC;QACzC,IAAI,GAAG,KAAK,OAAO,EAAE,CAAC;YACpB,MAAM,IAAI,GAAG,EAAE,CAAC,IAAI,CAAC;YACrB,IAAI,IAAI,KAAK,UAAU;gBAAE,OAAO,UAAU,CAAC;YAC3C,IAAI,IAAI,KAAK,OAAO;gBAAE,OAAO,OAAO,CAAC;YACrC,IAAI,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,OAAO;gBAAE,OAAO,QAAQ,CAAC;YAChF,IAAI,IAAI,KAAK,OAAO;gBAAE,OAAO,QAAQ,CAAC;YACtC,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,SAAS,cAAc,CAAC,EAAE;QACxB,MAAM,KAAK,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;QAE5F,MAAM,SAAS,GAAG,EAAE,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC;QAChD,IAAI,SAAS;YAAE,OAAO,KAAK,CAAC,SAAS,CAAC,CAAC;QAEvC,MAAM,UAAU,GAAG,EAAE,CAAC,YAAY,CAAC,iBAAiB,CAAC,CAAC;QACtD,IAAI,UAAU,EAAE,CAAC;YACf,MAAM,MAAM,GAAG,UAAU;iBACtB,KAAK,CAAC,KAAK,CAAC;iBACZ,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE;gBACV,MAAM,IAAI,GAAG,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC;gBACzC,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC;YACtC,CAAC,CAAC;iBACD,IAAI,CAAC,GAAG,CAAC,CAAC;YACb,IAAI,KAAK,CAAC,MAAM,CAAC;gBAAE,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC;QAC1C,CAAC;QAED,MAAM,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC;QACrC,IAAI,GAAG,KAAK,OAAO,IAAI,GAAG,KAAK,UAAU,IAAI,GAAG,KAAK,QAAQ,EAAE,CAAC;YAC9D,MAAM,KAAK,GAAG,EAAE,CAAC,MAAM,IAAI,EAAE,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC;YAChF,IAAI,KAAK,CAAC,KAAK,CAAC;gBAAE,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC;YACtC,IAAI,GAAG,KAAK,OAAO,IAAI,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,CAAC;gBACzF,OAAO,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC;YACzB,CAAC;YACD,MAAM,WAAW,GAAG,EAAE,CAAC,YAAY,CAAC,aAAa,CAAC,CAAC;YACnD,IAAI,WAAW;gBAAE,OAAO,KAAK,CAAC,WAAW,CAAC,CAAC;YAC3C,MAAM,QAAQ,GAAG,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;YACzC,IAAI,QAAQ;gBAAE,OAAO,KAAK,CAAC,QAAQ,CAAC,CAAC;QACvC,CAAC;QAED,MAAM,KAAK,GAAG,EAAE,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;QACvC,MAAM,IAAI,GAAG,KAAK,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC;QACnC,OAAO,IAAI,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC;IAC9B,CAAC;IAED,SAAS,cAAc,CAAC,EAAE;QACxB,MAAM,MAAM,GAAG,EAAE,CAAC,YAAY,CAAC,aAAa,CAAC,IAAI,EAAE,CAAC,YAAY,CAAC,cAAc,CAAC,CAAC;QACjF,IAAI,MAAM;YAAE,OAAO,gBAAgB,GAAG,MAAM,GAAG,IAAI,CAAC;QACpD,IAAI,EAAE,CAAC,EAAE;YAAE,OAAO,EAAE,CAAC,OAAO,CAAC,WAAW,EAAE,GAAG,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;QACrE,MAAM,QAAQ,GAAG,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;QACzC,IAAI,QAAQ;YAAE,OAAO,EAAE,CAAC,OAAO,CAAC,WAAW,EAAE,GAAG,SAAS,GAAG,QAAQ,GAAG,IAAI,CAAC;QAE5E,MAAM,KAAK,GAAG,EAAE,CAAC;QACjB,IAAI,IAAI,GAAG,EAAE,CAAC;QACd,OAAO,IAAI,IAAI,IAAI,KAAK,QAAQ,CAAC,IAAI,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC1D,IAAI,IAAI,CAAC,EAAE,EAAE,CAAC;gBACZ,KAAK,CAAC,OAAO,CAAC,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;gBACzC,MAAM;YACR,CAAC;YACD,IAAI,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC;YACtC,MAAM,MAAM,GAAG,IAAI,CAAC,aAAa,CAAC;YAClC,IAAI,MAAM,EAAE,CAAC;gBACX,MAAM,OAAO,GAAG,IAAI,CAAC;gBACrB,MAAM,OAAO,GAAG,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CACzC,MAAM,CAAC,QAAQ,EACf,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,OAAO,CAAC,OAAO,CACrC,CAAC;gBACF,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;oBAAE,IAAI,IAAI,eAAe,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC;YACzF,CAAC;YACD,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YACpB,IAAI,GAAG,MAAM,CAAC;QAChB,CAAC;QACD,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC3B,CAAC;IAED,MAAM,aAAa,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,KAAK,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAClI,MAAM,cAAc,GAAG,CAAC,MAAM,EAAE,YAAY,EAAE,QAAQ,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAE9H,SAAS,KAAK,CAAC,CAAC;QACd,OAAO,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAC/E,CAAC;IAED,gFAAgF;IAChF,yEAAyE;IACzE,2EAA2E;IAC3E,qEAAqE;IACrE,SAAS,OAAO,CAAC,EAAE;QACjB,IAAI,QAAQ,GAAG,EAAE,CAAC;QAClB,IAAI,IAAI,GAAG,EAAE,CAAC,aAAa,CAAC;QAC5B,OAAO,IAAI,IAAI,IAAI,KAAK,QAAQ,CAAC,IAAI,EAAE,CAAC;YACtC,MAAM,QAAQ,GAAG,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;YAC3C,IAAI,QAAQ,IAAI,cAAc,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;gBACxD,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC,IAAI,QAAQ,CAAC;gBAC9D,MAAM;YACR,CAAC;YACD,IAAI,aAAa,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;gBAChC,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC,IAAI,aAAa,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;gBACjF,MAAM;YACR,CAAC;YACD,IAAI,GAAG,IAAI,CAAC,aAAa,CAAC;QAC5B,CAAC;QAED,+DAA+D;QAC/D,IAAI,OAAO,GAAG,EAAE,CAAC;QACjB,MAAM,QAAQ,GAAG,QAAQ,CAAC,gBAAgB,CAAC,0CAA0C,CAAC,CAAC;QACvF,KAAK,IAAI,CAAC,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YAC9C,MAAM,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;YACtB,MAAM,GAAG,GAAG,CAAC,CAAC,uBAAuB,CAAC,EAAE,CAAC,CAAC;YAC1C,IAAI,GAAG,GAAG,IAAI,CAAC,2BAA2B,EAAE,CAAC;gBAC3C,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;gBAC/B,IAAI,OAAO;oBAAE,MAAM;YACrB,CAAC;QACH,CAAC;QAED,IAAI,QAAQ,IAAI,OAAO;YAAE,OAAO,QAAQ,GAAG,KAAK,GAAG,OAAO,CAAC;QAC3D,OAAO,QAAQ,IAAI,OAAO,IAAI,EAAE,CAAC;IACnC,CAAC;IAED,MAAM,IAAI,GAAG,EAAE,CAAC;IAChB,MAAM,QAAQ,GAAG,EAAE,CAAC;IACpB,MAAM,KAAK,GAAG,QAAQ,CAAC,gBAAgB,CAAC,oBAAoB,CAAC,CAAC;IAC9D,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,MAAM,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACpB,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;YAAE,SAAS;QAC7B,MAAM,QAAQ,GAAG,cAAc,CAAC,EAAE,CAAC,CAAC;QACpC,IAAI,IAAI,CAAC,QAAQ,CAAC;YAAE,SAAS;QAC7B,IAAI,CAAC,QAAQ,CAAC,GAAG,IAAI,CAAC;QACtB,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,cAAc,CAAC,EAAE,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IACxG,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Semantic matching + mapping diff — shared by the Playwright healer and by the
|
|
3
|
+
* zero-install web flow on ia-qa.com.
|
|
4
|
+
*
|
|
5
|
+
* Plain, dependency-free JS so the browser can import it as-is. Keep it that way:
|
|
6
|
+
* if the healer and the web tool ever compute different matches, a selector that
|
|
7
|
+
* ia-qa.com reports as "auto-healable" would still fail in CI, which is worse
|
|
8
|
+
* than reporting nothing.
|
|
9
|
+
*/
|
|
10
|
+
export function normalize(s: any): string;
|
|
11
|
+
/** Sørensen–Dice similarity over character bigrams. */
|
|
12
|
+
export function diceSimilarity(a: any, b: any): number;
|
|
13
|
+
/**
|
|
14
|
+
* Resolve `target` against live `candidates`. Returns one of:
|
|
15
|
+
* - { matched: true, selector, score, matchType, name, disambiguatedBy? }
|
|
16
|
+
* - { ambiguous: true, count } — several equally-good elements; refuse to
|
|
17
|
+
* guess (healing the wrong one is worse than failing). A human decides.
|
|
18
|
+
* - null — no candidate matches.
|
|
19
|
+
*
|
|
20
|
+
* Disambiguation: when role + accessible name collide (many "Delete" buttons),
|
|
21
|
+
* the element's `context` (nearest landmark + section heading) breaks the tie.
|
|
22
|
+
* If context can't reduce it to one, we return `ambiguous` rather than pick.
|
|
23
|
+
*/
|
|
24
|
+
export function matchElement(target: any, candidates: any): {
|
|
25
|
+
ambiguous: boolean;
|
|
26
|
+
count: any;
|
|
27
|
+
matched?: undefined;
|
|
28
|
+
selector?: undefined;
|
|
29
|
+
score?: undefined;
|
|
30
|
+
matchType?: undefined;
|
|
31
|
+
name?: undefined;
|
|
32
|
+
disambiguatedBy?: undefined;
|
|
33
|
+
} | {
|
|
34
|
+
matched: boolean;
|
|
35
|
+
selector: any;
|
|
36
|
+
score: any;
|
|
37
|
+
matchType: string;
|
|
38
|
+
name: any;
|
|
39
|
+
disambiguatedBy: string | undefined;
|
|
40
|
+
ambiguous?: undefined;
|
|
41
|
+
count?: undefined;
|
|
42
|
+
} | {
|
|
43
|
+
matched: boolean;
|
|
44
|
+
selector: any;
|
|
45
|
+
score: any;
|
|
46
|
+
matchType: string;
|
|
47
|
+
name: any;
|
|
48
|
+
ambiguous?: undefined;
|
|
49
|
+
count?: undefined;
|
|
50
|
+
disambiguatedBy?: undefined;
|
|
51
|
+
} | null;
|
|
52
|
+
/** Healer-facing shim: selector only when unambiguously matched, else null. */
|
|
53
|
+
export function heuristicMatch(target: any, candidates: any): any;
|
|
54
|
+
/**
|
|
55
|
+
* Diff a baseline mapping against a current one and produce a CI-style verdict.
|
|
56
|
+
*
|
|
57
|
+
* Per baseline element:
|
|
58
|
+
* - `ok` — selector still resolves and the accessible name is unchanged
|
|
59
|
+
* - `renamed` — selector still resolves but the accessible name changed. The
|
|
60
|
+
* test still PASSES, but this is a *content* drift (i18n / copy /
|
|
61
|
+
* a real product change) a human should eyeball — opposite of a
|
|
62
|
+
* structural break.
|
|
63
|
+
* - `healable` — selector is gone, but role + name (+ context) still identify
|
|
64
|
+
* exactly one element → deterministic old→new rewrite
|
|
65
|
+
* - `ambiguous` — selector is gone and several elements match equally; healing
|
|
66
|
+
* would be a guess, so we refuse and hand it to a human
|
|
67
|
+
* - `lost` — selector is gone and nothing matches: the test will hard-fail
|
|
68
|
+
* - `added` — element present now, absent from the baseline
|
|
69
|
+
*
|
|
70
|
+
* Verdict (drives the CI exit code): BLOCK if anything is lost or ambiguous,
|
|
71
|
+
* FIX if anything is only healable, else PASS (renamed does not fail the gate —
|
|
72
|
+
* the test still runs — but is surfaced for review).
|
|
73
|
+
*/
|
|
74
|
+
export function diffMappings(before: any, after: any): {
|
|
75
|
+
verdict: string;
|
|
76
|
+
counts: {
|
|
77
|
+
total: any;
|
|
78
|
+
ok: any;
|
|
79
|
+
renamed: any;
|
|
80
|
+
healable: any;
|
|
81
|
+
ambiguous: any;
|
|
82
|
+
lost: any;
|
|
83
|
+
added: any;
|
|
84
|
+
};
|
|
85
|
+
rows: any;
|
|
86
|
+
added: any;
|
|
87
|
+
};
|
|
88
|
+
export const FUZZY_THRESHOLD: 0.6;
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Semantic matching + mapping diff — shared by the Playwright healer and by the
|
|
4
|
+
* zero-install web flow on ia-qa.com.
|
|
5
|
+
*
|
|
6
|
+
* Plain, dependency-free JS so the browser can import it as-is. Keep it that way:
|
|
7
|
+
* if the healer and the web tool ever compute different matches, a selector that
|
|
8
|
+
* ia-qa.com reports as "auto-healable" would still fail in CI, which is worse
|
|
9
|
+
* than reporting nothing.
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.FUZZY_THRESHOLD = void 0;
|
|
13
|
+
exports.normalize = normalize;
|
|
14
|
+
exports.diceSimilarity = diceSimilarity;
|
|
15
|
+
exports.matchElement = matchElement;
|
|
16
|
+
exports.heuristicMatch = heuristicMatch;
|
|
17
|
+
exports.diffMappings = diffMappings;
|
|
18
|
+
function normalize(s) {
|
|
19
|
+
return (s == null ? '' : String(s)).toLowerCase().trim().replace(/\s+/g, ' ');
|
|
20
|
+
}
|
|
21
|
+
/** Sørensen–Dice similarity over character bigrams. */
|
|
22
|
+
function diceSimilarity(a, b) {
|
|
23
|
+
if (!a || !b)
|
|
24
|
+
return 0;
|
|
25
|
+
if (a === b)
|
|
26
|
+
return 1;
|
|
27
|
+
if (a.length < 2 || b.length < 2)
|
|
28
|
+
return 0;
|
|
29
|
+
const bigrams = (s) => {
|
|
30
|
+
const map = new Map();
|
|
31
|
+
for (let i = 0; i < s.length - 1; i++) {
|
|
32
|
+
const bg = s.slice(i, i + 2);
|
|
33
|
+
map.set(bg, (map.get(bg) || 0) + 1);
|
|
34
|
+
}
|
|
35
|
+
return map;
|
|
36
|
+
};
|
|
37
|
+
const aBigrams = bigrams(a);
|
|
38
|
+
const bBigrams = bigrams(b);
|
|
39
|
+
let overlap = 0;
|
|
40
|
+
for (const [bg, count] of aBigrams) {
|
|
41
|
+
overlap += Math.min(count, bBigrams.get(bg) || 0);
|
|
42
|
+
}
|
|
43
|
+
return (2 * overlap) / (a.length - 1 + b.length - 1);
|
|
44
|
+
}
|
|
45
|
+
exports.FUZZY_THRESHOLD = 0.6;
|
|
46
|
+
/**
|
|
47
|
+
* Resolve `target` against live `candidates`. Returns one of:
|
|
48
|
+
* - { matched: true, selector, score, matchType, name, disambiguatedBy? }
|
|
49
|
+
* - { ambiguous: true, count } — several equally-good elements; refuse to
|
|
50
|
+
* guess (healing the wrong one is worse than failing). A human decides.
|
|
51
|
+
* - null — no candidate matches.
|
|
52
|
+
*
|
|
53
|
+
* Disambiguation: when role + accessible name collide (many "Delete" buttons),
|
|
54
|
+
* the element's `context` (nearest landmark + section heading) breaks the tie.
|
|
55
|
+
* If context can't reduce it to one, we return `ambiguous` rather than pick.
|
|
56
|
+
*/
|
|
57
|
+
function matchElement(target, candidates) {
|
|
58
|
+
const sameRole = candidates.filter((c) => c.role === target.role);
|
|
59
|
+
const wanted = normalize(target.name);
|
|
60
|
+
const wantedCtx = normalize(target.context);
|
|
61
|
+
const exact = sameRole.filter((c) => normalize(c.name) === wanted && wanted !== '');
|
|
62
|
+
const exactPick = disambiguate(exact, wantedCtx);
|
|
63
|
+
if (exactPick.one) {
|
|
64
|
+
return { matched: true, selector: exactPick.one.selector, score: 1, matchType: 'exact', name: exactPick.one.name, disambiguatedBy: exactPick.by };
|
|
65
|
+
}
|
|
66
|
+
if (exactPick.ambiguous)
|
|
67
|
+
return { ambiguous: true, count: exact.length };
|
|
68
|
+
const scored = sameRole
|
|
69
|
+
.map((c) => ({ c, score: diceSimilarity(normalize(c.name), wanted) }))
|
|
70
|
+
.filter((x) => x.score >= exports.FUZZY_THRESHOLD)
|
|
71
|
+
.sort((a, b) => b.score - a.score);
|
|
72
|
+
if (scored.length === 0)
|
|
73
|
+
return null;
|
|
74
|
+
const top = scored[0].score;
|
|
75
|
+
const tied = scored.filter((x) => top - x.score < 0.05).map((x) => x.c);
|
|
76
|
+
const fuzzyPick = disambiguate(tied, wantedCtx);
|
|
77
|
+
if (fuzzyPick.one) {
|
|
78
|
+
const chosen = scored.find((x) => x.c === fuzzyPick.one);
|
|
79
|
+
return { matched: true, selector: fuzzyPick.one.selector, score: chosen.score, matchType: 'fuzzy', name: fuzzyPick.one.name, disambiguatedBy: fuzzyPick.by };
|
|
80
|
+
}
|
|
81
|
+
if (fuzzyPick.ambiguous)
|
|
82
|
+
return { ambiguous: true, count: tied.length };
|
|
83
|
+
return { matched: true, selector: scored[0].c.selector, score: top, matchType: 'fuzzy', name: scored[0].c.name };
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Reduce a set of same-name candidates to one.
|
|
87
|
+
* - 0 → { one: null }
|
|
88
|
+
* - 1 → { one }
|
|
89
|
+
* - >1 → try context: exactly one shares the target context → { one, by:'context' };
|
|
90
|
+
* otherwise { ambiguous: true }.
|
|
91
|
+
*/
|
|
92
|
+
function disambiguate(cands, wantedCtx) {
|
|
93
|
+
if (cands.length === 0)
|
|
94
|
+
return { one: null };
|
|
95
|
+
if (cands.length === 1)
|
|
96
|
+
return { one: cands[0] };
|
|
97
|
+
if (wantedCtx) {
|
|
98
|
+
const byCtx = cands.filter((c) => normalize(c.context) === wantedCtx);
|
|
99
|
+
if (byCtx.length === 1)
|
|
100
|
+
return { one: byCtx[0], by: 'context' };
|
|
101
|
+
}
|
|
102
|
+
return { ambiguous: true };
|
|
103
|
+
}
|
|
104
|
+
/** Healer-facing shim: selector only when unambiguously matched, else null. */
|
|
105
|
+
function heuristicMatch(target, candidates) {
|
|
106
|
+
const match = matchElement(target, candidates);
|
|
107
|
+
return match && match.matched ? match.selector : null;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Diff a baseline mapping against a current one and produce a CI-style verdict.
|
|
111
|
+
*
|
|
112
|
+
* Per baseline element:
|
|
113
|
+
* - `ok` — selector still resolves and the accessible name is unchanged
|
|
114
|
+
* - `renamed` — selector still resolves but the accessible name changed. The
|
|
115
|
+
* test still PASSES, but this is a *content* drift (i18n / copy /
|
|
116
|
+
* a real product change) a human should eyeball — opposite of a
|
|
117
|
+
* structural break.
|
|
118
|
+
* - `healable` — selector is gone, but role + name (+ context) still identify
|
|
119
|
+
* exactly one element → deterministic old→new rewrite
|
|
120
|
+
* - `ambiguous` — selector is gone and several elements match equally; healing
|
|
121
|
+
* would be a guess, so we refuse and hand it to a human
|
|
122
|
+
* - `lost` — selector is gone and nothing matches: the test will hard-fail
|
|
123
|
+
* - `added` — element present now, absent from the baseline
|
|
124
|
+
*
|
|
125
|
+
* Verdict (drives the CI exit code): BLOCK if anything is lost or ambiguous,
|
|
126
|
+
* FIX if anything is only healable, else PASS (renamed does not fail the gate —
|
|
127
|
+
* the test still runs — but is surfaced for review).
|
|
128
|
+
*/
|
|
129
|
+
function diffMappings(before, after) {
|
|
130
|
+
const beforeElements = (before && before.elements) || [];
|
|
131
|
+
const afterElements = (after && after.elements) || [];
|
|
132
|
+
const afterBySelector = new Map(afterElements.map((e) => [e.selector, e]));
|
|
133
|
+
const beforeSelectors = new Set(beforeElements.map((e) => e.selector));
|
|
134
|
+
const rows = beforeElements.map((el) => {
|
|
135
|
+
const still = afterBySelector.get(el.selector);
|
|
136
|
+
if (still) {
|
|
137
|
+
if (normalize(still.name) === normalize(el.name)) {
|
|
138
|
+
return { status: 'ok', role: el.role, name: el.name, selector: el.selector, context: el.context };
|
|
139
|
+
}
|
|
140
|
+
return { status: 'renamed', role: el.role, name: el.name, selector: el.selector, context: el.context, newName: still.name };
|
|
141
|
+
}
|
|
142
|
+
const match = matchElement(el, afterElements);
|
|
143
|
+
if (match && match.matched) {
|
|
144
|
+
return {
|
|
145
|
+
status: 'healable',
|
|
146
|
+
role: el.role,
|
|
147
|
+
name: el.name,
|
|
148
|
+
selector: el.selector,
|
|
149
|
+
context: el.context,
|
|
150
|
+
healedSelector: match.selector,
|
|
151
|
+
healedName: match.name,
|
|
152
|
+
score: match.score,
|
|
153
|
+
matchType: match.matchType,
|
|
154
|
+
disambiguatedBy: match.disambiguatedBy,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
if (match && match.ambiguous) {
|
|
158
|
+
return { status: 'ambiguous', role: el.role, name: el.name, selector: el.selector, context: el.context, candidateCount: match.count };
|
|
159
|
+
}
|
|
160
|
+
return { status: 'lost', role: el.role, name: el.name, selector: el.selector, context: el.context };
|
|
161
|
+
});
|
|
162
|
+
const healedSelectors = new Set(rows.filter((r) => r.status === 'healable').map((r) => r.healedSelector));
|
|
163
|
+
const added = afterElements
|
|
164
|
+
.filter((el) => !beforeSelectors.has(el.selector) && !healedSelectors.has(el.selector))
|
|
165
|
+
.map((el) => ({ status: 'added', role: el.role, name: el.name, selector: el.selector, context: el.context }));
|
|
166
|
+
const count = (s) => rows.filter((r) => r.status === s).length;
|
|
167
|
+
const counts = {
|
|
168
|
+
total: beforeElements.length,
|
|
169
|
+
ok: count('ok'),
|
|
170
|
+
renamed: count('renamed'),
|
|
171
|
+
healable: count('healable'),
|
|
172
|
+
ambiguous: count('ambiguous'),
|
|
173
|
+
lost: count('lost'),
|
|
174
|
+
added: added.length,
|
|
175
|
+
};
|
|
176
|
+
const verdict = counts.lost > 0 || counts.ambiguous > 0 ? 'BLOCK' : counts.healable > 0 ? 'FIX' : 'PASS';
|
|
177
|
+
return { verdict, counts, rows, added };
|
|
178
|
+
}
|
|
179
|
+
//# sourceMappingURL=match.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"match.js","sourceRoot":"","sources":["../../src/browser/match.js"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;;AAEH,8BAEC;AAGD,wCAmBC;AAeD,oCA2BC;AAoBD,wCAGC;AAsBD,oCAyDC;AAxKD,SAAgB,SAAS,CAAC,CAAC;IACzB,OAAO,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAChF,CAAC;AAED,uDAAuD;AACvD,SAAgB,cAAc,CAAC,CAAC,EAAE,CAAC;IACjC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC;QAAE,OAAO,CAAC,CAAC;IACvB,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IACtB,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,CAAC,CAAC;IAC3C,MAAM,OAAO,GAAG,CAAC,CAAC,EAAE,EAAE;QACpB,MAAM,GAAG,GAAG,IAAI,GAAG,EAAE,CAAC;QACtB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YACtC,MAAM,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;YAC7B,GAAG,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QACtC,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC,CAAC;IACF,MAAM,QAAQ,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAC5B,MAAM,QAAQ,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAC5B,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,KAAK,MAAM,CAAC,EAAE,EAAE,KAAK,CAAC,IAAI,QAAQ,EAAE,CAAC;QACnC,OAAO,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;IACpD,CAAC;IACD,OAAO,CAAC,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,GAAG,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AACvD,CAAC;AAEY,QAAA,eAAe,GAAG,GAAG,CAAC;AAEnC;;;;;;;;;;GAUG;AACH,SAAgB,YAAY,CAAC,MAAM,EAAE,UAAU;IAC7C,MAAM,QAAQ,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC;IAClE,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACtC,MAAM,SAAS,GAAG,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAE5C,MAAM,KAAK,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,MAAM,IAAI,MAAM,KAAK,EAAE,CAAC,CAAC;IACpF,MAAM,SAAS,GAAG,YAAY,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;IACjD,IAAI,SAAS,CAAC,GAAG,EAAE,CAAC;QAClB,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,CAAC,GAAG,CAAC,QAAQ,EAAE,KAAK,EAAE,CAAC,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,CAAC,GAAG,CAAC,IAAI,EAAE,eAAe,EAAE,SAAS,CAAC,EAAE,EAAE,CAAC;IACpJ,CAAC;IACD,IAAI,SAAS,CAAC,SAAS;QAAE,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC;IAEzE,MAAM,MAAM,GAAG,QAAQ;SACpB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,cAAc,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;SACrE,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,uBAAe,CAAC;SACzC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;IACrC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAErC,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;IAC5B,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACxE,MAAM,SAAS,GAAG,YAAY,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;IAChD,IAAI,SAAS,CAAC,GAAG,EAAE,CAAC;QAClB,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,GAAG,CAAC,CAAC;QACzD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,CAAC,GAAG,CAAC,QAAQ,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,CAAC,GAAG,CAAC,IAAI,EAAE,eAAe,EAAE,SAAS,CAAC,EAAE,EAAE,CAAC;IAC/J,CAAC;IACD,IAAI,SAAS,CAAC,SAAS;QAAE,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC;IACxE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;AACnH,CAAC;AAED;;;;;;GAMG;AACH,SAAS,YAAY,CAAC,KAAK,EAAE,SAAS;IACpC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC;IAC7C,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;IACjD,IAAI,SAAS,EAAE,CAAC;QACd,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,SAAS,CAAC,CAAC;QACtE,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,SAAS,EAAE,CAAC;IAClE,CAAC;IACD,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;AAC7B,CAAC;AAED,+EAA+E;AAC/E,SAAgB,cAAc,CAAC,MAAM,EAAE,UAAU;IAC/C,MAAM,KAAK,GAAG,YAAY,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IAC/C,OAAO,KAAK,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC;AACxD,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,SAAgB,YAAY,CAAC,MAAM,EAAE,KAAK;IACxC,MAAM,cAAc,GAAG,CAAC,MAAM,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;IACzD,MAAM,aAAa,GAAG,CAAC,KAAK,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;IACtD,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3E,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;IAEvE,MAAM,IAAI,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE;QACrC,MAAM,KAAK,GAAG,eAAe,CAAC,GAAG,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC;QAC/C,IAAI,KAAK,EAAE,CAAC;YACV,IAAI,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,SAAS,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;gBACjD,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,QAAQ,EAAE,EAAE,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,CAAC,OAAO,EAAE,CAAC;YACpG,CAAC;YACD,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,QAAQ,EAAE,EAAE,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC;QAC9H,CAAC;QACD,MAAM,KAAK,GAAG,YAAY,CAAC,EAAE,EAAE,aAAa,CAAC,CAAC;QAC9C,IAAI,KAAK,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;YAC3B,OAAO;gBACL,MAAM,EAAE,UAAU;gBAClB,IAAI,EAAE,EAAE,CAAC,IAAI;gBACb,IAAI,EAAE,EAAE,CAAC,IAAI;gBACb,QAAQ,EAAE,EAAE,CAAC,QAAQ;gBACrB,OAAO,EAAE,EAAE,CAAC,OAAO;gBACnB,cAAc,EAAE,KAAK,CAAC,QAAQ;gBAC9B,UAAU,EAAE,KAAK,CAAC,IAAI;gBACtB,KAAK,EAAE,KAAK,CAAC,KAAK;gBAClB,SAAS,EAAE,KAAK,CAAC,SAAS;gBAC1B,eAAe,EAAE,KAAK,CAAC,eAAe;aACvC,CAAC;QACJ,CAAC;QACD,IAAI,KAAK,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC;YAC7B,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,QAAQ,EAAE,EAAE,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,CAAC,OAAO,EAAE,cAAc,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC;QACxI,CAAC;QACD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,QAAQ,EAAE,EAAE,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,CAAC,OAAO,EAAE,CAAC;IACtG,CAAC,CAAC,CAAC;IAEH,MAAM,eAAe,GAAG,IAAI,GAAG,CAC7B,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,cAAc,CAAC,CACzE,CAAC;IACF,MAAM,KAAK,GAAG,aAAa;SACxB,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,eAAe,CAAC,GAAG,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC;SACtF,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,QAAQ,EAAE,EAAE,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;IAEhH,MAAM,KAAK,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC;IAC/D,MAAM,MAAM,GAAG;QACb,KAAK,EAAE,cAAc,CAAC,MAAM;QAC5B,EAAE,EAAE,KAAK,CAAC,IAAI,CAAC;QACf,OAAO,EAAE,KAAK,CAAC,SAAS,CAAC;QACzB,QAAQ,EAAE,KAAK,CAAC,UAAU,CAAC;QAC3B,SAAS,EAAE,KAAK,CAAC,WAAW,CAAC;QAC7B,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC;QACnB,KAAK,EAAE,KAAK,CAAC,MAAM;KACpB,CAAC;IAEF,MAAM,OAAO,GACX,MAAM,CAAC,IAAI,GAAG,CAAC,IAAI,MAAM,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC;IAE3F,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;AAC1C,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ia-qa-heal diff <baseline.json> <current.json>` — CI-gate verdict.
|
|
3
|
+
*
|
|
4
|
+
* Reads two mappings (produced by `ia-qa-heal map` or the browser snippet on
|
|
5
|
+
* ia-qa.com) and reports which selectors survived, which the runtime healer
|
|
6
|
+
* would recover, and which will hard-fail. Same engine as the web tool.
|
|
7
|
+
*
|
|
8
|
+
* Exit codes (so a pipeline can gate on it):
|
|
9
|
+
* 0 — PASS, or FIX without `--strict`
|
|
10
|
+
* 1 — BLOCK (a selector broke with no semantic match), or FIX with `--strict`
|
|
11
|
+
* 2 — bad usage / unreadable input
|
|
12
|
+
*/
|
|
13
|
+
export declare function runDiff(args: string[]): Promise<void>;
|