@jterrazz/test 10.0.0 → 11.0.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 +101 -35
- package/dist/ambiguity.js +126 -0
- package/dist/appium.adapter.js +399 -0
- package/dist/checker.js +0 -1
- package/dist/index.d.ts +500 -101
- package/dist/index.js +587 -464
- package/dist/intercept.js +37 -84
- package/dist/match.js +12 -1
- package/dist/oxlint.cjs +420 -117
- package/dist/oxlint.d.cts +1 -1
- package/dist/oxlint.d.ts +1 -1
- package/dist/oxlint.js +420 -117
- package/dist/playwright.adapter.js +3 -127
- package/dist/queue.js +555 -0
- package/package.json +13 -4
|
@@ -1,131 +1,7 @@
|
|
|
1
|
+
import { n as describeAmbiguity, t as AmbiguousElementError } from "./ambiguity.js";
|
|
1
2
|
import { mkdtempSync } from "node:fs";
|
|
2
3
|
import { resolve } from "node:path";
|
|
3
4
|
import { tmpdir } from "node:os";
|
|
4
|
-
//#region src/core/specification/website/ambiguity.ts
|
|
5
|
-
/**
|
|
6
|
-
* The ambiguity refusal — CONVENTIONS W3.
|
|
7
|
-
*
|
|
8
|
-
* A visit descriptor must designate exactly one element. Acting on "the
|
|
9
|
-
* first match" is the failure mode this module exists to prevent: the spec
|
|
10
|
-
* keeps passing while the visitor clicks something else, and nothing ever
|
|
11
|
-
* reports it. So when a descriptor matches several elements the framework
|
|
12
|
-
* refuses, and the refusal has to carry everything needed to fix it without
|
|
13
|
-
* opening a browser — the descriptor in the caller's own vocabulary, every
|
|
14
|
-
* candidate, and the concrete rewrites that would resolve it.
|
|
15
|
-
*
|
|
16
|
-
* Pure string building: it takes captured data and returns a message, so the
|
|
17
|
-
* wording is unit-testable and the browser integration stays a thin adapter.
|
|
18
|
-
*/
|
|
19
|
-
/** `kind` → the constructor that builds it, so errors echo the caller's source. */
|
|
20
|
-
const CONSTRUCTORS = {
|
|
21
|
-
banner: "banner",
|
|
22
|
-
button: "button",
|
|
23
|
-
complementary: "complementary",
|
|
24
|
-
contentinfo: "contentinfo",
|
|
25
|
-
field: "field",
|
|
26
|
-
form: "form",
|
|
27
|
-
heading: "heading",
|
|
28
|
-
link: "link",
|
|
29
|
-
main: "main",
|
|
30
|
-
navigation: "navigation",
|
|
31
|
-
region: "region",
|
|
32
|
-
search: "search",
|
|
33
|
-
testId: "testId",
|
|
34
|
-
text: "content"
|
|
35
|
-
};
|
|
36
|
-
/** The landmark a context string maps back to, for a copy-pasteable suggestion. */
|
|
37
|
-
const CONTEXT_LANDMARKS = {
|
|
38
|
-
aside: "complementary()",
|
|
39
|
-
banner: "banner()",
|
|
40
|
-
complementary: "complementary()",
|
|
41
|
-
contentinfo: "contentinfo()",
|
|
42
|
-
footer: "contentinfo()",
|
|
43
|
-
form: "form()",
|
|
44
|
-
header: "banner()",
|
|
45
|
-
main: "main()",
|
|
46
|
-
nav: "navigation()",
|
|
47
|
-
navigation: "navigation()",
|
|
48
|
-
search: "search()"
|
|
49
|
-
};
|
|
50
|
-
/**
|
|
51
|
-
* Render a descriptor as the source that would build it — `link('Articles')`,
|
|
52
|
-
* `within(navigation(), link('Articles', { exact: true }))`. The error speaks
|
|
53
|
-
* the vocabulary the author wrote, never playwright's.
|
|
54
|
-
*/
|
|
55
|
-
function formatElement(element) {
|
|
56
|
-
const bare = formatBare(element);
|
|
57
|
-
return element.scope ? `within(${formatElement(element.scope)}, ${bare})` : bare;
|
|
58
|
-
}
|
|
59
|
-
function formatBare(element) {
|
|
60
|
-
const args = [];
|
|
61
|
-
if (element.name !== void 0) args.push(JSON.stringify(element.name));
|
|
62
|
-
if (element.exact) args.push("{ exact: true }");
|
|
63
|
-
return `${CONSTRUCTORS[element.kind]}(${args.join(", ")})`;
|
|
64
|
-
}
|
|
65
|
-
/** `1. <a href="/articles">Articles</a> in <nav>` — one evidence line per candidate. */
|
|
66
|
-
function formatMatch(match, index) {
|
|
67
|
-
const attribute = match.detail ? ` ${quoteDetail(match)}` : "";
|
|
68
|
-
const context = match.context ? ` in <${match.context}>` : "";
|
|
69
|
-
return ` ${index + 1}. <${match.tag}${attribute}>${match.text}</${match.tag}>${context}`;
|
|
70
|
-
}
|
|
71
|
-
function quoteDetail(match) {
|
|
72
|
-
return `${match.tag === "a" ? "href" : "name"}=${JSON.stringify(match.detail)}`;
|
|
73
|
-
}
|
|
74
|
-
/**
|
|
75
|
-
* The rewrites worth offering, in the order they should be tried. Scoping is
|
|
76
|
-
* always available; `exact` only when it would actually narrow the set, and
|
|
77
|
-
* the count it would leave is stated so a still-ambiguous suggestion never
|
|
78
|
-
* reads as a fix.
|
|
79
|
-
*/
|
|
80
|
-
function formatFixes(element, matches) {
|
|
81
|
-
const fixes = [];
|
|
82
|
-
const landmarks = [...new Set(matches.map((match) => match.context && CONTEXT_LANDMARKS[match.context]).filter((landmark) => Boolean(landmark)))];
|
|
83
|
-
if (landmarks.length > 0 && !element.scope) fixes.push(`scope it within(${landmarks[0]}, ${formatBare(element)})${landmarks.length > 1 ? ` [also here: ${landmarks.slice(1).join(", ")}]` : ""}`);
|
|
84
|
-
else if (!element.scope) fixes.push(`scope it within(main(), ${formatBare(element)})`);
|
|
85
|
-
if (!element.exact && element.name !== void 0) {
|
|
86
|
-
const remaining = matches.filter((match) => match.text === element.name).length;
|
|
87
|
-
if (remaining > 0 && remaining < matches.length) fixes.push(`exact name ${formatBare({
|
|
88
|
-
...element,
|
|
89
|
-
exact: true
|
|
90
|
-
})} [leaves ${remaining} of ${matches.length}]`);
|
|
91
|
-
}
|
|
92
|
-
fixes.push("other element a heading(), button() or field() may name one thing where this does not");
|
|
93
|
-
return fixes;
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* The W3 refusal. A distinct class so the visit wrapper can recognize an
|
|
97
|
-
* already-complete message and let it through instead of nesting it inside
|
|
98
|
-
* `visit scenario failed: …`, which would print the whole thing twice.
|
|
99
|
-
*/
|
|
100
|
-
var AmbiguousElementError = class extends Error {
|
|
101
|
-
name = "AmbiguousElementError";
|
|
102
|
-
};
|
|
103
|
-
/**
|
|
104
|
-
* Build the refusal thrown when a descriptor matches more than one element.
|
|
105
|
-
*
|
|
106
|
-
* @param options.element The descriptor as the caller wrote it.
|
|
107
|
-
* @param options.matches Every candidate, in DOM order (already truncated).
|
|
108
|
-
* @param options.url The page the visitor was on when it happened.
|
|
109
|
-
*/
|
|
110
|
-
function describeAmbiguity(options) {
|
|
111
|
-
const { element, matches, url } = options;
|
|
112
|
-
const fixes = formatFixes(element, matches).map((fix) => ` • ${fix}`);
|
|
113
|
-
return [
|
|
114
|
-
`Ambiguous element: ${formatElement(element)} matched ${matches.length} elements on ${url}.`,
|
|
115
|
-
"",
|
|
116
|
-
"A spec must designate exactly one element. Acting on the first match would let",
|
|
117
|
-
"this test keep passing while the visitor interacts with something else.",
|
|
118
|
-
"",
|
|
119
|
-
"Matched:",
|
|
120
|
-
...matches.map((match, index) => formatMatch(match, index)),
|
|
121
|
-
"",
|
|
122
|
-
"Disambiguate with one of:",
|
|
123
|
-
...fixes,
|
|
124
|
-
"",
|
|
125
|
-
"Docs: docs/11-website.md#designating-exactly-one-element (CONVENTIONS W3)"
|
|
126
|
-
].join("\n");
|
|
127
|
-
}
|
|
128
|
-
//#endregion
|
|
129
5
|
//#region src/integrations/playwright/playwright.adapter.ts
|
|
130
6
|
/** How many candidates the ambiguity error enumerates before truncating. */
|
|
131
7
|
const MAX_REPORTED_MATCHES = 10;
|
|
@@ -245,9 +121,9 @@ var PlaywrightAdapter = class {
|
|
|
245
121
|
async open(url, options) {
|
|
246
122
|
const context = await (await this.launch()).newContext({ extraHTTPHeaders: options.headers });
|
|
247
123
|
if (options.external === "block") {
|
|
248
|
-
const
|
|
124
|
+
const allowed = /* @__PURE__ */ new Set([new URL(options.baseUrl).origin, ...options.allowedOrigins ?? []]);
|
|
249
125
|
await context.route("**/*", (route) => {
|
|
250
|
-
if (new URL(route.request().url()).origin
|
|
126
|
+
if (allowed.has(new URL(route.request().url()).origin)) route.continue();
|
|
251
127
|
else route.abort();
|
|
252
128
|
});
|
|
253
129
|
}
|