@ia-qa/pal 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 +80 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +84 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/plan.d.ts +59 -0
- package/dist/plan.js +83 -0
- package/dist/plan.js.map +1 -0
- package/dist/report.d.ts +2 -0
- package/dist/report.js +114 -0
- package/dist/report.js.map +1 -0
- package/dist/state.d.ts +20 -0
- package/dist/state.js +63 -0
- package/dist/state.js.map +1 -0
- package/dist/tour.d.ts +57 -0
- package/dist/tour.js +234 -0
- package/dist/tour.js.map +1 -0
- package/package.json +52 -0
package/README.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# @ia-qa/pal
|
|
2
|
+
|
|
3
|
+
**A QA pal next to your team — not instead of it.**
|
|
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**.
|
|
6
|
+
|
|
7
|
+
- **Local.** A headless browser on your machine. Nothing is uploaded, no account, no LLM.
|
|
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.
|
|
9
|
+
- **Honest about coverage.** A tour that measured part of your app says which part. A first tour establishes a baseline; it is never reported as a pass.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm i -D @ia-qa/pal
|
|
15
|
+
npx playwright install chromium
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
`ia-qa-pal` needs a `.ia-qa/config.json` with your `baseUrl` and pages. The setup wizard comes with the dependency:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npx ia-qa-heal init
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Behind a login? Either declare it in `init`, point at a Playwright `storageState` your suite already writes (`--session`), or log in once by hand with `npx ia-qa-heal login`.
|
|
25
|
+
|
|
26
|
+
## Use
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npx ia-qa-pal tour
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**First tour** — every page on the surface (no clicks), the first 10 pages that have closed menus, tabs or dialogs explored in depth, the checks, and a baseline.
|
|
33
|
+
|
|
34
|
+
**Every tour after** — each page re-captured the way its baseline was captured, compared with it, checked.
|
|
35
|
+
|
|
36
|
+
**`--more`** — explores the next batch in depth and adds it to the baseline. It refuses while the last tour ended on BLOCK: promoting those pages would accept drift nobody looked at.
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
ia-qa-pal tour — https://app.example · level 1 (with a login)
|
|
40
|
+
✅ PASS · 7937 ok · 1 renamed · 0 lost · 0 ambiguous · 0 rebound
|
|
41
|
+
47 of 47 pages under contract · 10 explored in depth of 47 pages with closed controls
|
|
42
|
+
29 warnings already reported — not listed again.
|
|
43
|
+
|
|
44
|
+
🔍 Not seen
|
|
45
|
+
37 pages with closed controls not explored yet — about 38 min (2400 controls × 0.94 s, pace measured on 412 clicks)
|
|
46
|
+
ia-qa-pal tour --more explores the next batch
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The time estimate only appears once a batch has been explored on your app: it is the pace measured there, never a constant.
|
|
50
|
+
|
|
51
|
+
### Options
|
|
52
|
+
|
|
53
|
+
| flag | |
|
|
54
|
+
|---|---|
|
|
55
|
+
| `--more` | explore the next batch in depth instead of a regression tour |
|
|
56
|
+
| `--batch <n>` | pages per depth batch (default 10) |
|
|
57
|
+
| `--json` | the whole report as one JSON document on stdout; progress stays on stderr |
|
|
58
|
+
| `--session <file>` | a Playwright `storageState` to reuse |
|
|
59
|
+
| `--config <dir>` | the project holding `.ia-qa/` (default: current directory) |
|
|
60
|
+
|
|
61
|
+
### Exit codes
|
|
62
|
+
|
|
63
|
+
| code | meaning |
|
|
64
|
+
|---|---|
|
|
65
|
+
| `0` | nothing needs a decision (warnings never fail a tour) |
|
|
66
|
+
| `1` | a dead link, or drift that blocks (an element lost, ambiguous, or a selector now pointing at another element) |
|
|
67
|
+
| `2` | could not answer: no config, nothing could be mapped, or the tour refused to run |
|
|
68
|
+
|
|
69
|
+
Read `2` as "no answer", never as "the app is broken".
|
|
70
|
+
|
|
71
|
+
## What it touches
|
|
72
|
+
|
|
73
|
+
- **Only your app.** Its pages load whatever they load in any browser.
|
|
74
|
+
- **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.
|
|
75
|
+
- **The link check** requests your app's own links with GET, skipping any URL that acts (`/logout`, `/delete`…).
|
|
76
|
+
- **Files** live in `.ia-qa/`: contracts, baseline, and `pal/state.json` (pace and already-reported findings). A saved session there holds live cookies — it is gitignored and never leaves your machine.
|
|
77
|
+
|
|
78
|
+
## Not yet
|
|
79
|
+
|
|
80
|
+
Reading your test suite (coverage of what it does not test, repairs proposed as a patch), a setup inside `ia-qa-pal` itself, a local MCP server for agents, and background exploration are planned next.
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const tour_1 = require("../tour");
|
|
5
|
+
const report_1 = require("../report");
|
|
6
|
+
const VERSION = require('../../package.json').version;
|
|
7
|
+
const HELP = `ia-qa-pal — a QA pal next to your team
|
|
8
|
+
|
|
9
|
+
One command walks your app, maps it, checks it and compares it with the last
|
|
10
|
+
tour, then reports what broke, what needs a decision and what it could not see.
|
|
11
|
+
Runs entirely on your machine with a local browser. No LLM, nothing uploaded.
|
|
12
|
+
|
|
13
|
+
[FOR AI AGENTS] You are driving this for a human. Read the whole report, the
|
|
14
|
+
"Not seen" section included: a clean tour over part of an app is not a clean
|
|
15
|
+
app, and you must say which part was not seen. A first tour establishes the
|
|
16
|
+
baseline — never report it as a pass. Never run \`ia-qa-heal login\`: it waits
|
|
17
|
+
for a human at a browser; ask them to run it.
|
|
18
|
+
|
|
19
|
+
Usage:
|
|
20
|
+
ia-qa-pal tour First run: every page on the surface, the first
|
|
21
|
+
batch in depth, checks, baseline.
|
|
22
|
+
Next runs: re-capture, compare, check.
|
|
23
|
+
--more explore the next batch in depth instead
|
|
24
|
+
--batch <n> pages per depth batch (default 10)
|
|
25
|
+
--json the report as one JSON document on stdout
|
|
26
|
+
--session <file> a Playwright storageState to reuse
|
|
27
|
+
--config <dir> project holding .ia-qa/ (default: here)
|
|
28
|
+
|
|
29
|
+
Exit codes: 0 nothing to decide · 1 something broke or needs a decision ·
|
|
30
|
+
2 could not answer (no config, nothing mapped, refused).
|
|
31
|
+
|
|
32
|
+
Needs a .ia-qa/config.json with a baseUrl and pages: ia-qa-heal init.
|
|
33
|
+
`;
|
|
34
|
+
function valueOf(args, flag) {
|
|
35
|
+
const i = args.indexOf(flag);
|
|
36
|
+
if (i === -1)
|
|
37
|
+
return undefined;
|
|
38
|
+
const v = args[i + 1];
|
|
39
|
+
if (v === undefined || v.startsWith('--'))
|
|
40
|
+
throw new Error(`${flag} needs a value.`);
|
|
41
|
+
return v;
|
|
42
|
+
}
|
|
43
|
+
async function main() {
|
|
44
|
+
const args = process.argv.slice(2);
|
|
45
|
+
const command = args[0];
|
|
46
|
+
if (!command || command === 'help' || args.includes('--help') || args.includes('-h')) {
|
|
47
|
+
process.stdout.write(HELP);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (command === '--version' || command === '-v') {
|
|
51
|
+
process.stdout.write(`${VERSION}\n`);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (command !== 'tour') {
|
|
55
|
+
process.stderr.write(`Unknown command "${command}". Run ia-qa-pal help.\n`);
|
|
56
|
+
process.exitCode = 2;
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const json = args.includes('--json');
|
|
60
|
+
try {
|
|
61
|
+
const batchRaw = valueOf(args, '--batch');
|
|
62
|
+
const batch = batchRaw === undefined ? undefined : Number(batchRaw);
|
|
63
|
+
if (batch !== undefined && (!Number.isInteger(batch) || batch < 1))
|
|
64
|
+
throw new Error('--batch needs a positive whole number.');
|
|
65
|
+
const result = await (0, tour_1.runTour)({
|
|
66
|
+
dir: valueOf(args, '--config'),
|
|
67
|
+
batch,
|
|
68
|
+
more: args.includes('--more'),
|
|
69
|
+
session: valueOf(args, '--session'),
|
|
70
|
+
});
|
|
71
|
+
process.stdout.write(json ? `${JSON.stringify(result, null, 2)}\n` : (0, report_1.renderReport)(result));
|
|
72
|
+
process.exitCode = result.exitCode;
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
76
|
+
if (json)
|
|
77
|
+
process.stdout.write(`${JSON.stringify({ schema: 'ia-qa-pal/tour@1', error: message, exitCode: 2 }, null, 2)}\n`);
|
|
78
|
+
else
|
|
79
|
+
process.stderr.write(`❌ ${message}\n`);
|
|
80
|
+
process.exitCode = 2;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
void main();
|
|
84
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { runTour } from './tour';
|
|
2
|
+
export type { TourOptions, TourResult } from './tour';
|
|
3
|
+
export { renderReport } from './report';
|
|
4
|
+
export { selectBatch, needingDepth, estimate, splitFindings, exitCodeFor, DEFAULT_BATCH } from './plan';
|
|
5
|
+
export type { Estimate, Pace, CheckSplit } from './plan';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEFAULT_BATCH = exports.exitCodeFor = exports.splitFindings = exports.estimate = exports.needingDepth = exports.selectBatch = exports.renderReport = exports.runTour = void 0;
|
|
4
|
+
var tour_1 = require("./tour");
|
|
5
|
+
Object.defineProperty(exports, "runTour", { enumerable: true, get: function () { return tour_1.runTour; } });
|
|
6
|
+
var report_1 = require("./report");
|
|
7
|
+
Object.defineProperty(exports, "renderReport", { enumerable: true, get: function () { return report_1.renderReport; } });
|
|
8
|
+
var plan_1 = require("./plan");
|
|
9
|
+
Object.defineProperty(exports, "selectBatch", { enumerable: true, get: function () { return plan_1.selectBatch; } });
|
|
10
|
+
Object.defineProperty(exports, "needingDepth", { enumerable: true, get: function () { return plan_1.needingDepth; } });
|
|
11
|
+
Object.defineProperty(exports, "estimate", { enumerable: true, get: function () { return plan_1.estimate; } });
|
|
12
|
+
Object.defineProperty(exports, "splitFindings", { enumerable: true, get: function () { return plan_1.splitFindings; } });
|
|
13
|
+
Object.defineProperty(exports, "exitCodeFor", { enumerable: true, get: function () { return plan_1.exitCodeFor; } });
|
|
14
|
+
Object.defineProperty(exports, "DEFAULT_BATCH", { enumerable: true, get: function () { return plan_1.DEFAULT_BATCH; } });
|
|
15
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,+BAAiC;AAAxB,+FAAA,OAAO,OAAA;AAEhB,mCAAwC;AAA/B,sGAAA,YAAY,OAAA;AACrB,+BAAwG;AAA/F,mGAAA,WAAW,OAAA;AAAE,oGAAA,YAAY,OAAA;AAAE,gGAAA,QAAQ,OAAA;AAAE,qGAAA,aAAa,OAAA;AAAE,mGAAA,WAAW,OAAA;AAAE,qGAAA,aAAa,OAAA"}
|
package/dist/plan.d.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { CheckReport, DirReport } from '@ia-qa/self-healing';
|
|
2
|
+
/**
|
|
3
|
+
* The decisions a tour makes, kept free of I/O so each one is tested without a browser.
|
|
4
|
+
* Nothing here computes a verdict: drift comes from `dirDiffAggregate`, soundness from
|
|
5
|
+
* `checkProject`. This file only decides what to run next and how to sort what came back.
|
|
6
|
+
*/
|
|
7
|
+
export declare const DEFAULT_BATCH = 10;
|
|
8
|
+
/**
|
|
9
|
+
* The next pages to explore in depth: configured order, only pages that have closed controls,
|
|
10
|
+
* never a page already explored. A page with nothing to open is complete on the surface, and
|
|
11
|
+
* proposing it would spend a click budget to learn nothing.
|
|
12
|
+
*/
|
|
13
|
+
export declare function selectBatch(pages: string[], explored: ReadonlySet<string>, openables: Readonly<Record<string, number>>, size: number): string[];
|
|
14
|
+
export declare function needingDepth(pages: string[], explored: ReadonlySet<string>, openables: Readonly<Record<string, number>>): string[];
|
|
15
|
+
export interface Pace {
|
|
16
|
+
totalMs: number;
|
|
17
|
+
totalClicks: number;
|
|
18
|
+
}
|
|
19
|
+
export interface Estimate {
|
|
20
|
+
pages: number;
|
|
21
|
+
clicks: number;
|
|
22
|
+
seconds: number;
|
|
23
|
+
/** What the number rests on, stated beside it. */
|
|
24
|
+
secondsPerClick: number;
|
|
25
|
+
measuredClicks: number;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Time left = pace measured on clicks this project already paid for × closed controls still
|
|
29
|
+
* unexplored. No pace, no number: an estimate before anything ran would be a constant dressed
|
|
30
|
+
* as a measurement.
|
|
31
|
+
*/
|
|
32
|
+
export declare function estimate(remaining: string[], openables: Readonly<Record<string, number>>, pace: Pace | undefined): Estimate | null;
|
|
33
|
+
export interface CheckSplit {
|
|
34
|
+
deadLinks: CheckReport['deadLinks'];
|
|
35
|
+
knownDeadLinks: number;
|
|
36
|
+
newUnnamed: CheckReport['unnamed'];
|
|
37
|
+
newAmbiguous: CheckReport['ambiguous'];
|
|
38
|
+
newOrphans: CheckReport['orphans'];
|
|
39
|
+
knownWarnings: number;
|
|
40
|
+
unverified: number;
|
|
41
|
+
skipped: CheckReport['skipped'];
|
|
42
|
+
keys: string[];
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* A finding already reported by a previous tour is counted, not listed again: a list that
|
|
46
|
+
* never shrinks stops being read by the third run. Dead links stay listed either way — a
|
|
47
|
+
* broken link is still broken the second time — and only their "already reported" count moves.
|
|
48
|
+
*/
|
|
49
|
+
export declare function splitFindings(report: CheckReport, known: ReadonlySet<string>): CheckSplit;
|
|
50
|
+
/**
|
|
51
|
+
* 0 nothing to decide · 1 something broke or needs a decision · 2 could not answer.
|
|
52
|
+
* The same contract as `ia-qa-heal`: a warning never fails a build, and "could not answer" is
|
|
53
|
+
* never reported as a defect of the app.
|
|
54
|
+
*/
|
|
55
|
+
export declare function exitCodeFor(input: {
|
|
56
|
+
measured: number;
|
|
57
|
+
drift?: Pick<DirReport, 'verdict'> | null;
|
|
58
|
+
deadLinks: number;
|
|
59
|
+
}): 0 | 1 | 2;
|
package/dist/plan.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEFAULT_BATCH = void 0;
|
|
4
|
+
exports.selectBatch = selectBatch;
|
|
5
|
+
exports.needingDepth = needingDepth;
|
|
6
|
+
exports.estimate = estimate;
|
|
7
|
+
exports.splitFindings = splitFindings;
|
|
8
|
+
exports.exitCodeFor = exitCodeFor;
|
|
9
|
+
/**
|
|
10
|
+
* The decisions a tour makes, kept free of I/O so each one is tested without a browser.
|
|
11
|
+
* Nothing here computes a verdict: drift comes from `dirDiffAggregate`, soundness from
|
|
12
|
+
* `checkProject`. This file only decides what to run next and how to sort what came back.
|
|
13
|
+
*/
|
|
14
|
+
exports.DEFAULT_BATCH = 10;
|
|
15
|
+
/**
|
|
16
|
+
* The next pages to explore in depth: configured order, only pages that have closed controls,
|
|
17
|
+
* never a page already explored. A page with nothing to open is complete on the surface, and
|
|
18
|
+
* proposing it would spend a click budget to learn nothing.
|
|
19
|
+
*/
|
|
20
|
+
function selectBatch(pages, explored, openables, size) {
|
|
21
|
+
return needingDepth(pages, explored, openables).slice(0, Math.max(0, size));
|
|
22
|
+
}
|
|
23
|
+
function needingDepth(pages, explored, openables) {
|
|
24
|
+
return pages.filter((p) => !explored.has(p) && (openables[p] ?? 0) > 0);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Time left = pace measured on clicks this project already paid for × closed controls still
|
|
28
|
+
* unexplored. No pace, no number: an estimate before anything ran would be a constant dressed
|
|
29
|
+
* as a measurement.
|
|
30
|
+
*/
|
|
31
|
+
function estimate(remaining, openables, pace) {
|
|
32
|
+
if (!pace || pace.totalClicks === 0 || remaining.length === 0)
|
|
33
|
+
return null;
|
|
34
|
+
const secondsPerClick = pace.totalMs / 1000 / pace.totalClicks;
|
|
35
|
+
const clicks = remaining.reduce((s, p) => s + (openables[p] ?? 0), 0);
|
|
36
|
+
return {
|
|
37
|
+
pages: remaining.length,
|
|
38
|
+
clicks,
|
|
39
|
+
seconds: Math.round(secondsPerClick * clicks),
|
|
40
|
+
secondsPerClick: Math.round(secondsPerClick * 100) / 100,
|
|
41
|
+
measuredClicks: pace.totalClicks,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* A finding already reported by a previous tour is counted, not listed again: a list that
|
|
46
|
+
* never shrinks stops being read by the third run. Dead links stay listed either way — a
|
|
47
|
+
* broken link is still broken the second time — and only their "already reported" count moves.
|
|
48
|
+
*/
|
|
49
|
+
function splitFindings(report, known) {
|
|
50
|
+
const keyed = (items, key) => items.map((item) => ({ item, key: key(item) }));
|
|
51
|
+
const dead = keyed(report.deadLinks, (d) => `dead ${d.url}`);
|
|
52
|
+
const unnamed = keyed(report.unnamed, (u) => `unnamed ${u.role} ${u.selector}`);
|
|
53
|
+
const ambiguous = keyed(report.ambiguous, (a) => `ambiguous ${a.role} ${a.name.trim().toLowerCase()}`);
|
|
54
|
+
const orphans = keyed(report.orphans, (o) => `orphan ${o.page}`);
|
|
55
|
+
const fresh = (xs) => xs.filter((x) => !known.has(x.key)).map((x) => x.item);
|
|
56
|
+
const warnings = [...unnamed, ...ambiguous, ...orphans];
|
|
57
|
+
return {
|
|
58
|
+
deadLinks: report.deadLinks,
|
|
59
|
+
knownDeadLinks: dead.filter((x) => known.has(x.key)).length,
|
|
60
|
+
newUnnamed: fresh(unnamed),
|
|
61
|
+
newAmbiguous: fresh(ambiguous),
|
|
62
|
+
newOrphans: fresh(orphans),
|
|
63
|
+
knownWarnings: warnings.filter((x) => known.has(x.key)).length,
|
|
64
|
+
unverified: report.unverifiedLinks?.length ?? 0,
|
|
65
|
+
skipped: report.skipped,
|
|
66
|
+
keys: [...dead, ...warnings].map((x) => x.key),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* 0 nothing to decide · 1 something broke or needs a decision · 2 could not answer.
|
|
71
|
+
* The same contract as `ia-qa-heal`: a warning never fails a build, and "could not answer" is
|
|
72
|
+
* never reported as a defect of the app.
|
|
73
|
+
*/
|
|
74
|
+
function exitCodeFor(input) {
|
|
75
|
+
if (input.measured === 0)
|
|
76
|
+
return 2;
|
|
77
|
+
if (input.deadLinks > 0)
|
|
78
|
+
return 1;
|
|
79
|
+
if (input.drift?.verdict === 'BLOCK')
|
|
80
|
+
return 1;
|
|
81
|
+
return 0;
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=plan.js.map
|
package/dist/plan.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plan.js","sourceRoot":"","sources":["../src/plan.ts"],"names":[],"mappings":"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"}
|
package/dist/report.d.ts
ADDED
package/dist/report.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.renderReport = renderReport;
|
|
4
|
+
/**
|
|
5
|
+
* The one rendering of a tour for a terminal. Four sections, each printed only when it holds
|
|
6
|
+
* something, in the order a reader acts on them: what is broken, what needs a person, what is
|
|
7
|
+
* ready to repair, what was not seen. "Not seen" is never dropped to make a report look clean —
|
|
8
|
+
* a green tour over a third of the app is the failure this tool exists to refuse.
|
|
9
|
+
*/
|
|
10
|
+
const plural = (n, one, many = `${one}s`) => `${n} ${n === 1 ? one : many}`;
|
|
11
|
+
function duration(seconds) {
|
|
12
|
+
if (seconds < 90)
|
|
13
|
+
return `${seconds} s`;
|
|
14
|
+
const minutes = Math.round(seconds / 60);
|
|
15
|
+
if (minutes < 90)
|
|
16
|
+
return `${minutes} min`;
|
|
17
|
+
return `${Math.floor(minutes / 60)} h ${String(minutes % 60).padStart(2, '0')}`;
|
|
18
|
+
}
|
|
19
|
+
function headline(r) {
|
|
20
|
+
if (r.refused)
|
|
21
|
+
return `⏸ Did not run — ${r.refused}`;
|
|
22
|
+
if (r.mode === 'first')
|
|
23
|
+
return '📌 Baseline established — nothing to compare yet. The next tour measures drift against it.';
|
|
24
|
+
if (r.mode === 'explore') {
|
|
25
|
+
const n = r.pages.exploredThisTour.length;
|
|
26
|
+
return n > 0
|
|
27
|
+
? `🔍 ${plural(n, 'page')} explored in depth and promoted to the baseline: ${r.pages.exploredThisTour.join(', ')}`
|
|
28
|
+
: '🔍 Nothing left to explore — every page with closed controls is already explored in depth.';
|
|
29
|
+
}
|
|
30
|
+
if (!r.drift)
|
|
31
|
+
return '⏸ No page could be compared with the baseline this tour.';
|
|
32
|
+
const t = r.drift.totals;
|
|
33
|
+
const word = r.drift.verdict === 'PASS' ? '✅ PASS' : r.drift.verdict === 'FIX' ? '🔧 FIX' : '⛔ BLOCK';
|
|
34
|
+
return `${word} · ${t.ok} ok · ${t.renamed} renamed · ${t.lost} lost · ${t.ambiguous} ambiguous · ${t.rebound} rebound`;
|
|
35
|
+
}
|
|
36
|
+
function renderReport(r) {
|
|
37
|
+
const out = [];
|
|
38
|
+
const level = r.level === 1 ? 'level 1 (with a login)' : 'level 0 (public pages)';
|
|
39
|
+
out.push(`ia-qa-pal tour — ${r.baseUrl} · ${level}`);
|
|
40
|
+
out.push(headline(r));
|
|
41
|
+
if (r.refused)
|
|
42
|
+
return out.join('\n') + '\n';
|
|
43
|
+
const p = r.pages;
|
|
44
|
+
const needing = p.explored + p.notExplored.length;
|
|
45
|
+
out.push(` ${p.underContract} of ${plural(p.configured, 'page')} under contract · ` +
|
|
46
|
+
`${p.explored} explored in depth of ${plural(needing, 'page')} with closed controls`);
|
|
47
|
+
const broken = r.check?.deadLinks ?? [];
|
|
48
|
+
if (broken.length > 0) {
|
|
49
|
+
out.push('', `🔴 Broken in the app (${broken.length})`);
|
|
50
|
+
for (const d of broken.slice(0, 10)) {
|
|
51
|
+
const where = d.from[0] ? ` — linked from ${d.from[0].page}${d.from[0].name ? ` ("${d.from[0].name}")` : ''}${d.from.length > 1 ? `, +${d.from.length - 1} more` : ''}` : '';
|
|
52
|
+
out.push(` ${String(d.status ?? d.error ?? 'no answer').padEnd(5)} ${d.url}${where}`);
|
|
53
|
+
}
|
|
54
|
+
if (broken.length > 10)
|
|
55
|
+
out.push(` …and ${broken.length - 10} more`);
|
|
56
|
+
if (r.check && r.check.knownDeadLinks > 0)
|
|
57
|
+
out.push(` ${r.check.knownDeadLinks} of these were already reported by a previous tour.`);
|
|
58
|
+
}
|
|
59
|
+
const decide = [];
|
|
60
|
+
const notMapped = new Set(p.notMapped.map((m) => m.page));
|
|
61
|
+
for (const b of r.drift?.blocking ?? []) {
|
|
62
|
+
const stale = notMapped.has(b.page) ? ' (not re-captured this tour — this is an older capture)' : '';
|
|
63
|
+
decide.push(` drift ${b.page}: ${b.lost} lost · ${b.ambiguous} ambiguous · ${b.rebound} rebound${stale}`);
|
|
64
|
+
}
|
|
65
|
+
if (r.check) {
|
|
66
|
+
const c = r.check;
|
|
67
|
+
if (c.newUnnamed.length > 0) {
|
|
68
|
+
const e = c.newUnnamed[0];
|
|
69
|
+
decide.push(` new ${plural(c.newUnnamed.length, 'element')} without an accessible name — e.g. ${e.role} ${e.selector} on ${e.pages[0]}`);
|
|
70
|
+
}
|
|
71
|
+
if (c.newAmbiguous.length > 0) {
|
|
72
|
+
const e = c.newAmbiguous[0];
|
|
73
|
+
decide.push(` new ${plural(c.newAmbiguous.length, 'name')} shared by several elements — e.g. ${e.role} "${e.name}" ×${e.count} on ${e.pages[0]}`);
|
|
74
|
+
}
|
|
75
|
+
if (c.newOrphans.length > 0) {
|
|
76
|
+
decide.push(` new ${plural(c.newOrphans.length, 'page')} nothing links to — ${c.newOrphans.slice(0, 3).map((o) => o.page).join(', ')}${c.newOrphans.length > 3 ? ', …' : ''}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (decide.length > 0) {
|
|
80
|
+
out.push('', '⛔ To decide', ...decide);
|
|
81
|
+
if (r.check && (r.check.newUnnamed.length > 0 || r.check.newAmbiguous.length > 0 || r.check.newOrphans.length > 0)) {
|
|
82
|
+
out.push(' Full list: ia-qa-heal check. Warnings never fail the tour.');
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (r.check && r.check.knownWarnings > 0)
|
|
86
|
+
out.push(` ${plural(r.check.knownWarnings, 'warning')} already reported — not listed again.`);
|
|
87
|
+
const unseen = [];
|
|
88
|
+
if (p.notExplored.length > 0) {
|
|
89
|
+
const e = r.estimate;
|
|
90
|
+
unseen.push(` ${plural(p.notExplored.length, 'page')} with closed controls not explored yet` +
|
|
91
|
+
(e ? ` — about ${duration(e.seconds)} (${e.clicks} controls × ${e.secondsPerClick} s, pace measured on ${e.measuredClicks} clicks)` : ''));
|
|
92
|
+
unseen.push(' ia-qa-pal tour --more explores the next batch');
|
|
93
|
+
}
|
|
94
|
+
for (const m of p.notMapped.slice(0, 10))
|
|
95
|
+
unseen.push(` not mapped ${m.page} — ${m.reason}`);
|
|
96
|
+
if (p.notMapped.length > 10)
|
|
97
|
+
unseen.push(` …and ${p.notMapped.length - 10} more pages not mapped`);
|
|
98
|
+
if (p.partlyExplored.length > 0)
|
|
99
|
+
unseen.push(` partly explored (click budget reached): ${p.partlyExplored.join(', ')}`);
|
|
100
|
+
if (p.notInBaseline.length > 0)
|
|
101
|
+
unseen.push(` not in the baseline yet: ${p.notInBaseline.slice(0, 5).join(', ')}${p.notInBaseline.length > 5 ? ', …' : ''}`);
|
|
102
|
+
for (const s of r.drift?.staleExcluded ?? [])
|
|
103
|
+
unseen.push(` not compared ${s} — same capture on both sides`);
|
|
104
|
+
if (r.drift?.depthMismatch)
|
|
105
|
+
unseen.push(` ${r.drift.depthMismatch}`);
|
|
106
|
+
if (r.check && r.check.unverified > 0)
|
|
107
|
+
unseen.push(` ${plural(r.check.unverified, 'link')} could not be verified (rate-limited or no answer)`);
|
|
108
|
+
for (const s of r.check?.skipped ?? [])
|
|
109
|
+
unseen.push(` check skipped — ${s.check}: ${s.why}`);
|
|
110
|
+
if (unseen.length > 0)
|
|
111
|
+
out.push('', '🔍 Not seen', ...unseen);
|
|
112
|
+
return out.join('\n') + '\n';
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=report.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report.js","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"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"}
|
package/dist/state.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Pace } from './plan';
|
|
2
|
+
/**
|
|
3
|
+
* What a tour remembers between runs, and only what cannot be read back from the contracts:
|
|
4
|
+
* which pages have explored depth is in each baseline file (`exploredDepth`), so it is never
|
|
5
|
+
* duplicated here.
|
|
6
|
+
*/
|
|
7
|
+
export interface PalState {
|
|
8
|
+
version: 1;
|
|
9
|
+
/** Closed controls per page, from the latest capture of that page. */
|
|
10
|
+
openables: Record<string, number>;
|
|
11
|
+
/** Measured exploration pace, accumulated over every deep batch this project ran. */
|
|
12
|
+
pace?: Pace;
|
|
13
|
+
/** Check findings already reported, so the next tour lists only what is new. */
|
|
14
|
+
knownFindings: string[];
|
|
15
|
+
/** Drift verdict of the latest regression tour — `--more` refuses to promote over a BLOCK. */
|
|
16
|
+
lastVerdict?: 'PASS' | 'FIX' | 'BLOCK';
|
|
17
|
+
}
|
|
18
|
+
export declare function statePath(): string;
|
|
19
|
+
export declare function loadState(): PalState;
|
|
20
|
+
export declare function saveState(state: PalState): void;
|
package/dist/state.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
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.statePath = statePath;
|
|
37
|
+
exports.loadState = loadState;
|
|
38
|
+
exports.saveState = saveState;
|
|
39
|
+
const fs = __importStar(require("fs"));
|
|
40
|
+
const path = __importStar(require("path"));
|
|
41
|
+
const self_healing_1 = require("@ia-qa/self-healing");
|
|
42
|
+
function statePath() {
|
|
43
|
+
return path.join(path.dirname((0, self_healing_1.mappingDir)()), 'pal', 'state.json');
|
|
44
|
+
}
|
|
45
|
+
function loadState() {
|
|
46
|
+
try {
|
|
47
|
+
const parsed = JSON.parse(fs.readFileSync(statePath(), 'utf8'));
|
|
48
|
+
if (parsed && parsed.version === 1)
|
|
49
|
+
return { ...parsed, openables: parsed.openables ?? {}, knownFindings: parsed.knownFindings ?? [] };
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
/* first tour, or a file this version cannot read: start clean rather than guess */
|
|
53
|
+
}
|
|
54
|
+
return { version: 1, openables: {}, knownFindings: [] };
|
|
55
|
+
}
|
|
56
|
+
function saveState(state) {
|
|
57
|
+
const file = statePath();
|
|
58
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
59
|
+
const tmp = `${file}.tmp`;
|
|
60
|
+
fs.writeFileSync(tmp, JSON.stringify(state, null, 2));
|
|
61
|
+
fs.renameSync(tmp, file);
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state.js","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsBA,8BAEC;AAED,8BAQC;AAED,8BAMC;AA1CD,uCAAyB;AACzB,2CAA6B;AAC7B,sDAAiD;AAoBjD,SAAgB,SAAS;IACvB,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,IAAA,yBAAU,GAAE,CAAC,EAAE,KAAK,EAAE,YAAY,CAAC,CAAC;AACpE,CAAC;AAED,SAAgB,SAAS;IACvB,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,SAAS,EAAE,EAAE,MAAM,CAAC,CAAa,CAAC;QAC5E,IAAI,MAAM,IAAI,MAAM,CAAC,OAAO,KAAK,CAAC;YAAE,OAAO,EAAE,GAAG,MAAM,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,IAAI,EAAE,EAAE,aAAa,EAAE,MAAM,CAAC,aAAa,IAAI,EAAE,EAAE,CAAC;IACzI,CAAC;IAAC,MAAM,CAAC;QACP,mFAAmF;IACrF,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,SAAS,EAAE,EAAE,EAAE,aAAa,EAAE,EAAE,EAAE,CAAC;AAC1D,CAAC;AAED,SAAgB,SAAS,CAAC,KAAe;IACvC,MAAM,IAAI,GAAG,SAAS,EAAE,CAAC;IACzB,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACtD,MAAM,GAAG,GAAG,GAAG,IAAI,MAAM,CAAC;IAC1B,EAAE,CAAC,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IACtD,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;AAC3B,CAAC"}
|
package/dist/tour.d.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { DirReport } from '@ia-qa/self-healing';
|
|
2
|
+
import type { Estimate, CheckSplit } from './plan';
|
|
3
|
+
export interface TourOptions {
|
|
4
|
+
/** Project directory holding `.ia-qa/`. Default: the current directory. */
|
|
5
|
+
dir?: string;
|
|
6
|
+
/** Pages explored in depth per batch. */
|
|
7
|
+
batch?: number;
|
|
8
|
+
/** Explore the next batch in depth instead of running a regression tour. */
|
|
9
|
+
more?: boolean;
|
|
10
|
+
session?: string;
|
|
11
|
+
/** Where progress goes. Never stdout: stdout is the report. */
|
|
12
|
+
onProgress?: (line: string) => void;
|
|
13
|
+
}
|
|
14
|
+
export interface TourResult {
|
|
15
|
+
schema: 'ia-qa-pal/tour@1';
|
|
16
|
+
mode: 'first' | 'regression' | 'explore';
|
|
17
|
+
baseUrl: string;
|
|
18
|
+
/** 0 = public pages only, 1 = a session or a login is configured. */
|
|
19
|
+
level: 0 | 1;
|
|
20
|
+
pages: {
|
|
21
|
+
configured: number;
|
|
22
|
+
underContract: number;
|
|
23
|
+
explored: number;
|
|
24
|
+
/** Pages still holding closed controls nobody opened. */
|
|
25
|
+
notExplored: string[];
|
|
26
|
+
/** Pages explored in depth by this tour. */
|
|
27
|
+
exploredThisTour: string[];
|
|
28
|
+
partlyExplored: string[];
|
|
29
|
+
notMapped: Array<{
|
|
30
|
+
page: string;
|
|
31
|
+
url: string;
|
|
32
|
+
reason: string;
|
|
33
|
+
}>;
|
|
34
|
+
notInBaseline: string[];
|
|
35
|
+
};
|
|
36
|
+
drift: null | {
|
|
37
|
+
verdict: DirReport['verdict'];
|
|
38
|
+
totals: DirReport['totals'];
|
|
39
|
+
blocking: Array<{
|
|
40
|
+
page: string;
|
|
41
|
+
lost: number;
|
|
42
|
+
ambiguous: number;
|
|
43
|
+
rebound: number;
|
|
44
|
+
}>;
|
|
45
|
+
staleExcluded: string[];
|
|
46
|
+
depthMismatch?: string;
|
|
47
|
+
};
|
|
48
|
+
check: null | (Omit<CheckSplit, 'keys'> & {
|
|
49
|
+
refused?: string;
|
|
50
|
+
});
|
|
51
|
+
estimate: Estimate | null;
|
|
52
|
+
/** Set when the tour declined to run, with the reason. */
|
|
53
|
+
refused?: string;
|
|
54
|
+
exitCode: 0 | 1 | 2;
|
|
55
|
+
durationMs: number;
|
|
56
|
+
}
|
|
57
|
+
export declare function runTour(opts?: TourOptions): Promise<TourResult>;
|
package/dist/tour.js
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
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.runTour = runTour;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const self_healing_1 = require("@ia-qa/self-healing");
|
|
40
|
+
const plan_1 = require("./plan");
|
|
41
|
+
const state_1 = require("./state");
|
|
42
|
+
const EMPTY_MAP = { mapped: 0, attempted: 0, failed: [], pages: [] };
|
|
43
|
+
/**
|
|
44
|
+
* The verbs print, and a tour's stdout belongs to its report. Everything they say is kept —
|
|
45
|
+
* moved to stderr, where a person still reads it and a pipe does not.
|
|
46
|
+
*/
|
|
47
|
+
async function toStderr(fn) {
|
|
48
|
+
const { log, info, warn } = console;
|
|
49
|
+
console.log = console.info = console.warn = (...args) => console.error(...args);
|
|
50
|
+
try {
|
|
51
|
+
return await fn();
|
|
52
|
+
}
|
|
53
|
+
finally {
|
|
54
|
+
console.log = log;
|
|
55
|
+
console.info = info;
|
|
56
|
+
console.warn = warn;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/** `runBaseline` reports a refusal through `process.exitCode`; a tour turns that into a throw. */
|
|
60
|
+
async function promote(pages) {
|
|
61
|
+
if (pages.length === 0)
|
|
62
|
+
return;
|
|
63
|
+
const before = process.exitCode;
|
|
64
|
+
process.exitCode = undefined;
|
|
65
|
+
try {
|
|
66
|
+
await toStderr(() => (0, self_healing_1.runBaseline)([], { pages }));
|
|
67
|
+
if (process.exitCode)
|
|
68
|
+
throw new Error(`baseline refused to promote ${pages.join(', ')}`);
|
|
69
|
+
}
|
|
70
|
+
finally {
|
|
71
|
+
process.exitCode = before;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function baselineDepths(names) {
|
|
75
|
+
const out = new Map();
|
|
76
|
+
for (const name of names) {
|
|
77
|
+
try {
|
|
78
|
+
out.set(name, (0, self_healing_1.loadMapping)(name, (0, self_healing_1.baselineDir)()).exploredDepth ?? 0);
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
/* not in the baseline yet */
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return out;
|
|
85
|
+
}
|
|
86
|
+
function hasLogin(config, session, dir) {
|
|
87
|
+
return !!(session || process.env.IAQA_SESSION || config.session || config.auth || fs.existsSync(path.join(dir, '.ia-qa', 'session.json')));
|
|
88
|
+
}
|
|
89
|
+
function recordPages(state, runs) {
|
|
90
|
+
for (const run of runs)
|
|
91
|
+
for (const p of run.pages)
|
|
92
|
+
state.openables[p.page] = p.openables;
|
|
93
|
+
const deep = runs.flatMap((r) => r.pages).filter((p) => p.clicks > 0);
|
|
94
|
+
if (deep.length === 0)
|
|
95
|
+
return;
|
|
96
|
+
const pace = state.pace ?? { totalMs: 0, totalClicks: 0 };
|
|
97
|
+
for (const p of deep) {
|
|
98
|
+
pace.totalMs += p.durationMs;
|
|
99
|
+
pace.totalClicks += p.clicks;
|
|
100
|
+
}
|
|
101
|
+
state.pace = pace;
|
|
102
|
+
}
|
|
103
|
+
async function runTour(opts = {}) {
|
|
104
|
+
const started = Date.now();
|
|
105
|
+
const progress = opts.onProgress ?? ((line) => console.error(line));
|
|
106
|
+
const dir = path.resolve(opts.dir ?? process.cwd());
|
|
107
|
+
(0, self_healing_1.setBaseDir)(dir);
|
|
108
|
+
const config = (0, self_healing_1.loadConfig)();
|
|
109
|
+
const names = config.pages.map((p) => p.name);
|
|
110
|
+
const state = (0, state_1.loadState)();
|
|
111
|
+
const size = opts.batch ?? plan_1.DEFAULT_BATCH;
|
|
112
|
+
const session = opts.session;
|
|
113
|
+
const host = (() => {
|
|
114
|
+
try {
|
|
115
|
+
return new URL(config.baseUrl).host;
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return config.baseUrl;
|
|
119
|
+
}
|
|
120
|
+
})();
|
|
121
|
+
const before = baselineDepths(names);
|
|
122
|
+
const mode = opts.more ? 'explore' : before.size > 0 ? 'regression' : 'first';
|
|
123
|
+
const result = {
|
|
124
|
+
schema: 'ia-qa-pal/tour@1',
|
|
125
|
+
mode,
|
|
126
|
+
baseUrl: config.baseUrl,
|
|
127
|
+
level: hasLogin(config, session, dir) ? 1 : 0,
|
|
128
|
+
pages: { configured: names.length, underContract: 0, explored: 0, notExplored: [], exploredThisTour: [], partlyExplored: [], notMapped: [], notInBaseline: [] },
|
|
129
|
+
drift: null,
|
|
130
|
+
check: null,
|
|
131
|
+
estimate: null,
|
|
132
|
+
exitCode: 0,
|
|
133
|
+
durationMs: 0,
|
|
134
|
+
};
|
|
135
|
+
const finish = () => {
|
|
136
|
+
result.durationMs = Date.now() - started;
|
|
137
|
+
return result;
|
|
138
|
+
};
|
|
139
|
+
if (mode === 'explore') {
|
|
140
|
+
if (before.size === 0) {
|
|
141
|
+
result.refused = 'Nothing to explore yet — run `ia-qa-pal tour` first: it establishes the baseline.';
|
|
142
|
+
result.exitCode = 2;
|
|
143
|
+
return finish();
|
|
144
|
+
}
|
|
145
|
+
if (state.lastVerdict === 'BLOCK') {
|
|
146
|
+
result.refused =
|
|
147
|
+
'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.';
|
|
148
|
+
result.exitCode = 2;
|
|
149
|
+
return finish();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
progress(`🌐 ${host} · ${names.length} configured pages. Only this app is opened (its pages load what they load in any browser); exploration blocks every non-GET request.`);
|
|
153
|
+
let runs = [];
|
|
154
|
+
let measured = 0;
|
|
155
|
+
if (mode === 'first') {
|
|
156
|
+
progress(`🗺 Surface: every page, no clicks…`);
|
|
157
|
+
const surface = await toStderr(() => (0, self_healing_1.runMap)(undefined, { pages: names, session }));
|
|
158
|
+
recordPages(state, [surface]);
|
|
159
|
+
const mapped = surface.pages.map((p) => p.page);
|
|
160
|
+
const batch = (0, plan_1.selectBatch)(mapped, new Set(), state.openables, size);
|
|
161
|
+
progress(`🔍 Depth: ${batch.length} page${batch.length === 1 ? '' : 's'} with closed controls…`);
|
|
162
|
+
const deep = batch.length > 0 ? await toStderr(() => (0, self_healing_1.runMap)(undefined, { pages: batch, deep: 1, session })) : EMPTY_MAP;
|
|
163
|
+
recordPages(state, [deep]);
|
|
164
|
+
await promote(mapped);
|
|
165
|
+
runs = [surface, deep];
|
|
166
|
+
measured = surface.mapped;
|
|
167
|
+
}
|
|
168
|
+
else if (mode === 'regression') {
|
|
169
|
+
const inBaseline = names.filter((n) => before.has(n));
|
|
170
|
+
const depths = new Map();
|
|
171
|
+
for (const n of inBaseline)
|
|
172
|
+
depths.set(before.get(n), [...(depths.get(before.get(n)) ?? []), n]);
|
|
173
|
+
for (const [depth, pages] of [...depths.entries()].sort((a, b) => a[0] - b[0])) {
|
|
174
|
+
progress(`🗺 Re-capturing ${pages.length} page${pages.length === 1 ? '' : 's'} ${depth > 0 ? `at depth ${depth}` : 'on the surface'}, as in the baseline…`);
|
|
175
|
+
const run = await toStderr(() => (0, self_healing_1.runMap)(undefined, { pages, session, ...(depth > 0 ? { deep: depth } : {}) }));
|
|
176
|
+
runs.push(run);
|
|
177
|
+
measured += run.mapped;
|
|
178
|
+
}
|
|
179
|
+
recordPages(state, runs);
|
|
180
|
+
const { aggregate, reports } = (0, self_healing_1.dirDiffAggregate)((0, self_healing_1.baselineDir)(), (0, self_healing_1.mappingDir)());
|
|
181
|
+
if (reports.length > 0) {
|
|
182
|
+
result.drift = {
|
|
183
|
+
verdict: aggregate.verdict,
|
|
184
|
+
totals: aggregate.totals,
|
|
185
|
+
blocking: aggregate.pageReports
|
|
186
|
+
.filter((p) => p.verdict === 'BLOCK')
|
|
187
|
+
.map((p) => ({ page: p.name, lost: p.counts.lost, ambiguous: p.counts.ambiguous, rebound: p.counts.rebound })),
|
|
188
|
+
staleExcluded: aggregate.staleExcluded ?? [],
|
|
189
|
+
...(aggregate.depthMismatch ? { depthMismatch: aggregate.depthMismatch.why } : {}),
|
|
190
|
+
};
|
|
191
|
+
state.lastVerdict = aggregate.verdict;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
const explored = new Set([...before.entries()].filter(([, d]) => d > 0).map(([n]) => n));
|
|
196
|
+
const batch = (0, plan_1.selectBatch)(names.filter((n) => before.has(n)), explored, state.openables, size);
|
|
197
|
+
if (batch.length > 0) {
|
|
198
|
+
progress(`🔍 Depth: ${batch.length} more page${batch.length === 1 ? '' : 's'}…`);
|
|
199
|
+
const deep = await toStderr(() => (0, self_healing_1.runMap)(undefined, { pages: batch, deep: 1, session }));
|
|
200
|
+
recordPages(state, [deep]);
|
|
201
|
+
await promote(deep.pages.map((p) => p.page));
|
|
202
|
+
runs = [deep];
|
|
203
|
+
measured = deep.mapped;
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
measured = before.size;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
if (mode !== 'explore') {
|
|
210
|
+
const outcome = await toStderr(() => (0, self_healing_1.checkProject)({ onNotice: progress }));
|
|
211
|
+
if (outcome.ok) {
|
|
212
|
+
const { keys, ...split } = (0, plan_1.splitFindings)(outcome.report, new Set(state.knownFindings));
|
|
213
|
+
result.check = split;
|
|
214
|
+
state.knownFindings = keys;
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
result.check = null;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
const after = baselineDepths(names);
|
|
221
|
+
const exploredNow = new Set([...after.entries()].filter(([, d]) => d > 0).map(([n]) => n));
|
|
222
|
+
result.pages.underContract = after.size;
|
|
223
|
+
result.pages.notInBaseline = names.filter((n) => !after.has(n));
|
|
224
|
+
result.pages.explored = exploredNow.size;
|
|
225
|
+
result.pages.notExplored = (0, plan_1.needingDepth)(names.filter((n) => after.has(n)), exploredNow, state.openables);
|
|
226
|
+
result.pages.exploredThisTour = names.filter((n) => exploredNow.has(n) && !((before.get(n) ?? 0) > 0));
|
|
227
|
+
result.pages.partlyExplored = runs.flatMap((r) => r.pages).filter((p) => p.truncated).map((p) => p.page);
|
|
228
|
+
result.pages.notMapped = runs.flatMap((r) => r.failed).map((f) => ({ page: f.page, url: f.url, reason: f.error }));
|
|
229
|
+
result.estimate = (0, plan_1.estimate)(result.pages.notExplored, state.openables, state.pace);
|
|
230
|
+
result.exitCode = (0, plan_1.exitCodeFor)({ measured, drift: result.drift, deadLinks: result.check?.deadLinks.length ?? 0 });
|
|
231
|
+
(0, state_1.saveState)(state);
|
|
232
|
+
return finish();
|
|
233
|
+
}
|
|
234
|
+
//# sourceMappingURL=tour.js.map
|
package/dist/tour.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tour.js","sourceRoot":"","sources":["../src/tour.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2HA,0BAqIC;AAhQD,uCAAyB;AACzB,2CAA6B;AAC7B,sDAU6B;AAE7B,iCAAwG;AAExG,mCAA+C;AAgD/C,MAAM,SAAS,GAAc,EAAE,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;AAEhF;;;GAGG;AACH,KAAK,UAAU,QAAQ,CAAI,EAAoB;IAC7C,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC;IACpC,OAAO,CAAC,GAAG,GAAG,OAAO,CAAC,IAAI,GAAG,OAAO,CAAC,IAAI,GAAG,CAAC,GAAG,IAAe,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC;IAC3F,IAAI,CAAC;QACH,OAAO,MAAM,EAAE,EAAE,CAAC;IACpB,CAAC;YAAS,CAAC;QACT,OAAO,CAAC,GAAG,GAAG,GAAG,CAAC;QAClB,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;QACpB,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;IACtB,CAAC;AACH,CAAC;AAED,kGAAkG;AAClG,KAAK,UAAU,OAAO,CAAC,KAAe;IACpC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO;IAC/B,MAAM,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;IAChC,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC;IAC7B,IAAI,CAAC;QACH,MAAM,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAA,0BAAW,EAAC,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;QACjD,IAAI,OAAO,CAAC,QAAQ;YAAE,MAAM,IAAI,KAAK,CAAC,+BAA+B,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC3F,CAAC;YAAS,CAAC;QACT,OAAO,CAAC,QAAQ,GAAG,MAAM,CAAC;IAC5B,CAAC;AACH,CAAC;AAED,SAAS,cAAc,CAAC,KAAe;IACrC,MAAM,GAAG,GAAG,IAAI,GAAG,EAAkB,CAAC;IACtC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC;YACH,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,IAAA,0BAAW,EAAC,IAAI,EAAE,IAAA,0BAAW,GAAE,CAAC,CAAC,aAAa,IAAI,CAAC,CAAC,CAAC;QACrE,CAAC;QAAC,MAAM,CAAC;YACP,6BAA6B;QAC/B,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,QAAQ,CAAC,MAAkB,EAAE,OAA2B,EAAE,GAAW;IAC5E,OAAO,CAAC,CAAC,CAAC,OAAO,IAAI,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC;AAC7I,CAAC;AAED,SAAS,WAAW,CAAC,KAAe,EAAE,IAAiB;IACrD,KAAK,MAAM,GAAG,IAAI,IAAI;QAAE,KAAK,MAAM,CAAC,IAAI,GAAG,CAAC,KAAK;YAAE,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC;IACzF,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACtE,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO;IAC9B,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,CAAC;IAC1D,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACrB,IAAI,CAAC,OAAO,IAAI,CAAC,CAAC,UAAU,CAAC;QAC7B,IAAI,CAAC,WAAW,IAAI,CAAC,CAAC,MAAM,CAAC;IAC/B,CAAC;IACD,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC;AACpB,CAAC;AAEM,KAAK,UAAU,OAAO,CAAC,OAAoB,EAAE;IAClD,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC3B,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,IAAI,CAAC,CAAC,IAAY,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;IAC5E,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IACpD,IAAA,yBAAU,EAAC,GAAG,CAAC,CAAC;IAChB,MAAM,MAAM,GAAG,IAAA,yBAAU,GAAE,CAAC;IAC5B,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IAC9C,MAAM,KAAK,GAAG,IAAA,iBAAS,GAAE,CAAC;IAC1B,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,IAAI,oBAAa,CAAC;IACzC,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;IAC7B,MAAM,IAAI,GAAG,CAAC,GAAG,EAAE;QACjB,IAAI,CAAC;YACH,OAAO,IAAI,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC;QACtC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,MAAM,CAAC,OAAO,CAAC;QACxB,CAAC;IACH,CAAC,CAAC,EAAE,CAAC;IAEL,MAAM,MAAM,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC;IACrC,MAAM,IAAI,GAAuB,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,OAAO,CAAC;IAElG,MAAM,MAAM,GAAe;QACzB,MAAM,EAAE,kBAAkB;QAC1B,IAAI;QACJ,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,KAAK,EAAE,QAAQ,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC7C,KAAK,EAAE,EAAE,UAAU,EAAE,KAAK,CAAC,MAAM,EAAE,aAAa,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,WAAW,EAAE,EAAE,EAAE,gBAAgB,EAAE,EAAE,EAAE,cAAc,EAAE,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,aAAa,EAAE,EAAE,EAAE;QAC/J,KAAK,EAAE,IAAI;QACX,KAAK,EAAE,IAAI;QACX,QAAQ,EAAE,IAAI;QACd,QAAQ,EAAE,CAAC;QACX,UAAU,EAAE,CAAC;KACd,CAAC;IACF,MAAM,MAAM,GAAG,GAAe,EAAE;QAC9B,MAAM,CAAC,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC;QACzC,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC;IAEF,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YACtB,MAAM,CAAC,OAAO,GAAG,mFAAmF,CAAC;YACrG,MAAM,CAAC,QAAQ,GAAG,CAAC,CAAC;YACpB,OAAO,MAAM,EAAE,CAAC;QAClB,CAAC;QACD,IAAI,KAAK,CAAC,WAAW,KAAK,OAAO,EAAE,CAAC;YAClC,MAAM,CAAC,OAAO;gBACZ,qKAAqK,CAAC;YACxK,MAAM,CAAC,QAAQ,GAAG,CAAC,CAAC;YACpB,OAAO,MAAM,EAAE,CAAC;QAClB,CAAC;IACH,CAAC;IAED,QAAQ,CAAC,MAAM,IAAI,MAAM,KAAK,CAAC,MAAM,sIAAsI,CAAC,CAAC;IAE7K,IAAI,IAAI,GAAgB,EAAE,CAAC;IAC3B,IAAI,QAAQ,GAAG,CAAC,CAAC;IAEjB,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;QACrB,QAAQ,CAAC,qCAAqC,CAAC,CAAC;QAChD,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAA,qBAAM,EAAC,SAAS,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC;QACnF,WAAW,CAAC,KAAK,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;QAC9B,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAChD,MAAM,KAAK,GAAG,IAAA,kBAAW,EAAC,MAAM,EAAE,IAAI,GAAG,EAAE,EAAE,KAAK,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;QACpE,QAAQ,CAAC,aAAa,KAAK,CAAC,MAAM,QAAQ,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,wBAAwB,CAAC,CAAC;QACjG,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAA,qBAAM,EAAC,SAAS,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACxH,WAAW,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;QAC3B,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;QACtB,IAAI,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QACvB,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC;IAC5B,CAAC;SAAM,IAAI,IAAI,KAAK,YAAY,EAAE,CAAC;QACjC,MAAM,UAAU,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QACtD,MAAM,MAAM,GAAG,IAAI,GAAG,EAAoB,CAAC;QAC3C,KAAK,MAAM,CAAC,IAAI,UAAU;YAAE,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAE,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAE,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QACnG,KAAK,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAC/E,QAAQ,CAAC,oBAAoB,KAAK,CAAC,MAAM,QAAQ,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,YAAY,KAAK,EAAE,CAAC,CAAC,CAAC,gBAAgB,uBAAuB,CAAC,CAAC;YAC7J,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAA,qBAAM,EAAC,SAAS,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;YAC/G,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACf,QAAQ,IAAI,GAAG,CAAC,MAAM,CAAC;QACzB,CAAC;QACD,WAAW,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QACzB,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,GAAG,IAAA,+BAAgB,EAAC,IAAA,0BAAW,GAAE,EAAE,IAAA,yBAAU,GAAE,CAAC,CAAC;QAC7E,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACvB,MAAM,CAAC,KAAK,GAAG;gBACb,OAAO,EAAE,SAAS,CAAC,OAAO;gBAC1B,MAAM,EAAE,SAAS,CAAC,MAAM;gBACxB,QAAQ,EAAE,SAAS,CAAC,WAAW;qBAC5B,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,OAAO,CAAC;qBACpC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC,MAAM,CAAC,SAAS,EAAE,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC;gBAChH,aAAa,EAAE,SAAS,CAAC,aAAa,IAAI,EAAE;gBAC5C,GAAG,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,SAAS,CAAC,aAAa,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACnF,CAAC;YACF,KAAK,CAAC,WAAW,GAAG,SAAS,CAAC,OAAO,CAAC;QACxC,CAAC;IACH,CAAC;SAAM,CAAC;QACN,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QACzF,MAAM,KAAK,GAAG,IAAA,kBAAW,EAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;QAC/F,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACrB,QAAQ,CAAC,aAAa,KAAK,CAAC,MAAM,aAAa,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC;YACjF,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAA,qBAAM,EAAC,SAAS,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC;YACzF,WAAW,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;YAC3B,MAAM,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;YAC7C,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC;YACd,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC;QACzB,CAAC;aAAM,CAAC;YACN,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC;QACzB,CAAC;IACH,CAAC;IAED,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAA,2BAAY,EAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC;QAC3E,IAAI,OAAO,CAAC,EAAE,EAAE,CAAC;YACf,MAAM,EAAE,IAAI,EAAE,GAAG,KAAK,EAAE,GAAG,IAAA,oBAAa,EAAC,OAAO,CAAC,MAAM,EAAE,IAAI,GAAG,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC;YACvF,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC;YACrB,KAAK,CAAC,aAAa,GAAG,IAAI,CAAC;QAC7B,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,KAAK,GAAG,IAAI,CAAC;QACtB,CAAC;IACH,CAAC;IAED,MAAM,KAAK,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC;IACpC,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3F,MAAM,CAAC,KAAK,CAAC,aAAa,GAAG,KAAK,CAAC,IAAI,CAAC;IACxC,MAAM,CAAC,KAAK,CAAC,aAAa,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IAChE,MAAM,CAAC,KAAK,CAAC,QAAQ,GAAG,WAAW,CAAC,IAAI,CAAC;IACzC,MAAM,CAAC,KAAK,CAAC,WAAW,GAAG,IAAA,mBAAY,EAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IACzG,MAAM,CAAC,KAAK,CAAC,gBAAgB,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IACvG,MAAM,CAAC,KAAK,CAAC,cAAc,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAc,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IACtH,MAAM,CAAC,KAAK,CAAC,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IACnH,MAAM,CAAC,QAAQ,GAAG,IAAA,eAAQ,EAAC,MAAM,CAAC,KAAK,CAAC,WAAW,EAAE,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;IAClF,MAAM,CAAC,QAAQ,GAAG,IAAA,kBAAW,EAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,SAAS,EAAE,MAAM,CAAC,KAAK,EAAE,SAAS,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC,CAAC;IAEjH,IAAA,iBAAS,EAAC,KAAK,CAAC,CAAC;IACjB,OAAO,MAAM,EAAE,CAAC;AAClB,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ia-qa/pal",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A QA pal next to your team: one command walks your web app, maps it, checks it and diffs it against yesterday, then reports what broke, what needs a decision and what it could not see. Local, deterministic, no LLM.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"qa",
|
|
7
|
+
"testing",
|
|
8
|
+
"regression",
|
|
9
|
+
"playwright",
|
|
10
|
+
"e2e",
|
|
11
|
+
"self-healing"
|
|
12
|
+
],
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"author": "Jean-Christophe Jamet",
|
|
15
|
+
"homepage": "https://www.ia-qa.com",
|
|
16
|
+
"type": "commonjs",
|
|
17
|
+
"main": "dist/index.js",
|
|
18
|
+
"types": "dist/index.d.ts",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"default": "./dist/index.js"
|
|
23
|
+
},
|
|
24
|
+
"./package.json": "./package.json"
|
|
25
|
+
},
|
|
26
|
+
"bin": {
|
|
27
|
+
"ia-qa-pal": "dist/cli/index.js"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"dist",
|
|
31
|
+
"README.md"
|
|
32
|
+
],
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "tsc -p tsconfig.json",
|
|
35
|
+
"dev": "tsc -p tsconfig.json --watch",
|
|
36
|
+
"prepublishOnly": "npm run build"
|
|
37
|
+
},
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=18.0.0"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@ia-qa/self-healing": "^1.10.0"
|
|
43
|
+
},
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"playwright": ">=1.40.0"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@types/node": "^20.14.0",
|
|
49
|
+
"playwright": "^1.45.0",
|
|
50
|
+
"typescript": "^5.5.0"
|
|
51
|
+
}
|
|
52
|
+
}
|