@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 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/name/` project into the cwd (trailing `/` = contents; layers) |
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
@@ -1,4 +1,4 @@
1
- //#region src/core/specification/website/ambiguity.ts
1
+ //#region src/specification/facets/website/ambiguity.ts
2
2
  /**
3
3
  * The ambiguity refusal — CONVENTIONS W3.
4
4
  *
@@ -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/core/specification/mobile/ambiguity.ts
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/core/specification/mobile/projection.ts
79
- const TAG_PATTERN = /<(?<openTag>[A-Za-z][\w.]*)(?<rawAttributes>(?:\s+[\w:.-]+="[^"]*")*)\s*(?<selfClosing>\/?)>|<\/(?<closeTag>[\w.]*)\s*>/g;
80
- const ATTRIBUTE_PATTERN = /(?<key>[\w:.-]+)="(?<value>[^"]*)"/g;
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
  "&amp;": "&",
83
83
  "&apos;": "'",
@@ -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.replace(/&(?:#x(?<hex>[\dA-Fa-f]+)|#(?<decimal>\d+)|(?<named>amp|apos|gt|lt|quot));/g, (entity, hex, decimal) => {
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
- if (stack.length === 0) root = node;
117
- else stack[stack.length - 1].children.push(node);
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["label"] || void 0;
126
- const value = node.attributes["value"] || void 0;
127
- const identifier = node.attributes["name"] || void 0;
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["visible"] === "true") {
155
- const text = node.attributes["label"] || node.attributes["value"] || void 0;
156
- if (text !== void 0 && texts[texts.length - 1] !== text) texts.push(text);
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
- if (root.tag === "AppiumAUT" && root.children.length === 1) root = root.children[0];
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) => new Promise((resolveDelay) => setTimeout(resolveDelay, 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+/g, " ").trim().slice(0, 80) : void 0;
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 offscreen = await scope.$$(`-ios predicate string:${compilePredicate(level, { anyVisibility: true })}`);
357
- if (await offscreen.length > 0) try {
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: offscreen[0].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
- scope = matches[0];
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++) evidence.push(await captureMatch(matches[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})"/g)].map((match) => match.groups?.["label"] ?? ""))].slice(0, 15);
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}`);