@ia-qa/pal 0.2.0 → 0.4.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 +50 -10
- package/TUTORIAL.md +144 -27
- package/dist/accept.d.ts +20 -0
- package/dist/accept.js +119 -0
- package/dist/accept.js.map +1 -0
- package/dist/cli/index.js +97 -18
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.d.ts +95 -0
- package/dist/mcp/server.js +251 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/plan.d.ts +60 -1
- package/dist/plan.js +59 -1
- package/dist/plan.js.map +1 -1
- package/dist/report.js +113 -28
- package/dist/report.js.map +1 -1
- package/dist/run.d.ts +66 -0
- package/dist/run.js +209 -0
- package/dist/run.js.map +1 -0
- package/dist/setup.d.ts +11 -0
- package/dist/setup.js +52 -4
- package/dist/setup.js.map +1 -1
- package/dist/state.d.ts +3 -0
- package/dist/state.js.map +1 -1
- package/dist/suite.d.ts +63 -0
- package/dist/suite.js +145 -0
- package/dist/suite.js.map +1 -0
- package/dist/tour.d.ts +66 -6
- package/dist/tour.js +125 -30
- package/dist/tour.js.map +1 -1
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**A QA pal next to your team — not instead of it.**
|
|
4
4
|
|
|
5
|
-
One command walks your web app, maps every page, checks it, and compares it with the last tour. Then it tells you four things, in the order you act on them: what is **broken**, what **needs a decision**, what is **ready to repair**, and what it **could not see**.
|
|
5
|
+
One command walks your web app, maps every page, checks it, and compares it with the last tour — and, when you declare your end-to-end suite, reads it too. Then it tells you four things, in the order you act on them: what is **broken**, what **needs a decision**, what is **ready to repair**, and what it **could not see**.
|
|
6
6
|
|
|
7
7
|
- **Local.** A headless browser on your machine. Nothing is uploaded, no account, no LLM.
|
|
8
8
|
- **Deterministic.** Verdicts come from [`@ia-qa/self-healing`](https://www.npmjs.com/package/@ia-qa/self-healing) — the same engine as `ia-qa-heal diff` and `ia-qa-heal check`, so the two can never disagree.
|
|
@@ -23,7 +23,7 @@ npx playwright install chromium
|
|
|
23
23
|
npx ia-qa-pal tour
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
**Not set up yet?** In a terminal, the first `tour` asks
|
|
26
|
+
**Not set up yet?** In a terminal, the first `tour` asks where the app lives and how it logs in — and, when it finds a test suite in the project, whether to read it — then reads its pages from `sitemap.xml` (or, without one, follows its links: same host, GET only, at most 50 pages), shows you exactly what it will write, and writes it only when you say yes. Nothing it asks takes a password.
|
|
27
27
|
|
|
28
28
|
From CI or an agent, the same setup with flags:
|
|
29
29
|
|
|
@@ -60,11 +60,54 @@ ia-qa-pal tour — https://app.example · level 1 (with a login)
|
|
|
60
60
|
|
|
61
61
|
The time estimate only appears once a batch has been explored on your app: it is the pace measured there, never a constant.
|
|
62
62
|
|
|
63
|
+
## What fails a tour, and what does not
|
|
64
|
+
|
|
65
|
+
- **Drift on what a page shows when it loads** — a control lost, ambiguous, or a selector now reaching another element — fails the tour.
|
|
66
|
+
- **Drift on what is reached only by clicking** — a profile opened from a ranking, a button that exists in one state of the app — is listed under *To decide* with the clicks that reached it, and does **not** fail the tour: on an app with live data it may simply depend on what the click landed on. `--strict` makes it fail too.
|
|
67
|
+
- **A label that differs only by a number** (`Cart (3)` → `Cart (5)`, `Activity60` → `Activity30`) comes with the `nameMask` to paste into `.ia-qa/config.json`; the element stays under contract, only its label stops being compared.
|
|
68
|
+
- **A change you intended**: review it, then make it the reference for those pages only —
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
npx ia-qa-pal accept settings checkout # or --all: every page the last tour reported
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## With your test suite
|
|
75
|
+
|
|
76
|
+
Declare it at setup (`--tests tests/`, or answer yes when the tour finds it) and every tour also reads it:
|
|
77
|
+
|
|
78
|
+
- **🔧 Ready to repair** — when a selector your tests write stops reaching its element while the element is still there, the rewrite is listed with `file:line` and the command that applies it after you review it. pal never edits your tests.
|
|
79
|
+
- **⛔ To decide** — locator names your tests use that nothing on the mapped pages carries, and renamed labels your tests locate by name.
|
|
80
|
+
- **🔍 Not seen** — `ia-qa-pal tour --suite` runs the `testCommand` from your config once, with the capture hook, and names the pages your suite **never visits**. The capture is kept apart from the tour's contracts. Your specs need the hook for this — one import:
|
|
81
|
+
|
|
82
|
+
```js
|
|
83
|
+
import { test, expect } from '@ia-qa/self-healing/capture';
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Without it, the report says the suite ran but recorded no page — never that it visits nothing.
|
|
87
|
+
|
|
88
|
+
## In the background, and for agents
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
npx ia-qa-pal tour --background # returns at once
|
|
92
|
+
npx ia-qa-pal status # phase and progress, then the report
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
One tour runs per project; a second is refused while the first is alive, and a tour whose process died is reported as interrupted, never as running.
|
|
96
|
+
|
|
97
|
+
`ia-qa-pal-mcp` is the same thing as a local stdio MCP server — `pal_project`, `pal_setup`, `pal_tour` (always in the background, so no client times out) and `pal_status`. No tool takes a command, and there is no login tool.
|
|
98
|
+
|
|
99
|
+
```json
|
|
100
|
+
{ "mcpServers": { "ia-qa-pal": { "command": "npx", "args": ["-y", "-p", "@ia-qa/pal", "ia-qa-pal-mcp"] } } }
|
|
101
|
+
```
|
|
102
|
+
|
|
63
103
|
### Options
|
|
64
104
|
|
|
65
105
|
| flag | |
|
|
66
106
|
|---|---|
|
|
67
107
|
| `--more` | explore the next batch in depth instead of a regression tour |
|
|
108
|
+
| `--suite` | also run your test command once, with the capture hook |
|
|
109
|
+
| `--background` | start detached and return at once |
|
|
110
|
+
| `--strict` | also fail on drift reached only by clicking |
|
|
68
111
|
| `--batch <n>` | pages per depth batch (default 10) |
|
|
69
112
|
| `--json` | the whole report as one JSON document on stdout; progress stays on stderr |
|
|
70
113
|
| `--session <file>` | a Playwright `storageState` to reuse |
|
|
@@ -74,9 +117,9 @@ The time estimate only appears once a batch has been explored on your app: it is
|
|
|
74
117
|
|
|
75
118
|
| code | meaning |
|
|
76
119
|
|---|---|
|
|
77
|
-
| `0` | nothing needs a decision (warnings never fail a tour) |
|
|
78
|
-
| `1` | a dead link,
|
|
79
|
-
| `2` | could not answer: not set up, nothing could be mapped,
|
|
120
|
+
| `0` | nothing needs a decision (warnings and proposed repairs never fail a tour) |
|
|
121
|
+
| `1` | a dead link, drift on what pages show on load (an element lost, ambiguous, or a selector now pointing at another element), a test about to act on the wrong element, or your suite failed when run with `--suite` |
|
|
122
|
+
| `2` | could not answer: not set up, nothing could be mapped, the tour refused to run, or one is already running |
|
|
80
123
|
|
|
81
124
|
Read `2` as "no answer", never as "the app is broken".
|
|
82
125
|
|
|
@@ -85,8 +128,5 @@ Read `2` as "no answer", never as "the app is broken".
|
|
|
85
128
|
- **Only your app.** Its pages load whatever they load in any browser.
|
|
86
129
|
- **Exploration is read-only.** While menus, tabs and dialogs are opened, every non-GET request is blocked, and controls named like actions (log out, delete, pay…) are never clicked.
|
|
87
130
|
- **The link check** requests your app's own links with GET, skipping any URL that acts (`/logout`, `/delete`…).
|
|
88
|
-
- **
|
|
89
|
-
|
|
90
|
-
## Not yet
|
|
91
|
-
|
|
92
|
-
Reading your test suite (coverage of what it does not test, repairs proposed as a patch), a local MCP server for agents, and background exploration are planned next.
|
|
131
|
+
- **Your test command** runs only with `--suite` (or `suite: true` from an agent), and it is always the one in your config — never a string passed on the call.
|
|
132
|
+
- **Files** live in `.ia-qa/`: contracts, baseline, `usage.json` (your suite's locators), and `pal/` (the measured pace, findings already reported, the current run and its log). A saved session there holds live cookies — it is gitignored and never leaves your machine.
|
package/TUTORIAL.md
CHANGED
|
@@ -33,10 +33,10 @@ In your project folder:
|
|
|
33
33
|
npx ia-qa-pal tour
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
The project is not set up yet, so the tour asks
|
|
36
|
+
The project is not set up yet, so the tour asks first: where the app lives, how it logs in, and — only when it finds a test suite in the folder — whether to read it (section 7).
|
|
37
37
|
|
|
38
38
|
```
|
|
39
|
-
This project is not set up yet —
|
|
39
|
+
This project is not set up yet — a few questions first.
|
|
40
40
|
|
|
41
41
|
Where does the app live? (https://…) https://www.ia-qa.com
|
|
42
42
|
Does the app ask you to log in?
|
|
@@ -96,10 +96,10 @@ The report has at most four sections, and each one appears only when it holds so
|
|
|
96
96
|
|
|
97
97
|
| section | what it holds | what you do |
|
|
98
98
|
|---|---|---|
|
|
99
|
-
| 🔴 **Broken
|
|
100
|
-
| ⛔ **To decide** | drift
|
|
101
|
-
| 🔧 **Ready to repair** |
|
|
102
|
-
| 🔍 **Not seen** | pages not explored, not mapped, only partly explored, links it could not verify | decide whether it matters |
|
|
99
|
+
| 🔴 **Broken** | links your app itself answers with an error; your suite failing under `--suite` | fix it |
|
|
100
|
+
| ⛔ **To decide** | drift, and findings seen for the first time | a person looks — then fixes, or `accept`s |
|
|
101
|
+
| 🔧 **Ready to repair** | with your test suite declared: selector rewrites for your tests (section 7) | review, then apply with `ia-qa-heal fix` |
|
|
102
|
+
| 🔍 **Not seen** | pages not explored, not mapped, not compared, only partly explored, links it could not verify, pages your suite never visits | decide whether it matters |
|
|
103
103
|
|
|
104
104
|
Three things are worth knowing about this first report:
|
|
105
105
|
|
|
@@ -128,15 +128,47 @@ ia-qa-pal tour — https://www.nodemented.com · level 1 (with a login)
|
|
|
128
128
|
|
|
129
129
|
Each page is captured again **the way its baseline was captured** — on the surface, or opened in depth — and compared with it. A finding already reported is counted, not listed again: a list that never shrinks stops being read by the third tour.
|
|
130
130
|
|
|
131
|
-
When something moved
|
|
131
|
+
When something moved — a real tour of a browser game with live data, five minutes after its baseline:
|
|
132
132
|
|
|
133
133
|
```
|
|
134
|
-
|
|
134
|
+
ia-qa-pal tour — https://project-1jpdv.vercel.app · level 2 (with your test suite)
|
|
135
|
+
⛔ BLOCK · 1471 ok · 0 renamed · 2 lost · 0 ambiguous · 1 rebound
|
|
136
|
+
17 of 17 pages under contract · 10 explored in depth of 16 pages with closed controls
|
|
135
137
|
|
|
136
138
|
⛔ To decide
|
|
137
|
-
drift
|
|
139
|
+
drift jeu-journal: button "📡Activité60" rebound
|
|
140
|
+
behind 2 elements reached only by clicking changed — they may depend on the data the click lands on:
|
|
141
|
+
jeu-chantier: button "👽 Intercepter" lost, after "🌌Galaxie"
|
|
142
|
+
jeu-chantier: button "Reset" lost, after "🌌Galaxie"
|
|
143
|
+
If these changes are intended: ia-qa-pal accept jeu-journal jeu-chantier
|
|
144
|
+
61 warnings already reported — not listed again.
|
|
145
|
+
|
|
146
|
+
💡 1 label differs only by a number — a counter or a clock that changes on its own, and it is holding the verdict.
|
|
147
|
+
e.g. "📡Activité60" → "📡Activité30". To stop comparing those labels (the element stays under contract), add to .ia-qa/config.json:
|
|
148
|
+
"nameMask": ["📡Activité*"]
|
|
138
149
|
```
|
|
139
150
|
|
|
151
|
+
Two kinds of drift, and they do not weigh the same:
|
|
152
|
+
|
|
153
|
+
- **What a page shows when it loads** fails the tour. Here, `📡Activité60` is a counter that moved on its own — the 💡 line gives the `nameMask` to paste once.
|
|
154
|
+
- **What is reached only by clicking** is listed with the clicks that reached it and does **not** fail the tour. "Intercepter" exists only while the game has something to intercept: the tour did nothing wrong, the data changed. `--strict` makes these fail too.
|
|
155
|
+
|
|
156
|
+
With the `nameMask` added, the next tour of the same game:
|
|
157
|
+
|
|
158
|
+
```
|
|
159
|
+
⚠️ Drift only behind interaction — not failing the tour (--strict would) · 1472 ok · 0 renamed · 2 lost · 0 ambiguous · 0 rebound
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
and it exits `0`, still listing the two buttons under *To decide*.
|
|
163
|
+
|
|
164
|
+
When a change **is** intended — you renamed a button, removed a page section — review it and make it the reference for those pages only:
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
npx ia-qa-pal accept jeu-chantier # or --all: every page the last tour reported
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Nothing else in the baseline moves. An agent never runs this for you: accepting drift is the decision the report hands to a person.
|
|
171
|
+
|
|
140
172
|
What the words mean:
|
|
141
173
|
|
|
142
174
|
| word | meaning |
|
|
@@ -158,9 +190,9 @@ The control stays under contract; only its label is no longer compared.
|
|
|
158
190
|
|
|
159
191
|
| code | meaning |
|
|
160
192
|
|---|---|
|
|
161
|
-
| `0` | nothing needs a decision — warnings never fail a tour |
|
|
162
|
-
| `1` | a dead link, or
|
|
163
|
-
| `2` | no answer: not set up, nothing could be mapped,
|
|
193
|
+
| `0` | nothing needs a decision — warnings, proposed repairs and drift behind interaction never fail a tour |
|
|
194
|
+
| `1` | a dead link, drift on what pages show on load, a test about to act on the wrong element, or your suite failed under `--suite` |
|
|
195
|
+
| `2` | no answer: not set up, nothing could be mapped, the tour refused to run, or one is already running |
|
|
164
196
|
|
|
165
197
|
Read `2` as "could not answer", never as "the app is broken".
|
|
166
198
|
|
|
@@ -179,7 +211,7 @@ npx ia-qa-pal tour --more
|
|
|
179
211
|
|
|
180
212
|
`--more` explores the next batch and adds those pages to the baseline. Run it when you have the time — the estimate told you how much. `--batch <n>` changes the batch size.
|
|
181
213
|
|
|
182
|
-
It **refuses** when the last tour ended on
|
|
214
|
+
It **refuses** when the last tour ended on drift that fails (accept or fix it first — `ia-qa-pal accept` clears it):
|
|
183
215
|
|
|
184
216
|
```
|
|
185
217
|
⏸ Did not run — The last tour ended on BLOCK. Exploring now would promote those pages as they are today and accept that drift unseen — resolve it, then run `ia-qa-pal tour` again.
|
|
@@ -217,12 +249,78 @@ npx ia-qa-pal setup --url https://staging.shop.example --login session --session
|
|
|
217
249
|
|
|
218
250
|
---
|
|
219
251
|
|
|
220
|
-
## 7.
|
|
252
|
+
## 7. With your test suite
|
|
253
|
+
|
|
254
|
+
A tour already tells you what changed in the app. With your end-to-end suite declared, it also tells you what that change does to **your tests**.
|
|
255
|
+
|
|
256
|
+
Declare it once — the setup offers it when it finds a `tests/`, `e2e/` or `cypress/e2e/` folder with specs, or with flags:
|
|
257
|
+
|
|
258
|
+
```bash
|
|
259
|
+
npx ia-qa-pal setup --url https://staging.shop.example --login none --tests tests --yes
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
The command that runs the suite is detected from `package.json` (`test:e2e`, `e2e`…) or a `playwright.config`; set it yourself with `--test-command "npm run test:e2e"`.
|
|
263
|
+
|
|
264
|
+
From then on, every tour reads the suite's locators before capturing, and the report gains three things.
|
|
265
|
+
|
|
266
|
+
**🔧 Ready to repair.** A selector your tests write stopped reaching its element — the class changed, a `data-testid` was added — while the element itself is still on the page (illustrative):
|
|
267
|
+
|
|
268
|
+
```
|
|
269
|
+
🔧 Ready to repair (1 replacement in 1 file)
|
|
270
|
+
tests/settings.spec.js:17 .btn-primary → #prefs > button:nth-of-type(1)
|
|
271
|
+
Apply after review: npx -p @ia-qa/self-healing ia-qa-heal fix (pal never edits your tests)
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
pal only lists it. `ia-qa-heal fix` computes the same rewrite and applies it — review it with `git diff` before committing. A rewrite that could break a passing test (two pages wanting the same string to become two different things, an XPath inside `By.xpath`) is left out and named, never guessed.
|
|
275
|
+
|
|
276
|
+
**⛔ To decide.** Locator names your tests use that nothing on the mapped pages carries — `getByRole('button', { name: 'Save' })` when no button is called Save anymore — with `file:line`. It is advisory: the name may live on a page or in a state the tour did not map. And when a label your tests locate by name was renamed, the tour says so.
|
|
277
|
+
|
|
278
|
+
**🔍 Which pages your suite never visits.** This one needs the suite to run:
|
|
279
|
+
|
|
280
|
+
```bash
|
|
281
|
+
npx ia-qa-pal tour --suite
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
It runs the `testCommand` from your config once, with the capture hook armed, and keeps only the list of pages the suite really opened — its capture never mixes with the tour's contracts. The answer stays in the report until the next `--suite` (illustrative):
|
|
285
|
+
|
|
286
|
+
```
|
|
287
|
+
🔍 Not seen
|
|
288
|
+
your suite never visits 14 of 17 pages (run of 2026-09-15): faq, jeu-galaxie, jeu-classement, …
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
Your specs need the capture hook — one import, a drop-in for Playwright's own:
|
|
292
|
+
|
|
293
|
+
```js
|
|
294
|
+
import { test, expect } from '@ia-qa/self-healing/capture';
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
Without it, the report says the suite ran but recorded no page, and names the import. It never says your suite visits nothing. If the suite fails during `--suite`, the tour reports it under **Broken** and exits `1`.
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
301
|
+
## 8. In the background
|
|
302
|
+
|
|
303
|
+
A tour on a large app takes a while. Start it and get your terminal back:
|
|
304
|
+
|
|
305
|
+
```bash
|
|
306
|
+
npx ia-qa-pal tour --background
|
|
307
|
+
npx ia-qa-pal status
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
```
|
|
311
|
+
⏳ A tour is running (pid 34136, since 2026-09-15T14:04:32Z) — depth 4/10 · jeu-planete
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
When it is done, `status` prints the report. Only one tour runs per project: a second is refused while the first is alive, because two tours would write the same contracts. A tour whose process died before finishing is reported as **interrupted** — never as still running, never as a result. Its output is in `.ia-qa/pal/run.log`.
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
## 9. In CI
|
|
221
319
|
|
|
222
320
|
Build the baseline on your machine, commit it, and let CI compare.
|
|
223
321
|
|
|
224
322
|
```bash
|
|
225
|
-
git add .ia-qa/config.json .ia-qa/baseline .ia-qa/pal
|
|
323
|
+
git add .ia-qa/config.json .ia-qa/baseline .ia-qa/pal/state.json
|
|
226
324
|
git commit -m "ia-qa-pal baseline"
|
|
227
325
|
```
|
|
228
326
|
|
|
@@ -241,13 +339,29 @@ Never commit `.ia-qa/session.json`.
|
|
|
241
339
|
|
|
242
340
|
---
|
|
243
341
|
|
|
244
|
-
##
|
|
342
|
+
## 10. With an AI agent
|
|
245
343
|
|
|
246
|
-
An agent can drive `ia-qa-pal` from a shell
|
|
344
|
+
An agent can drive `ia-qa-pal` from a shell, or through its local MCP server:
|
|
345
|
+
|
|
346
|
+
```json
|
|
347
|
+
{ "mcpServers": { "ia-qa-pal": { "command": "npx", "args": ["-y", "-p", "@ia-qa/pal", "ia-qa-pal-mcp"] } } }
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
| tool | what it does |
|
|
351
|
+
|---|---|
|
|
352
|
+
| `pal_project` | is the project set up, its level, its baseline, a tour running? |
|
|
353
|
+
| `pal_setup` | writes the config from **your** answers — never overwrites one |
|
|
354
|
+
| `pal_tour` | starts a tour in the background and returns at once, so no client times out |
|
|
355
|
+
| `pal_status` | progress, then the full report |
|
|
356
|
+
|
|
357
|
+
No tool takes a command — your test command comes from your config, which you wrote — and there is no login tool.
|
|
358
|
+
|
|
359
|
+
The rules the agent must follow are also in `ia-qa-pal --help`:
|
|
247
360
|
|
|
248
361
|
- **Read "Not seen".** A clean tour over part of an app is not a clean app, and the agent must say which part was not seen.
|
|
249
362
|
- **A first tour is not a pass.** It established the baseline.
|
|
250
363
|
- **Never answer a setup question for you, and never run `ia-qa-heal login`.** That command waits for a person at a browser; the agent gives it to you instead.
|
|
364
|
+
- **Never apply a proposed repair without your review, and never add `--suite` without asking** — it runs your suite on your machine.
|
|
251
365
|
|
|
252
366
|
Asked to tour a project that is not set up, `ia-qa-pal tour --json` answers with what is missing rather than guessing:
|
|
253
367
|
|
|
@@ -264,31 +378,34 @@ Asked to tour a project that is not set up, `ia-qa-pal tour --json` answers with
|
|
|
264
378
|
|
|
265
379
|
---
|
|
266
380
|
|
|
267
|
-
##
|
|
381
|
+
## 11. What it touches
|
|
268
382
|
|
|
269
383
|
- **Only your app.** Its pages load whatever they load in any browser (fonts, analytics…).
|
|
270
384
|
- **Exploration is read-only.** While menus, tabs and dialogs are opened, every non-GET request is blocked, and controls named like actions (log out, delete, pay, send…) are never clicked.
|
|
271
385
|
- **The link check** requests your app's own links with GET and skips any URL that acts.
|
|
272
|
-
- **
|
|
386
|
+
- **Your test files are read, never written.** Your test command runs only with `--suite`.
|
|
387
|
+
- **Files** live in `.ia-qa/`: `config.json`, `mapping/` (today), `baseline/` (the reference), `usage.json` (your suite's locators), `pal/state.json` (the measured pace, the findings already reported, the last `--suite` run), `pal/run.json` and `pal/run.log` (the current or last tour).
|
|
273
388
|
|
|
274
389
|
---
|
|
275
390
|
|
|
276
|
-
##
|
|
391
|
+
## 12. When something looks wrong
|
|
277
392
|
|
|
278
393
|
**A page is "not mapped" with a timeout.** One slow page costs that page, not the tour. It stays in **Not seen**; if its drift is shown, the report marks it as an older capture.
|
|
279
394
|
|
|
280
|
-
**
|
|
395
|
+
**"N pages landed on a login page instead".** The session expired, or never worked. Log in again with `ia-qa-heal login`, then run the tour. Drift on those pages is from an older capture: it is not counted, and `accept` refuses them until a tour captures them again.
|
|
396
|
+
|
|
397
|
+
**"not compared — its contract file cannot be read".** A file under `.ia-qa/baseline/` or `.ia-qa/mapping/` is corrupt. Restore it from git, or run the tour again to re-capture it.
|
|
281
398
|
|
|
282
|
-
**`renamed` on a label you did not touch.** A counter, a date or generated content.
|
|
399
|
+
**`renamed` or `rebound` on a label you did not touch.** A counter, a date or generated content. The report gives the `nameMask` to paste (section 4).
|
|
400
|
+
|
|
401
|
+
**Drift behind interaction on every tour.** Those elements depend on the app's data. They never fail the tour unless you pass `--strict`; if they are noise you never want to read, `ia-qa-pal accept` the page after the data settles, or keep the page on the surface.
|
|
283
402
|
|
|
284
403
|
**"only partly explored (click budget reached)".** The page has more closed controls than one exploration opens (150 clicks). Its contract covers part of the page, and the report says so.
|
|
285
404
|
|
|
286
|
-
|
|
405
|
+
**"A tour is already running" but nothing is.** Run `ia-qa-pal status`: a tour whose process died shows as interrupted, and the next tour starts normally.
|
|
287
406
|
|
|
288
|
-
|
|
407
|
+
**"your suite ran but recorded no page".** Your specs do not import the capture hook (section 7).
|
|
289
408
|
|
|
290
|
-
|
|
291
|
-
- **A local MCP server** so agents call `tour` as a tool.
|
|
292
|
-
- **Background exploration**, followed from the local console.
|
|
409
|
+
---
|
|
293
410
|
|
|
294
411
|
`ia-qa-pal` is built on [`@ia-qa/self-healing`](https://www.npmjs.com/package/@ia-qa/self-healing): the verdicts it prints are the ones `ia-qa-heal diff` and `ia-qa-heal check` compute, so the two never disagree.
|
package/dist/accept.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A person looked at the drift and it is what they wanted: a new label, a removed button, data
|
|
3
|
+
* that moved. Only those pages' latest captures become the reference — nothing else in `mapping/`
|
|
4
|
+
* is promoted, and nothing is accepted that the last tour did not capture.
|
|
5
|
+
*
|
|
6
|
+
* Deliberately not an MCP tool: accepting drift is the decision the whole report hands to a person.
|
|
7
|
+
*/
|
|
8
|
+
export interface AcceptOutcome {
|
|
9
|
+
accepted: string[];
|
|
10
|
+
skipped: Array<{
|
|
11
|
+
page: string;
|
|
12
|
+
reason: string;
|
|
13
|
+
}>;
|
|
14
|
+
refused?: string;
|
|
15
|
+
}
|
|
16
|
+
/** The pages whose drift the last finished tour reported. */
|
|
17
|
+
export declare function driftedPages(dir: string): string[];
|
|
18
|
+
export declare function acceptPages(dirGiven: string, pagesGiven: string[], opts?: {
|
|
19
|
+
all?: boolean;
|
|
20
|
+
}): Promise<AcceptOutcome>;
|
package/dist/accept.js
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
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.driftedPages = driftedPages;
|
|
37
|
+
exports.acceptPages = acceptPages;
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const self_healing_1 = require("@ia-qa/self-healing");
|
|
41
|
+
const run_1 = require("./run");
|
|
42
|
+
const state_1 = require("./state");
|
|
43
|
+
/** The pages whose drift the last finished tour reported. */
|
|
44
|
+
function driftedPages(dir) {
|
|
45
|
+
const drift = (0, run_1.readRun)(dir)?.report?.drift;
|
|
46
|
+
if (!drift)
|
|
47
|
+
return [];
|
|
48
|
+
return [...new Set([...drift.onLoad, ...drift.behindInteraction].map((f) => f.page).concat(drift.suitePages))];
|
|
49
|
+
}
|
|
50
|
+
async function acceptPages(dirGiven, pagesGiven, opts = {}) {
|
|
51
|
+
const dir = path.resolve(dirGiven);
|
|
52
|
+
if (!fs.existsSync(path.join(dir, '.ia-qa', 'config.json'))) {
|
|
53
|
+
return { accepted: [], skipped: [], refused: 'This project is not set up yet — there is no .ia-qa/config.json.' };
|
|
54
|
+
}
|
|
55
|
+
if ((0, run_1.activeRun)(dir))
|
|
56
|
+
return { accepted: [], skipped: [], refused: 'A tour is running on this project — accept after it finishes (ia-qa-pal status).' };
|
|
57
|
+
(0, self_healing_1.setBaseDir)(dir);
|
|
58
|
+
const configured = new Set((0, self_healing_1.loadConfig)().pages.map((p) => p.name));
|
|
59
|
+
const wanted = opts.all ? driftedPages(dir) : pagesGiven;
|
|
60
|
+
if (wanted.length === 0) {
|
|
61
|
+
return { accepted: [], skipped: [], refused: opts.all ? 'The last tour reported no drift to accept.' : 'Name the pages to accept, or --all for every page the last tour reported.' };
|
|
62
|
+
}
|
|
63
|
+
const ready = [];
|
|
64
|
+
const skipped = [];
|
|
65
|
+
const notRecaptured = new Set(((0, run_1.readRun)(dir)?.report?.pages.notMapped ?? []).map((m) => m.page));
|
|
66
|
+
for (const page of wanted) {
|
|
67
|
+
if (!configured.has(page)) {
|
|
68
|
+
skipped.push({ page, reason: 'not a configured page' });
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (notRecaptured.has(page)) {
|
|
72
|
+
skipped.push({ page, reason: 'the last tour could not re-capture it — accepting would promote an older capture; run a tour first' });
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
let current;
|
|
76
|
+
try {
|
|
77
|
+
current = (0, self_healing_1.loadMapping)(page, (0, self_healing_1.mappingDir)());
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
skipped.push({ page, reason: 'no capture yet — run a tour first' });
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
if ((0, self_healing_1.loadMapping)(page, (0, self_healing_1.baselineDir)()).capturedAt === current.capturedAt) {
|
|
85
|
+
skipped.push({ page, reason: 'the baseline already is its latest capture' });
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
/* not in the baseline yet: accepting it adds it */
|
|
91
|
+
}
|
|
92
|
+
ready.push(page);
|
|
93
|
+
}
|
|
94
|
+
if (ready.length > 0) {
|
|
95
|
+
const { log, info, warn } = console;
|
|
96
|
+
console.log = console.info = console.warn = (...a) => console.error(...a);
|
|
97
|
+
const before = process.exitCode;
|
|
98
|
+
process.exitCode = undefined;
|
|
99
|
+
try {
|
|
100
|
+
await (0, self_healing_1.runBaseline)([], { pages: ready });
|
|
101
|
+
if (process.exitCode)
|
|
102
|
+
return { accepted: [], skipped, refused: `baseline refused to promote ${ready.join(', ')}` };
|
|
103
|
+
}
|
|
104
|
+
finally {
|
|
105
|
+
process.exitCode = before;
|
|
106
|
+
console.log = log;
|
|
107
|
+
console.info = info;
|
|
108
|
+
console.warn = warn;
|
|
109
|
+
}
|
|
110
|
+
const remaining = driftedPages(dir).filter((p) => !ready.includes(p));
|
|
111
|
+
const state = (0, state_1.loadState)();
|
|
112
|
+
if (state.lastVerdict === 'BLOCK' && remaining.length === 0) {
|
|
113
|
+
delete state.lastVerdict;
|
|
114
|
+
(0, state_1.saveState)(state);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return { accepted: ready, skipped };
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=accept.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"accept.js","sourceRoot":"","sources":["../src/accept.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqBA,oCAIC;AAED,kCAkEC;AA7FD,uCAAyB;AACzB,2CAA6B;AAC7B,sDAAgH;AAChH,+BAA2C;AAC3C,mCAA+C;AAgB/C,6DAA6D;AAC7D,SAAgB,YAAY,CAAC,GAAW;IACtC,MAAM,KAAK,GAAG,IAAA,aAAO,EAAC,GAAG,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC;IAC1C,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,CAAC;IACtB,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,GAAG,KAAK,CAAC,iBAAiB,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;AACjH,CAAC;AAEM,KAAK,UAAU,WAAW,CAAC,QAAgB,EAAE,UAAoB,EAAE,OAA0B,EAAE;IACpG,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACnC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,EAAE,aAAa,CAAC,CAAC,EAAE,CAAC;QAC5D,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,kEAAkE,EAAE,CAAC;IACpH,CAAC;IACD,IAAI,IAAA,eAAS,EAAC,GAAG,CAAC;QAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,kFAAkF,EAAE,CAAC;IAEtJ,IAAA,yBAAU,EAAC,GAAG,CAAC,CAAC;IAChB,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,IAAA,yBAAU,GAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IAClE,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC;IACzD,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,4CAA4C,CAAC,CAAC,CAAC,2EAA2E,EAAE,CAAC;IACvL,CAAC;IAED,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,MAAM,OAAO,GAA6B,EAAE,CAAC;IAC7C,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,CAAC,IAAA,aAAO,EAAC,GAAG,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IAChG,KAAK,MAAM,IAAI,IAAI,MAAM,EAAE,CAAC;QAC1B,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YAC1B,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,uBAAuB,EAAE,CAAC,CAAC;YACxD,SAAS;QACX,CAAC;QACD,IAAI,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YAC5B,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,oGAAoG,EAAE,CAAC,CAAC;YACrI,SAAS;QACX,CAAC;QACD,IAAI,OAAO,CAAC;QACZ,IAAI,CAAC;YACH,OAAO,GAAG,IAAA,0BAAW,EAAC,IAAI,EAAE,IAAA,yBAAU,GAAE,CAAC,CAAC;QAC5C,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,mCAAmC,EAAE,CAAC,CAAC;YACpE,SAAS;QACX,CAAC;QACD,IAAI,CAAC;YACH,IAAI,IAAA,0BAAW,EAAC,IAAI,EAAE,IAAA,0BAAW,GAAE,CAAC,CAAC,UAAU,KAAK,OAAO,CAAC,UAAU,EAAE,CAAC;gBACvE,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,4CAA4C,EAAE,CAAC,CAAC;gBAC7E,SAAS;YACX,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,mDAAmD;QACrD,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACnB,CAAC;IAED,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACrB,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC;QACpC,OAAO,CAAC,GAAG,GAAG,OAAO,CAAC,IAAI,GAAG,OAAO,CAAC,IAAI,GAAG,CAAC,GAAG,CAAY,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;QACrF,MAAM,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;QAChC,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC;QAC7B,IAAI,CAAC;YACH,MAAM,IAAA,0BAAW,EAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;YACxC,IAAI,OAAO,CAAC,QAAQ;gBAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,+BAA+B,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;QACrH,CAAC;gBAAS,CAAC;YACT,OAAO,CAAC,QAAQ,GAAG,MAAM,CAAC;YAC1B,OAAO,CAAC,GAAG,GAAG,GAAG,CAAC;YAClB,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;YACpB,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;QACtB,CAAC;QACD,MAAM,SAAS,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;QACtE,MAAM,KAAK,GAAG,IAAA,iBAAS,GAAE,CAAC;QAC1B,IAAI,KAAK,CAAC,WAAW,KAAK,OAAO,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC5D,OAAO,KAAK,CAAC,WAAW,CAAC;YACzB,IAAA,iBAAS,EAAC,KAAK,CAAC,CAAC;QACnB,CAAC;IACH,CAAC;IACD,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;AACtC,CAAC"}
|