@jterrazz/test 15.0.0 → 15.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 +1 -1
- package/dist/ambiguity.js +1 -1
- package/dist/appium.adapter.js +33 -24
- package/dist/artifacts.js +503 -525
- package/dist/checker.js +60 -59
- package/dist/index.d.ts +587 -660
- package/dist/index.js +191 -188
- package/dist/match.js +6 -16
- package/dist/oxlint.cjs +513 -514
- package/dist/oxlint.d.cts +7064 -15
- package/dist/oxlint.d.ts +7064 -15
- package/dist/oxlint.js +513 -514
- package/dist/playwright.adapter.js +36 -14
- package/dist/queue.js +27 -28
- package/dist/spec-document.js +19 -25
- package/dist/vitest.d.ts +6 -6
- package/dist/vitest.js +2 -2
- package/package.json +10 -7
package/README.md
CHANGED
|
@@ -296,7 +296,7 @@ Every artefact a run produces lands under `.artifacts/<tool>/` — one `.gitigno
|
|
|
296
296
|
| --------------------------------------- | ------------ | ----------------------------------------------------------------------------------------------------------- |
|
|
297
297
|
| `.seed("file.sql", { database? })` | all | Load SQL from `_seeds/` — `database` is the record key (mandatory with ≥ 2 databases, forbidden with 1) |
|
|
298
298
|
| `.fixture("file")` | cli | Copy the feature-local `_fixtures/file` into the working directory |
|
|
299
|
-
| `.fixture("$FIXTURES/name/")` | cli | Spread the shared `specs/_fixtures
|
|
299
|
+
| `.fixture("$FIXTURES/name/")` | cli | Spread the shared `specs/_fixtures/<name>/` project into the cwd (trailing `/` = contents; layers) |
|
|
300
300
|
| `.env({ KEY: "value" })` | cli | Set env vars on the child (`null` unsets, `$WORKDIR` expands, calls merge) |
|
|
301
301
|
| `.headers({ "Accept-Language": "fr" })` | api, website | Set HTTP request headers (merge on top of `.http` file headers, or on the browser context) |
|
|
302
302
|
| `.intercept(contracts)` | all but cli | Declare the world: a `defineContracts(...)` composite — MSW on api/jobs, the stub backend on website/mobile |
|
package/dist/ambiguity.js
CHANGED
package/dist/appium.adapter.js
CHANGED
|
@@ -2,7 +2,7 @@ import { r as formatElement, t as AmbiguousElementError } from "./ambiguity.js";
|
|
|
2
2
|
import { mkdtempSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { resolve } from "node:path";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
|
-
//#region src/
|
|
5
|
+
//#region src/specification/facets/mobile/ambiguity.ts
|
|
6
6
|
/**
|
|
7
7
|
* The ambiguity refusal, mobile edition — CONVENTIONS W3.
|
|
8
8
|
*
|
|
@@ -75,9 +75,9 @@ function describeMobileAmbiguity(options) {
|
|
|
75
75
|
].join("\n");
|
|
76
76
|
}
|
|
77
77
|
//#endregion
|
|
78
|
-
//#region src/
|
|
79
|
-
const TAG_PATTERN = /<(?<openTag>[A-Za-z][\w.]*)(?<rawAttributes>(?:\s+[\w:.-]+="[^"]*")*)\s*(?<selfClosing>\/?)>|<\/(?<closeTag>[\w.]*)\s*>/
|
|
80
|
-
const ATTRIBUTE_PATTERN = /(?<key>[\w:.-]+)="(?<value>[^"]*)"/
|
|
78
|
+
//#region src/specification/facets/mobile/projection.ts
|
|
79
|
+
const TAG_PATTERN = /<(?<openTag>[A-Za-z][\w.]*)(?<rawAttributes>(?:\s+[\w:.-]+="[^"]*")*)\s*(?<selfClosing>\/?)>|<\/(?<closeTag>[\w.]*)\s*>/gu;
|
|
80
|
+
const ATTRIBUTE_PATTERN = /(?<key>[\w:.-]+)="(?<value>[^"]*)"/gu;
|
|
81
81
|
const NAMED_ENTITIES = {
|
|
82
82
|
"&": "&",
|
|
83
83
|
"'": "'",
|
|
@@ -87,7 +87,7 @@ const NAMED_ENTITIES = {
|
|
|
87
87
|
};
|
|
88
88
|
/** Decode the XML entities an attribute value can carry. */
|
|
89
89
|
function decodeEntities(value) {
|
|
90
|
-
return value.
|
|
90
|
+
return value.replaceAll(/&(?:#x(?<hex>[\dA-Fa-f]+)|#(?<decimal>\d+)|(?<named>amp|apos|gt|lt|quot));/gu, (entity, hex, decimal) => {
|
|
91
91
|
if (hex) return String.fromCodePoint(Number.parseInt(hex, 16));
|
|
92
92
|
if (decimal) return String.fromCodePoint(Number(decimal));
|
|
93
93
|
return NAMED_ENTITIES[entity] ?? entity;
|
|
@@ -113,8 +113,9 @@ function parsePageSource(xml) {
|
|
|
113
113
|
children: [],
|
|
114
114
|
tag: openTag
|
|
115
115
|
};
|
|
116
|
-
|
|
117
|
-
|
|
116
|
+
const parent = stack.at(-1);
|
|
117
|
+
if (parent === void 0) root = node;
|
|
118
|
+
else parent.children.push(node);
|
|
118
119
|
if (!selfClosing) stack.push(node);
|
|
119
120
|
}
|
|
120
121
|
if (!root) throw new Error("projectScreen(): the page source contains no XML element");
|
|
@@ -122,11 +123,11 @@ function parsePageSource(xml) {
|
|
|
122
123
|
}
|
|
123
124
|
/** The substance of a node — what survives projection, or nothing. */
|
|
124
125
|
function substance(node) {
|
|
125
|
-
const label = node.attributes
|
|
126
|
-
const value = node.attributes
|
|
127
|
-
const identifier = node.attributes
|
|
126
|
+
const label = node.attributes.label || void 0;
|
|
127
|
+
const value = node.attributes.value || void 0;
|
|
128
|
+
const identifier = node.attributes.name || void 0;
|
|
128
129
|
return {
|
|
129
|
-
type: node.tag.replace(/^XCUIElementType
|
|
130
|
+
type: node.tag.replace(/^XCUIElementType/u, ""),
|
|
130
131
|
...label === void 0 ? {} : { label },
|
|
131
132
|
...value === void 0 || value === label ? {} : { value },
|
|
132
133
|
...identifier === void 0 || identifier === label ? {} : { identifier }
|
|
@@ -151,9 +152,9 @@ function projectNode(node) {
|
|
|
151
152
|
}
|
|
152
153
|
/** Collect the visible texts in document order, collapsing consecutive repeats. */
|
|
153
154
|
function collectTexts(node, texts) {
|
|
154
|
-
if (node.attributes
|
|
155
|
-
const text = node.attributes
|
|
156
|
-
if (text !== void 0 && texts
|
|
155
|
+
if (node.attributes.visible === "true") {
|
|
156
|
+
const text = node.attributes.label || node.attributes.value || void 0;
|
|
157
|
+
if (text !== void 0 && texts.at(-1) !== text) texts.push(text);
|
|
157
158
|
}
|
|
158
159
|
for (const child of node.children) collectTexts(child, texts);
|
|
159
160
|
}
|
|
@@ -164,7 +165,8 @@ function collectTexts(node, texts) {
|
|
|
164
165
|
*/
|
|
165
166
|
function projectScreen(xml) {
|
|
166
167
|
let root = parsePageSource(xml);
|
|
167
|
-
|
|
168
|
+
const [onlyChild] = root.children;
|
|
169
|
+
if (root.tag === "AppiumAUT" && root.children.length === 1 && onlyChild !== void 0) root = onlyChild;
|
|
168
170
|
const texts = [];
|
|
169
171
|
for (const child of root.children) collectTexts(child, texts);
|
|
170
172
|
const own = substance(root);
|
|
@@ -185,7 +187,9 @@ const POLL_INTERVAL_MS = 500;
|
|
|
185
187
|
const MAX_REPORTED_MATCHES = 10;
|
|
186
188
|
/** WebDriverAgent builds once per simulator — the first session is the slow one. */
|
|
187
189
|
const DEFAULT_WDA_LAUNCH_TIMEOUT_MS = 24e4;
|
|
188
|
-
const delay = (ms) =>
|
|
190
|
+
const delay = async (ms) => {
|
|
191
|
+
await new Promise((resolveDelay) => setTimeout(resolveDelay, ms));
|
|
192
|
+
};
|
|
189
193
|
/** Escape a name for an NSPredicate single-quoted string literal. */
|
|
190
194
|
function escapePredicate(value) {
|
|
191
195
|
return value.replaceAll("\\", String.raw`\\`).replaceAll("'", String.raw`\'`);
|
|
@@ -234,9 +238,9 @@ async function captureMatch(element) {
|
|
|
234
238
|
element.getAttribute("value"),
|
|
235
239
|
element.getAttribute("name")
|
|
236
240
|
]);
|
|
237
|
-
const shortLabel = label ? label.replaceAll(/\s+/
|
|
241
|
+
const shortLabel = label ? label.replaceAll(/\s+/gu, " ").trim().slice(0, 80) : void 0;
|
|
238
242
|
return {
|
|
239
|
-
type: (type ?? "Unknown").replace(/^XCUIElementType
|
|
243
|
+
type: (type ?? "Unknown").replace(/^XCUIElementType/u, ""),
|
|
240
244
|
...shortLabel === void 0 ? {} : { label: shortLabel },
|
|
241
245
|
...value && value !== label ? { value } : {},
|
|
242
246
|
...identifier && identifier !== label ? { identifier } : {}
|
|
@@ -353,10 +357,10 @@ var AppiumAdapter = class {
|
|
|
353
357
|
const count = await matches.length;
|
|
354
358
|
if (count === 0) {
|
|
355
359
|
if (options?.tryScroll) {
|
|
356
|
-
const
|
|
357
|
-
if (
|
|
360
|
+
const first = (await scope.$$(`-ios predicate string:${compilePredicate(level, { anyVisibility: true })}`))[0];
|
|
361
|
+
if (first !== void 0) try {
|
|
358
362
|
await driver.executeScript("mobile: scroll", [{
|
|
359
|
-
elementId:
|
|
363
|
+
elementId: first.elementId,
|
|
360
364
|
toVisible: true
|
|
361
365
|
}]);
|
|
362
366
|
} catch {}
|
|
@@ -368,7 +372,9 @@ var AppiumAdapter = class {
|
|
|
368
372
|
element: level,
|
|
369
373
|
matches: await this.captureMatches(matches)
|
|
370
374
|
}));
|
|
371
|
-
|
|
375
|
+
const next = matches[0];
|
|
376
|
+
if (next === void 0) return null;
|
|
377
|
+
scope = next;
|
|
372
378
|
}
|
|
373
379
|
return scope;
|
|
374
380
|
}
|
|
@@ -376,7 +382,10 @@ var AppiumAdapter = class {
|
|
|
376
382
|
async captureMatches(matches) {
|
|
377
383
|
const count = Math.min(await matches.length, MAX_REPORTED_MATCHES);
|
|
378
384
|
const evidence = [];
|
|
379
|
-
for (let index = 0; index < count; index++)
|
|
385
|
+
for (let index = 0; index < count; index++) {
|
|
386
|
+
const match = matches[index];
|
|
387
|
+
if (match !== void 0) evidence.push(await captureMatch(match));
|
|
388
|
+
}
|
|
380
389
|
return evidence;
|
|
381
390
|
}
|
|
382
391
|
/** Screenshot the failing state into a temp file; never masks the original error. */
|
|
@@ -394,7 +403,7 @@ var AppiumAdapter = class {
|
|
|
394
403
|
let excerpt = "";
|
|
395
404
|
try {
|
|
396
405
|
const source = await driver.getPageSource();
|
|
397
|
-
const labels = [...new Set([...source.matchAll(/label="(?<label>[^"]{2,60})"/
|
|
406
|
+
const labels = [...new Set([...source.matchAll(/label="(?<label>[^"]{2,60})"/gu)].map((match) => match.groups?.label ?? ""))].slice(0, 15);
|
|
398
407
|
if (labels.length > 0) excerpt = `\nCurrently on screen: ${labels.join(" | ")}`;
|
|
399
408
|
} catch {}
|
|
400
409
|
return /* @__PURE__ */ new Error(`${verb}(${formatElement(element)}) timed out after ${this.actionTimeoutMs}ms — no visible match.${excerpt}`);
|