@ia-qa/self-healing 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +91 -0
- package/TUTORIAL.md +399 -0
- package/dist/aom.d.ts +22 -0
- package/dist/aom.js +58 -0
- package/dist/aom.js.map +1 -0
- package/dist/browser/contract.d.ts +1 -0
- package/dist/browser/contract.js +107 -0
- package/dist/browser/contract.js.map +1 -0
- package/dist/browser/extract.d.ts +25 -0
- package/dist/browser/extract.js +194 -0
- package/dist/browser/extract.js.map +1 -0
- package/dist/browser/match.d.ts +88 -0
- package/dist/browser/match.js +179 -0
- package/dist/browser/match.js.map +1 -0
- package/dist/cli/diff.d.ts +13 -0
- package/dist/cli/diff.js +143 -0
- package/dist/cli/diff.js.map +1 -0
- package/dist/cli/fix.d.ts +17 -0
- package/dist/cli/fix.js +144 -0
- package/dist/cli/fix.js.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +50 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/init.d.ts +8 -0
- package/dist/cli/init.js +185 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/map.d.ts +9 -0
- package/dist/cli/map.js +118 -0
- package/dist/cli/map.js.map +1 -0
- package/dist/config.d.ts +55 -0
- package/dist/config.js +111 -0
- package/dist/config.js.map +1 -0
- package/dist/fixEngine.d.ts +49 -0
- package/dist/fixEngine.js +154 -0
- package/dist/fixEngine.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -0
- package/dist/launcher.d.ts +38 -0
- package/dist/launcher.js +38 -0
- package/dist/launcher.js.map +1 -0
- package/dist/mapUrl.d.ts +34 -0
- package/dist/mapUrl.js +90 -0
- package/dist/mapUrl.js.map +1 -0
- package/dist/markdown.d.ts +10 -0
- package/dist/markdown.js +54 -0
- package/dist/markdown.js.map +1 -0
- package/dist/mcp/server.d.ts +2 -0
- package/dist/mcp/server.js +251 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/playwright/healer.d.ts +39 -0
- package/dist/playwright/healer.js +84 -0
- package/dist/playwright/healer.js.map +1 -0
- package/package.json +54 -0
package/README.md
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# @ia-qa/self-healing (MVP β private)
|
|
2
|
+
|
|
3
|
+
Self-healing E2E toolkit: a **local MCP server** for AI agents, a guided **CLI**, an accessibility-tree page mapper, and runtime helpers (`aiClick` / `aiFill`) that recover from broken selectors. Framework- and language-agnostic (Cypress, Playwright, Seleniumβ¦; JS/TS, Python, Javaβ¦).
|
|
4
|
+
|
|
5
|
+
> π **New here? Read [TUTORIAL.md](./TUTORIAL.md)** β the step-by-step, no-jargon guide. This README is the reference.
|
|
6
|
+
|
|
7
|
+
**It runs entirely on the end user's machine.** It brings its own headless Chromium, so it sees `localhost`, staging, or an app behind a VPN β and **no data ever leaves the machine**. Nothing is hosted.
|
|
8
|
+
|
|
9
|
+
## Two front doors, one engine
|
|
10
|
+
|
|
11
|
+
| | For | Entry point |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| **MCP server** (stdio) | AI agents β "map my app at \<url\>" | `ia-qa-heal-mcp` β tools `map_app`, `diff_mappings`, `fix_tests` |
|
|
14
|
+
| **CLI** | humans & CI | `ia-qa-heal` β `init`, `map`, `diff`, `fix` |
|
|
15
|
+
|
|
16
|
+
Agent config (Claude Code / Desktop, Cursorβ¦):
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"mcpServers": {
|
|
20
|
+
"ia-qa-self-healing": {
|
|
21
|
+
"command": "npx",
|
|
22
|
+
"args": ["-y", "-p", "@ia-qa/self-healing", "ia-qa-heal-mcp"]
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
`-p` is required, not cosmetic: the package ships **two** bins (`ia-qa-heal`, `ia-qa-heal-mcp`), so `npx @ia-qa/self-healing β¦` cannot resolve which to run and silently starts nothing. Verified against a packed tarball.
|
|
28
|
+
The MCP server is a dependency-free JSON-RPC 2.0 stdio implementation (`src/mcp/server.ts`) β no SDK, matching the repo's zero-dep client ethos. It does **not** require the separate Playwright MCP: it drives its own browser. stdout is the protocol channel; logs go to stderr.
|
|
29
|
+
|
|
30
|
+
### Browser reuse β no second Chromium (`src/launcher.ts`)
|
|
31
|
+
Playwright's browser cache is **machine-wide and shared** (`%LOCALAPPDATA%\ms-playwright`, `~/.cache/ms-playwright`). If the user already has *any* Playwright install β their own tests or the Playwright MCP β the bundled Chromium is already there and we reuse it for free. There is no "second Chromium" to avoid; the only real cost is **revision skew** (a different Playwright version pins a different Chromium revision). For that case, and for machines that forbid the download, the launcher resolves in this order:
|
|
32
|
+
|
|
33
|
+
1. explicit executable β `browserPath` / `$IAQA_BROWSER_PATH`
|
|
34
|
+
2. explicit channel β `browserChannel` / `$IAQA_BROWSER_CHANNEL` (`chrome`, `msedge`β¦) β **zero download**
|
|
35
|
+
3. Playwright's bundled Chromium (shared cache β usually already present)
|
|
36
|
+
4. automatic fallback to the system Chrome, then Edge
|
|
37
|
+
|
|
38
|
+
Only if all four fail does it error, listing the three fixes. `map_app` exposes `browser_channel` / `browser_path` and always reports which browser it used. For DOM extraction the engine is irrelevant β Chrome, Edge and Chromium yield the same contract.
|
|
39
|
+
|
|
40
|
+
## How it works
|
|
41
|
+
|
|
42
|
+
1. **`ia-qa-heal init`** β interactive wizard. Choose the framework, where credentials live (**local `.env`** or **AWS SSM Parameter Store**), the login flow, and the pages to map. Writes `.ia-qa/config.json` β it contains **secret references only, never values**.
|
|
43
|
+
2. **`ia-qa-heal map [page]`** β resolves credentials at runtime (env or SSM via your local AWS credential chain), launches headless Chromium, logs in if configured, and extracts every **visible interactive element** (role + accessible name + stable selector) to **two files** per page: `.ia-qa/mapping/<page>.json` (for tooling / `diff`) and `.ia-qa/mapping/<page>.md` (the **page contract** β the framework-agnostic artifact you hand to an LLM to auto-heal tests):
|
|
44
|
+
```json
|
|
45
|
+
{
|
|
46
|
+
"page": "login",
|
|
47
|
+
"url": "/login",
|
|
48
|
+
"capturedAt": "2026-07-15T10:00:00.000Z",
|
|
49
|
+
"elements": [
|
|
50
|
+
{ "role": "button", "name": "Se connecter", "selector": "button#login" }
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
The `<page>.md` groups elements by role and carries a self-describing header telling a model exactly how to use it. **The CI auto-heal loop:** when a UI test fails, hand the failing test file + the current `<page>.md` to an LLM β it finds the element by role + accessible name, reads the current selector, and rewrites the test in **whatever framework and language** it is written in (Cypress, Playwright, Selenium; JS or Python). The contract is the source of truth; the model handles the syntax. The same contract is downloadable from the zero-install **Selector Drift Detector** page on ia-qa.com.
|
|
55
|
+
3. **`ia-qa-heal diff <before.json> <after.json>`** β CI-gate verdict. Diffs two mappings (from `map` or the ia-qa.com browser snippet) and reports which selectors survived, which the runtime healer would recover, and which will hard-fail:
|
|
56
|
+
```
|
|
57
|
+
β BLOCK
|
|
58
|
+
before.json β after.json Β· 3 ok Β· 2 healable Β· 1 lost Β· 2 new
|
|
59
|
+
π§ healable button "Se connecter" button#login-btn β button.btn-primary (exact name)
|
|
60
|
+
β lost button "Supprimer le compte" button#delete β (no match)
|
|
61
|
+
```
|
|
62
|
+
Exit codes for pipelines: `0` = PASS or FIX, `1` = BLOCK (add `--strict` to fail on FIX too), `2` = bad input. Use `--json` for machine-readable output. Same diff engine as the web tool (`src/browser/match.js`).
|
|
63
|
+
|
|
64
|
+
Every element carries a **context** (nearest landmark + section heading) so two elements with the same role + accessible name (five "Delete" buttons) stay distinguishable. The diff classifies each baseline element as `ok`, `renamed` (selector valid but the accessible name changed β a *content* drift to eyeball), `healable` (deterministic oldβnew rewrite), `ambiguous` (several equal matches β the tool **refuses to guess**, a human decides), or `lost` (no match).
|
|
65
|
+
4. **`ia-qa-heal fix <before.json> <after.json> <test-pathsβ¦>`** β the deterministic half. Applies **only** the `healable` rewrites to your test files, replacing the selector only where it appears as a quoted string literal (`'β¦'`/`"β¦"`/`` `β¦` ``, quote-safe for attribute selectors), across any framework/language. LOST / AMBIGUOUS / RENAMED are printed but never touched β they need judgment. It **edits the working tree and never commits**: review with `git diff`, then push or discard. `--dry-run` previews the plan. This is "prepare the ground, the human decides" as a command.
|
|
66
|
+
5. **Runtime healing** β in your Playwright tests:
|
|
67
|
+
```ts
|
|
68
|
+
import { aiClick, aiFill } from '@ia-qa/self-healing';
|
|
69
|
+
|
|
70
|
+
await aiFill(page, 'input[name="email"]', user);
|
|
71
|
+
await aiClick(page, 'button#login');
|
|
72
|
+
```
|
|
73
|
+
If the selector fails, the helper re-scans the live page, matches the element by role + accessible name (exact, then fuzzy Dice β₯ 0.6 β or your own `llmResolver`), retries on the healed selector, and logs a warning telling you to update the test and re-map.
|
|
74
|
+
|
|
75
|
+
## Design notes
|
|
76
|
+
|
|
77
|
+
- `page.accessibility.snapshot()` is deprecated **and returns no selectors**, so mapping walks the DOM directly and computes ARIA role + accname + a stable selector (`data-testid` β `id` β `name` β short structural path).
|
|
78
|
+
- **Shared browser-safe core.** `src/browser/` holds plain, self-contained JS (no imports, no TS syntax) used by both the CLI and the ia-qa.com web tool: `extract.js` (DOM walk), `match.js` (semantic matching + mapping diff), `contract.js` (Markdown page-contract renderer). The CLI feeds `extractInPage` to Playwright's `page.evaluate()` and the healer re-runs it live; ia-qa.com imports `extract.js` with `?raw` to show it as a copy-paste DevTools snippet and imports `match.js`/`contract.js` directly. Result: the zero-install web flow produces byte-identical mappings, verdicts and `.md` contracts to `ia-qa-heal` (verified on the same page). Keep everything in `src/browser/` dependency-free or all consumers break.
|
|
79
|
+
- Secrets: never written to disk. `env` reads `process.env`; `aws-ssm` calls `GetParameter` with decryption using the AWS SDK default credential chain (needs `ssm:GetParameter`, plus `kms:Decrypt` for SecureString).
|
|
80
|
+
- `llmResolverStub` in `src/playwright/healer.ts` is the extension point for a real LLM/embedding-based resolver (e.g. via `POST https://www.ia-qa.com/mcp/call`).
|
|
81
|
+
|
|
82
|
+
## Build & run
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
cd packages/self-healing
|
|
86
|
+
npm run build # tsc β dist/
|
|
87
|
+
node dist/cli/index.js init
|
|
88
|
+
node dist/cli/index.js map
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
`playwright` is a peer dependency β the consuming test project provides it (`npx playwright install chromium` if needed).
|
package/TUTORIAL.md
ADDED
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
# Self-healing tests β the complete beginner's guide
|
|
2
|
+
|
|
3
|
+
**What this does, in one sentence:** it takes a photo of every button, link and field in your web app, so when a developer changes the UI and your tests break, you get a ready-to-review fix instead of a red pipeline and an afternoon of detective work.
|
|
4
|
+
|
|
5
|
+
**You do not need to understand any of the internals to use it.** Follow the steps.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Table of contents
|
|
10
|
+
1. [Is this for me?](#1-is-this-for-me)
|
|
11
|
+
2. [The 30-second mental model](#2-the-30-second-mental-model)
|
|
12
|
+
3. [Before you start](#3-before-you-start)
|
|
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)
|
|
15
|
+
6. [Your first real workflow](#6-your-first-real-workflow-the-whole-point)
|
|
16
|
+
7. [Reading the verdict](#7-reading-the-verdict)
|
|
17
|
+
8. [Putting it in CI](#8-putting-it-in-ci)
|
|
18
|
+
9. [Troubleshooting](#9-troubleshooting)
|
|
19
|
+
10. [FAQ](#10-faq)
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## 1. Is this for me?
|
|
24
|
+
|
|
25
|
+
**Yes** if you have automated UI tests (Cypress, Playwright, Selenium, WebdriverIO β in any language) that break when the front-end changes.
|
|
26
|
+
|
|
27
|
+
**No** if you only have unit tests or API tests. This tool is about UI selectors.
|
|
28
|
+
|
|
29
|
+
It works on **your** app: `localhost`, a staging server, or an internal app behind a **VPN**. Everything runs on your machine. **Nothing is ever sent to ia-qa.com or anywhere else.**
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## 2. The 30-second mental model
|
|
34
|
+
|
|
35
|
+
Your test says "click `button#login`". A developer renames a CSS class, `button#login` no longer exists, your test fails. The button is still there β it's still the button that says *"Se connecter"*. Only its address changed.
|
|
36
|
+
|
|
37
|
+
This tool:
|
|
38
|
+
1. **Maps** your app β a *page contract*: every element listed as *role + name + where it lives + its current selector*.
|
|
39
|
+
2. **Diffs** yesterday's contract against today's β tells you exactly what moved.
|
|
40
|
+
3. **Fixes** the safe ones automatically, and **hands you the rest**.
|
|
41
|
+
|
|
42
|
+
> π **The golden rule:** the tool **never** commits and **never** pushes. It prepares the change; **you** decide. A test that silently repairs itself is not a test.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## 3. Before you start
|
|
47
|
+
|
|
48
|
+
You need **Node.js 18 or newer**. Check it:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
node --version
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
If you get `v18.x.x` or higher (e.g. `v20.11.0`), you're good. If you get "command not found", install Node from [nodejs.org](https://nodejs.org) (take the "LTS" button) and reopen your terminal.
|
|
55
|
+
|
|
56
|
+
That's the only prerequisite. A browser downloads automatically the first time β you don't have to do anything.
|
|
57
|
+
|
|
58
|
+
### π‘ "I don't want to download another browser"
|
|
59
|
+
|
|
60
|
+
Good news, usually you won't:
|
|
61
|
+
|
|
62
|
+
- **If you already use Playwright** (your own tests, or the Playwright MCP), the browser is **already on your machine** and this tool reuses it. Playwright keeps one shared cache for the whole machine (`%LOCALAPPDATA%\ms-playwright` on Windows, `~/.cache/ms-playwright` on macOS/Linux). **Nothing extra is downloaded.**
|
|
63
|
+
- **If you have Google Chrome or Edge installed**, you can use that instead and download *nothing at all*:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
# Windows (PowerShell)
|
|
67
|
+
$env:IAQA_BROWSER_CHANNEL = "chrome"
|
|
68
|
+
# macOS / Linux
|
|
69
|
+
export IAQA_BROWSER_CHANNEL=chrome
|
|
70
|
+
```
|
|
71
|
+
(`msedge` also works. Or point at any browser: `IAQA_BROWSER_PATH=/path/to/chrome`.)
|
|
72
|
+
|
|
73
|
+
With an agent, just ask: **"Map my app at http://localhost:3000 using the chrome channel"**.
|
|
74
|
+
|
|
75
|
+
- **If nothing is installed**, it downloads Chromium once (~150 MB) β the same thing any browser-automation tool does.
|
|
76
|
+
|
|
77
|
+
Whatever it uses, it tells you: `Browser: system browser (channel "chrome")`.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## 4. Path A β I use an AI agent (easiest)
|
|
82
|
+
|
|
83
|
+
*Use this if you have Claude Code, Claude Desktop, Cursor, or another MCP-compatible agent. You'll talk to your app in plain English.*
|
|
84
|
+
|
|
85
|
+
### Step 4.1 β Add the tool to your agent
|
|
86
|
+
|
|
87
|
+
Find your agent's MCP config file and add the `ia-qa-self-healing` block below.
|
|
88
|
+
|
|
89
|
+
| Agent | Config file |
|
|
90
|
+
|---|---|
|
|
91
|
+
| **Claude Code** | run `claude mcp add` (or edit `.mcp.json` in your project) |
|
|
92
|
+
| **Claude Desktop (Windows)** | `%APPDATA%\Claude\claude_desktop_config.json` |
|
|
93
|
+
| **Claude Desktop (macOS)** | `~/Library/Application Support/Claude/claude_desktop_config.json` |
|
|
94
|
+
| **Cursor** | `~/.cursor/mcp.json` |
|
|
95
|
+
|
|
96
|
+
```json
|
|
97
|
+
{
|
|
98
|
+
"mcpServers": {
|
|
99
|
+
"ia-qa-self-healing": {
|
|
100
|
+
"command": "npx",
|
|
101
|
+
"args": ["-y", "-p", "@ia-qa/self-healing", "ia-qa-heal-mcp"]
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
> β οΈ Copy it exactly β the `-p` matters. The package ships two commands (`ia-qa-heal` and `ia-qa-heal-mcp`), and `-p` is what tells `npx` which one to run. Without it, nothing starts.
|
|
108
|
+
|
|
109
|
+
<details>
|
|
110
|
+
<summary><b>Running from the source repo instead?</b> (click)</summary>
|
|
111
|
+
|
|
112
|
+
If you cloned the repo rather than installing from npm:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
cd packages/self-healing
|
|
116
|
+
npm install
|
|
117
|
+
npm run build
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Then point the config at the built file (use the **absolute** path):
|
|
121
|
+
|
|
122
|
+
```json
|
|
123
|
+
{
|
|
124
|
+
"mcpServers": {
|
|
125
|
+
"ia-qa-self-healing": {
|
|
126
|
+
"command": "node",
|
|
127
|
+
"args": ["/absolute/path/to/packages/self-healing/dist/mcp/server.js"]
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
</details>
|
|
133
|
+
|
|
134
|
+
### Step 4.2 β Restart your agent
|
|
135
|
+
|
|
136
|
+
Fully quit and reopen it. Otherwise it won't see the new tool.
|
|
137
|
+
|
|
138
|
+
### Step 4.3 β Check it worked
|
|
139
|
+
|
|
140
|
+
Ask your agent:
|
|
141
|
+
|
|
142
|
+
> **"What ia-qa self-healing tools do you have?"**
|
|
143
|
+
|
|
144
|
+
It should list three: `map_app`, `diff_mappings`, `fix_tests`. If it doesn't, jump to [Troubleshooting](#9-troubleshooting).
|
|
145
|
+
|
|
146
|
+
### Step 4.4 β Map your first page π
|
|
147
|
+
|
|
148
|
+
Start your app (e.g. `npm start`, running on `http://localhost:3000`). Then say:
|
|
149
|
+
|
|
150
|
+
> **"Map my app at http://localhost:3000 and save the contract to .ia-qa/mapping"**
|
|
151
|
+
|
|
152
|
+
The agent launches a browser in the background and gives you back something like:
|
|
153
|
+
|
|
154
|
+
```markdown
|
|
155
|
+
# Page contract β home
|
|
156
|
+
|
|
157
|
+
- **URL:** `http://localhost:3000`
|
|
158
|
+
- **Interactive elements:** 12
|
|
159
|
+
|
|
160
|
+
## button (3)
|
|
161
|
+
|
|
162
|
+
| Accessible name | Context | Selector |
|
|
163
|
+
| --- | --- | --- |
|
|
164
|
+
| Se connecter | main βΊ Connexion | `button#login-btn` |
|
|
165
|
+
| Valider | main βΊ Paiement | `button[data-testid="pay"]` |
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
**That's your baseline.** Commit it β it lives in git like code:
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
git add .ia-qa/mapping
|
|
172
|
+
git commit -m "Add page contract baseline"
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Step 4.5 β Map several pages
|
|
176
|
+
|
|
177
|
+
Just ask:
|
|
178
|
+
|
|
179
|
+
> **"Map these routes on http://localhost:3000: /, /login, /checkout, /account β save each to .ia-qa/mapping"**
|
|
180
|
+
|
|
181
|
+
Or let it find them:
|
|
182
|
+
|
|
183
|
+
> **"Look at my router config, list all the routes, then map them"**
|
|
184
|
+
|
|
185
|
+
It will show you the route list and ask you to confirm before crawling. Say yes.
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## 5. Path B β I use a terminal
|
|
190
|
+
|
|
191
|
+
*Use this if you'd rather run commands, or you're setting up CI.*
|
|
192
|
+
|
|
193
|
+
### Step 5.1 β Install
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
npm install -D @ia-qa/self-healing
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Step 5.2 β Configure once
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
npx ia-qa-heal init
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
It asks a few questions (your app's URL, whether there's a login, which pages to map) and writes `.ia-qa/config.json`.
|
|
206
|
+
|
|
207
|
+
> π **Your password is never written to that file** β only the *name* of the environment variable that holds it.
|
|
208
|
+
|
|
209
|
+
### Step 5.3 β Map
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
npx ia-qa-heal map
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
You get two files per page in `.ia-qa/mapping/`:
|
|
216
|
+
- `checkout.json` β for the tools
|
|
217
|
+
- `checkout.md` β for humans and AI
|
|
218
|
+
|
|
219
|
+
Commit them.
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## 6. Your first real workflow (the whole point)
|
|
224
|
+
|
|
225
|
+
Here's the loop you'll actually live in.
|
|
226
|
+
|
|
227
|
+
### Step 6.1 β You have a baseline
|
|
228
|
+
From step 4.4 or 5.3, committed to git. Call it `checkout.baseline.json` (just copy it):
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
cp .ia-qa/mapping/checkout.json .ia-qa/mapping/checkout.baseline.json
|
|
232
|
+
git add .ia-qa/mapping && git commit -m "baseline"
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Step 6.2 β Someone changes the UI
|
|
236
|
+
A developer ships a redesign. Your tests start failing. **Normal.**
|
|
237
|
+
|
|
238
|
+
### Step 6.3 β Re-map
|
|
239
|
+
Agent: **"Re-map http://localhost:3000/checkout to .ia-qa/mapping"**
|
|
240
|
+
Terminal: `npx ia-qa-heal map checkout`
|
|
241
|
+
|
|
242
|
+
### Step 6.4 β See what moved
|
|
243
|
+
Agent: **"Diff the baseline against the new contract"**
|
|
244
|
+
Terminal:
|
|
245
|
+
```bash
|
|
246
|
+
npx ia-qa-heal diff .ia-qa/mapping/checkout.baseline.json .ia-qa/mapping/checkout.json
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Step 6.5 β Apply the safe fixes
|
|
250
|
+
Agent: **"Fix my tests in the tests/ folder using that diff"**
|
|
251
|
+
Terminal:
|
|
252
|
+
```bash
|
|
253
|
+
# Preview first β nothing is written
|
|
254
|
+
npx ia-qa-heal fix .ia-qa/mapping/checkout.baseline.json .ia-qa/mapping/checkout.json tests/ --dry-run
|
|
255
|
+
|
|
256
|
+
# Do it
|
|
257
|
+
npx ia-qa-heal fix .ia-qa/mapping/checkout.baseline.json .ia-qa/mapping/checkout.json tests/
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Step 6.6 β You review. You decide.
|
|
261
|
+
```bash
|
|
262
|
+
git diff # look at exactly what changed
|
|
263
|
+
npm test # re-run your tests
|
|
264
|
+
```
|
|
265
|
+
Happy? Commit and push. Not happy? `git checkout .` and nothing happened. **The tool never pushed anything.**
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
## 7. Reading the verdict
|
|
270
|
+
|
|
271
|
+
You'll see one of three words. This is the whole product β learn these.
|
|
272
|
+
|
|
273
|
+
| Verdict | Meaning | What you do |
|
|
274
|
+
|---|---|---|
|
|
275
|
+
| β
**PASS** | Nothing moved. | Nothing. Go home. |
|
|
276
|
+
| π§ **FIX** | Selectors broke, but each has exactly one obvious replacement. | Run `fix`, review `git diff`, commit. |
|
|
277
|
+
| β **BLOCK** | Something is **lost** or **ambiguous**. | Read below. A human is required. |
|
|
278
|
+
|
|
279
|
+
And per element:
|
|
280
|
+
|
|
281
|
+
| Status | Plain English | Auto-fixed? |
|
|
282
|
+
|---|---|---|
|
|
283
|
+
| **ok** | Unchanged. | β |
|
|
284
|
+
| **renamed** | The selector still works, but the *label* changed ("Buy" β "Subscribe"). Your test still passes. | β No β **look at it**. It might be a real product change your test should have caught. |
|
|
285
|
+
| **healable** | The selector moved; the same element is clearly still there. | β
Yes |
|
|
286
|
+
| **ambiguous** | Several elements match equally (five "Delete" buttons). | β No β **the tool refuses to guess.** Healing the wrong one would make a broken test green. Add a `data-testid` to that element. |
|
|
287
|
+
| **lost** | Gone. Nothing matches. | β No β the element was removed or renamed beyond recognition. Fix or delete the test. |
|
|
288
|
+
|
|
289
|
+
> π‘ **Why "ambiguous" refuses instead of picking:** a test that clicks the *wrong* Delete button and passes is worse than a test that fails. Failing is honest.
|
|
290
|
+
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
## 8. Putting it in CI
|
|
294
|
+
|
|
295
|
+
The `diff` command returns an **exit code**, so your pipeline can gate on it:
|
|
296
|
+
|
|
297
|
+
- `0` β PASS or FIX (tests will run)
|
|
298
|
+
- `1` β BLOCK (a human must look)
|
|
299
|
+
- `2` β bad input
|
|
300
|
+
|
|
301
|
+
```yaml
|
|
302
|
+
# .github/workflows/selector-drift.yml
|
|
303
|
+
name: Selector drift
|
|
304
|
+
on: [pull_request]
|
|
305
|
+
|
|
306
|
+
jobs:
|
|
307
|
+
drift:
|
|
308
|
+
runs-on: ubuntu-latest
|
|
309
|
+
steps:
|
|
310
|
+
- uses: actions/checkout@v4
|
|
311
|
+
- uses: actions/setup-node@v4
|
|
312
|
+
with: { node-version: '20' }
|
|
313
|
+
- run: npm ci
|
|
314
|
+
- run: npx playwright install --with-deps chromium
|
|
315
|
+
|
|
316
|
+
- name: Start the app
|
|
317
|
+
run: npm start &
|
|
318
|
+
|
|
319
|
+
- name: Re-map the app
|
|
320
|
+
run: npx ia-qa-heal map
|
|
321
|
+
|
|
322
|
+
- name: Fail if selectors drifted with no safe fix
|
|
323
|
+
run: npx ia-qa-heal diff .ia-qa/mapping/checkout.baseline.json .ia-qa/mapping/checkout.json
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
Add `--strict` if you want **FIX** to fail the build too (forces tests to be updated, not just healed).
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
|
|
330
|
+
## 9. Troubleshooting
|
|
331
|
+
|
|
332
|
+
**"My agent doesn't see the tools"**
|
|
333
|
+
- Did you fully quit and reopen the agent? (Not just close the window.)
|
|
334
|
+
- Is the JSON valid? A trailing comma breaks it. Paste it into [jsonlint.com](https://jsonlint.com).
|
|
335
|
+
- Using the source path? It must be **absolute** (`/Users/you/...` or `C:\\Users\\you\\...`), not `./dist`.
|
|
336
|
+
|
|
337
|
+
**"It's downloading something huge the first time"**
|
|
338
|
+
That's Chromium (~150 MB), once. Normal β any browser-automation tool does this. To avoid it entirely, reuse a browser you already have: set `IAQA_BROWSER_CHANNEL=chrome` (see [Before you start](#3-before-you-start)). If you already use Playwright anywhere, nothing is downloaded at all β the cache is shared.
|
|
339
|
+
|
|
340
|
+
**"No usable browser found"**
|
|
341
|
+
The error tells you the three ways out: `npx playwright install chromium`, or `IAQA_BROWSER_CHANNEL=chrome`, or `IAQA_BROWSER_PATH=/path/to/chrome`.
|
|
342
|
+
|
|
343
|
+
**"The contract is half empty"**
|
|
344
|
+
Your app is probably still loading when the snapshot is taken. Tell the agent:
|
|
345
|
+
> "Map it again, but wait for the selector `#app-ready` first"
|
|
346
|
+
|
|
347
|
+
Or in terminal, the app needs more time to settle β check the page really renders at that URL.
|
|
348
|
+
|
|
349
|
+
**"It can't get past my login"**
|
|
350
|
+
- Terminal: `ia-qa-heal init` sets up the login flow for you.
|
|
351
|
+
- Agent: log in manually first, or ask it to log in step by step, then map.
|
|
352
|
+
|
|
353
|
+
**"ERR_CONNECTION_REFUSED"**
|
|
354
|
+
Your app isn't running at that URL. Start it first and open the URL in your own browser to check.
|
|
355
|
+
|
|
356
|
+
**"0 elements found"**
|
|
357
|
+
The page may be a canvas/WebGL app, or have no accessible elements. This tool needs real buttons, links and inputs. (If a screen reader can't use your app, neither can this β that's an accessibility bug worth fixing anyway.)
|
|
358
|
+
|
|
359
|
+
---
|
|
360
|
+
|
|
361
|
+
## 10. FAQ
|
|
362
|
+
|
|
363
|
+
**Does my code or my app's content get sent anywhere?**
|
|
364
|
+
No. Everything runs on your machine and writes local files. There is no network call to ia-qa.com. That's why it works behind a VPN.
|
|
365
|
+
|
|
366
|
+
**Will it commit or push my code?**
|
|
367
|
+
Never. It edits files in your working tree at most. You review with `git diff` and decide.
|
|
368
|
+
|
|
369
|
+
**Do I need the Playwright MCP too?**
|
|
370
|
+
No. This tool brings its own browser. One install, not two.
|
|
371
|
+
|
|
372
|
+
**Cypress or Playwright or Selenium?**
|
|
373
|
+
All of them, and any language. The `fix` command replaces the selector wherever it appears as a quoted string β that's the same in JavaScript, Python, Java, C#, Ruby.
|
|
374
|
+
|
|
375
|
+
**Why is my "renamed" element not fixed automatically?**
|
|
376
|
+
Because the selector still works β your test isn't broken. But the button now says something different, which might be a real change worth failing on. Only a human knows.
|
|
377
|
+
|
|
378
|
+
**What's the `.md` file for?**
|
|
379
|
+
It's the same contract, written for humans and AI. When a test breaks, hand your AI assistant the failing test **plus** that `.md` and ask it to fix the test β it has everything it needs, in any framework.
|
|
380
|
+
|
|
381
|
+
**Can I run this against a public website?**
|
|
382
|
+
Yes, any URL your machine can reach.
|
|
383
|
+
|
|
384
|
+
---
|
|
385
|
+
|
|
386
|
+
## Cheat sheet
|
|
387
|
+
|
|
388
|
+
```bash
|
|
389
|
+
npx ia-qa-heal init # one-time setup
|
|
390
|
+
npx ia-qa-heal map # scan β .ia-qa/mapping/*.json + *.md
|
|
391
|
+
npx ia-qa-heal diff before.json after.json # verdict: PASS / FIX / BLOCK
|
|
392
|
+
npx ia-qa-heal fix before.json after.json tests/ --dry-run # preview
|
|
393
|
+
npx ia-qa-heal fix before.json after.json tests/ # apply (never commits)
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
Talking to an agent instead:
|
|
397
|
+
> "Map my app at http://localhost:3000 to .ia-qa/mapping"
|
|
398
|
+
> "Diff the baseline against the new contract"
|
|
399
|
+
> "Fix my tests in tests/ β show me the diff, don't commit"
|
package/dist/aom.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Page } from 'playwright';
|
|
2
|
+
/**
|
|
3
|
+
* Mapping I/O. The extraction itself lives in `browser/extract.js` β it is plain
|
|
4
|
+
* self-contained JS because Playwright serializes it into the page and the web
|
|
5
|
+
* tool on ia-qa.com serves the same file as a copy-paste DevTools snippet.
|
|
6
|
+
*/
|
|
7
|
+
export interface MappedElement {
|
|
8
|
+
role: string;
|
|
9
|
+
name: string;
|
|
10
|
+
selector: string;
|
|
11
|
+
/** Disambiguating container hint: nearest landmark + section heading. May be empty. */
|
|
12
|
+
context?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface PageMapping {
|
|
15
|
+
page: string;
|
|
16
|
+
url: string;
|
|
17
|
+
capturedAt: string;
|
|
18
|
+
elements: MappedElement[];
|
|
19
|
+
}
|
|
20
|
+
export declare function extractInteractiveElements(page: Page): Promise<MappedElement[]>;
|
|
21
|
+
export declare function saveMapping(mapping: PageMapping, dir?: string): string;
|
|
22
|
+
export declare function loadMapping(pageName: string, dir?: string): PageMapping;
|
package/dist/aom.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.extractInteractiveElements = extractInteractiveElements;
|
|
37
|
+
exports.saveMapping = saveMapping;
|
|
38
|
+
exports.loadMapping = loadMapping;
|
|
39
|
+
const fs = __importStar(require("fs"));
|
|
40
|
+
const config_1 = require("./config");
|
|
41
|
+
const extract_1 = require("./browser/extract");
|
|
42
|
+
async function extractInteractiveElements(page) {
|
|
43
|
+
return page.evaluate(extract_1.extractInPage);
|
|
44
|
+
}
|
|
45
|
+
function saveMapping(mapping, dir = (0, config_1.mappingDir)()) {
|
|
46
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
47
|
+
const file = (0, config_1.mappingPath)(mapping.page, dir);
|
|
48
|
+
fs.writeFileSync(file, JSON.stringify(mapping, null, 2) + '\n', 'utf8');
|
|
49
|
+
return file;
|
|
50
|
+
}
|
|
51
|
+
function loadMapping(pageName, dir = (0, config_1.mappingDir)()) {
|
|
52
|
+
const file = (0, config_1.mappingPath)(pageName, dir);
|
|
53
|
+
if (!fs.existsSync(file)) {
|
|
54
|
+
throw new Error(`No mapping found for page "${pageName}" (${file}). Run \`ia-qa-heal map ${pageName}\` first.`);
|
|
55
|
+
}
|
|
56
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=aom.js.map
|
package/dist/aom.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aom.js","sourceRoot":"","sources":["../src/aom.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0BA,gEAEC;AAED,kCAKC;AAED,kCAQC;AA7CD,uCAAyB;AAEzB,qCAAmD;AACnD,+CAAkD;AAuB3C,KAAK,UAAU,0BAA0B,CAAC,IAAU;IACzD,OAAO,IAAI,CAAC,QAAQ,CAAC,uBAAa,CAA6B,CAAC;AAClE,CAAC;AAED,SAAgB,WAAW,CAAC,OAAoB,EAAE,MAAc,IAAA,mBAAU,GAAE;IAC1E,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACvC,MAAM,IAAI,GAAG,IAAA,oBAAW,EAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAC5C,EAAE,CAAC,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,MAAM,CAAC,CAAC;IACxE,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAgB,WAAW,CAAC,QAAgB,EAAE,MAAc,IAAA,mBAAU,GAAE;IACtE,MAAM,IAAI,GAAG,IAAA,oBAAW,EAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IACxC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CACb,8BAA8B,QAAQ,MAAM,IAAI,2BAA2B,QAAQ,WAAW,CAC/F,CAAC;IACJ,CAAC;IACD,OAAO,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAgB,CAAC;AAClE,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function renderPageContract(mapping: any): string;
|