@ia-qa/pal 0.1.0 → 0.3.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 +54 -14
- package/TUTORIAL.md +375 -0
- package/dist/cli/index.js +181 -20
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.js +10 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.d.ts +89 -0
- package/dist/mcp/server.js +250 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/plan.d.ts +2 -0
- package/dist/plan.js +2 -0
- package/dist/plan.js.map +1 -1
- package/dist/report.js +71 -19
- package/dist/report.js.map +1 -1
- package/dist/run.d.ts +65 -0
- package/dist/run.js +171 -0
- package/dist/run.js.map +1 -0
- package/dist/setup.d.ts +45 -0
- package/dist/setup.js +210 -0
- package/dist/setup.js.map +1 -0
- 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 +44 -5
- package/dist/tour.js +92 -10
- package/dist/tour.js.map +1 -1
- package/package.json +6 -4
package/dist/setup.js
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
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.LOGIN_COMMAND = void 0;
|
|
37
|
+
exports.configExists = configExists;
|
|
38
|
+
exports.detectTests = detectTests;
|
|
39
|
+
exports.runSetup = runSetup;
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
const self_healing_1 = require("@ia-qa/self-healing");
|
|
43
|
+
exports.LOGIN_COMMAND = 'npx -p @ia-qa/self-healing ia-qa-heal login';
|
|
44
|
+
const CRAWL_CAP = 50;
|
|
45
|
+
const LOGIN_CHOICES = { '1': 'none', '2': 'browser', '3': 'session' };
|
|
46
|
+
function configExists(dir) {
|
|
47
|
+
return fs.existsSync(path.join(dir, '.ia-qa', 'config.json'));
|
|
48
|
+
}
|
|
49
|
+
function withHome(pages) {
|
|
50
|
+
const hasHome = pages.some((p) => {
|
|
51
|
+
try {
|
|
52
|
+
return new URL(p.url, 'http://x').pathname === '/';
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
if (hasHome)
|
|
59
|
+
return pages;
|
|
60
|
+
const name = pages.some((p) => p.name === 'home') ? 'home-page' : 'home';
|
|
61
|
+
return [{ name, url: '/' }, ...pages];
|
|
62
|
+
}
|
|
63
|
+
function request(baseUrl, pages, login, session, tests, testCommand) {
|
|
64
|
+
const mode = login === 'none' ? 'none' : 'session';
|
|
65
|
+
return {
|
|
66
|
+
baseUrl,
|
|
67
|
+
pages,
|
|
68
|
+
login: { mode, ...(login === 'session' && session ? { sessionFile: session } : {}) },
|
|
69
|
+
...(tests && tests.length > 0 ? { tests: { paths: tests, ...(testCommand ? { command: testCommand } : {}) } } : {}),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
const TEST_DIRS = ['tests', 'e2e', 'test', 'cypress/e2e', 'specs'];
|
|
73
|
+
const SPEC_FILE = /\.(spec|test|cy)\.[cm]?[jt]sx?$/;
|
|
74
|
+
function hasSpecs(dir, depth = 0) {
|
|
75
|
+
if (depth > 3)
|
|
76
|
+
return false;
|
|
77
|
+
let entries;
|
|
78
|
+
try {
|
|
79
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
return entries.some((e) => e.isFile() ? SPEC_FILE.test(e.name) : e.isDirectory() && e.name !== 'node_modules' && hasSpecs(path.join(dir, e.name), depth + 1));
|
|
85
|
+
}
|
|
86
|
+
/** Folders that look like an end-to-end suite. Proposed to a person, never assumed. */
|
|
87
|
+
function detectTests(dir) {
|
|
88
|
+
const paths = TEST_DIRS.filter((d) => hasSpecs(path.join(dir, d)));
|
|
89
|
+
return { paths, command: paths.length > 0 ? (0, self_healing_1.detectTestCommand)(dir) : undefined };
|
|
90
|
+
}
|
|
91
|
+
async function runSetup(opts = {}) {
|
|
92
|
+
const dir = path.resolve(opts.dir ?? process.cwd());
|
|
93
|
+
const progress = opts.onProgress ?? ((line) => console.error(line));
|
|
94
|
+
const refuse = (problems, extra = {}) => ({ written: false, pages: [], problems, ...extra });
|
|
95
|
+
if (configExists(dir)) {
|
|
96
|
+
return refuse([`${path.join(dir, '.ia-qa', 'config.json')} already exists — setup never overwrites one. Run: ia-qa-pal tour`]);
|
|
97
|
+
}
|
|
98
|
+
let url = opts.url?.trim();
|
|
99
|
+
if (!url && opts.ask)
|
|
100
|
+
url = (await opts.ask('Where does the app live? (https://…) ')).trim();
|
|
101
|
+
if (!url)
|
|
102
|
+
return refuse(['The app address is missing: --url https://your-app.example']);
|
|
103
|
+
if (!(0, self_healing_1.isWebAddress)(url))
|
|
104
|
+
return refuse([`"${url}" is not a web address — it starts with http:// or https://.`]);
|
|
105
|
+
let login = opts.login;
|
|
106
|
+
if (!login && opts.ask) {
|
|
107
|
+
const answer = (await opts.ask('Does the app ask you to log in?\n' +
|
|
108
|
+
' 1 no\n' +
|
|
109
|
+
' 2 yes — I log in by hand in a browser (SSO, MFA, anything)\n' +
|
|
110
|
+
' 3 yes — my test suite already saves a Playwright storageState file\n' +
|
|
111
|
+
'Choice [1-3]: ')).trim();
|
|
112
|
+
login = LOGIN_CHOICES[answer];
|
|
113
|
+
}
|
|
114
|
+
if (!login)
|
|
115
|
+
return refuse(['How the app logs in is missing: --login none | browser | session']);
|
|
116
|
+
let session = opts.session?.trim();
|
|
117
|
+
if (login === 'session' && !session && opts.ask)
|
|
118
|
+
session = (await opts.ask('Path of that storageState file: ')).trim();
|
|
119
|
+
if (login === 'session' && !session)
|
|
120
|
+
return refuse(['--login session needs the file: --session path/to/storageState.json']);
|
|
121
|
+
const host = new URL(url).host;
|
|
122
|
+
progress(`🗺 Reading ${host}/sitemap.xml (and robots.txt) — a plain fetch, nothing else.`);
|
|
123
|
+
let pages = [];
|
|
124
|
+
let pagesFrom = 'home';
|
|
125
|
+
try {
|
|
126
|
+
const sitemap = await (0, self_healing_1.readSitemap)(url);
|
|
127
|
+
pages = sitemap.pages;
|
|
128
|
+
if (pages.length > 0) {
|
|
129
|
+
pagesFrom = 'sitemap';
|
|
130
|
+
progress(` Found ${pages.length} page${pages.length === 1 ? '' : 's'}${sitemap.capped ? ' (the sitemap holds more — stopped at the cap)' : ''}.`);
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
for (const line of sitemap.reason)
|
|
134
|
+
progress(` ${line}`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
catch (err) {
|
|
138
|
+
progress(` No sitemap: ${err instanceof Error ? err.message : String(err)}`);
|
|
139
|
+
}
|
|
140
|
+
if (pages.length === 0) {
|
|
141
|
+
progress(`🕸 Following links from ${host} in a headless browser — same host, GET only, at most ${CRAWL_CAP} pages, action links skipped.`);
|
|
142
|
+
try {
|
|
143
|
+
(0, self_healing_1.setBaseDir)(dir);
|
|
144
|
+
const plan = (0, self_healing_1.planSetup)(request(url, [{ name: 'home', url: '/' }], login, session), dir);
|
|
145
|
+
const found = await (0, self_healing_1.crawlApp)(plan.config, { maxPages: CRAWL_CAP, ...(session ? { session: path.resolve(dir, session) } : {}) });
|
|
146
|
+
pages = found.map((c) => ({ name: c.name, url: c.url }));
|
|
147
|
+
if (pages.length > 0)
|
|
148
|
+
pagesFrom = 'crawl';
|
|
149
|
+
progress(` Found ${pages.length} page${pages.length === 1 ? '' : 's'}.`);
|
|
150
|
+
}
|
|
151
|
+
catch (err) {
|
|
152
|
+
progress(` Could not follow links: ${err instanceof Error ? err.message : String(err)}`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
pages = withHome(pages);
|
|
156
|
+
let tests = opts.tests?.map((t) => t.trim()).filter(Boolean);
|
|
157
|
+
let testCommand = opts.testCommand?.trim();
|
|
158
|
+
if (!tests && opts.ask) {
|
|
159
|
+
const found = detectTests(dir);
|
|
160
|
+
if (found.paths.length > 0) {
|
|
161
|
+
const answer = (await opts.ask(`Found a test suite in ${found.paths.join(', ')}${found.command ? ` (run with: ${found.command})` : ''}. ` +
|
|
162
|
+
'Read it too — repairs to its selectors, and which pages it never visits? [Y/n] ')).trim().toLowerCase();
|
|
163
|
+
if (answer === '' || answer === 'y' || answer === 'yes') {
|
|
164
|
+
tests = found.paths;
|
|
165
|
+
testCommand = testCommand || found.command;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
const body = request(url, pages, login, session, tests, testCommand);
|
|
170
|
+
const plan = (0, self_healing_1.planSetup)(body, dir);
|
|
171
|
+
const outcome = {
|
|
172
|
+
written: false,
|
|
173
|
+
baseUrl: url,
|
|
174
|
+
pages: plan.config.pages,
|
|
175
|
+
pagesFrom,
|
|
176
|
+
login,
|
|
177
|
+
...(plan.config.testPaths ? { tests: plan.config.testPaths } : {}),
|
|
178
|
+
...(plan.config.testPaths && plan.config.testCommand ? { testCommand: plan.config.testCommand } : {}),
|
|
179
|
+
problems: plan.problems,
|
|
180
|
+
};
|
|
181
|
+
if (plan.problems.length > 0)
|
|
182
|
+
return outcome;
|
|
183
|
+
const file = path.join(dir, '.ia-qa', 'config.json');
|
|
184
|
+
const names = plan.config.pages.map((p) => p.name);
|
|
185
|
+
progress('');
|
|
186
|
+
progress(`📄 This writes ${file}`);
|
|
187
|
+
progress(` app ${url}`);
|
|
188
|
+
progress(` pages ${names.length} from the ${pagesFrom === 'home' ? 'home page only' : pagesFrom}: ${names.slice(0, 8).join(', ')}${names.length > 8 ? `, +${names.length - 8} more` : ''}`);
|
|
189
|
+
progress(` login ${login === 'none' ? 'none' : login === 'browser' ? `by hand — ${exports.LOGIN_COMMAND} saves .ia-qa/session.json` : `storageState ${session}`}`);
|
|
190
|
+
progress(` tests ${plan.config.testPaths ? `${plan.config.testPaths.join(', ')}${plan.config.testCommand ? ` · run with: ${plan.config.testCommand}` : ''}` : 'none'}`);
|
|
191
|
+
progress(' Nothing else is written. No field holds a secret.');
|
|
192
|
+
if (!opts.yes) {
|
|
193
|
+
if (!opts.ask)
|
|
194
|
+
return { ...outcome, problems: ['Not written: confirm with --yes.'] };
|
|
195
|
+
const confirm = (await opts.ask('Write it? [y/N] ')).trim().toLowerCase();
|
|
196
|
+
if (confirm !== 'y' && confirm !== 'yes')
|
|
197
|
+
return { ...outcome, problems: ['Not written — nothing changed.'] };
|
|
198
|
+
}
|
|
199
|
+
const result = (0, self_healing_1.writeSetup)(body, dir);
|
|
200
|
+
if (!result.written)
|
|
201
|
+
return { ...outcome, problems: [result.error] };
|
|
202
|
+
const needsSession = login === 'browser' && !fs.existsSync(path.join(dir, '.ia-qa', 'session.json'));
|
|
203
|
+
return {
|
|
204
|
+
...outcome,
|
|
205
|
+
written: true,
|
|
206
|
+
file: result.file,
|
|
207
|
+
next: needsSession ? exports.LOGIN_COMMAND : 'ia-qa-pal tour',
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
//# sourceMappingURL=setup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup.js","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":"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"}
|
package/dist/state.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Pace } from './plan';
|
|
2
|
+
import type { SuiteRun } from './suite';
|
|
2
3
|
/**
|
|
3
4
|
* What a tour remembers between runs, and only what cannot be read back from the contracts:
|
|
4
5
|
* which pages have explored depth is in each baseline file (`exploredDepth`), so it is never
|
|
@@ -14,6 +15,8 @@ export interface PalState {
|
|
|
14
15
|
knownFindings: string[];
|
|
15
16
|
/** Drift verdict of the latest regression tour — `--more` refuses to promote over a BLOCK. */
|
|
16
17
|
lastVerdict?: 'PASS' | 'FIX' | 'BLOCK';
|
|
18
|
+
/** The latest `--suite` run: which pages the suite visited, and how it exited. */
|
|
19
|
+
suite?: SuiteRun;
|
|
17
20
|
}
|
|
18
21
|
export declare function statePath(): string;
|
|
19
22
|
export declare function loadState(): PalState;
|
package/dist/state.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state.js","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"state.js","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyBA,8BAEC;AAED,8BAQC;AAED,8BAMC;AA7CD,uCAAyB;AACzB,2CAA6B;AAC7B,sDAAiD;AAuBjD,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/suite.d.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { IaQaConfig } from '@ia-qa/self-healing';
|
|
2
|
+
/**
|
|
3
|
+
* Level 2: the test suite. Every answer here comes from `@ia-qa/self-healing` — the inventory
|
|
4
|
+
* `ingest` writes, the plan `fix --dry-run` computes, the names `audit` judges, the pages the
|
|
5
|
+
* capture hook records. This file only runs them in the right place and keeps what came back.
|
|
6
|
+
*/
|
|
7
|
+
export declare const APPLY_COMMAND = "npx -p @ia-qa/self-healing ia-qa-heal fix";
|
|
8
|
+
export declare const CAPTURE_IMPORT = "import { test, expect } from '@ia-qa/self-healing/capture'";
|
|
9
|
+
export interface SuiteInventory {
|
|
10
|
+
paths: string[];
|
|
11
|
+
missingPaths: string[];
|
|
12
|
+
filesScanned: number;
|
|
13
|
+
selectors: number;
|
|
14
|
+
names: number;
|
|
15
|
+
}
|
|
16
|
+
export interface RepairPlan {
|
|
17
|
+
files: Array<{
|
|
18
|
+
file: string;
|
|
19
|
+
replacements: Array<{
|
|
20
|
+
from: string;
|
|
21
|
+
to: string;
|
|
22
|
+
count: number;
|
|
23
|
+
lines: number[];
|
|
24
|
+
}>;
|
|
25
|
+
}>;
|
|
26
|
+
total: number;
|
|
27
|
+
/** Occurrences found and deliberately left alone, with why — never silently dropped. */
|
|
28
|
+
leftAlone: number;
|
|
29
|
+
command: string;
|
|
30
|
+
}
|
|
31
|
+
export interface SuiteAudit {
|
|
32
|
+
deadNames: Array<{
|
|
33
|
+
name: string;
|
|
34
|
+
sites: Array<{
|
|
35
|
+
file: string;
|
|
36
|
+
line: number;
|
|
37
|
+
}>;
|
|
38
|
+
}>;
|
|
39
|
+
judged: number;
|
|
40
|
+
notJudged: number;
|
|
41
|
+
}
|
|
42
|
+
export interface SuiteRun {
|
|
43
|
+
command: string;
|
|
44
|
+
exitCode: number;
|
|
45
|
+
/** Pages the suite really visited, as the capture hook recorded them. Null: nothing was recorded. */
|
|
46
|
+
visited: string[] | null;
|
|
47
|
+
at: string;
|
|
48
|
+
}
|
|
49
|
+
/** `ingest`: what the suite writes as locators. Written to `usage.json`, where map, diff and fix read it. */
|
|
50
|
+
export declare function inventory(dir: string, config: IaQaConfig): SuiteInventory | null;
|
|
51
|
+
/** `fix --dry-run` over baseline/ → mapping/: the rewrites it would apply. Nothing is written. */
|
|
52
|
+
export declare function repairPlan(dir: string): Promise<RepairPlan | null>;
|
|
53
|
+
/** `audit`: locator names the suite writes that nothing on the mapped pages carries. Advisory. */
|
|
54
|
+
export declare function auditSuite(dir: string, config: IaQaConfig): SuiteAudit | null;
|
|
55
|
+
/**
|
|
56
|
+
* Run the suite with the capture hook armed, and learn which pages it visits.
|
|
57
|
+
*
|
|
58
|
+
* The capture lands in `.ia-qa/pal/suite-run/`, never in `mapping/`: a capture taken during a
|
|
59
|
+
* test run reads a page differently from `map`, and mixing the two in one directory is the
|
|
60
|
+
* phantom drift `ia-qa-heal run` warns about. Only the list of visited pages is kept.
|
|
61
|
+
* The command is the one in config.json — never a string from a caller.
|
|
62
|
+
*/
|
|
63
|
+
export declare function runSuite(dir: string, config: IaQaConfig, onOutput: (chunk: string) => void): Promise<SuiteRun | null>;
|
package/dist/suite.js
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
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.CAPTURE_IMPORT = exports.APPLY_COMMAND = void 0;
|
|
37
|
+
exports.inventory = inventory;
|
|
38
|
+
exports.repairPlan = repairPlan;
|
|
39
|
+
exports.auditSuite = auditSuite;
|
|
40
|
+
exports.runSuite = runSuite;
|
|
41
|
+
const fs = __importStar(require("fs"));
|
|
42
|
+
const path = __importStar(require("path"));
|
|
43
|
+
const child_process_1 = require("child_process");
|
|
44
|
+
const self_healing_1 = require("@ia-qa/self-healing");
|
|
45
|
+
/**
|
|
46
|
+
* Level 2: the test suite. Every answer here comes from `@ia-qa/self-healing` — the inventory
|
|
47
|
+
* `ingest` writes, the plan `fix --dry-run` computes, the names `audit` judges, the pages the
|
|
48
|
+
* capture hook records. This file only runs them in the right place and keeps what came back.
|
|
49
|
+
*/
|
|
50
|
+
exports.APPLY_COMMAND = 'npx -p @ia-qa/self-healing ia-qa-heal fix';
|
|
51
|
+
exports.CAPTURE_IMPORT = "import { test, expect } from '@ia-qa/self-healing/capture'";
|
|
52
|
+
const rel = (dir, file) => path.relative(dir, file).replace(/\\/g, '/') || file;
|
|
53
|
+
function withExitCode(fn) {
|
|
54
|
+
const before = process.exitCode;
|
|
55
|
+
process.exitCode = undefined;
|
|
56
|
+
return fn().finally(() => {
|
|
57
|
+
process.exitCode = before;
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
/** `ingest`: what the suite writes as locators. Written to `usage.json`, where map, diff and fix read it. */
|
|
61
|
+
function inventory(dir, config) {
|
|
62
|
+
const declared = config.testPaths ?? [];
|
|
63
|
+
if (declared.length === 0)
|
|
64
|
+
return null;
|
|
65
|
+
const resolved = declared.map((p) => path.resolve(dir, p));
|
|
66
|
+
const missingPaths = declared.filter((_, i) => !fs.existsSync(resolved[i]));
|
|
67
|
+
const { usage } = (0, self_healing_1.ingestPaths)(resolved.filter((p) => fs.existsSync(p)), dir);
|
|
68
|
+
(0, self_healing_1.saveUsage)(usage, dir);
|
|
69
|
+
return {
|
|
70
|
+
paths: declared,
|
|
71
|
+
missingPaths,
|
|
72
|
+
filesScanned: usage.scanned,
|
|
73
|
+
selectors: Object.keys(usage.selectors).length,
|
|
74
|
+
names: Object.keys(usage.names ?? {}).length,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/** `fix --dry-run` over baseline/ → mapping/: the rewrites it would apply. Nothing is written. */
|
|
78
|
+
async function repairPlan(dir) {
|
|
79
|
+
const result = await withExitCode(() => (0, self_healing_1.runFix)(['--dry-run']));
|
|
80
|
+
if (!result || !result.dryRun)
|
|
81
|
+
return null;
|
|
82
|
+
const files = result.edits
|
|
83
|
+
.filter((e) => e.replacements.length > 0)
|
|
84
|
+
.map((e) => ({
|
|
85
|
+
file: rel(dir, e.file),
|
|
86
|
+
replacements: e.replacements.map((r) => ({ from: r.from, to: r.to, count: r.count, lines: r.lines ?? [] })),
|
|
87
|
+
}));
|
|
88
|
+
return {
|
|
89
|
+
files,
|
|
90
|
+
total: result.totalReplacements,
|
|
91
|
+
leftAlone: result.edits.reduce((s, e) => s + (e.skipped ?? []).reduce((t, k) => t + k.count, 0), 0),
|
|
92
|
+
command: exports.APPLY_COMMAND,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
/** `audit`: locator names the suite writes that nothing on the mapped pages carries. Advisory. */
|
|
96
|
+
function auditSuite(dir, config) {
|
|
97
|
+
const outcome = (0, self_healing_1.auditProject)({ testPaths: (config.testPaths ?? []).map((p) => path.resolve(dir, p)) });
|
|
98
|
+
if (!outcome.ok)
|
|
99
|
+
return null;
|
|
100
|
+
return {
|
|
101
|
+
deadNames: outcome.report.deadLocators.map((d) => ({
|
|
102
|
+
name: d.name,
|
|
103
|
+
sites: d.sites.map((s) => ({ file: rel(dir, s.file), line: s.line })),
|
|
104
|
+
})),
|
|
105
|
+
judged: outcome.report.locatorsJudged,
|
|
106
|
+
notJudged: outcome.report.locatorsNotJudged,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Run the suite with the capture hook armed, and learn which pages it visits.
|
|
111
|
+
*
|
|
112
|
+
* The capture lands in `.ia-qa/pal/suite-run/`, never in `mapping/`: a capture taken during a
|
|
113
|
+
* test run reads a page differently from `map`, and mixing the two in one directory is the
|
|
114
|
+
* phantom drift `ia-qa-heal run` warns about. Only the list of visited pages is kept.
|
|
115
|
+
* The command is the one in config.json — never a string from a caller.
|
|
116
|
+
*/
|
|
117
|
+
async function runSuite(dir, config, onOutput) {
|
|
118
|
+
const command = config.testCommand;
|
|
119
|
+
if (!command)
|
|
120
|
+
return null;
|
|
121
|
+
const isolated = path.join(dir, '.ia-qa', 'pal', 'suite-run');
|
|
122
|
+
fs.rmSync(isolated, { recursive: true, force: true });
|
|
123
|
+
fs.mkdirSync(path.join(isolated, '.ia-qa'), { recursive: true });
|
|
124
|
+
fs.writeFileSync(path.join(isolated, '.ia-qa', 'config.json'), JSON.stringify(config, null, 2));
|
|
125
|
+
const exitCode = await new Promise((resolve) => {
|
|
126
|
+
const child = (0, child_process_1.spawn)(command, {
|
|
127
|
+
cwd: dir,
|
|
128
|
+
shell: true,
|
|
129
|
+
env: { ...process.env, IAQA_CAPTURE: '1', IAQA_CONFIG_DIR: isolated },
|
|
130
|
+
});
|
|
131
|
+
child.stdout.on('data', (d) => onOutput(d.toString()));
|
|
132
|
+
child.stderr.on('data', (d) => onOutput(d.toString()));
|
|
133
|
+
child.on('error', () => resolve(127));
|
|
134
|
+
child.on('close', (code) => resolve(code ?? 1));
|
|
135
|
+
});
|
|
136
|
+
let visited = null;
|
|
137
|
+
try {
|
|
138
|
+
visited = (0, self_healing_1.mergeCaptures)(isolated)?.pages ?? null;
|
|
139
|
+
}
|
|
140
|
+
finally {
|
|
141
|
+
fs.rmSync(isolated, { recursive: true, force: true });
|
|
142
|
+
}
|
|
143
|
+
return { command, exitCode, visited: visited ? [...visited].sort() : null, at: new Date().toISOString() };
|
|
144
|
+
}
|
|
145
|
+
//# sourceMappingURL=suite.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"suite.js","sourceRoot":"","sources":["../src/suite.ts"],"names":[],"mappings":"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"}
|
package/dist/tour.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { DirReport } from '@ia-qa/self-healing';
|
|
2
2
|
import type { Estimate, CheckSplit } from './plan';
|
|
3
|
+
import { CAPTURE_IMPORT } from './suite';
|
|
4
|
+
import type { SuiteInventory, RepairPlan, SuiteRun } from './suite';
|
|
3
5
|
export interface TourOptions {
|
|
4
6
|
/** Project directory holding `.ia-qa/`. Default: the current directory. */
|
|
5
7
|
dir?: string;
|
|
@@ -7,16 +9,26 @@ export interface TourOptions {
|
|
|
7
9
|
batch?: number;
|
|
8
10
|
/** Explore the next batch in depth instead of running a regression tour. */
|
|
9
11
|
more?: boolean;
|
|
12
|
+
/** Run the test suite from config.json with the capture hook, to learn which pages it visits. */
|
|
13
|
+
suite?: boolean;
|
|
10
14
|
session?: string;
|
|
11
15
|
/** Where progress goes. Never stdout: stdout is the report. */
|
|
12
16
|
onProgress?: (line: string) => void;
|
|
17
|
+
/** Machine-readable progress: which phase, how far. */
|
|
18
|
+
onEvent?: (event: TourEvent) => void;
|
|
19
|
+
}
|
|
20
|
+
export interface TourEvent {
|
|
21
|
+
phase: 'surface' | 'depth' | 'recapture' | 'check' | 'suite' | 'repairs' | 'done';
|
|
22
|
+
done?: number;
|
|
23
|
+
total?: number;
|
|
24
|
+
page?: string;
|
|
13
25
|
}
|
|
14
26
|
export interface TourResult {
|
|
15
27
|
schema: 'ia-qa-pal/tour@1';
|
|
16
28
|
mode: 'first' | 'regression' | 'explore';
|
|
17
29
|
baseUrl: string;
|
|
18
|
-
/** 0 = public pages only, 1 = a session or a login is configured. */
|
|
19
|
-
level: 0 | 1;
|
|
30
|
+
/** 0 = public pages only, 1 = a session or a login is configured, 2 = the test suite is declared too. */
|
|
31
|
+
level: 0 | 1 | 2;
|
|
20
32
|
pages: {
|
|
21
33
|
configured: number;
|
|
22
34
|
underContract: number;
|
|
@@ -44,14 +56,41 @@ export interface TourResult {
|
|
|
44
56
|
}>;
|
|
45
57
|
staleExcluded: string[];
|
|
46
58
|
depthMismatch?: string;
|
|
59
|
+
/** Renamed labels the suite locates by name, and selectors of the suite that broke or now reach another element. */
|
|
60
|
+
suiteFindings?: {
|
|
61
|
+
names: number;
|
|
62
|
+
bindings: number;
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
check: null | Omit<CheckSplit, 'keys'>;
|
|
66
|
+
suite: null | {
|
|
67
|
+
inventory: SuiteInventory;
|
|
68
|
+
repairs: RepairPlan | null;
|
|
69
|
+
/** Locator names the suite writes that nothing mapped carries, first seen this tour. Advisory. */
|
|
70
|
+
newDeadNames: Array<{
|
|
71
|
+
name: string;
|
|
72
|
+
sites: Array<{
|
|
73
|
+
file: string;
|
|
74
|
+
line: number;
|
|
75
|
+
}>;
|
|
76
|
+
}>;
|
|
77
|
+
knownDeadNames: number;
|
|
78
|
+
/** The latest `--suite` run — this tour's, or an earlier one, with its date. */
|
|
79
|
+
run: SuiteRun | null;
|
|
80
|
+
ranThisTour: boolean;
|
|
81
|
+
/** Configured pages the suite did not visit in that run. Null: not measured. */
|
|
82
|
+
notVisited: string[] | null;
|
|
47
83
|
};
|
|
48
|
-
check: null | (Omit<CheckSplit, 'keys'> & {
|
|
49
|
-
refused?: string;
|
|
50
|
-
});
|
|
51
84
|
estimate: Estimate | null;
|
|
52
85
|
/** Set when the tour declined to run, with the reason. */
|
|
53
86
|
refused?: string;
|
|
87
|
+
/** The project has no config yet: what a setup needs, and the command that provides it. */
|
|
88
|
+
setupRequired?: {
|
|
89
|
+
missing: Array<'url' | 'login'>;
|
|
90
|
+
command: string;
|
|
91
|
+
};
|
|
54
92
|
exitCode: 0 | 1 | 2;
|
|
55
93
|
durationMs: number;
|
|
56
94
|
}
|
|
57
95
|
export declare function runTour(opts?: TourOptions): Promise<TourResult>;
|
|
96
|
+
export { CAPTURE_IMPORT };
|