@ia-qa/self-healing 1.7.13 → 1.7.15
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 +39 -0
- package/SPEC-selector-resolution.md +18 -0
- package/TUTORIAL.md +44 -1
- package/dist/aom.d.ts +29 -0
- package/dist/aom.js +32 -0
- package/dist/aom.js.map +1 -1
- package/dist/captureMerge.js +45 -0
- package/dist/captureMerge.js.map +1 -1
- package/dist/cli/diff.d.ts +10 -0
- package/dist/cli/diff.js +17 -16
- package/dist/cli/diff.js.map +1 -1
- package/dist/cypress/capture.js +7 -5
- package/dist/cypress/capture.js.map +1 -1
- package/dist/discovery/safety.js +1 -2
- package/dist/discovery/safety.js.map +1 -1
- package/dist/htmlReport.js +15 -0
- package/dist/htmlReport.js.map +1 -1
- package/dist/mcp/server.js +12 -0
- package/dist/mcp/server.js.map +1 -1
- package/dist/pageName.d.ts +36 -1
- package/dist/pageName.js +91 -11
- package/dist/pageName.js.map +1 -1
- package/dist/playwright/capture.js +6 -4
- package/dist/playwright/capture.js.map +1 -1
- package/dist/selenium/capture.js +7 -4
- package/dist/selenium/capture.js.map +1 -1
- package/package.json +1 -1
package/dist/pageName.d.ts
CHANGED
|
@@ -6,8 +6,43 @@ import type { IaQaConfig } from './config';
|
|
|
6
6
|
* same page differently produce contracts that never meet: every page becomes a
|
|
7
7
|
* phantom lost/new pair and the diff is garbage. The healer, `map`, and run-time
|
|
8
8
|
* capture all funnel through here for exactly that reason.
|
|
9
|
+
*
|
|
10
|
+
* A page name is a **filename**, so it is also an identity claim: two URLs that
|
|
11
|
+
* resolve to the same name are asserting they are the same page. A pathname alone
|
|
12
|
+
* cannot carry that claim across hosts — five products with a `/login` are five
|
|
13
|
+
* different pages, and naming them all `login` made whichever the suite happened
|
|
14
|
+
* to visit last overwrite the others. The host is therefore part of the name
|
|
15
|
+
* whenever it is not the app's own (see `hostPrefix`).
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* A URL's host with a leading `www.` folded away — the key that answers "is this
|
|
19
|
+
* the same site?".
|
|
20
|
+
*
|
|
21
|
+
* Comparing raw hosts makes an apex `baseUrl` and a `www.` URL (or the `www.`→apex
|
|
22
|
+
* 301 that is the single most common way the two differ in the wild) look like two
|
|
23
|
+
* different applications. `discovery/safety.ts` builds its crawl-permission key on
|
|
24
|
+
* top of this; page naming uses the host alone, because `http` vs `https` on one
|
|
25
|
+
* host is never two applications — and renaming every contract over a protocol
|
|
26
|
+
* upgrade would be the phantom drift this file exists to prevent.
|
|
27
|
+
*/
|
|
28
|
+
export declare function foldHost(u: URL, strictHost?: boolean): string;
|
|
29
|
+
/**
|
|
30
|
+
* Slug a URL into a page name. `baseUrl` is optional only because the discovery
|
|
31
|
+
* paths already filter to one host before they get here; every capture path passes
|
|
32
|
+
* it, and without it two hosts sharing a pathname collide into one contract.
|
|
33
|
+
*/
|
|
34
|
+
export declare function pageNameFromUrl(url: string, baseUrl?: string): string;
|
|
35
|
+
/**
|
|
36
|
+
* The key a capture buffers a page under while a test run is in flight.
|
|
37
|
+
*
|
|
38
|
+
* The resolved name *and* the host it was seen on. With a config the two are
|
|
39
|
+
* already one-to-one — the name carries the host — so this is a no-op there. Without
|
|
40
|
+
* one, `resolvePageName` has no `baseUrl` to namespace against, and keying by name
|
|
41
|
+
* alone would union two applications' `/login` inside a single worker, where nothing
|
|
42
|
+
* downstream could ever see that it happened. Keeping them apart costs nothing and
|
|
43
|
+
* lets the merge say so out loud (`captureMerge.ts`).
|
|
9
44
|
*/
|
|
10
|
-
export declare function
|
|
45
|
+
export declare function captureKey(name: string, url: string): string;
|
|
11
46
|
export type PageNameResolution =
|
|
12
47
|
/** The URL is a configured page — use the name its contract already has. */
|
|
13
48
|
{
|
package/dist/pageName.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.foldHost = foldHost;
|
|
3
4
|
exports.pageNameFromUrl = pageNameFromUrl;
|
|
5
|
+
exports.captureKey = captureKey;
|
|
4
6
|
exports.resolvePageName = resolvePageName;
|
|
5
7
|
/**
|
|
6
8
|
* URL → logical page name, the one resolver every capture path must share.
|
|
@@ -9,30 +11,108 @@ exports.resolvePageName = resolvePageName;
|
|
|
9
11
|
* same page differently produce contracts that never meet: every page becomes a
|
|
10
12
|
* phantom lost/new pair and the diff is garbage. The healer, `map`, and run-time
|
|
11
13
|
* capture all funnel through here for exactly that reason.
|
|
14
|
+
*
|
|
15
|
+
* A page name is a **filename**, so it is also an identity claim: two URLs that
|
|
16
|
+
* resolve to the same name are asserting they are the same page. A pathname alone
|
|
17
|
+
* cannot carry that claim across hosts — five products with a `/login` are five
|
|
18
|
+
* different pages, and naming them all `login` made whichever the suite happened
|
|
19
|
+
* to visit last overwrite the others. The host is therefore part of the name
|
|
20
|
+
* whenever it is not the app's own (see `hostPrefix`).
|
|
12
21
|
*/
|
|
13
|
-
|
|
14
|
-
|
|
22
|
+
/**
|
|
23
|
+
* A URL's host with a leading `www.` folded away — the key that answers "is this
|
|
24
|
+
* the same site?".
|
|
25
|
+
*
|
|
26
|
+
* Comparing raw hosts makes an apex `baseUrl` and a `www.` URL (or the `www.`→apex
|
|
27
|
+
* 301 that is the single most common way the two differ in the wild) look like two
|
|
28
|
+
* different applications. `discovery/safety.ts` builds its crawl-permission key on
|
|
29
|
+
* top of this; page naming uses the host alone, because `http` vs `https` on one
|
|
30
|
+
* host is never two applications — and renaming every contract over a protocol
|
|
31
|
+
* upgrade would be the phantom drift this file exists to prevent.
|
|
32
|
+
*/
|
|
33
|
+
function foldHost(u, strictHost = false) {
|
|
34
|
+
return strictHost ? u.host : u.host.replace(/^www\./i, '');
|
|
35
|
+
}
|
|
36
|
+
function parseUrl(url) {
|
|
15
37
|
try {
|
|
16
|
-
|
|
38
|
+
return new URL(url);
|
|
17
39
|
}
|
|
18
40
|
catch {
|
|
19
|
-
|
|
41
|
+
return null;
|
|
20
42
|
}
|
|
21
|
-
|
|
22
|
-
|
|
43
|
+
}
|
|
44
|
+
/** Trailing slashes and query strings do not make another page. */
|
|
45
|
+
function normalizePath(pathname) {
|
|
46
|
+
return pathname.replace(/\/+$/, '') || '/';
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The `host--` part of a name, or '' when the URL belongs to the app itself.
|
|
50
|
+
*
|
|
51
|
+
* Empty for every single-host suite, which is what keeps existing contracts
|
|
52
|
+
* byte-identical: the prefix only appears where a collision was previously
|
|
53
|
+
* possible. Sanitized to what `mappingPath` allows, so the name a diff pairs on
|
|
54
|
+
* and the filename it lands in stay the same string.
|
|
55
|
+
*/
|
|
56
|
+
function hostPrefix(here, baseUrl) {
|
|
57
|
+
if (!baseUrl)
|
|
58
|
+
return '';
|
|
59
|
+
const base = parseUrl(baseUrl);
|
|
60
|
+
if (!base || foldHost(base) === foldHost(here))
|
|
61
|
+
return '';
|
|
62
|
+
return here.host.replace(/[^a-zA-Z0-9.-]/g, '-') + '--';
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Slug a URL into a page name. `baseUrl` is optional only because the discovery
|
|
66
|
+
* paths already filter to one host before they get here; every capture path passes
|
|
67
|
+
* it, and without it two hosts sharing a pathname collide into one contract.
|
|
68
|
+
*/
|
|
69
|
+
function pageNameFromUrl(url, baseUrl) {
|
|
70
|
+
const parsed = parseUrl(url);
|
|
71
|
+
const pathname = parsed ? parsed.pathname : '/';
|
|
72
|
+
const slug = pathname.replace(/^\/+|\/+$/g, '').replace(/\//g, '-') || 'home';
|
|
73
|
+
return parsed ? hostPrefix(parsed, baseUrl) + slug : slug;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* The key a capture buffers a page under while a test run is in flight.
|
|
77
|
+
*
|
|
78
|
+
* The resolved name *and* the host it was seen on. With a config the two are
|
|
79
|
+
* already one-to-one — the name carries the host — so this is a no-op there. Without
|
|
80
|
+
* one, `resolvePageName` has no `baseUrl` to namespace against, and keying by name
|
|
81
|
+
* alone would union two applications' `/login` inside a single worker, where nothing
|
|
82
|
+
* downstream could ever see that it happened. Keeping them apart costs nothing and
|
|
83
|
+
* lets the merge say so out loud (`captureMerge.ts`).
|
|
84
|
+
*/
|
|
85
|
+
function captureKey(name, url) {
|
|
86
|
+
const parsed = parseUrl(url);
|
|
87
|
+
return parsed ? `${name}\u0000${foldHost(parsed)}` : name;
|
|
23
88
|
}
|
|
24
89
|
function resolvePageName(url, config) {
|
|
25
90
|
if (!config)
|
|
26
91
|
return { kind: 'unconfigured', name: pageNameFromUrl(url) };
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
92
|
+
const here = parseUrl(url);
|
|
93
|
+
if (!here)
|
|
94
|
+
return { kind: 'unconfigured', name: pageNameFromUrl(url) };
|
|
95
|
+
const currentPath = normalizePath(here.pathname);
|
|
96
|
+
// Host *and* path: a configured `/login` names this app's login page, never the
|
|
97
|
+
// one on the partner portal the suite also drives. A target declared with an
|
|
98
|
+
// absolute URL on another host is how a multi-app suite names those explicitly.
|
|
99
|
+
const sameTarget = (p) => {
|
|
100
|
+
let target;
|
|
101
|
+
try {
|
|
102
|
+
target = new URL(p.url, config.baseUrl);
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
return foldHost(target) === foldHost(here) && normalizePath(target.pathname) === currentPath;
|
|
108
|
+
};
|
|
109
|
+
const match = config.pages.find((p) => !p.steps?.length && sameTarget(p));
|
|
30
110
|
if (match)
|
|
31
111
|
return { kind: 'configured', name: match.name };
|
|
32
|
-
const behindSteps = config.pages.filter((p) => p.steps?.length &&
|
|
112
|
+
const behindSteps = config.pages.filter((p) => p.steps?.length && sameTarget(p));
|
|
33
113
|
if (behindSteps.length > 0) {
|
|
34
114
|
return { kind: 'steps', candidates: behindSteps.map((p) => p.name) };
|
|
35
115
|
}
|
|
36
|
-
return { kind: 'unconfigured', name: pageNameFromUrl(url) };
|
|
116
|
+
return { kind: 'unconfigured', name: pageNameFromUrl(url, config.baseUrl) };
|
|
37
117
|
}
|
|
38
118
|
//# sourceMappingURL=pageName.js.map
|
package/dist/pageName.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pageName.js","sourceRoot":"","sources":["../src/pageName.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"pageName.js","sourceRoot":"","sources":["../src/pageName.ts"],"names":[],"mappings":"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"}
|
|
@@ -91,8 +91,10 @@ async function capturePage(page) {
|
|
|
91
91
|
const elements = await (0, aom_1.extractInteractiveElements)(page);
|
|
92
92
|
if (elements.length === 0)
|
|
93
93
|
return;
|
|
94
|
-
const
|
|
95
|
-
state.pages.
|
|
94
|
+
const key = (0, pageName_1.captureKey)(resolved.name, url);
|
|
95
|
+
const prev = state.pages.get(key);
|
|
96
|
+
state.pages.set(key, {
|
|
97
|
+
page: resolved.name,
|
|
96
98
|
url,
|
|
97
99
|
elements: prev ? (0, captureMerge_1.unionElements)(prev.elements, elements) : elements,
|
|
98
100
|
});
|
|
@@ -108,8 +110,8 @@ function writeShard() {
|
|
|
108
110
|
const dir = (0, captureMerge_1.captureDir)();
|
|
109
111
|
fs.mkdirSync(dir, { recursive: true });
|
|
110
112
|
const worker = process.env.TEST_WORKER_INDEX ?? String(process.pid);
|
|
111
|
-
const pages = Array.from(state.pages, (
|
|
112
|
-
page:
|
|
113
|
+
const pages = Array.from(state.pages.values(), (c) => ({
|
|
114
|
+
page: c.page,
|
|
113
115
|
url: c.url,
|
|
114
116
|
elements: c.elements,
|
|
115
117
|
}));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"capture.js","sourceRoot":"","sources":["../../src/playwright/capture.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BA,wCAEC;
|
|
1
|
+
{"version":3,"file":"capture.js","sourceRoot":"","sources":["../../src/playwright/capture.ts"],"names":[],"mappings":"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"}
|
package/dist/selenium/capture.js
CHANGED
|
@@ -67,6 +67,7 @@ exports.CAPTURE_ENV = 'IAQA_CAPTURE';
|
|
|
67
67
|
function captureEnabled() {
|
|
68
68
|
return process.env[exports.CAPTURE_ENV] === '1';
|
|
69
69
|
}
|
|
70
|
+
/** Keyed by `captureKey()`, not by page name — see there. */
|
|
70
71
|
const pages = new Map();
|
|
71
72
|
let cfg = null;
|
|
72
73
|
let cfgLoaded = false;
|
|
@@ -89,8 +90,8 @@ function writeShard() {
|
|
|
89
90
|
const dir = (0, captureMerge_1.captureDir)();
|
|
90
91
|
fs.mkdirSync(dir, { recursive: true });
|
|
91
92
|
const worker = process.env.TEST_WORKER_INDEX ?? String(process.pid);
|
|
92
|
-
const shardPages = Array.from(pages, (
|
|
93
|
-
page:
|
|
93
|
+
const shardPages = Array.from(pages.values(), (c) => ({
|
|
94
|
+
page: c.page,
|
|
94
95
|
url: c.url,
|
|
95
96
|
elements: c.elements,
|
|
96
97
|
}));
|
|
@@ -125,8 +126,10 @@ async function capture(driver) {
|
|
|
125
126
|
const elements = (await driver.executeScript(extract_1.extractInPage)) || [];
|
|
126
127
|
if (elements.length === 0)
|
|
127
128
|
return;
|
|
128
|
-
const
|
|
129
|
-
pages.
|
|
129
|
+
const key = (0, pageName_1.captureKey)(resolved.name, url);
|
|
130
|
+
const prev = pages.get(key);
|
|
131
|
+
pages.set(key, {
|
|
132
|
+
page: resolved.name,
|
|
130
133
|
url,
|
|
131
134
|
elements: prev ? (0, captureMerge_1.unionElements)(prev.elements, elements) : elements,
|
|
132
135
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"capture.js","sourceRoot":"","sources":["../../src/selenium/capture.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCA,wCAEC;
|
|
1
|
+
{"version":3,"file":"capture.js","sourceRoot":"","sources":["../../src/selenium/capture.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ia-qa/self-healing",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.15",
|
|
4
4
|
"description": "Your Playwright, Cypress or Selenium tests break when a selector moves — this finds the element again and rewrites the test. Deterministic: no LLM decides whether your build passes. Runs entirely on your machine, with a local MCP server for agents.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"self-healing",
|