@ia-qa/self-healing 1.7.7 → 1.7.10
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 +36 -0
- package/TUTORIAL.md +45 -1
- package/dist/cli/args.js +1 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/explain.d.ts +45 -0
- package/dist/cli/explain.js +302 -0
- package/dist/cli/explain.js.map +1 -0
- package/dist/cli/index.js +18 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/explain.d.ts +225 -0
- package/dist/explain.js +373 -0
- package/dist/explain.js.map +1 -0
- package/dist/ingest.d.ts +13 -0
- package/dist/ingest.js +215 -7
- package/dist/ingest.js.map +1 -1
- package/dist/mcp/server.d.ts +56 -0
- package/dist/mcp/server.js +73 -0
- package/dist/mcp/server.js.map +1 -1
- package/package.json +1 -1
- package/skills/ia-qa-heal/SKILL.md +10 -1
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { Usage } from './ingest';
|
|
2
|
+
/**
|
|
3
|
+
* The core of `ia-qa-heal explain` — turn a test FAILURE into a drift verdict.
|
|
4
|
+
*
|
|
5
|
+
* Every other verb in this package starts from a *contract*: map, baseline, diff.
|
|
6
|
+
* Nobody arrives holding a contract. They arrive holding a red build and an error
|
|
7
|
+
* message, and until now there was no door for that. `explain` is the door: it takes
|
|
8
|
+
* whatever the runner printed, works out which locator the failure is about, and asks
|
|
9
|
+
* the existing two-moment machinery what happened to it.
|
|
10
|
+
*
|
|
11
|
+
* Two rules shape everything below, and both exist to keep this from becoming the
|
|
12
|
+
* guessing engine it would otherwise be:
|
|
13
|
+
*
|
|
14
|
+
* 1. **It never parses a runner's grammar.** Not `NoSuchElementException`, not
|
|
15
|
+
* `Timed out retrying after 4000ms`, not `waiting for locator`. That list never ends
|
|
16
|
+
* and rots at every upstream release. Instead the failure text is intersected with
|
|
17
|
+
* the locator inventory `ingest` already built by reading the SOURCE — so the
|
|
18
|
+
* question is never "what does this runner's error mean" but "does this text quote a
|
|
19
|
+
* string my suite is known to use as a locator". That is framework-agnostic and
|
|
20
|
+
* language-agnostic for free, and it hands back `file:line` as a side effect.
|
|
21
|
+
*
|
|
22
|
+
* 2. **The contract is the arbiter, never the extractor.** Bending the inventory
|
|
23
|
+
* lookup (see below) means a stray key can now be read; the guard is that a matched
|
|
24
|
+
* string still has to be judged against the mapping before it can produce anything
|
|
25
|
+
* but BLOCK. A bad extraction therefore degrades to "I cannot tell", never to a
|
|
26
|
+
* wrong rewrite.
|
|
27
|
+
*
|
|
28
|
+
* A note on rule 2, because it bends a documented invariant. `ingest.ts` says: *every
|
|
29
|
+
* consumer of `usage.selectors` looks it up by a selector taken from the contract, so a
|
|
30
|
+
* key matching no element is never read* — that is what licenses its permissive Page
|
|
31
|
+
* Object pattern. This file is the first consumer to look up by a string taken from a
|
|
32
|
+
* *message*. False-positive keys become reachable here, and the containment tier below
|
|
33
|
+
* is where that is most likely. It is bounded on purpose: such a candidate finds
|
|
34
|
+
* nothing in the contract and comes back `not-measured` / `not-in-contract` — BLOCK,
|
|
35
|
+
* for a human, which is exactly what "I matched something meaningless" should cost.
|
|
36
|
+
*
|
|
37
|
+
* This file is pure: no fs, no config, no console, no browser. It is handed the
|
|
38
|
+
* failure text, the inventory, and an already-computed diff. `cli/explain.ts` does the
|
|
39
|
+
* I/O, exactly as `checks.ts` / `cli/check.ts` are split.
|
|
40
|
+
*/
|
|
41
|
+
/** One failing test, as read from a JUnit file or from raw text. */
|
|
42
|
+
export interface FailureInput {
|
|
43
|
+
/** The test name, when the source gave one. */
|
|
44
|
+
test?: string;
|
|
45
|
+
/** The file the runner attributed it to, when it said. Not the locator's file. */
|
|
46
|
+
file?: string;
|
|
47
|
+
/** message + detail, concatenated — the haystack. */
|
|
48
|
+
text: string;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* How a locator was recovered from the failure text.
|
|
52
|
+
*
|
|
53
|
+
* `quoted` is an exact match against a string the message put in quotes — the shape
|
|
54
|
+
* essentially every runner uses, and effectively certain. `contained` is the fallback
|
|
55
|
+
* for messages that quote nothing; it is reported as such, because a reader deciding
|
|
56
|
+
* whether to trust a finding needs to know which of the two produced it.
|
|
57
|
+
*/
|
|
58
|
+
export type CandidateSource = 'quoted' | 'contained';
|
|
59
|
+
export interface LocatorCandidate {
|
|
60
|
+
kind: 'selector' | 'name';
|
|
61
|
+
/** The literal as the inventory holds it — for a name, as the test file writes it. */
|
|
62
|
+
literal: string;
|
|
63
|
+
source: CandidateSource;
|
|
64
|
+
sites: Array<{
|
|
65
|
+
file: string;
|
|
66
|
+
line: number;
|
|
67
|
+
}>;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Why a locator got the verdict it got. Exported because the terminal, the JSON and the
|
|
71
|
+
* MCP tool all state it, and a fourth wording would be a fourth thing to drift.
|
|
72
|
+
*/
|
|
73
|
+
export type ExplainReason = 'resolves' | 'name-intact' | 'broken-repairable' | 'renamed-repairable' | 'rebound' | 'ambiguous' | 'gone' | 'unattributable' | 'never-bound' | 'not-in-contract' | 'not-measured';
|
|
74
|
+
export interface ExplainFinding {
|
|
75
|
+
kind: 'selector' | 'name';
|
|
76
|
+
literal: string;
|
|
77
|
+
source: CandidateSource;
|
|
78
|
+
verdict: 'PASS' | 'FIX' | 'BLOCK';
|
|
79
|
+
reason: ExplainReason;
|
|
80
|
+
/** Every place the suite writes this literal, from the inventory. */
|
|
81
|
+
sites: Array<{
|
|
82
|
+
file: string;
|
|
83
|
+
line: number;
|
|
84
|
+
}>;
|
|
85
|
+
/** Which mapped page produced the verdict, when one did. */
|
|
86
|
+
page?: string;
|
|
87
|
+
/**
|
|
88
|
+
* The rewrite to PROPOSE. Never applied here — `explain` is read-only, and the
|
|
89
|
+
* apply path stays `fix`, with all of its refusals intact.
|
|
90
|
+
*/
|
|
91
|
+
repair?: {
|
|
92
|
+
from: string;
|
|
93
|
+
to: string;
|
|
94
|
+
};
|
|
95
|
+
/** What the locator reaches now — `rebound` only, and the reason it is BLOCK. */
|
|
96
|
+
now?: string;
|
|
97
|
+
}
|
|
98
|
+
export interface ExplainReport {
|
|
99
|
+
verdict: 'PASS' | 'FIX' | 'BLOCK';
|
|
100
|
+
failuresRead: number;
|
|
101
|
+
findings: ExplainFinding[];
|
|
102
|
+
/**
|
|
103
|
+
* Quoted strings in the failure that LOOK like locators but are in no inventory.
|
|
104
|
+
* Named rather than guessed at: it usually means `ingest` has not seen the file,
|
|
105
|
+
* or the locator is built dynamically and no static scan can reach it.
|
|
106
|
+
*/
|
|
107
|
+
unmatched: string[];
|
|
108
|
+
pagesCompared: number;
|
|
109
|
+
/** Pairs the diff could not judge. A page left out is never counted as a pass. */
|
|
110
|
+
stalePages: string[];
|
|
111
|
+
}
|
|
112
|
+
export declare function unescapeXml(s: string): string;
|
|
113
|
+
/**
|
|
114
|
+
* Read the failures out of a JUnit XML report.
|
|
115
|
+
*
|
|
116
|
+
* JUnit is the input format for the same reason `--junit` is an output format here:
|
|
117
|
+
* every major runner in every language either emits it or has a reporter that does, so
|
|
118
|
+
* one parser covers pytest, Jest, Playwright, Cypress, JUnit, WebdriverIO, Robot and
|
|
119
|
+
* whatever comes next — without this package knowing any of them exist.
|
|
120
|
+
*
|
|
121
|
+
* Hand-rolled on purpose: this package ships with one runtime dependency and an XML
|
|
122
|
+
* library for four regexes would be a poor trade. There is no official JUnit XSD, so
|
|
123
|
+
* the conservative intersection is all that is safe to rely on anyway — `testcase`
|
|
124
|
+
* carrying `failure` or `error`, either self-closed or with a body.
|
|
125
|
+
*/
|
|
126
|
+
export declare function parseJUnitFailures(xml: string): FailureInput[];
|
|
127
|
+
/**
|
|
128
|
+
* Every quoted run in the text, in the three quote styles error messages use.
|
|
129
|
+
*
|
|
130
|
+
* This is the whole of the "parser": runners quote the locator they could not find —
|
|
131
|
+
* `'.btn-primary'`, `"#pay-now"`, `` `Pay now` `` — and that convention is shared far
|
|
132
|
+
* more widely than any message grammar. Newlines terminate a run so an unbalanced quote
|
|
133
|
+
* in a stack trace cannot swallow the rest of the report.
|
|
134
|
+
*
|
|
135
|
+
* The three styles are scanned INDEPENDENTLY, and that is not a detail. One alternating
|
|
136
|
+
* regex scans left to right and lets whichever quote opens first consume everything
|
|
137
|
+
* after it — so WebdriverIO's `Can't call click on element with selector ".btn-primary"
|
|
138
|
+
* because element wasn't found` pairs the apostrophes of *Can't* and *wasn't*, swallows
|
|
139
|
+
* the real locator between them, and the failure comes back with nothing found. English
|
|
140
|
+
* prose is full of apostrophes; a separate pass per style cannot lose a locator to one.
|
|
141
|
+
*
|
|
142
|
+
* The cost is over-generation — that apostrophe pair still yields its nonsense run — and
|
|
143
|
+
* it is free: every run is then looked up in the inventory, and a run that names nothing
|
|
144
|
+
* a test writes matches nothing and disappears.
|
|
145
|
+
*/
|
|
146
|
+
export declare function quotedRuns(text: string): string[];
|
|
147
|
+
/**
|
|
148
|
+
* Which inventoried locators does this failure name?
|
|
149
|
+
*
|
|
150
|
+
* Two tiers, and the second is skipped per kind as soon as the first finds anything:
|
|
151
|
+
* a message that quotes its locators has told us which they are, and adding
|
|
152
|
+
* containment hits on top of that only adds noise to an answer we already have.
|
|
153
|
+
*/
|
|
154
|
+
export declare function extractLocators(failures: FailureInput[], usage: Usage): {
|
|
155
|
+
candidates: LocatorCandidate[];
|
|
156
|
+
unmatched: string[];
|
|
157
|
+
};
|
|
158
|
+
/**
|
|
159
|
+
* The shapes this file needs out of an already-computed diff, declared structurally
|
|
160
|
+
* rather than imported from `cli/diff.ts`.
|
|
161
|
+
*
|
|
162
|
+
* Same reason `nameDrift.ts` declares `RenamedRowLike`: it keeps the judge a pure
|
|
163
|
+
* function with no import edge into the CLI layer, so a test can hand it two literals
|
|
164
|
+
* and a hand-written report without a filesystem, a config, or a browser.
|
|
165
|
+
*/
|
|
166
|
+
export interface ExplainRowLike {
|
|
167
|
+
status: string;
|
|
168
|
+
role: string;
|
|
169
|
+
name: string;
|
|
170
|
+
selector: string;
|
|
171
|
+
newName?: string;
|
|
172
|
+
healedSelector?: string;
|
|
173
|
+
}
|
|
174
|
+
export interface ExplainNameFindingLike {
|
|
175
|
+
oldName: string;
|
|
176
|
+
newName: string;
|
|
177
|
+
fixable: boolean;
|
|
178
|
+
unattributable: Array<{
|
|
179
|
+
file: string;
|
|
180
|
+
line: number;
|
|
181
|
+
}>;
|
|
182
|
+
}
|
|
183
|
+
export interface ExplainBindingLike {
|
|
184
|
+
selector: string;
|
|
185
|
+
was: string;
|
|
186
|
+
status: string;
|
|
187
|
+
now?: string;
|
|
188
|
+
healedSelector?: string;
|
|
189
|
+
}
|
|
190
|
+
export interface ExplainPageLike {
|
|
191
|
+
name: string;
|
|
192
|
+
rows: ExplainRowLike[];
|
|
193
|
+
nameDrift?: ExplainNameFindingLike[];
|
|
194
|
+
bindingDrift?: ExplainBindingLike[];
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* What the CURRENT capture saw each inventoried selector reach — `mapping/_resolved/`.
|
|
198
|
+
*
|
|
199
|
+
* Needed for the half of the answer a diff cannot give. A diff is silent about a
|
|
200
|
+
* selector that did not drift, and silence has two meanings that must never be
|
|
201
|
+
* conflated: *it still resolves* (PASS, the real product of this verb) and *nobody ever
|
|
202
|
+
* probed it* (not measured, and never a pass). Only the resolution files separate them.
|
|
203
|
+
*/
|
|
204
|
+
export type ResolutionIndex = Map<string, Array<{
|
|
205
|
+
page: string;
|
|
206
|
+
status: string;
|
|
207
|
+
}>>;
|
|
208
|
+
export declare function judgeLocators(candidates: LocatorCandidate[], pages: ExplainPageLike[], resolutions: ResolutionIndex): ExplainFinding[];
|
|
209
|
+
/**
|
|
210
|
+
* The verdict of the whole failure — the worst of its locators.
|
|
211
|
+
*
|
|
212
|
+
* PASS only when every locator resolves, and it is the answer this verb exists for:
|
|
213
|
+
* *your selectors are fine, this red build is about something else*. It says nothing
|
|
214
|
+
* about what that something else is. A re-run that passes proves "not reproducible
|
|
215
|
+
* right now", never "flaky", and this tool has not re-run anything at all.
|
|
216
|
+
*/
|
|
217
|
+
export declare function explainVerdict(findings: ExplainFinding[]): 'PASS' | 'FIX' | 'BLOCK';
|
|
218
|
+
export declare function explainHeadline(r: Pick<ExplainReport, 'verdict' | 'findings'>): string;
|
|
219
|
+
export declare function explainCounts(r: Pick<ExplainReport, 'failuresRead' | 'pagesCompared' | 'stalePages'>): string[];
|
|
220
|
+
/**
|
|
221
|
+
* The one-line "what do I do now" per reason, shared by every surface that renders a
|
|
222
|
+
* finding. Kept here so the terminal, the JSON and an agent cannot word the same
|
|
223
|
+
* verdict differently — the rule `checkHeadline` and `auditHeadline` already follow.
|
|
224
|
+
*/
|
|
225
|
+
export declare const REASON_ADVICE: Record<ExplainReason, string>;
|
package/dist/explain.js
ADDED
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.REASON_ADVICE = void 0;
|
|
4
|
+
exports.unescapeXml = unescapeXml;
|
|
5
|
+
exports.parseJUnitFailures = parseJUnitFailures;
|
|
6
|
+
exports.quotedRuns = quotedRuns;
|
|
7
|
+
exports.extractLocators = extractLocators;
|
|
8
|
+
exports.judgeLocators = judgeLocators;
|
|
9
|
+
exports.explainVerdict = explainVerdict;
|
|
10
|
+
exports.explainHeadline = explainHeadline;
|
|
11
|
+
exports.explainCounts = explainCounts;
|
|
12
|
+
const ingest_1 = require("./ingest");
|
|
13
|
+
const nameLocators_1 = require("./nameLocators");
|
|
14
|
+
/* ------------------------------------------------------------------ *
|
|
15
|
+
* JUnit input
|
|
16
|
+
* ------------------------------------------------------------------ */
|
|
17
|
+
const ENTITIES = {
|
|
18
|
+
amp: '&',
|
|
19
|
+
lt: '<',
|
|
20
|
+
gt: '>',
|
|
21
|
+
quot: '"',
|
|
22
|
+
apos: "'",
|
|
23
|
+
};
|
|
24
|
+
function unescapeXml(s) {
|
|
25
|
+
return s.replace(/&(#x?[0-9a-fA-F]+|[a-zA-Z]+);/g, (whole, body) => {
|
|
26
|
+
if (body[0] === '#') {
|
|
27
|
+
const code = body[1] === 'x' || body[1] === 'X'
|
|
28
|
+
? parseInt(body.slice(2), 16)
|
|
29
|
+
: parseInt(body.slice(1), 10);
|
|
30
|
+
return Number.isFinite(code) && code > 0 ? String.fromCodePoint(code) : whole;
|
|
31
|
+
}
|
|
32
|
+
const hit = ENTITIES[body.toLowerCase()];
|
|
33
|
+
return hit === undefined ? whole : hit;
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
function attrs(raw) {
|
|
37
|
+
const out = {};
|
|
38
|
+
const re = /([\w:.-]+)\s*=\s*(?:"([^"]*)"|'([^']*)')/g;
|
|
39
|
+
let m;
|
|
40
|
+
while ((m = re.exec(raw)) !== null) {
|
|
41
|
+
out[m[1]] = unescapeXml(m[2] !== undefined ? m[2] : m[3]);
|
|
42
|
+
}
|
|
43
|
+
return out;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Read the failures out of a JUnit XML report.
|
|
47
|
+
*
|
|
48
|
+
* JUnit is the input format for the same reason `--junit` is an output format here:
|
|
49
|
+
* every major runner in every language either emits it or has a reporter that does, so
|
|
50
|
+
* one parser covers pytest, Jest, Playwright, Cypress, JUnit, WebdriverIO, Robot and
|
|
51
|
+
* whatever comes next — without this package knowing any of them exist.
|
|
52
|
+
*
|
|
53
|
+
* Hand-rolled on purpose: this package ships with one runtime dependency and an XML
|
|
54
|
+
* library for four regexes would be a poor trade. There is no official JUnit XSD, so
|
|
55
|
+
* the conservative intersection is all that is safe to rely on anyway — `testcase`
|
|
56
|
+
* carrying `failure` or `error`, either self-closed or with a body.
|
|
57
|
+
*/
|
|
58
|
+
function parseJUnitFailures(xml) {
|
|
59
|
+
const out = [];
|
|
60
|
+
const caseRe = /<testcase\b([^>]*?)(?:\/>|>([\s\S]*?)<\/testcase\s*>)/g;
|
|
61
|
+
let c;
|
|
62
|
+
while ((c = caseRe.exec(xml)) !== null) {
|
|
63
|
+
const body = c[2];
|
|
64
|
+
if (!body)
|
|
65
|
+
continue;
|
|
66
|
+
const meta = attrs(c[1]);
|
|
67
|
+
const failRe = /<(failure|error)\b([^>]*?)(?:\/>|>([\s\S]*?)<\/\1\s*>)/g;
|
|
68
|
+
let f;
|
|
69
|
+
while ((f = failRe.exec(body)) !== null) {
|
|
70
|
+
const fa = attrs(f[2]);
|
|
71
|
+
const detail = f[3] ? unescapeXml(stripCdata(f[3])) : '';
|
|
72
|
+
const text = [fa.message ?? '', detail].filter(Boolean).join('\n');
|
|
73
|
+
if (!text.trim())
|
|
74
|
+
continue;
|
|
75
|
+
out.push({
|
|
76
|
+
test: meta.name || undefined,
|
|
77
|
+
file: meta.file || meta.classname || undefined,
|
|
78
|
+
text,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return out;
|
|
83
|
+
}
|
|
84
|
+
function stripCdata(s) {
|
|
85
|
+
return s.replace(/<!\[CDATA\[([\s\S]*?)\]\]>/g, '$1');
|
|
86
|
+
}
|
|
87
|
+
/* ------------------------------------------------------------------ *
|
|
88
|
+
* Extraction — intersection, not parsing
|
|
89
|
+
* ------------------------------------------------------------------ */
|
|
90
|
+
/**
|
|
91
|
+
* A `contained` match must be at least this long. Short literals turn a long stack
|
|
92
|
+
* trace into a lottery: `.btn` occurs inside a dozen unrelated words, and a false
|
|
93
|
+
* candidate costs a reader more than a missed one — they can always pass `--message`
|
|
94
|
+
* with the locator themselves.
|
|
95
|
+
*/
|
|
96
|
+
const MIN_CONTAINED = 5;
|
|
97
|
+
/** Beyond this, the failure is not about one locator and listing more helps nobody. */
|
|
98
|
+
const MAX_CANDIDATES = 25;
|
|
99
|
+
/**
|
|
100
|
+
* Every quoted run in the text, in the three quote styles error messages use.
|
|
101
|
+
*
|
|
102
|
+
* This is the whole of the "parser": runners quote the locator they could not find —
|
|
103
|
+
* `'.btn-primary'`, `"#pay-now"`, `` `Pay now` `` — and that convention is shared far
|
|
104
|
+
* more widely than any message grammar. Newlines terminate a run so an unbalanced quote
|
|
105
|
+
* in a stack trace cannot swallow the rest of the report.
|
|
106
|
+
*
|
|
107
|
+
* The three styles are scanned INDEPENDENTLY, and that is not a detail. One alternating
|
|
108
|
+
* regex scans left to right and lets whichever quote opens first consume everything
|
|
109
|
+
* after it — so WebdriverIO's `Can't call click on element with selector ".btn-primary"
|
|
110
|
+
* because element wasn't found` pairs the apostrophes of *Can't* and *wasn't*, swallows
|
|
111
|
+
* the real locator between them, and the failure comes back with nothing found. English
|
|
112
|
+
* prose is full of apostrophes; a separate pass per style cannot lose a locator to one.
|
|
113
|
+
*
|
|
114
|
+
* The cost is over-generation — that apostrophe pair still yields its nonsense run — and
|
|
115
|
+
* it is free: every run is then looked up in the inventory, and a run that names nothing
|
|
116
|
+
* a test writes matches nothing and disappears.
|
|
117
|
+
*/
|
|
118
|
+
function quotedRuns(text) {
|
|
119
|
+
const hits = [];
|
|
120
|
+
for (const re of [/'([^'\n]{1,200})'/g, /"([^"\n]{1,200})"/g, /`([^`\n]{1,200})`/g]) {
|
|
121
|
+
let m;
|
|
122
|
+
while ((m = re.exec(text)) !== null) {
|
|
123
|
+
if (m[1] && m[1].trim())
|
|
124
|
+
hits.push({ at: m.index, value: m[1] });
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return hits.sort((a, b) => a.at - b.at).map((h) => h.value);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Which inventoried locators does this failure name?
|
|
131
|
+
*
|
|
132
|
+
* Two tiers, and the second is skipped per kind as soon as the first finds anything:
|
|
133
|
+
* a message that quotes its locators has told us which they are, and adding
|
|
134
|
+
* containment hits on top of that only adds noise to an answer we already have.
|
|
135
|
+
*/
|
|
136
|
+
function extractLocators(failures, usage) {
|
|
137
|
+
const text = failures.map((f) => f.text).join('\n');
|
|
138
|
+
const runs = quotedRuns(text);
|
|
139
|
+
const runSet = new Set(runs);
|
|
140
|
+
const normRuns = new Set(runs.map((r) => (0, nameLocators_1.normalizeName)(r)));
|
|
141
|
+
const selectorKeys = Object.keys(usage.selectors ?? {});
|
|
142
|
+
const nameKeys = Object.keys(usage.names ?? {});
|
|
143
|
+
const selectors = [];
|
|
144
|
+
const names = [];
|
|
145
|
+
for (const key of selectorKeys) {
|
|
146
|
+
if (!runSet.has(key))
|
|
147
|
+
continue;
|
|
148
|
+
selectors.push({
|
|
149
|
+
kind: 'selector',
|
|
150
|
+
literal: key,
|
|
151
|
+
source: 'quoted',
|
|
152
|
+
sites: (usage.selectors[key] ?? []).map((s) => ({ file: s.file, line: s.line })),
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
for (const key of nameKeys) {
|
|
156
|
+
if (!normRuns.has(key))
|
|
157
|
+
continue;
|
|
158
|
+
const sites = usage.names?.[key] ?? [];
|
|
159
|
+
names.push({
|
|
160
|
+
kind: 'name',
|
|
161
|
+
// The inventory is keyed normalized, but every message a human reads must quote
|
|
162
|
+
// what is actually in their file — `"save"` when the line says `'Save'` costs a grep.
|
|
163
|
+
literal: sites[0]?.text ?? key,
|
|
164
|
+
source: 'quoted',
|
|
165
|
+
sites: sites.map((s) => ({ file: s.file, line: s.line })),
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
if (selectors.length === 0) {
|
|
169
|
+
for (const key of selectorKeys) {
|
|
170
|
+
if (key.length < MIN_CONTAINED || !text.includes(key))
|
|
171
|
+
continue;
|
|
172
|
+
selectors.push({
|
|
173
|
+
kind: 'selector',
|
|
174
|
+
literal: key,
|
|
175
|
+
source: 'contained',
|
|
176
|
+
sites: (usage.selectors[key] ?? []).map((s) => ({ file: s.file, line: s.line })),
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (names.length === 0) {
|
|
181
|
+
const normText = (0, nameLocators_1.normalizeName)(text);
|
|
182
|
+
for (const key of nameKeys) {
|
|
183
|
+
if (key.length < MIN_CONTAINED || !normText.includes(key))
|
|
184
|
+
continue;
|
|
185
|
+
const sites = usage.names?.[key] ?? [];
|
|
186
|
+
names.push({
|
|
187
|
+
kind: 'name',
|
|
188
|
+
literal: sites[0]?.text ?? key,
|
|
189
|
+
source: 'contained',
|
|
190
|
+
sites: sites.map((s) => ({ file: s.file, line: s.line })),
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
const candidates = [...dropSubsumed(selectors), ...dropSubsumed(names)].slice(0, MAX_CANDIDATES);
|
|
195
|
+
// A quoted string that reads like a selector and is in no inventory is worth naming:
|
|
196
|
+
// it is the signature of an un-ingested file or of a dynamically built locator, and
|
|
197
|
+
// both are things the user can act on. Silence there would look like "nothing found".
|
|
198
|
+
const known = new Set(selectorKeys);
|
|
199
|
+
const knownNames = new Set(nameKeys);
|
|
200
|
+
const unmatched = Array.from(new Set(runs)).filter((r) => (0, ingest_1.plausibleSelector)(r) && !known.has(r) && !knownNames.has((0, nameLocators_1.normalizeName)(r)));
|
|
201
|
+
return { candidates, unmatched };
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Drop a `contained` literal that sits inside a longer accepted one — `.btn` reported
|
|
205
|
+
* beside `.btn-primary` is one finding pretending to be two. `quoted` literals are
|
|
206
|
+
* never dropped: if the message quoted `.btn`, then `.btn` is what failed, whatever
|
|
207
|
+
* else the inventory happens to contain.
|
|
208
|
+
*/
|
|
209
|
+
function dropSubsumed(list) {
|
|
210
|
+
const sorted = [...list].sort((a, b) => b.literal.length - a.literal.length);
|
|
211
|
+
const kept = [];
|
|
212
|
+
for (const c of sorted) {
|
|
213
|
+
if (c.source === 'contained' && kept.some((k) => k.literal.includes(c.literal)))
|
|
214
|
+
continue;
|
|
215
|
+
kept.push(c);
|
|
216
|
+
}
|
|
217
|
+
return kept;
|
|
218
|
+
}
|
|
219
|
+
const HEALTHY_BINDING = new Set(['bound', 'anchored', 'outside']);
|
|
220
|
+
function judgeSelector(c, pages, resolutions) {
|
|
221
|
+
const base = { kind: 'selector', literal: c.literal, source: c.source, sites: c.sites };
|
|
222
|
+
// The two-moment verdict first: it is the only one that can see a binding that broke,
|
|
223
|
+
// and the only one that can see the worst case here — a string that now reaches a
|
|
224
|
+
// DIFFERENT element, where the test goes green and acts on the wrong thing.
|
|
225
|
+
for (const p of pages) {
|
|
226
|
+
const d = (p.bindingDrift ?? []).find((x) => x.selector === c.literal);
|
|
227
|
+
if (!d)
|
|
228
|
+
continue;
|
|
229
|
+
if (d.status === 'broken' && d.healedSelector) {
|
|
230
|
+
return { ...base, verdict: 'FIX', reason: 'broken-repairable', page: p.name,
|
|
231
|
+
repair: { from: c.literal, to: d.healedSelector } };
|
|
232
|
+
}
|
|
233
|
+
if (d.status === 'rebound') {
|
|
234
|
+
return { ...base, verdict: 'BLOCK', reason: 'rebound', page: p.name, now: d.now };
|
|
235
|
+
}
|
|
236
|
+
if (d.status === 'ambiguous') {
|
|
237
|
+
return { ...base, verdict: 'BLOCK', reason: 'ambiguous', page: p.name };
|
|
238
|
+
}
|
|
239
|
+
return { ...base, verdict: 'BLOCK', reason: 'gone', page: p.name };
|
|
240
|
+
}
|
|
241
|
+
// The literal may itself BE a contract selector (a suite written against `map`'s
|
|
242
|
+
// output, or a `data-testid` the extractor also picked). Then the element rows judge it.
|
|
243
|
+
for (const p of pages) {
|
|
244
|
+
const row = p.rows.find((r) => r.selector === c.literal);
|
|
245
|
+
if (!row)
|
|
246
|
+
continue;
|
|
247
|
+
if (row.status === 'healable' && row.healedSelector) {
|
|
248
|
+
return { ...base, verdict: 'FIX', reason: 'broken-repairable', page: p.name,
|
|
249
|
+
repair: { from: c.literal, to: row.healedSelector } };
|
|
250
|
+
}
|
|
251
|
+
if (row.status === 'ok' || row.status === 'renamed') {
|
|
252
|
+
return { ...base, verdict: 'PASS', reason: 'resolves', page: p.name };
|
|
253
|
+
}
|
|
254
|
+
if (row.status === 'rebound')
|
|
255
|
+
return { ...base, verdict: 'BLOCK', reason: 'rebound', page: p.name };
|
|
256
|
+
if (row.status === 'ambiguous')
|
|
257
|
+
return { ...base, verdict: 'BLOCK', reason: 'ambiguous', page: p.name };
|
|
258
|
+
return { ...base, verdict: 'BLOCK', reason: 'gone', page: p.name };
|
|
259
|
+
}
|
|
260
|
+
// No drift row. Did anything actually look at this string?
|
|
261
|
+
const seen = resolutions.get(c.literal);
|
|
262
|
+
if (!seen || seen.length === 0) {
|
|
263
|
+
return { ...base, verdict: 'BLOCK', reason: 'not-measured' };
|
|
264
|
+
}
|
|
265
|
+
const healthy = seen.find((s) => HEALTHY_BINDING.has(s.status));
|
|
266
|
+
if (healthy)
|
|
267
|
+
return { ...base, verdict: 'PASS', reason: 'resolves', page: healthy.page };
|
|
268
|
+
if (seen.some((s) => s.status === 'ambiguous')) {
|
|
269
|
+
return { ...base, verdict: 'BLOCK', reason: 'ambiguous', page: seen[0].page };
|
|
270
|
+
}
|
|
271
|
+
// Probed everywhere, reaches nothing anywhere — and it did not break between the two
|
|
272
|
+
// moments, so there is no surviving element to heal towards. `audit` is the verb that
|
|
273
|
+
// answers "did it ever exist", not this one.
|
|
274
|
+
return { ...base, verdict: 'BLOCK', reason: 'never-bound', page: seen[0].page };
|
|
275
|
+
}
|
|
276
|
+
function judgeName(c, pages) {
|
|
277
|
+
const base = { kind: 'name', literal: c.literal, source: c.source, sites: c.sites };
|
|
278
|
+
const key = (0, nameLocators_1.normalizeName)(c.literal);
|
|
279
|
+
for (const p of pages) {
|
|
280
|
+
const f = (p.nameDrift ?? []).find((x) => (0, nameLocators_1.normalizeName)(x.oldName) === key);
|
|
281
|
+
if (!f)
|
|
282
|
+
continue;
|
|
283
|
+
// A rewrite this tool cannot prove correct is not a rewrite. `getByText` and
|
|
284
|
+
// `cy.contains` name a string without saying what carries it, and the contract holds
|
|
285
|
+
// interactive elements only — editing one could break a test that was passing.
|
|
286
|
+
if (f.unattributable.length > 0) {
|
|
287
|
+
return { ...base, verdict: 'BLOCK', reason: 'unattributable', page: p.name };
|
|
288
|
+
}
|
|
289
|
+
if (f.fixable) {
|
|
290
|
+
return { ...base, verdict: 'FIX', reason: 'renamed-repairable', page: p.name,
|
|
291
|
+
repair: { from: c.literal, to: f.newName } };
|
|
292
|
+
}
|
|
293
|
+
return { ...base, verdict: 'BLOCK', reason: 'ambiguous', page: p.name };
|
|
294
|
+
}
|
|
295
|
+
for (const p of pages) {
|
|
296
|
+
const row = p.rows.find((r) => (0, nameLocators_1.normalizeName)(r.name) === key);
|
|
297
|
+
if (!row)
|
|
298
|
+
continue;
|
|
299
|
+
// `healable` moved the SELECTOR, not the label — a locator that names a role and a
|
|
300
|
+
// name does not care where the element sits in the DOM. For this kind, that is a pass.
|
|
301
|
+
if (row.status === 'ok' || row.status === 'healable') {
|
|
302
|
+
return { ...base, verdict: 'PASS', reason: 'name-intact', page: p.name };
|
|
303
|
+
}
|
|
304
|
+
if (row.status === 'ambiguous')
|
|
305
|
+
return { ...base, verdict: 'BLOCK', reason: 'ambiguous', page: p.name };
|
|
306
|
+
if (row.status === 'rebound')
|
|
307
|
+
return { ...base, verdict: 'BLOCK', reason: 'rebound', page: p.name };
|
|
308
|
+
// `renamed` with no nameDrift finding beside it means the inventory never saw this
|
|
309
|
+
// call site. Reporting the label as intact would be exactly wrong.
|
|
310
|
+
return { ...base, verdict: 'BLOCK', reason: row.status === 'renamed' ? 'unattributable' : 'gone', page: p.name };
|
|
311
|
+
}
|
|
312
|
+
// The label is nowhere in the compared pages. That is not a pass — it may live on a
|
|
313
|
+
// page nobody mapped, which is a coverage answer, not a drift one.
|
|
314
|
+
return { ...base, verdict: 'BLOCK', reason: 'not-in-contract' };
|
|
315
|
+
}
|
|
316
|
+
function judgeLocators(candidates, pages, resolutions) {
|
|
317
|
+
const findings = candidates.map((c) => c.kind === 'selector' ? judgeSelector(c, pages, resolutions) : judgeName(c, pages));
|
|
318
|
+
const rank = { BLOCK: 0, FIX: 1, PASS: 2 };
|
|
319
|
+
return findings.sort((a, b) => rank[a.verdict] - rank[b.verdict]);
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* The verdict of the whole failure — the worst of its locators.
|
|
323
|
+
*
|
|
324
|
+
* PASS only when every locator resolves, and it is the answer this verb exists for:
|
|
325
|
+
* *your selectors are fine, this red build is about something else*. It says nothing
|
|
326
|
+
* about what that something else is. A re-run that passes proves "not reproducible
|
|
327
|
+
* right now", never "flaky", and this tool has not re-run anything at all.
|
|
328
|
+
*/
|
|
329
|
+
function explainVerdict(findings) {
|
|
330
|
+
if (findings.some((f) => f.verdict === 'BLOCK'))
|
|
331
|
+
return 'BLOCK';
|
|
332
|
+
if (findings.some((f) => f.verdict === 'FIX'))
|
|
333
|
+
return 'FIX';
|
|
334
|
+
return 'PASS';
|
|
335
|
+
}
|
|
336
|
+
function explainHeadline(r) {
|
|
337
|
+
const n = r.findings.length;
|
|
338
|
+
const locs = `${n} locator${n === 1 ? '' : 's'}`;
|
|
339
|
+
if (r.verdict === 'PASS')
|
|
340
|
+
return `PASS · ${locs} still resolve${n === 1 ? 's' : ''} — not selector drift`;
|
|
341
|
+
if (r.verdict === 'FIX') {
|
|
342
|
+
const k = r.findings.filter((f) => f.verdict === 'FIX').length;
|
|
343
|
+
return `FIX · ${k} of ${locs} drifted, repair proposed`;
|
|
344
|
+
}
|
|
345
|
+
const k = r.findings.filter((f) => f.verdict === 'BLOCK').length;
|
|
346
|
+
return `BLOCK · ${k} of ${locs} need${k === 1 ? 's' : ''} a human`;
|
|
347
|
+
}
|
|
348
|
+
function explainCounts(r) {
|
|
349
|
+
return [
|
|
350
|
+
`${r.failuresRead} failure${r.failuresRead === 1 ? '' : 's'} read`,
|
|
351
|
+
`${r.pagesCompared} page${r.pagesCompared === 1 ? '' : 's'} compared`,
|
|
352
|
+
r.stalePages.length > 0 ? `${r.stalePages.length} stale` : '',
|
|
353
|
+
].filter(Boolean);
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* The one-line "what do I do now" per reason, shared by every surface that renders a
|
|
357
|
+
* finding. Kept here so the terminal, the JSON and an agent cannot word the same
|
|
358
|
+
* verdict differently — the rule `checkHeadline` and `auditHeadline` already follow.
|
|
359
|
+
*/
|
|
360
|
+
exports.REASON_ADVICE = {
|
|
361
|
+
resolves: 'This selector still reaches its element. The failure is not drift — look at timing, test data, or an app error.',
|
|
362
|
+
'name-intact': 'This label still exists on the mapped contract. The failure is not a rename — look elsewhere.',
|
|
363
|
+
'broken-repairable': 'The string reaches nothing, but its element survived. `ia-qa-heal fix` can rewrite it.',
|
|
364
|
+
'renamed-repairable': 'The element is the same, its label changed. `ia-qa-heal fix` can rewrite the role-stating calls.',
|
|
365
|
+
rebound: 'WORST CASE: it now reaches a DIFFERENT element. The test can go green while acting on the wrong thing. Never rewritten automatically — read it yourself.',
|
|
366
|
+
ambiguous: 'Two or more elements answer to this. Any rewrite would be a coin flip, so none is offered.',
|
|
367
|
+
gone: 'The element it pointed at is not in the current capture. Nothing to rewrite towards.',
|
|
368
|
+
unattributable: 'The call names a string without saying what carries it (getByText, cy.contains…). Rewriting it could break a test that was passing, so it is only reported.',
|
|
369
|
+
'never-bound': 'This string reached nothing at either moment. It is not drift — check the selector was ever correct, or that the page is mapped.',
|
|
370
|
+
'not-in-contract': 'No compared page holds this label. It may live on a page you do not map — `ia-qa-heal discover` lists those.',
|
|
371
|
+
'not-measured': 'Nothing probed this string. Run `ia-qa-heal ingest` then `ia-qa-heal map` so the binding exists, then ask again.',
|
|
372
|
+
};
|
|
373
|
+
//# sourceMappingURL=explain.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"explain.js","sourceRoot":"","sources":["../src/explain.ts"],"names":[],"mappings":"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"}
|
package/dist/ingest.d.ts
CHANGED
|
@@ -56,6 +56,19 @@ export declare function usagePath(cwd?: string): string;
|
|
|
56
56
|
* Shared call shapes (`.type`, `.press`, `.fill`…) take text in one framework and
|
|
57
57
|
* a selector in another, so a captured literal is only recorded when it *reads*
|
|
58
58
|
* like a selector. A bare tag (`click('button')`) is the accepted miss.
|
|
59
|
+
*
|
|
60
|
+
* "Reads like a selector" used to mean *contains a structural character* — a `>`,
|
|
61
|
+
* a `[`, a leading `.`. That test cannot tell `div > span` from `age > 18`, and
|
|
62
|
+
* the permissive Page Object shape below offers it every `foo = '…'` in the suite,
|
|
63
|
+
* so a SQL condition typed into a field was inventoried as a selector. The cost
|
|
64
|
+
* was argued as one line in an advisory count; `explain` then made it a red gate
|
|
65
|
+
* (`exit 1`) pointing at a healthy test line. So the test is now *validity*, not
|
|
66
|
+
* presence: `18` is not a type selector, `.5` is not a class, `fix:` is not a
|
|
67
|
+
* pseudo. Seven of the eight parasites in the reference corpus are refused by the
|
|
68
|
+
* grammar alone, and none of them needed a list of known-bad shapes.
|
|
69
|
+
*
|
|
70
|
+
* What the grammar cannot see stays: `a.b.c` is a valid selector whether it names
|
|
71
|
+
* an element or a JWT. That residue is real and is not worth a length heuristic.
|
|
59
72
|
*/
|
|
60
73
|
export declare function plausibleSelector(value: string): boolean;
|
|
61
74
|
export declare function scanText(text: string, file: string): Array<{
|