@ia-qa/self-healing 1.6.17 → 1.7.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.
Files changed (45) hide show
  1. package/README.md +120 -1
  2. package/TUTORIAL.md +31 -4
  3. package/dist/browser/resolve.d.ts +20 -0
  4. package/dist/browser/resolve.js +78 -0
  5. package/dist/browser/resolve.js.map +1 -0
  6. package/dist/cli/args.js +3 -2
  7. package/dist/cli/args.js.map +1 -1
  8. package/dist/cli/baseline.js +16 -2
  9. package/dist/cli/baseline.js.map +1 -1
  10. package/dist/cli/diff.d.ts +21 -1
  11. package/dist/cli/diff.js +143 -25
  12. package/dist/cli/diff.js.map +1 -1
  13. package/dist/cli/discover.js +9 -0
  14. package/dist/cli/discover.js.map +1 -1
  15. package/dist/cli/fix.js +33 -0
  16. package/dist/cli/fix.js.map +1 -1
  17. package/dist/cli/index.js +50 -1
  18. package/dist/cli/index.js.map +1 -1
  19. package/dist/cli/login.d.ts +28 -0
  20. package/dist/cli/login.js +179 -0
  21. package/dist/cli/login.js.map +1 -0
  22. package/dist/cli/map.d.ts +19 -3
  23. package/dist/cli/map.js +191 -17
  24. package/dist/cli/map.js.map +1 -1
  25. package/dist/config.d.ts +68 -0
  26. package/dist/config.js +94 -1
  27. package/dist/config.js.map +1 -1
  28. package/dist/crawl.d.ts +2 -0
  29. package/dist/crawl.js +13 -4
  30. package/dist/crawl.js.map +1 -1
  31. package/dist/fixEngine.d.ts +23 -0
  32. package/dist/fixEngine.js +27 -0
  33. package/dist/fixEngine.js.map +1 -1
  34. package/dist/launcher.d.ts +7 -0
  35. package/dist/launcher.js +4 -4
  36. package/dist/launcher.js.map +1 -1
  37. package/dist/resolution.d.ts +96 -0
  38. package/dist/resolution.js +197 -0
  39. package/dist/resolution.js.map +1 -0
  40. package/dist/ui/page.js +10 -0
  41. package/dist/ui/page.js.map +1 -1
  42. package/dist/ui/server.js +66 -0
  43. package/dist/ui/server.js.map +1 -1
  44. package/package.json +1 -1
  45. package/skills/ia-qa-heal/SKILL.md +25 -1
package/README.md CHANGED
@@ -173,7 +173,63 @@ Everything the tool needs lives in **one JSON file**, written for you by `ia-qa-
173
173
  - **"My diff BLOCKs on links that just… rotate (a news feed, a blog widget)?"** → That is content churn, not UI drift: the feed's links entered the contract as if they were buttons, and the next capture caught the feed mid-rotation. Name what rotates in `"volatile"` (see above) and it never enters a contract again — the diff even prints a hint pointing here when a BLOCK is made of lost external links.
174
174
  - **"The same rows come back as `renamed` every run — a cart counter, a call total, a clock."** → Only the *label* is moving, so only the label should stop being compared: put the pattern in `"nameMask"` (`"Cart (*)"`), not in `"volatile"`. `volatile` would delete the element from the contract — counter, button, href and coverage together — so a real selector break on it would go unnoticed forever; a mask keeps all of that gating and exempts the name alone. The diff prints the exact config line to paste when it spots the pattern. Two masks it refuses to apply, and says so: one that would hide a label your tests locate by name (that is a red suite behind a green gate), and one that would leave two elements of the same role wearing one name (a locator coin flip the mask itself created).
175
175
  - **"How do I add a page behind a click, not a URL?"** → add `steps` to that page (see `billing` above): a list of `{ "click": { "role": "...", "name": "..." } }`. Named by role + accessible name, exactly like the contract — never a CSS selector.
176
- - **"My app spans several domains behind a single sign-on (federated SSO) — does `map` cover it?"** → **Not with one `auth` block.** `auth` runs **one** login against **one** domain, before the first page. A federated IdP that re-challenges each project/domain with its own scope (`?projectName=…`, a different sub-domain per portal) is never satisfied by that single login — the second domain bounces back to a login the first one never covered, and the page maps empty (map now warns when an authenticated target returns 0 elements). Two ways through: **one `.ia-qa/` per domain/portal**, each selected with `--config <dir>` (see **Monorepos** below), or **`ia-qa-heal run` with `IAQA_CAPTURE=1`** so your own suite — which already handles the full SSO dance (cookies, redirects, per-project scope) — does the auth and the contract is captured during that real run. For a multi-portal app, capture-during-run is the intended path; `map --auth` is for single-domain apps.
176
+ - **"My app spans several domains behind a single sign-on (federated SSO) — does `map` cover it?"** → **Not with one `auth` block.** `auth` runs **one** login against **one** domain, before the first page. A federated IdP that re-challenges each project/domain with its own scope (`?projectName=…`, a different sub-domain per portal) is never satisfied by that single login — the second domain bounces back to a login the first one never covered, and the page maps empty (map now warns when an authenticated target returns 0 elements). Two ways through: **one `.ia-qa/` per domain/portal**, each selected with `--config <dir>` (see **Monorepos** below), or **`ia-qa-heal run` with `IAQA_CAPTURE=1`** so your own suite — which already handles the full SSO dance (cookies, redirects, per-project scope) — does the auth and the contract is captured during that real run. And since 1.7.0 there is a third, which is the general one: **`ia-qa-heal login`** — you log in once in a visible browser, whatever the app asks, and `map` reuses that session (see below). For a multi-portal app, capture-during-run is the intended path in CI; `map --auth` is for single-domain apps.
177
+
178
+ ### Behind any login — `ia-qa-heal login`
179
+
180
+ `auth` fills a form: a username field, a password field, a submit button. That is *one shape of
181
+ login*, and plenty of apps do not have it — federated SSO, MFA, a consent screen, a magic link, a
182
+ device check. No list of selectors gets through any of those, so for those apps "add an `auth`
183
+ block" is not an answer at all.
184
+
185
+ ```bash
186
+ ia-qa-heal login # opens a visible browser at your first configured page
187
+ # you log in there, however your app asks
188
+ ia-qa-heal map # reuses that session — no auth block needed
189
+ ```
190
+
191
+ It models nothing about your login, which is exactly why it works anywhere: the human does
192
+ whatever the app requires, and the browser session is saved to `.ia-qa/session.json` in
193
+ Playwright's `storageState` shape. `map` and `discover --crawl` pick it up automatically and say
194
+ so; it takes precedence over `auth` when both exist (you ran `login` *because* the form fill
195
+ could not get through — re-running it silently would be the tool arguing with you).
196
+
197
+ - **That file is a secret.** It holds live cookies: whoever has it is logged in as you. It never
198
+ leaves your machine — nothing here uploads it — and `login` writes `.ia-qa/.gitignore` so a
199
+ commit cannot carry it.
200
+ - **It needs a person.** It refuses without a TTY and under CI, and an AI agent driving the CLI
201
+ cannot perform it: the agent hands the step to you by name.
202
+ - **It expires.** When the session dies, `map` hits the login wall again and says to re-run
203
+ `login` — it never silently maps a login form under the name of your dashboard.
204
+ - **It is not a CI mechanism.** In a pipeline, use `auth` (reproducible, secrets from env/SSM) or
205
+ capture-during-run (`IAQA_CAPTURE=1` + `testCommand`, your own suite logs itself in).
206
+
207
+ `--url <path>` opens a page other than the first one in `config.pages`.
208
+
209
+ #### You already have a session — use it
210
+
211
+ An authenticated Playwright suite usually writes a `storageState` in `globalSetup`. Point at
212
+ it and skip logging in a second time:
213
+
214
+ ```bash
215
+ ia-qa-heal map --session playwright/.auth/user.json # a flag
216
+ IAQA_SESSION=playwright/.auth/user.json ia-qa-heal map # or an env var
217
+ ```
218
+
219
+ ```jsonc
220
+ // …or in .ia-qa/config.json, so every verb picks it up:
221
+ "session": "playwright/.auth/user.json"
222
+ ```
223
+
224
+ Precedence: `--session` → `IAQA_SESSION` → `config.session` → the file `login` wrote. Relative
225
+ paths resolve from the project root, and `--session` works on `map`, `discover` and `login`
226
+ (there it says *where to write*, so a refresh updates the file you already point at).
227
+
228
+ **A session you named and that cannot be read stops the run** — it never falls through to
229
+ "no session". Silently ignoring it would capture the login page under your pages' names, which
230
+ is the one failure this tool treats as worse than stopping. Same for a file that is not a
231
+ storageState: it says so, instead of failing later inside the browser.
232
+
177
233
 
178
234
  ### Monorepos — one config per portal (`--config <dir>`)
179
235
 
@@ -337,6 +393,24 @@ capture → ingest → diff → dry-run of the fixes → your confirmation → f
337
393
  - **After the fix, `run` re-runs your suite once more (without `IAQA_CAPTURE`) to confirm the rewrite is actually green** — a selector fix is only worth anything if the tests pass after it. The exit code follows that re-run: `0` if the suite passes, `1` if it still fails (the rewrite landed but something else is red). `--no-verify` applies the fix and skips the re-run; the re-run is also skipped, with a reason, when nothing was rewritten or there is no `testCommand`.
338
394
  - With `--report`, the HTML for a `run` additionally carries the **applied** `old → new` edits (file and line numbers) and the verify verdict — the file-level before/after the `diff` report only promised.
339
395
 
396
+ ### Already ran your suite with `IAQA_CAPTURE=1`? Just `diff`
397
+
398
+ `run` is a convenience, not the only door. If the capture already happened — your suite ran with
399
+ `IAQA_CAPTURE=1`, in CI or by hand — the verdict is one offline command away:
400
+
401
+ ```bash
402
+ IAQA_CAPTURE=1 npx playwright test # your suite, as usual
403
+ ia-qa-heal diff # merges .capture/ and compares — no browser, no re-run
404
+ ```
405
+
406
+ `diff`, `fix` and `baseline` all merge the staging directory (`.ia-qa/mapping/.capture/`) on the
407
+ way in and say so. **Do not run `map` to "finish" a capture**: `map` opens its own browser and
408
+ starts over, without whatever session your suite had.
409
+
410
+ And on a slow suite, `run` runs it **twice** — once to capture, once to verify the fix. `run
411
+ --no-verify` keeps the first only; the exit code then reflects the diff rather than a re-proved
412
+ green suite.
413
+
340
414
  ### Capture during your test run — `@ia-qa/self-healing/capture`
341
415
 
342
416
  One line at the top of a spec (or wrap your own extended `test` with `withCapture`):
@@ -402,6 +476,51 @@ Statically scans your tests/POMs → `.ia-qa/usage.json`. Two halves, because su
402
476
 
403
477
  Purely static: no execution, no network. A locator built by concatenation or holding `${…}` is invisible here — and invisible to `fix` too, which is why it is skipped rather than guessed at.
404
478
 
479
+ ### Page Objects written in CSS — `_resolved/`
480
+
481
+ Your Page Object says `.btn-primary`. The contract says `#pay-now`. Both name the same
482
+ button, and until they are **bound** every drift row reads `not referenced by your tests` —
483
+ technically true, practically useless, and the reason a CSS-anchored suite used to get
484
+ nothing out of a diff.
485
+
486
+ So `map` binds them: with `usage.json` present (run `ia-qa-heal ingest` first), each
487
+ selector your tests write is evaluated against the page and recorded in
488
+ `.ia-qa/mapping/_resolved/<page>.json`. `baseline` promotes those alongside the contracts,
489
+ and `diff` compares the two moments.
490
+
491
+ What that buys you — an example that no other check in this package can catch:
492
+
493
+ ```
494
+ 🔧 FIX
495
+ 2 ok · 0 renamed · 0 healable · 0 lost ← the contract did not drift at all
496
+
497
+ 🔗 1 selector your tests write no longer reaches what it used to:
498
+ 🔧 .btn-primary → #pay-now
499
+ the element is still there, only its address moved.
500
+ tests/checkout.spec.js:1, tests/checkout.spec.js:4
501
+ ```
502
+
503
+ The button never moved; someone renamed a CSS class. The element diff is spotless, the
504
+ suite is broken, and `ia-qa-heal fix` rewrites the literal in your files.
505
+
506
+ Four outcomes, and only one of them is repaired automatically:
507
+
508
+ | | |
509
+ |---|---|
510
+ | the string now reaches **nothing**, and the element is still there | 🔧 rewritten |
511
+ | the string now reaches a **different** element | ⛔ never rewritten — your test still passes and acts on the wrong thing |
512
+ | the string now reaches **several** elements | ⛔ a coin flip, a human decides |
513
+ | the element it named is **gone** | ⛔ nothing to rewrite towards |
514
+
515
+ And the case that stays quiet: a selector matching something the contract does not hold —
516
+ a row, a cell, a container. That is most of a real Page Object, it is proof the selector
517
+ **works**, and calling it "not found" would bury the four rows above under hundreds of
518
+ false ones.
519
+
520
+ No `usage.json` ⇒ no bindings ⇒ nothing changes. Same rule as name drift: **no inventory,
521
+ no escalation.** Today the bindings are captured by `map`; capture-during-run is specified
522
+ in `SPEC-selector-resolution.md` and not built yet.
523
+
405
524
  ### The rename your suite actually depends on
406
525
 
407
526
  A relabelled element is classified `renamed`: the selector still resolves, the click still lands on the right thing. **For a CSS-anchored suite that is harmless. For a name-anchored one it is fatal** — `getByRole('button', { name: 'Save' })` stops matching the moment the button says "Enregistrer", and a selector-only diff would exit 0 while your suite goes red.
package/TUTORIAL.md CHANGED
@@ -248,14 +248,41 @@ It asks a few questions (your app's URL, whether there's a login, which pages to
248
248
 
249
249
  - **🔑 Credentials** — you store the *name* of an environment variable, never the password itself. The real value stays in your `.env` / shell / AWS. **Loading it is your job:** the CLI reads `process.env` and never opens a `.env` file, so a key sitting only in `.env` will not be seen — run `map` under `npx dotenv-cli --` (or Node's `--env-file-if-exists`, 20.12+). Only `map` logs in; `diff`, `baseline`, `audit` and `check` read the mapping and need no credentials. If a variable is missing, the tool stops and names the exact variable to set — it never guesses and never writes a secret to disk.
250
250
  - **🌐 URLs to visit** — the list of pages to map. A page that lives behind a click instead of its own URL (a tab or modal an app swaps in) is reached by naming the control to click, not by a URL.
251
- - **🔐 One login** — the wizard sets up a single login sequence. **Want to test as several users (admin, then guest)?** Two options: keep a separate `.ia-qa/` config per role — select each with `--config <dir>`, the same flag a monorepo uses to keep one config per app/portal — **or** skip the login wiring entirely and use `ia-qa-heal run` (Step 6.7) — it captures the map *while your own test suite runs*, so whatever logins your tests already do, for however many users (and across however many domains), are covered automatically.
251
+ - **🔐 One login** — the wizard sets up a single login sequence. **And a form fill only covers a login that *is* a form** — SSO with MFA, a consent screen, a magic link and a device check are out of reach of any list of selectors. For those, `ia-qa-heal login` opens a visible browser, you log in once however the app asks, and the session is reused by `map` (Step 5.2a). Already have a `storageState` from your suite's `globalSetup`? Point at it with `--session <file>`, `IAQA_SESSION`, or `"session"` in the config — no second login. **Want to test as several users (admin, then guest)?** Two options: keep a separate `.ia-qa/` config per role — select each with `--config <dir>`, the same flag a monorepo uses to keep one config per app/portal — **or** skip the login wiring entirely and use `ia-qa-heal run` (Step 6.7) — it captures the map *while your own test suite runs*, so whatever logins your tests already do, for however many users (and across however many domains), are covered automatically.
252
252
 
253
253
  The full annotated `config.json` — every field explained, plus a note for AI agents helping you set it up — is in the package's [README on npm](https://www.npmjs.com/package/@ia-qa/self-healing).
254
254
 
255
255
  > 🔐 **Your password is never written to that file** — only the *name* of the environment variable that holds it.
256
256
 
257
- ### Step 5.2b(optional) Find the pages you're *not* testing: `discover`
257
+ ### Step 5.2aBehind a login? `ia-qa-heal login`
258
+
259
+ Most apps worth mapping are behind a login, and only some of those logins are a form. This verb
260
+ models **nothing** about yours, which is why it works anywhere:
261
+
262
+ ```bash
263
+ npx ia-qa-heal login # a real browser window opens at your first configured page
264
+ # you log in there — SSO, MFA, a consent screen, a magic link, all of it
265
+ npx ia-qa-heal map # reuses that session; no "auth" block needed
266
+ ```
267
+
268
+ Nothing is typed for you and no credential is read or stored by the tool. What is saved is the
269
+ **browser session** (`.ia-qa/session.json`) — live cookies, so treat it like a password: it stays
270
+ on your machine, nothing uploads it, and `login` writes `.ia-qa/.gitignore` so a commit cannot
271
+ carry it. It expires like any session; when it does, `map` says so and tells you to run this again
272
+ rather than quietly mapping the login page under your dashboard's name.
258
273
 
274
+ **Already have a session?** An authenticated Playwright suite usually writes a `storageState` in
275
+ `globalSetup` — point at it instead of logging in twice: `ia-qa-heal map --session
276
+ playwright/.auth/user.json` (or `IAQA_SESSION=…`, or `"session"` in `.ia-qa/config.json`).
277
+
278
+ Two things it deliberately will not do: it **refuses without a terminal and under CI** (it waits
279
+ for a person — twice: at the browser, then at the keyboard), and it is **not a CI mechanism**. In a
280
+ pipeline, use `auth` (secrets from env/SSM) or capture-during-run (Step 6.7), where your own suite
281
+ logs itself in. An AI agent driving the CLI cannot perform this step — it will hand it to you.
282
+
283
+
284
+
285
+ ### Step 5.2b — (optional) Find the pages you're *not* testing: `discover`
259
286
  You list a handful of pages to map — but how many pages does your app actually have? `discover` answers that: it reads your whole site (the sitemap, or a safe crawl) and shows you **what you're not covering yet**. It **only suggests** — it changes nothing until you add `--apply`.
260
287
 
261
288
  ```bash
@@ -265,7 +292,7 @@ npx ia-qa-heal discover --crawl --apply # happy with the list? add the missing
265
292
  ```
266
293
 
267
294
  - **The sitemap way** (default) is instant and touches nothing — it treats `www.yoursite.com` and `yoursite.com` as the same site, so you don't have to match them exactly. Its limit: a sitemap lists your *public* pages and stops at the login.
268
- - **The crawl way** (`--crawl`) goes further: it logs in like `map` does and follows links to find the pages behind the login. It's built to be **safe** — it only ever *reads* pages (never clicks "Delete", "Log out", "Pay"…), and to peek inside a menu or dropdown it just **opens** it and moves on. Nothing is submitted, nothing is changed.
295
+ - **The crawl way** (`--crawl`) goes further: it authenticates exactly like `map` does — the same `auth` block, or the same saved session (`ia-qa-heal login` / `--session`) — and follows links to find the pages behind the login. It's built to be **safe** — it only ever *reads* pages (never clicks "Delete", "Log out", "Pay"…), and to peek inside a menu or dropdown it just **opens** it and moves on. Nothing is submitted, nothing is changed.
269
296
 
270
297
  You'll see your coverage gap:
271
298
 
@@ -455,7 +482,7 @@ Three optional config keys make it better (`ia-qa-heal init` asks for them):
455
482
  ```
456
483
  (Other frameworks / non-JS Selenium: write a capture shard directly — the format is documented in the README. The shard, not the import, is the real interface.)
457
484
 
458
- (Playwright: your own extended `test`? Wrap it: `withCapture(myTest)`.) All are **inert without `IAQA_CAPTURE=1`** — a normal run pays nothing. **This is also the way through a multi-domain / federated-SSO app**: `map`'s single login can't reach every portal, but your suite already logs into each — capture rides that real run, with no `auth` block and without ever touching your credentials. (Monorepo with one `.ia-qa/` per portal? Set `IAQA_CONFIG_DIR=apps/xsp` so the shards land in the right one.) No `testCommand` → `run` falls back to `map`.
485
+ (Playwright: your own extended `test`? Wrap it: `withCapture(myTest)`.) All are **inert without `IAQA_CAPTURE=1`** — a normal run pays nothing. **This is also the way through a multi-domain / federated-SSO app**: `map`'s single login can't reach every portal, but your suite already logs into each — capture rides that real run, with no `auth` block and without ever touching your credentials. (No suite yet, or you want `map` to work on its own? `ia-qa-heal login` is the other way in: one browser, one human login, one reused session — see Step 5.2a.) (Monorepo with one `.ia-qa/` per portal? Set `IAQA_CONFIG_DIR=apps/xsp` so the shards land in the right one.) No `testCommand` → `run` falls back to `map`.
459
486
 
460
487
  - **`testPaths`** (e.g. `["tests/"]`) — feeds `ia-qa-heal ingest`, which inventories the selector literals your tests/POMs actually use into `.ia-qa/usage.json`. `diff` then tells you *used 3× in 2 test files* vs *not referenced by your tests*, and `fix` finds your files on its own.
461
488
 
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Resolve a Page Object's selector strings against the live DOM.
3
+ *
4
+ * Runs inside the browser (serialized by Playwright into `page.evaluate`), so it stays
5
+ * dependency-free like everything else in `src/browser/`. Unlike its neighbours it is
6
+ * **npm-only**: the web tool imports `match.js`, `contract.js` and `extract.js`, and
7
+ * nothing here is needed there — a page contract has no Page Object.
8
+ *
9
+ * Why this exists: `ingest` inventories the literal strings a suite uses (`.btn-primary`),
10
+ * while the contract stores a selector `extract.js` synthesized (`#pay-now`). The two
11
+ * never intersect, so a CSS-written suite gets "0 drifted selectors referenced by your
12
+ * tests" on every run — true, and useless. The missing fact is that both name the same
13
+ * button, and only a browser can establish it.
14
+ *
15
+ * See SPEC-selector-resolution.md. The statuses are load-bearing, `outside` above all:
16
+ * a Page Object is full of selectors for containers, rows and cells, which are real
17
+ * elements the contract deliberately does not hold. Calling those "not found" would print
18
+ * hundreds of false findings on the first run.
19
+ */
20
+ export function resolveSelectors(args: any): {};
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveSelectors = resolveSelectors;
4
+ /**
5
+ * Resolve a Page Object's selector strings against the live DOM.
6
+ *
7
+ * Runs inside the browser (serialized by Playwright into `page.evaluate`), so it stays
8
+ * dependency-free like everything else in `src/browser/`. Unlike its neighbours it is
9
+ * **npm-only**: the web tool imports `match.js`, `contract.js` and `extract.js`, and
10
+ * nothing here is needed there — a page contract has no Page Object.
11
+ *
12
+ * Why this exists: `ingest` inventories the literal strings a suite uses (`.btn-primary`),
13
+ * while the contract stores a selector `extract.js` synthesized (`#pay-now`). The two
14
+ * never intersect, so a CSS-written suite gets "0 drifted selectors referenced by your
15
+ * tests" on every run — true, and useless. The missing fact is that both name the same
16
+ * button, and only a browser can establish it.
17
+ *
18
+ * See SPEC-selector-resolution.md. The statuses are load-bearing, `outside` above all:
19
+ * a Page Object is full of selectors for containers, rows and cells, which are real
20
+ * elements the contract deliberately does not hold. Calling those "not found" would print
21
+ * hundreds of false findings on the first run.
22
+ */
23
+ function resolveSelectors(args) {
24
+ // One argument: Playwright serializes this function into the page and calls it with a
25
+ // single serializable value. Two parameters would silently arrive as one.
26
+ const pomSelectors = args.pom;
27
+ const contractSelectors = args.contract;
28
+ // Element → the contract selector that names it. Built once; first writer wins, so two
29
+ // contract entries resolving to the same node cannot make a binding look ambiguous.
30
+ const contracted = new Map();
31
+ for (const cs of contractSelectors) {
32
+ try {
33
+ const el = document.querySelector(cs);
34
+ if (el && !contracted.has(el))
35
+ contracted.set(el, cs);
36
+ }
37
+ catch {
38
+ /* a contract selector the browser cannot parse costs its own entry, not the page */
39
+ }
40
+ }
41
+ const out = {};
42
+ for (const pom of pomSelectors) {
43
+ let nodes;
44
+ try {
45
+ nodes = Array.prototype.slice.call(document.querySelectorAll(pom));
46
+ }
47
+ catch {
48
+ // Not parseable as a selector — usually a template string the static scan caught
49
+ // mid-concatenation, occasionally a real bug in the test.
50
+ out[pom] = { status: 'invalid' };
51
+ continue;
52
+ }
53
+ if (nodes.length === 0) {
54
+ // Says nothing about whether the element exists: this page, this state, this moment.
55
+ out[pom] = { status: 'unresolved' };
56
+ continue;
57
+ }
58
+ const mapped = [];
59
+ for (const n of nodes) {
60
+ const cs = contracted.get(n);
61
+ if (cs && mapped.indexOf(cs) === -1)
62
+ mapped.push(cs);
63
+ }
64
+ if (nodes.length === 1 && mapped.length === 1) {
65
+ out[pom] = { status: 'bound', selector: mapped[0] };
66
+ }
67
+ else if (mapped.length === 0) {
68
+ // It matches the page, just not anything under contract — a row, a cell, a heading.
69
+ // Proof the selector works, and never a finding.
70
+ out[pom] = { status: 'outside' };
71
+ }
72
+ else {
73
+ out[pom] = { status: 'ambiguous', count: nodes.length };
74
+ }
75
+ }
76
+ return out;
77
+ }
78
+ //# sourceMappingURL=resolve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve.js","sourceRoot":"","sources":["../../src/browser/resolve.js"],"names":[],"mappings":";;AAmBA,4CAoDC;AAvED;;;;;;;;;;;;;;;;;;GAkBG;AACH,SAAgB,gBAAgB,CAAC,IAAI;IACnC,sFAAsF;IACtF,0EAA0E;IAC1E,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC;IAC9B,MAAM,iBAAiB,GAAG,IAAI,CAAC,QAAQ,CAAC;IACxC,uFAAuF;IACvF,oFAAoF;IACpF,MAAM,UAAU,GAAG,IAAI,GAAG,EAAE,CAAC;IAC7B,KAAK,MAAM,EAAE,IAAI,iBAAiB,EAAE,CAAC;QACnC,IAAI,CAAC;YACH,MAAM,EAAE,GAAG,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC;YACtC,IAAI,EAAE,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;gBAAE,UAAU,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;QACxD,CAAC;QAAC,MAAM,CAAC;YACP,oFAAoF;QACtF,CAAC;IACH,CAAC;IAED,MAAM,GAAG,GAAG,EAAE,CAAC;IACf,KAAK,MAAM,GAAG,IAAI,YAAY,EAAE,CAAC;QAC/B,IAAI,KAAK,CAAC;QACV,IAAI,CAAC;YACH,KAAK,GAAG,KAAK,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAC;QACrE,CAAC;QAAC,MAAM,CAAC;YACP,iFAAiF;YACjF,0DAA0D;YAC1D,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;YACjC,SAAS;QACX,CAAC;QAED,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvB,qFAAqF;YACrF,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC;YACpC,SAAS;QACX,CAAC;QAED,MAAM,MAAM,GAAG,EAAE,CAAC;QAClB,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;YACtB,MAAM,EAAE,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YAC7B,IAAI,EAAE,IAAI,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC;gBAAE,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACvD,CAAC;QAED,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC9C,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;QACtD,CAAC;aAAM,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC/B,oFAAoF;YACpF,iDAAiD;YACjD,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;QACnC,CAAC;aAAM,CAAC;YACN,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC;QAC1D,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
package/dist/cli/args.js CHANGED
@@ -16,8 +16,9 @@ exports.rejectUnknownFlags = rejectUnknownFlags;
16
16
  */
17
17
  exports.KNOWN_FLAGS = {
18
18
  init: [],
19
- discover: ['--sitemap', '--crawl', '--no-reveal', '--strict-host', '--apply', '--max', '--depth'],
20
- map: ['--screenshots'],
19
+ discover: ['--sitemap', '--crawl', '--no-reveal', '--strict-host', '--apply', '--max', '--depth', '--session'],
20
+ login: ['--url', '--session'],
21
+ map: ['--screenshots', '--session'],
21
22
  baseline: [],
22
23
  diff: ['--strict', '--json', '--junit', '--dir', '--report', '--open'],
23
24
  fix: ['--dry-run', '--locators', '--dir'],
@@ -1 +1 @@
1
- {"version":3,"file":"args.js","sourceRoot":"","sources":["../../src/cli/args.ts"],"names":[],"mappings":";;;AA6BA,gDAeC;AA5CD;;;;;;;;;;;GAWG;AACU,QAAA,WAAW,GAAsC;IAC5D,IAAI,EAAE,EAAE;IACR,QAAQ,EAAE,CAAC,WAAW,EAAE,SAAS,EAAE,aAAa,EAAE,eAAe,EAAE,SAAS,EAAE,OAAO,EAAE,SAAS,CAAC;IACjG,GAAG,EAAE,CAAC,eAAe,CAAC;IACtB,QAAQ,EAAE,EAAE;IACZ,IAAI,EAAE,CAAC,UAAU,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,CAAC;IACtE,GAAG,EAAE,CAAC,WAAW,EAAE,YAAY,EAAE,OAAO,CAAC;IACzC,MAAM,EAAE,CAAC,QAAQ,CAAC;IAClB,KAAK,EAAE,CAAC,UAAU,EAAE,QAAQ,CAAC;IAC7B,KAAK,EAAE,CAAC,UAAU,EAAE,QAAQ,EAAE,WAAW,EAAE,eAAe,CAAC;IAC3D,KAAK,EAAE,CAAC,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,CAAC;IAChD,GAAG,EAAE,CAAC,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,CAAC;IACtF,KAAK,EAAE,CAAC,WAAW,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,CAAC;IAC7D,OAAO,EAAE,CAAC,QAAQ,EAAE,SAAS,CAAC;IAC9B,EAAE,EAAE,CAAC,QAAQ,EAAE,WAAW,EAAE,YAAY,EAAE,cAAc,EAAE,UAAU,EAAE,WAAW,CAAC;CACnF,CAAC;AAEF,SAAgB,kBAAkB,CAAC,OAAe,EAAE,IAAc;IAChE,MAAM,KAAK,GAAG,mBAAW,CAAC,OAAO,CAAC,CAAC;IACnC,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO;IAChC,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACrB,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,SAAS;QACjC,IAAI,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC;YAAE,SAAS;QAChC,IAAI,CAAC,KAAK,MAAM,IAAI,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;YAC1C,MAAM,IAAI,KAAK,CACb,eAAe,OAAO,0FAA0F;gBAC9G,4GAA4G;gBAC5G,8FAA8F,CACjG,CAAC;QACJ,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,iBAAiB,CAAC,qBAAqB,OAAO,8CAA8C,CAAC,CAAC;IAChH,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"args.js","sourceRoot":"","sources":["../../src/cli/args.ts"],"names":[],"mappings":";;;AA8BA,gDAeC;AA7CD;;;;;;;;;;;GAWG;AACU,QAAA,WAAW,GAAsC;IAC5D,IAAI,EAAE,EAAE;IACR,QAAQ,EAAE,CAAC,WAAW,EAAE,SAAS,EAAE,aAAa,EAAE,eAAe,EAAE,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,WAAW,CAAC;IAC9G,KAAK,EAAE,CAAC,OAAO,EAAE,WAAW,CAAC;IAC7B,GAAG,EAAE,CAAC,eAAe,EAAE,WAAW,CAAC;IACnC,QAAQ,EAAE,EAAE;IACZ,IAAI,EAAE,CAAC,UAAU,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,CAAC;IACtE,GAAG,EAAE,CAAC,WAAW,EAAE,YAAY,EAAE,OAAO,CAAC;IACzC,MAAM,EAAE,CAAC,QAAQ,CAAC;IAClB,KAAK,EAAE,CAAC,UAAU,EAAE,QAAQ,CAAC;IAC7B,KAAK,EAAE,CAAC,UAAU,EAAE,QAAQ,EAAE,WAAW,EAAE,eAAe,CAAC;IAC3D,KAAK,EAAE,CAAC,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,CAAC;IAChD,GAAG,EAAE,CAAC,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,CAAC;IACtF,KAAK,EAAE,CAAC,WAAW,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,CAAC;IAC7D,OAAO,EAAE,CAAC,QAAQ,EAAE,SAAS,CAAC;IAC9B,EAAE,EAAE,CAAC,QAAQ,EAAE,WAAW,EAAE,YAAY,EAAE,cAAc,EAAE,UAAU,EAAE,WAAW,CAAC;CACnF,CAAC;AAEF,SAAgB,kBAAkB,CAAC,OAAe,EAAE,IAAc;IAChE,MAAM,KAAK,GAAG,mBAAW,CAAC,OAAO,CAAC,CAAC;IACnC,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO;IAChC,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACrB,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,SAAS;QACjC,IAAI,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC;YAAE,SAAS;QAChC,IAAI,CAAC,KAAK,MAAM,IAAI,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;YAC1C,MAAM,IAAI,KAAK,CACb,eAAe,OAAO,0FAA0F;gBAC9G,4GAA4G;gBAC5G,8FAA8F,CACjG,CAAC;QACJ,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,iBAAiB,CAAC,qBAAqB,OAAO,8CAA8C,CAAC,CAAC;IAChH,CAAC;AACH,CAAC"}
@@ -70,6 +70,10 @@ async function runBaseline(args = []) {
70
70
  }
71
71
  const pages = jsonFilesIn(from);
72
72
  const layouts = jsonFilesIn(path.join(from, config_1.LAYOUT_DIRNAME));
73
+ // Bindings are promoted with the contracts they describe: a binding verdict is a claim
74
+ // about two moments, exactly like a drift verdict, so the "before" has to be promotable
75
+ // the same way. Absent when no inventory exists — then nothing downstream changes.
76
+ const resolved = jsonFilesIn(path.join(from, config_1.RESOLVED_DIRNAME));
73
77
  if (pages.length === 0 && layouts.length === 0) {
74
78
  console.error(`❌ ${path.resolve(from)} holds no mapping files — run \`ia-qa-heal map\` first.`);
75
79
  process.exitCode = 2;
@@ -82,6 +86,7 @@ async function runBaseline(args = []) {
82
86
  // meant to be committed, so deleting is reversible; keeping is the silent lie.
83
87
  const staleFiles = jsonFilesIn(to).filter((f) => !pages.includes(f));
84
88
  const staleLayouts = jsonFilesIn(path.join(to, config_1.LAYOUT_DIRNAME)).filter((f) => !layouts.includes(f));
89
+ const staleResolved = jsonFilesIn(path.join(to, config_1.RESOLVED_DIRNAME)).filter((f) => !resolved.includes(f));
85
90
  fs.mkdirSync(to, { recursive: true });
86
91
  for (const file of pages) {
87
92
  fs.copyFileSync(path.join(from, file), path.join(to, file));
@@ -92,19 +97,28 @@ async function runBaseline(args = []) {
92
97
  fs.copyFileSync(path.join(from, config_1.LAYOUT_DIRNAME, file), path.join(to, config_1.LAYOUT_DIRNAME, file));
93
98
  }
94
99
  }
100
+ if (resolved.length > 0) {
101
+ fs.mkdirSync(path.join(to, config_1.RESOLVED_DIRNAME), { recursive: true });
102
+ for (const file of resolved) {
103
+ fs.copyFileSync(path.join(from, config_1.RESOLVED_DIRNAME, file), path.join(to, config_1.RESOLVED_DIRNAME, file));
104
+ }
105
+ }
95
106
  for (const file of staleFiles)
96
107
  fs.unlinkSync(path.join(to, file));
97
108
  for (const file of staleLayouts)
98
109
  fs.unlinkSync(path.join(to, config_1.LAYOUT_DIRNAME, file));
99
- const removed = staleFiles.length + staleLayouts.length;
110
+ for (const file of staleResolved)
111
+ fs.unlinkSync(path.join(to, config_1.RESOLVED_DIRNAME, file));
112
+ const removed = staleFiles.length + staleLayouts.length + staleResolved.length;
100
113
  const total = pages.length + layouts.length;
101
114
  console.log(`\n📌 Baseline promoted → ${rel(to)}`);
102
115
  console.log(` ${pages.length} ${pages.length === 1 ? 'page' : 'pages'}` +
103
116
  (layouts.length > 0 ? ` · ${layouts.length} ${layouts.length === 1 ? 'layout' : 'layouts'}` : '') +
117
+ (resolved.length > 0 ? ` · ${resolved.length} with selector bindings` : '') +
104
118
  (replaced > 0 ? ` (replaced a baseline of ${replaced} ${replaced === 1 ? 'file' : 'files'})` : ''));
105
119
  if (removed > 0) {
106
120
  console.log(` 🧹 Removed ${removed} stale ${removed === 1 ? 'file' : 'files'} the new mapping no longer produces: ` +
107
- [...staleFiles, ...staleLayouts.map((f) => `${config_1.LAYOUT_DIRNAME}/${f}`)].join(', '));
121
+ [...staleFiles, ...staleLayouts.map((f) => `${config_1.LAYOUT_DIRNAME}/${f}`), ...staleResolved.map((f) => `${config_1.RESOLVED_DIRNAME}/${f}`)].join(', '));
108
122
  }
109
123
  // Detect phantom contracts: mapping files that don't match any configured page.
110
124
  // These are left behind when a page is removed from config.json — they get promoted
@@ -1 +1 @@
1
- {"version":3,"file":"baseline.js","sourceRoot":"","sources":["../../src/cli/baseline.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"baseline.js","sourceRoot":"","sources":["../../src/cli/baseline.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import { MaskOutcome } from '../nameMask';
2
+ import { BindingDrift } from '../resolution';
2
3
  import { Usage } from '../ingest';
3
4
  import { NameDriftFinding } from '../nameDrift';
4
5
  import { FixOutcome, ReportEvidence } from '../htmlReport';
@@ -91,7 +92,7 @@ export declare function printNameDrift(findings: NameDriftFinding[] | undefined)
91
92
  * and never in `--json` mode, where it would corrupt the machine-readable payload.
92
93
  * Shared with `run`, which ends on the same verdict.
93
94
  */
94
- export declare function driftSummaryLine(verb: string, verdict: 'PASS' | 'FIX' | 'BLOCK', c: Report['counts'], nameDrift?: NameDriftFinding[]): void;
95
+ export declare function driftSummaryLine(verb: string, verdict: 'PASS' | 'FIX' | 'BLOCK', c: Report['counts'], nameDrift?: NameDriftFinding[], bindingDrift?: BindingDrift[]): void;
95
96
  export interface DirDiffCollection {
96
97
  reports: Array<{
97
98
  name: string;
@@ -101,6 +102,13 @@ export interface DirDiffCollection {
101
102
  /** Pairs where both sides came out of the same `map` run — incapable of showing drift. */
102
103
  stale: string[];
103
104
  }
105
+ /**
106
+ * A broken binding whose element survived is repairable — FIX. Everything else is a
107
+ * decision: `rebound` most of all, where the test still passes and now acts on a
108
+ * different element. That is the selector-space twin of the element `rebound`, and the
109
+ * one failure no other check in this package can see.
110
+ */
111
+ export declare function escalateBindingVerdict(verdict: Report['verdict'], drift: BindingDrift[]): Report['verdict'];
104
112
  /**
105
113
  * The collection half of a directory diff, shared with `ia-qa-heal run`: walk two
106
114
  * directories of mapping files, pair by filename (layouts in `_layouts/` first),
@@ -130,6 +138,12 @@ export interface DirReport {
130
138
  nameDrift?: NameDriftFinding[];
131
139
  /** `config.nameMask`'s effect across the walk — applied patterns and refusals, unioned. */
132
140
  nameMask?: MaskOutcome;
141
+ /**
142
+ * Selector strings from the suite whose binding to a contracted element broke.
143
+ * Present only once `ingest` has inventoried the tests **and** `map` has resolved
144
+ * them — absent means "not measured", never "none". Same rule as `nameDrift`.
145
+ */
146
+ bindingDrift?: BindingDrift[];
133
147
  }
134
148
  export declare function aggregateReports(reports: Array<{
135
149
  name: string;
@@ -189,4 +203,10 @@ export interface Report {
189
203
  * refused with the reason. Absent when no mask is configured.
190
204
  */
191
205
  nameMask?: MaskOutcome;
206
+ /**
207
+ * Selector strings from the suite whose binding to a contracted element broke.
208
+ * Present only once `ingest` has inventoried the tests **and** the capture has
209
+ * resolved them — absent means "not measured", never "none". Same rule as `nameDrift`.
210
+ */
211
+ bindingDrift?: BindingDrift[];
192
212
  }