@ia-qa/self-healing 1.7.13 → 1.7.15
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 +39 -0
- package/SPEC-selector-resolution.md +18 -0
- package/TUTORIAL.md +44 -1
- package/dist/aom.d.ts +29 -0
- package/dist/aom.js +32 -0
- package/dist/aom.js.map +1 -1
- package/dist/captureMerge.js +45 -0
- package/dist/captureMerge.js.map +1 -1
- package/dist/cli/diff.d.ts +10 -0
- package/dist/cli/diff.js +17 -16
- package/dist/cli/diff.js.map +1 -1
- package/dist/cypress/capture.js +7 -5
- package/dist/cypress/capture.js.map +1 -1
- package/dist/discovery/safety.js +1 -2
- package/dist/discovery/safety.js.map +1 -1
- package/dist/htmlReport.js +15 -0
- package/dist/htmlReport.js.map +1 -1
- package/dist/mcp/server.js +12 -0
- package/dist/mcp/server.js.map +1 -1
- package/dist/pageName.d.ts +36 -1
- package/dist/pageName.js +91 -11
- package/dist/pageName.js.map +1 -1
- package/dist/playwright/capture.js +6 -4
- package/dist/playwright/capture.js.map +1 -1
- package/dist/selenium/capture.js +7 -4
- package/dist/selenium/capture.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -209,6 +209,33 @@ extensions and the code paths are exercised, but the test suite's rewrite cases
|
|
|
209
209
|
against `.js`/`.ts` files. Treat non-JS rewriting as working-but-less-travelled: run
|
|
210
210
|
`fix --dry-run` first, which you should be doing anyway.
|
|
211
211
|
|
|
212
|
+
### One more axis: how much of the page is under contract at all
|
|
213
|
+
|
|
214
|
+
`map` records the state a page **loads in**. Everything behind a dropdown, a tab, an accordion
|
|
215
|
+
or a modal is in no contract — so when it drifts, nothing says so. It is a coverage hole and
|
|
216
|
+
never a false alarm, which is what makes it survive: the gate stays green over a surface it
|
|
217
|
+
never looked at.
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
ia-qa-heal map --deep # open them, and contract what appears
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Measured on ia-qa.com: one page 105 → 133 elements, another 126 → 264. Across eight unrelated
|
|
224
|
+
sites (MDN, Bootstrap, Material UI, GOV.UK, Wikipedia, Docusaurus…), seven gained elements —
|
|
225
|
+
the gain is largest on component-framework apps and near zero on content sites, so it is worth
|
|
226
|
+
saying which one you have before reaching for it.
|
|
227
|
+
|
|
228
|
+
- **It cannot mutate your app.** Every non-GET request is aborted for the walk, and controls
|
|
229
|
+
named like actions (*log out*, *delete*, *pay*…) are never clicked — a logout handler that
|
|
230
|
+
only clears local storage sends no request for the network guard to catch.
|
|
231
|
+
- **What it reaches but cannot contract is reported**, not hidden (`↩ N left out`): a selector
|
|
232
|
+
must name exactly one element, and a positional one captured inside a menu can collide with
|
|
233
|
+
the page behind it.
|
|
234
|
+
- **A deep baseline is only comparable to a deep capture.** Mixing the two reports everything
|
|
235
|
+
behind a menu as `lost`; `diff` and the MCP both warn before the verdict.
|
|
236
|
+
- **~1 minute per page** against seconds for a plain `map`. It is for building a baseline, not
|
|
237
|
+
for a per-commit gate.
|
|
238
|
+
|
|
212
239
|
## The verdict
|
|
213
240
|
|
|
214
241
|
```
|
|
@@ -341,6 +368,18 @@ and they never fail your tests. Each worker writes its own shard to
|
|
|
341
368
|
page contracts `map` produces — byte-compatible, named from `config.pages` so they pair with
|
|
342
369
|
your baseline.
|
|
343
370
|
|
|
371
|
+
**One suite, several applications.** A page name is a filename, so two URLs sharing one name
|
|
372
|
+
claim to be the same page — and five products each with a `/login` are not. A URL on a host
|
|
373
|
+
other than your `baseUrl` is therefore named `<host>--<path>`
|
|
374
|
+
(`partner.example.com--login`), never `login`. Single-host suites are unaffected: the prefix
|
|
375
|
+
only appears where two applications would otherwise have overwritten each other's contract, and
|
|
376
|
+
`www.`/apex and `http`/`https` count as the same host. To choose the name yourself, declare the
|
|
377
|
+
page with its **absolute** URL:
|
|
378
|
+
|
|
379
|
+
```json
|
|
380
|
+
{ "name": "partner-login", "url": "https://partner.example.com/login" }
|
|
381
|
+
```
|
|
382
|
+
|
|
344
383
|
**Any other framework or language.** The shard format *is* the interface; the helpers above are
|
|
345
384
|
sugar over it. Run the extractor in the page (`extract.js` in this package, also a copy-paste
|
|
346
385
|
DevTools snippet on ia-qa.com) and drop a JSON file into `.ia-qa/mapping/.capture/`:
|
|
@@ -297,6 +297,24 @@ Requirements when it is built:
|
|
|
297
297
|
silently compared against one captured by `map`. Same reasoning as `run`'s
|
|
298
298
|
source-mismatch warning.
|
|
299
299
|
|
|
300
|
+
### 7.1 `map --deep` reaches states, and resolution deliberately does not follow it
|
|
301
|
+
|
|
302
|
+
`--deep` (see CLAUDE.md) opens menus, tabs and dialogs and contracts what they reveal. It would
|
|
303
|
+
be easy to assume selector resolution comes along — it does not, and the restraint is deliberate.
|
|
304
|
+
`resolvePageSelectors` runs against the **loaded** state, before exploration starts, because
|
|
305
|
+
exploration ends with the page in whatever state its walk finished in. Resolving after it would
|
|
306
|
+
probe the suite's selectors against an open modal and report a *working* binding as broken —
|
|
307
|
+
the one error this package treats as worse than the drift it heals.
|
|
308
|
+
|
|
309
|
+
So a deep contract holds elements with **no binding row**. That is `unresolved`-shaped, and §2.2
|
|
310
|
+
already governs it: silence is never evidence of absence. What it costs is real and worth stating
|
|
311
|
+
— the highest-value finding here, a selector that now reaches a *different* element, cannot be
|
|
312
|
+
made about anything behind a menu.
|
|
313
|
+
|
|
314
|
+
Closing that gap is phase 2's job, not a patch to `map`: the states `--deep` reaches are the same
|
|
315
|
+
states the shards see for free, and resolution belongs where the page is already standing in one.
|
|
316
|
+
Do not "fix" it by moving `resolvePageSelectors` after `explorePage`.
|
|
317
|
+
|
|
300
318
|
---
|
|
301
319
|
|
|
302
320
|
## 8. What this feature is not
|
package/TUTORIAL.md
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
2. [The 30-second mental model](#2-the-30-second-mental-model)
|
|
12
12
|
3. [Before you start](#3-before-you-start)
|
|
13
13
|
4. [Path A — I use an AI agent (easiest)](#4-path-a--i-use-an-ai-agent-easiest)
|
|
14
|
-
5. [Path B — I use a terminal](#5-path-b--i-use-a-terminal)
|
|
14
|
+
5. [Path B — I use a terminal](#5-path-b--i-use-a-terminal) — including [menus, tabs and modals: `map --deep`](#step-53b--what-about-my-menus-tabs-and-modals-map---deep)
|
|
15
15
|
6. [Your first real workflow](#6-your-first-real-workflow-the-whole-point) — including [a test just failed, is it drift?](#step-64b--you-didnt-run-diff-you-got-a-red-build--explain) · [the console, if you don't want a terminal](#step-68--no-terminal-open-the-console)
|
|
16
16
|
7. [Reading the verdict](#7-reading-the-verdict)
|
|
17
17
|
8. [Putting it in CI](#8-putting-it-in-ci) — [JUnit trends](#82-trend-charts-for-free--junit) · [the run history](#83-the-trend--ia-qa-heal-history)
|
|
@@ -344,6 +344,48 @@ Plus, for the whole app:
|
|
|
344
344
|
|
|
345
345
|
Commit them.
|
|
346
346
|
|
|
347
|
+
### Step 5.3b — What about my menus, tabs and modals? `map --deep`
|
|
348
|
+
|
|
349
|
+
A page has more than one state, and `map` records the one it loads in. Everything behind a
|
|
350
|
+
dropdown, a tab, an accordion or a dialog is simply not there — so if it drifts, nothing tells
|
|
351
|
+
you. It is a **coverage** hole rather than a false alarm, which is what makes it easy to miss:
|
|
352
|
+
the gate stays green over a surface it never looked at.
|
|
353
|
+
|
|
354
|
+
```bash
|
|
355
|
+
npx ia-qa-heal map --deep
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
It opens what can be opened and contracts what appears. On ia-qa.com: `/method` 105 → 133
|
|
359
|
+
elements, the home page 126 → 264.
|
|
360
|
+
|
|
361
|
+
```
|
|
362
|
+
✔ 264 interactive elements → .ia-qa/mapping/home.json + .ia-qa/mapping/home.md
|
|
363
|
+
🔍 deep: +140 behind interaction (124 on load) · 37 states · 54 clicks, 9 reloads
|
|
364
|
+
↩ 133 revealed elements left out — selector already names another element
|
|
365
|
+
🛡 34 non-GET requests blocked during exploration (nothing mutating left the browser)
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
Four things worth knowing before you use it:
|
|
369
|
+
|
|
370
|
+
**It cannot break your app.** Every non-GET request is aborted for the whole walk, so a click
|
|
371
|
+
that would have deleted, paid or submitted anything never reaches your server — and controls
|
|
372
|
+
whose name reads like an action (*log out*, *delete*, *pay*…) are not clicked at all, because a
|
|
373
|
+
logout handler that just clears local storage sends no request for the guard to stop.
|
|
374
|
+
|
|
375
|
+
**Elements it reaches but cannot contract are reported, not hidden** (`↩ 133 left out`). A
|
|
376
|
+
selector must name exactly one element; a positional one captured inside a menu can collide with
|
|
377
|
+
an element on the page behind it, and a contract holding the same selector twice makes the diff
|
|
378
|
+
pair rows at random. Those are dropped and counted.
|
|
379
|
+
|
|
380
|
+
**A deep baseline may only be diffed against a deep capture.** Mixing them reports everything
|
|
381
|
+
behind a menu as `lost` — the tool warns you before the verdict, in the CLI and over MCP alike.
|
|
382
|
+
Decide once, per baseline.
|
|
383
|
+
|
|
384
|
+
**It costs about a minute per page** where a plain `map` costs seconds. Use it to build or
|
|
385
|
+
refresh a baseline, not on every commit. `--depth 2` goes one click deeper; it was measured at
|
|
386
|
+
three times the time for a contract that never finishes, so leave it alone unless you have a
|
|
387
|
+
reason.
|
|
388
|
+
|
|
347
389
|
### Step 5.4 — Day one, before any drift: `audit`
|
|
348
390
|
|
|
349
391
|
Everything after this point pays off at the *next* UI change. `audit` pays off now: it asks **does my test suite still name things that exist?**
|
|
@@ -858,6 +900,7 @@ npx ia-qa-heal init # one-time setup
|
|
|
858
900
|
npx ia-qa-heal discover [--crawl] [--apply] # optional: propose pages for config.json (sitemap or safe crawl)
|
|
859
901
|
npx ia-qa-heal map # scan → *.json + *.md + _overview.md + _navigation.md/.svg
|
|
860
902
|
npx ia-qa-heal map --screenshots # + one JPEG per page, shown in the --report HTML (gitignore _shots/)
|
|
903
|
+
npx ia-qa-heal map --deep # + open menus/tabs/dialogs and contract what they reveal (see below)
|
|
861
904
|
npx ia-qa-heal baseline # promote mapping/ → baseline/ (commit it)
|
|
862
905
|
# …change the app, then `map` again…
|
|
863
906
|
npx ia-qa-heal diff # verdict: PASS / FIX / BLOCK (baseline/ vs mapping/)
|
package/dist/aom.d.ts
CHANGED
|
@@ -102,6 +102,35 @@ export declare function isSameCapture(baseline: {
|
|
|
102
102
|
} | null | undefined, current: {
|
|
103
103
|
capturedAt?: string;
|
|
104
104
|
} | null | undefined): boolean;
|
|
105
|
+
/**
|
|
106
|
+
* Were these two contracts explored to the same depth?
|
|
107
|
+
*
|
|
108
|
+
* A deep contract holds the elements behind menus, tabs and dialogs; a shallow one cannot, so
|
|
109
|
+
* diffing one against the other reports every one of them as `lost`. That is not a handful of
|
|
110
|
+
* noisy rows — most of a page's surface can sit behind interaction, so the mismatch manufactures
|
|
111
|
+
* the whole BLOCK out of how the two captures were taken.
|
|
112
|
+
*
|
|
113
|
+
* Absent means depth 0 — the loaded state only. That is a *known* value, not an unknown one:
|
|
114
|
+
* every contract written before `--deep` existed describes exactly one state. (`captureState` is
|
|
115
|
+
* the opposite case, where absent genuinely means "cannot tell", and must never read as a match.)
|
|
116
|
+
*
|
|
117
|
+
* Lives here, beside `isSameCapture`, for the same reason: both doors into a diff — the CLI and
|
|
118
|
+
* the MCP server — have to agree on what is not a comparison. One warning and the other silent
|
|
119
|
+
* would just move the phantom verdict to whichever door the user knocked on.
|
|
120
|
+
*/
|
|
121
|
+
export declare function depthMismatch(baseline: {
|
|
122
|
+
exploredDepth?: number;
|
|
123
|
+
} | null | undefined, current: {
|
|
124
|
+
exploredDepth?: number;
|
|
125
|
+
} | null | undefined): {
|
|
126
|
+
baseline: number;
|
|
127
|
+
current: number;
|
|
128
|
+
} | null;
|
|
129
|
+
/** The sentence every door prints or attaches when `depthMismatch` returns non-null. */
|
|
130
|
+
export declare function depthMismatchMessage(m: {
|
|
131
|
+
baseline: number;
|
|
132
|
+
current: number;
|
|
133
|
+
}): string;
|
|
105
134
|
export declare function extractInteractiveElements(page: Page): Promise<MappedElement[]>;
|
|
106
135
|
export declare function saveMapping(mapping: PageMapping, dir?: string): string;
|
|
107
136
|
export declare function loadMapping(pageName: string, dir?: string): PageMapping;
|
package/dist/aom.js
CHANGED
|
@@ -34,6 +34,8 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.isSameCapture = isSameCapture;
|
|
37
|
+
exports.depthMismatch = depthMismatch;
|
|
38
|
+
exports.depthMismatchMessage = depthMismatchMessage;
|
|
37
39
|
exports.extractInteractiveElements = extractInteractiveElements;
|
|
38
40
|
exports.saveMapping = saveMapping;
|
|
39
41
|
exports.loadMapping = loadMapping;
|
|
@@ -63,6 +65,36 @@ const extract_1 = require("./browser/extract");
|
|
|
63
65
|
function isSameCapture(baseline, current) {
|
|
64
66
|
return Boolean(baseline?.capturedAt) && baseline.capturedAt === current?.capturedAt;
|
|
65
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* Were these two contracts explored to the same depth?
|
|
70
|
+
*
|
|
71
|
+
* A deep contract holds the elements behind menus, tabs and dialogs; a shallow one cannot, so
|
|
72
|
+
* diffing one against the other reports every one of them as `lost`. That is not a handful of
|
|
73
|
+
* noisy rows — most of a page's surface can sit behind interaction, so the mismatch manufactures
|
|
74
|
+
* the whole BLOCK out of how the two captures were taken.
|
|
75
|
+
*
|
|
76
|
+
* Absent means depth 0 — the loaded state only. That is a *known* value, not an unknown one:
|
|
77
|
+
* every contract written before `--deep` existed describes exactly one state. (`captureState` is
|
|
78
|
+
* the opposite case, where absent genuinely means "cannot tell", and must never read as a match.)
|
|
79
|
+
*
|
|
80
|
+
* Lives here, beside `isSameCapture`, for the same reason: both doors into a diff — the CLI and
|
|
81
|
+
* the MCP server — have to agree on what is not a comparison. One warning and the other silent
|
|
82
|
+
* would just move the phantom verdict to whichever door the user knocked on.
|
|
83
|
+
*/
|
|
84
|
+
function depthMismatch(baseline, current) {
|
|
85
|
+
const b = baseline?.exploredDepth ?? 0;
|
|
86
|
+
const c = current?.exploredDepth ?? 0;
|
|
87
|
+
return b === c ? null : { baseline: b, current: c };
|
|
88
|
+
}
|
|
89
|
+
/** The sentence every door prints or attaches when `depthMismatch` returns non-null. */
|
|
90
|
+
function depthMismatchMessage(m) {
|
|
91
|
+
const deeper = m.baseline > m.current ? 'baseline' : 'current capture';
|
|
92
|
+
const shallower = deeper === 'baseline' ? 'current capture' : 'baseline';
|
|
93
|
+
return (`Exploration depth mismatch — the ${deeper} was captured with \`map --deep\`, the ${shallower} ` +
|
|
94
|
+
`was not (depth ${m.baseline} vs ${m.current}). A deep contract holds the elements behind ` +
|
|
95
|
+
`menus, tabs and dialogs; a shallow one cannot, so those elements read as \`lost\` here. Most ` +
|
|
96
|
+
`of this verdict is probably that, not drift in your app. Capture both sides the same way.`);
|
|
97
|
+
}
|
|
66
98
|
async function extractInteractiveElements(page) {
|
|
67
99
|
return page.evaluate(extract_1.extractInPage);
|
|
68
100
|
}
|
package/dist/aom.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"aom.js","sourceRoot":"","sources":["../src/aom.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2GA,sCAKC;AAED,gEAEC;AAED,kCAwBC;AAED,kCAQC;
|
|
1
|
+
{"version":3,"file":"aom.js","sourceRoot":"","sources":["../src/aom.ts"],"names":[],"mappings":"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"}
|
package/dist/captureMerge.js
CHANGED
|
@@ -40,6 +40,7 @@ exports.mergeCapturesAnnounced = mergeCapturesAnnounced;
|
|
|
40
40
|
const fs = __importStar(require("fs"));
|
|
41
41
|
const path = __importStar(require("path"));
|
|
42
42
|
const config_1 = require("./config");
|
|
43
|
+
const pageName_1 = require("./pageName");
|
|
43
44
|
const aom_1 = require("./aom");
|
|
44
45
|
const markdown_1 = require("./markdown");
|
|
45
46
|
const union_1 = require("./browser/union");
|
|
@@ -58,6 +59,39 @@ exports.CAPTURE_DIRNAME = '.capture';
|
|
|
58
59
|
function captureDir(cwd = (0, config_1.baseDir)()) {
|
|
59
60
|
return path.join((0, config_1.mappingDir)(cwd), exports.CAPTURE_DIRNAME);
|
|
60
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* One contract, two applications — say so instead of writing it.
|
|
64
|
+
*
|
|
65
|
+
* A page name is an identity claim, and `resolvePageName` keeps it honest by putting
|
|
66
|
+
* the host in the name whenever the URL is not the app's own. That needs a `baseUrl`,
|
|
67
|
+
* so it needs a config; without one every host's `/login` still resolves to `login`,
|
|
68
|
+
* and this merge would union five products into one contract, then diff it against
|
|
69
|
+
* whichever one it happened to hold last — a permanent phantom drift on a suite that
|
|
70
|
+
* is green, offered to `fix` as a rename.
|
|
71
|
+
*
|
|
72
|
+
* We cannot pick the namespace here (there is no declared app to be foreign *to*, and
|
|
73
|
+
* inventing one from whichever host appeared most would make the names depend on
|
|
74
|
+
* worker scheduling). So we name the collision and the remedy, rather than let silence
|
|
75
|
+
* read as "checked, fine". The remedy differs by cause, and the cause is *checked*, not
|
|
76
|
+
* assumed: with a config in place these names cannot come from this version's capture,
|
|
77
|
+
* so they came from a stale `.capture/` or a hand-written shard.
|
|
78
|
+
*/
|
|
79
|
+
function warnHostCollisions(byPage, configured) {
|
|
80
|
+
for (const [name, { hosts }] of byPage) {
|
|
81
|
+
if (hosts.size < 2)
|
|
82
|
+
continue;
|
|
83
|
+
const remedy = configured
|
|
84
|
+
? ` This version names an off-\`baseUrl\` page \`<host>--<path>\`, so these shards were ` +
|
|
85
|
+
`written by an older version or by hand. Delete \`${config_1.CONFIG_DIR}/mapping/.capture/\`, re-run ` +
|
|
86
|
+
`the suite, and declare any page you want named yourself in "pages" with its absolute URL.`
|
|
87
|
+
: ` There is no ${config_1.CONFIG_DIR}/config.json here, so nothing names the app these pages ` +
|
|
88
|
+
`belong to; with a "baseUrl", every other host is prefixed into the page name instead. ` +
|
|
89
|
+
`Run \`ia-qa-heal init\`, or declare each one in "pages" with its absolute URL.`;
|
|
90
|
+
console.warn(`⚠ Page "${name}" was captured from ${hosts.size} different hosts this run ` +
|
|
91
|
+
`(${Array.from(hosts).join(', ')}) — they are sharing one contract, so its diff is not ` +
|
|
92
|
+
`meaningful.\n${remedy}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
61
95
|
function mergeCaptures(cwd = (0, config_1.baseDir)()) {
|
|
62
96
|
const dir = captureDir(cwd);
|
|
63
97
|
if (!fs.existsSync(dir))
|
|
@@ -84,9 +118,17 @@ function mergeCaptures(cwd = (0, config_1.baseDir)()) {
|
|
|
84
118
|
if (!p?.page || !Array.isArray(p.elements))
|
|
85
119
|
continue;
|
|
86
120
|
const prev = byPage.get(p.page);
|
|
121
|
+
const hosts = prev ? prev.hosts : new Set();
|
|
122
|
+
try {
|
|
123
|
+
hosts.add((0, pageName_1.foldHost)(new URL(p.url)));
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
/* an unparseable URL says nothing about which app this was */
|
|
127
|
+
}
|
|
87
128
|
byPage.set(p.page, {
|
|
88
129
|
url: p.url,
|
|
89
130
|
elements: prev ? (0, union_1.unionElements)(prev.elements, p.elements) : p.elements,
|
|
131
|
+
hosts,
|
|
90
132
|
});
|
|
91
133
|
}
|
|
92
134
|
}
|
|
@@ -94,12 +136,15 @@ function mergeCaptures(cwd = (0, config_1.baseDir)()) {
|
|
|
94
136
|
// here at the merge — one place covering the Playwright, Cypress and Selenium
|
|
95
137
|
// hooks at once. No config, no patterns: live capture without one stays as-is.
|
|
96
138
|
let patterns = [];
|
|
139
|
+
let configured = false;
|
|
97
140
|
try {
|
|
98
141
|
patterns = (0, volatile_1.parseVolatilePatterns)((0, config_1.loadConfig)(cwd).volatile);
|
|
142
|
+
configured = true;
|
|
99
143
|
}
|
|
100
144
|
catch {
|
|
101
145
|
/* config optional for a merge */
|
|
102
146
|
}
|
|
147
|
+
warnHostCollisions(byPage, configured);
|
|
103
148
|
const capturedAt = new Date().toISOString();
|
|
104
149
|
const outDir = (0, config_1.mappingDir)(cwd);
|
|
105
150
|
const pages = [];
|
package/dist/captureMerge.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"captureMerge.js","sourceRoot":"","sources":["../src/captureMerge.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"captureMerge.js","sourceRoot":"","sources":["../src/captureMerge.ts"],"names":[],"mappings":"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"}
|
package/dist/cli/diff.d.ts
CHANGED
|
@@ -167,6 +167,16 @@ export interface DirReport {
|
|
|
167
167
|
totals: Report['counts'];
|
|
168
168
|
/** Pairs left out of the verdict: same capture on both sides, so incapable of showing drift. */
|
|
169
169
|
staleExcluded?: string[];
|
|
170
|
+
/**
|
|
171
|
+
* The two sides were explored to different depths (`map --deep`) — so much of this verdict is
|
|
172
|
+
* probably an artefact of how the contracts were captured rather than drift in the app.
|
|
173
|
+
* Carried on the aggregate so `--json` and the report state it, not only the terminal.
|
|
174
|
+
*/
|
|
175
|
+
depthMismatch?: {
|
|
176
|
+
baseline: number;
|
|
177
|
+
current: number;
|
|
178
|
+
why: string;
|
|
179
|
+
};
|
|
170
180
|
/**
|
|
171
181
|
* Renamed labels the suite locates by name, de-duped across pages — a shared
|
|
172
182
|
* header renames once, not once per page that carries it.
|
package/dist/cli/diff.js
CHANGED
|
@@ -365,6 +365,11 @@ async function runDiff(args) {
|
|
|
365
365
|
refuseToCompare('same-capture', sameCaptureMessage(baseline.capturedAt), 'Change the app and run `ia-qa-heal map` again, so the two sides are different moments.', json);
|
|
366
366
|
return;
|
|
367
367
|
}
|
|
368
|
+
// Before the verdict, never after: a depth mismatch does not colour a few rows, it can invent
|
|
369
|
+
// the entire BLOCK. Same check as `diff --dir` and the MCP — one shared function, three doors.
|
|
370
|
+
const depths = (0, aom_1.depthMismatch)(baseline, current);
|
|
371
|
+
if (depths && !json)
|
|
372
|
+
console.error(`\n⚠️ ${(0, aom_1.depthMismatchMessage)(depths)}`);
|
|
368
373
|
const usage = (0, ingest_1.loadUsage)();
|
|
369
374
|
const report = attachBaselineHrefs((0, nameMask_1.diffWithMasks)(baseline, current, (0, nameMask_1.loadNameMasks)(), usage), baseline);
|
|
370
375
|
// Same rule as the directory walk: the resolutions sit next to the mappings they
|
|
@@ -547,7 +552,7 @@ async function runDirDiff(baselineDir, currentDir, opts) {
|
|
|
547
552
|
console.error(`❌ "${currentDir}" is not a directory.`);
|
|
548
553
|
process.exit(2);
|
|
549
554
|
}
|
|
550
|
-
const { reports, stale, systemic, depthMismatch } = collectDirDiff(baselineDir, currentDir);
|
|
555
|
+
const { reports, stale, systemic, depthMismatch: depths } = collectDirDiff(baselineDir, currentDir);
|
|
551
556
|
if (reports.length === 0 && stale.length > 0) {
|
|
552
557
|
refuseToCompare('same-capture', `❌ Nothing to compare: ${stale.length === 1 ? 'the only pair is' : `all ${stale.length} pairs are`} ` +
|
|
553
558
|
`the same capture on both sides.\n\n` +
|
|
@@ -566,21 +571,19 @@ async function runDirDiff(baselineDir, currentDir, opts) {
|
|
|
566
571
|
// it can invent the entire BLOCK, because everything the deep side found behind a menu is
|
|
567
572
|
// simply absent from the shallow side and reads as `lost`. Someone who meets the verdict
|
|
568
573
|
// first will start hand-repairing drift that never happened.
|
|
569
|
-
if (
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
console.error(`\n⚠️ Exploration depth mismatch — the ${deeper} was captured with \`map --deep\`, the ` +
|
|
573
|
-
`${shallower} was not\n (depth ${depthMismatch.baseline} vs ${depthMismatch.current}). ` +
|
|
574
|
-
`A deep contract holds the elements behind menus, tabs and dialogs;\n a shallow one ` +
|
|
575
|
-
`cannot, so those elements read as \`lost\` here. Most of what follows is probably that,\n` +
|
|
576
|
-
` not drift in your app.\n` +
|
|
577
|
-
` Capture both sides the same way: \`ia-qa-heal map --deep\` before diffing, or re-baseline` +
|
|
578
|
-
` without it.`);
|
|
574
|
+
if (depths) {
|
|
575
|
+
console.error(`\n⚠️ ${(0, aom_1.depthMismatchMessage)(depths)}\n` +
|
|
576
|
+
` \`ia-qa-heal map --deep\` before diffing, or re-baseline without it.`);
|
|
579
577
|
}
|
|
580
578
|
// Aggregate verdict
|
|
581
579
|
const aggregate = aggregateReports(reports);
|
|
582
580
|
if (stale.length > 0)
|
|
583
581
|
aggregate.staleExcluded = stale;
|
|
582
|
+
// Onto the aggregate, not only the terminal: `--json` is what a pipeline reads, and a CI that
|
|
583
|
+
// sees BLOCK without knowing the two sides were captured differently is the same blind spot
|
|
584
|
+
// the MCP had. Every consumer of a verdict gets the caveat attached to it.
|
|
585
|
+
if (depths)
|
|
586
|
+
aggregate.depthMismatch = { ...depths, why: (0, aom_1.depthMismatchMessage)(depths) };
|
|
584
587
|
if (systemic.groups.length > 0)
|
|
585
588
|
aggregate.systemic = systemic;
|
|
586
589
|
Object.assign(aggregate, (0, nextStep_1.nextStepFor)(aggregate.verdict, {
|
|
@@ -683,8 +686,7 @@ function collectDirDiff(baselineDir, currentDir) {
|
|
|
683
686
|
// Exploration depth, settled the same way and for the same reason. Absent means the loaded
|
|
684
687
|
// state only, which is depth 0 — a real value here, not unknown: every contract written
|
|
685
688
|
// before `--deep` existed describes exactly one state, and that is knowable.
|
|
686
|
-
let
|
|
687
|
-
let currentDepth = 0;
|
|
689
|
+
let deepest = null;
|
|
688
690
|
// Kept out of the verdict rather than counted as `ok` — N ok that were never
|
|
689
691
|
// compared is exactly the false green this guard exists to stop.
|
|
690
692
|
const stale = [];
|
|
@@ -695,8 +697,7 @@ function collectDirDiff(baselineDir, currentDir) {
|
|
|
695
697
|
const assess = (baseline, current, baselineFile, currentFile, page) => {
|
|
696
698
|
baselineState ??= baseline.captureState;
|
|
697
699
|
currentState ??= current.captureState;
|
|
698
|
-
|
|
699
|
-
currentDepth = Math.max(currentDepth, current.exploredDepth ?? 0);
|
|
700
|
+
deepest ??= (0, aom_1.depthMismatch)(baseline, current);
|
|
700
701
|
const report = attachBaselineHrefs((0, nameMask_1.diffWithMasks)(baseline, current, masks, usage), baseline);
|
|
701
702
|
report.nameDrift = (0, nameDrift_1.assessNameDrift)(report.rows, current.elements, usage);
|
|
702
703
|
report.verdict = (0, nameDrift_1.escalateVerdict)(report.verdict, report.nameDrift);
|
|
@@ -760,7 +761,7 @@ function collectDirDiff(baselineDir, currentDir) {
|
|
|
760
761
|
reports,
|
|
761
762
|
stale,
|
|
762
763
|
systemic,
|
|
763
|
-
depthMismatch:
|
|
764
|
+
depthMismatch: deepest,
|
|
764
765
|
};
|
|
765
766
|
}
|
|
766
767
|
function aggregateReports(reports) {
|