@delacour/warden-e2e 0.13.0 → 0.15.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/{chunk-tnr47n9c.mjs → chunk-e3cry5br.mjs} +828 -255
- package/index.mjs +144 -14
- package/package.json +2 -2
- package/preload.mjs +375 -63
- package/types/core/src/affected/affected.schema.d.ts +5 -0
- package/types/core/src/builds/builds.defaults.d.ts +8 -0
- package/types/core/src/builds/config.schema.d.ts +9 -0
- package/types/core/src/builds/define-config.d.ts +9 -1
- package/types/core/src/conditions/conditions.config.d.ts +6 -1
- package/types/core/src/conditions/conditions.types.d.ts +21 -0
- package/types/core/src/conditions/inject-state.d.ts +3 -2
- package/types/e2e/src/a11y/a11y-target.d.ts +2 -0
- package/types/e2e/src/conditions/condition-events.d.ts +6 -1
- package/types/e2e/src/conditions/device-conditions.d.ts +8 -2
- package/types/e2e/src/device.d.ts +10 -2
- package/types/e2e/src/driver/android/android-settings.d.ts +9 -2
- package/types/e2e/src/driver/ax-wedge.d.ts +38 -0
- package/types/e2e/src/driver/bridge-stall.d.ts +41 -0
- package/types/e2e/src/driver/driver.types.d.ts +35 -2
- package/types/e2e/src/driver/ios/inject.d.ts +17 -2
- package/types/e2e/src/driver/ios/ios-driver.d.ts +17 -2
- package/types/e2e/src/driver/ios/ios-keyboard.d.ts +39 -0
- package/types/e2e/src/driver/ios/simd-client.d.ts +2 -0
- package/types/e2e/src/driver/ios/simd-protocol.d.ts +129 -0
- package/types/e2e/src/driver/keyboard-state.d.ts +9 -0
- package/types/e2e/src/driver/physical-conditions.d.ts +1 -1
- package/types/e2e/src/driver/read-deadline.d.ts +4 -0
- package/types/e2e/src/driver/settle.d.ts +11 -1
- package/types/e2e/src/errors.d.ts +3 -1
- package/types/e2e/src/expect/a11y-matchers.d.ts +7 -0
- package/types/e2e/src/expect/matchers.d.ts +5 -0
- package/types/e2e/src/index.d.ts +2 -1
- package/types/e2e/src/runner/bridge-evidence.d.ts +12 -0
- package/types/e2e/src/runner/results.d.ts +29 -0
- package/types/e2e/src/screen/actions.d.ts +7 -0
- package/types/e2e/src/screen/context.d.ts +11 -12
- package/types/e2e/src/screen/degraded-read.d.ts +23 -0
- package/types/e2e/src/screen/keyboard-cover.d.ts +26 -0
- package/types/e2e/src/screen/locator.d.ts +61 -7
- package/types/e2e/src/screen/react-locator.d.ts +17 -2
- package/types/e2e/src/screen/react-merge.d.ts +10 -0
- package/types/e2e/src/session.d.ts +6 -0
|
@@ -236,6 +236,10 @@ function settleDetailOf(outcome) {
|
|
|
236
236
|
}
|
|
237
237
|
var DEFAULT_SETTLE_QUIET_MS = 250;
|
|
238
238
|
var DEFAULT_SETTLE_TIMEOUT_MS = 3000;
|
|
239
|
+
var HASH_FALLBACK_MIN_QUIET_MS = 500;
|
|
240
|
+
function hashFallbackQuietMs(quietMs) {
|
|
241
|
+
return Math.max(quietMs, HASH_FALLBACK_MIN_QUIET_MS);
|
|
242
|
+
}
|
|
239
243
|
var DEFAULT_POLL_MS = 50;
|
|
240
244
|
async function settleByHash(poll, deps) {
|
|
241
245
|
const { quietMs, timeoutMs, now, sleep } = deps;
|
|
@@ -260,7 +264,7 @@ async function settleByHash(poll, deps) {
|
|
|
260
264
|
return { quiet: false, waitedMs: t - start };
|
|
261
265
|
}
|
|
262
266
|
}
|
|
263
|
-
function
|
|
267
|
+
function elementKey(element) {
|
|
264
268
|
const { x, y, width, height } = element.frame;
|
|
265
269
|
return JSON.stringify([
|
|
266
270
|
element.role,
|
|
@@ -276,7 +280,7 @@ function tuple(element) {
|
|
|
276
280
|
function snapshotHash(snapshot) {
|
|
277
281
|
let text = "";
|
|
278
282
|
for (const element of snapshot.flat)
|
|
279
|
-
text += `${
|
|
283
|
+
text += `${elementKey(element)}
|
|
280
284
|
`;
|
|
281
285
|
return Bun.hash(text).toString(36);
|
|
282
286
|
}
|
|
@@ -430,6 +434,11 @@ function fieldContent(entry) {
|
|
|
430
434
|
const text = fieldValue(entry);
|
|
431
435
|
return text === "" ? { kind: "empty" } : { kind: "text", text };
|
|
432
436
|
}
|
|
437
|
+
function maskedReading(entry, text) {
|
|
438
|
+
const content = fieldContent(entry);
|
|
439
|
+
const count = content.kind === "dots" ? content.count : 0;
|
|
440
|
+
return count === graphemes(text) ? text : "\u2022".repeat(count);
|
|
441
|
+
}
|
|
433
442
|
function clearKeyPresses(entry) {
|
|
434
443
|
const content = fieldContent(entry);
|
|
435
444
|
switch (content.kind) {
|
|
@@ -479,6 +488,34 @@ function assertDegrees(degrees) {
|
|
|
479
488
|
}
|
|
480
489
|
}
|
|
481
490
|
|
|
491
|
+
// ../../packages/e2e/src/screen/degraded-read.ts
|
|
492
|
+
function degradedRead(snapshot) {
|
|
493
|
+
if (snapshot.fellBackFrom !== undefined) {
|
|
494
|
+
return { kind: "fallback", from: snapshot.fellBackFrom, backend: snapshot.backend };
|
|
495
|
+
}
|
|
496
|
+
const [root, ...others] = snapshot.elements;
|
|
497
|
+
if (root !== undefined && others.length === 0 && root.children.length === 0) {
|
|
498
|
+
const { width, height } = root.frame;
|
|
499
|
+
if (width * height === 0)
|
|
500
|
+
return { kind: "emptyRoot", backend: snapshot.backend };
|
|
501
|
+
}
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
function describeDegraded(read) {
|
|
505
|
+
if (read.kind === "emptyRoot")
|
|
506
|
+
return `the ${read.backend} read returned an empty root (backend: ${read.backend})`;
|
|
507
|
+
return `the read fell back from ${read.from} to ${read.backend} (fellBackFrom: ${read.from}, backend: ${read.backend}), which can drop React Native testIDs`;
|
|
508
|
+
}
|
|
509
|
+
var REACT_NOTES = {
|
|
510
|
+
unanswered: "the React tree did not answer",
|
|
511
|
+
"no-devtools": "there is no React tree to ask (no devtools: not a dev client with Metro)",
|
|
512
|
+
"not-testid": "only a plain getByTestId can be asked of the React tree"
|
|
513
|
+
};
|
|
514
|
+
function degradedFailure(locator, read, waitedMs, react, screen) {
|
|
515
|
+
const message = `${locator} could not be confirmed within ${waitedMs} ms: bridge degraded, ${describeDegraded(read)}, ` + `and ${REACT_NOTES[react]}. A missing node on such a read is not proof of absence.`;
|
|
516
|
+
return new E2eError2("READ_DEGRADED", message, screen);
|
|
517
|
+
}
|
|
518
|
+
|
|
482
519
|
// ../../packages/e2e/src/screen/roles.ts
|
|
483
520
|
var QUERY_ROLES = [
|
|
484
521
|
"button",
|
|
@@ -725,7 +762,7 @@ function nonEmpty(value) {
|
|
|
725
762
|
}
|
|
726
763
|
var MASKED_VALUE = /^[\u2022\u25CF]+$/;
|
|
727
764
|
function isSecure(element, traits) {
|
|
728
|
-
return element.role === "textfield" && element.value !== undefined && MASKED_VALUE.test(element.value) || element.role === "securefield" || traits.has("password") || traits.has("secure") || element.rawType.toLowerCase().includes("secure");
|
|
765
|
+
return element.role === "textfield" && element.value !== undefined && MASKED_VALUE.test(element.value) || element.role === "securefield" || traits.has("password") || traits.has("secure") || traits.has("secure-text-field") || element.rawType.toLowerCase().includes("secure");
|
|
729
766
|
}
|
|
730
767
|
function deriveChecked(element, role, spelled, traits, value) {
|
|
731
768
|
if (spelled?.checked !== undefined)
|
|
@@ -984,6 +1021,60 @@ async function typeOver(driver, sleep, prepared, text, via) {
|
|
|
984
1021
|
return typeText(driver, sleep, text);
|
|
985
1022
|
}
|
|
986
1023
|
|
|
1024
|
+
// ../../packages/e2e/src/driver/keyboard-state.ts
|
|
1025
|
+
function keyboardElement(snapshot) {
|
|
1026
|
+
const { height } = snapshot.screen;
|
|
1027
|
+
const onScreen = snapshot.flat.filter((e) => e.role === "keyboard" && e.hidden !== true && e.frame.width > 0 && e.frame.height > 0 && (height <= 0 || e.frame.y < height && e.frame.y + e.frame.height > 0));
|
|
1028
|
+
return onScreen.sort((a, b) => b.frame.width * b.frame.height - a.frame.width * a.frame.height)[0];
|
|
1029
|
+
}
|
|
1030
|
+
function keyboardVisible(snapshot) {
|
|
1031
|
+
return keyboardElement(snapshot) !== undefined;
|
|
1032
|
+
}
|
|
1033
|
+
function keyboardFrame(snapshot) {
|
|
1034
|
+
return keyboardElement(snapshot)?.frame;
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
// ../../packages/e2e/src/screen/keyboard-cover.ts
|
|
1038
|
+
var COVER_MARGIN = 40;
|
|
1039
|
+
var SWIPE_INSET = 12;
|
|
1040
|
+
var SCROLL_SWIPE = { durationMs: 350, momentum: false };
|
|
1041
|
+
var SCROLL_ROLES = new Set(["scrollview", "list"]);
|
|
1042
|
+
function scrollContainer(entry) {
|
|
1043
|
+
for (let node = entry.parent;node !== undefined; node = node.parent) {
|
|
1044
|
+
if (SCROLL_ROLES.has(node.element.role))
|
|
1045
|
+
return node;
|
|
1046
|
+
}
|
|
1047
|
+
return;
|
|
1048
|
+
}
|
|
1049
|
+
function coverOf(entry, point, snapshot) {
|
|
1050
|
+
const keyboard = keyboardFrame(snapshot);
|
|
1051
|
+
if (keyboard === undefined || entry.element.role === "keyboard")
|
|
1052
|
+
return;
|
|
1053
|
+
const inside = point.x >= keyboard.x && point.x <= keyboard.x + keyboard.width && point.y >= keyboard.y && point.y <= keyboard.y + keyboard.height;
|
|
1054
|
+
if (!inside)
|
|
1055
|
+
return;
|
|
1056
|
+
const container = scrollContainer(entry);
|
|
1057
|
+
const shift = point.y - (keyboard.y - COVER_MARGIN);
|
|
1058
|
+
if (container === undefined)
|
|
1059
|
+
return { top: keyboard.y, shift };
|
|
1060
|
+
const { frame } = container.element;
|
|
1061
|
+
const bottom = Math.min(frame.y + frame.height, keyboard.y);
|
|
1062
|
+
const free = { x: frame.x, y: frame.y, width: frame.width, height: Math.max(0, bottom - frame.y) };
|
|
1063
|
+
return free.height > 2 * SWIPE_INSET ? { top: keyboard.y, scroller: free, shift } : { top: keyboard.y, shift };
|
|
1064
|
+
}
|
|
1065
|
+
function scrollPath(scroller, shift) {
|
|
1066
|
+
const x = scroller.x + scroller.width / 2;
|
|
1067
|
+
const room = scroller.height - 2 * SWIPE_INSET;
|
|
1068
|
+
const travel = Math.max(1, Math.min(shift, room));
|
|
1069
|
+
const from = scroller.y + scroller.height - SWIPE_INSET;
|
|
1070
|
+
return { from: { x, y: from }, to: { x, y: from - travel } };
|
|
1071
|
+
}
|
|
1072
|
+
var COVER_SCROLL_SWIPE = SCROLL_SWIPE;
|
|
1073
|
+
async function dismissKeyboard(driver) {
|
|
1074
|
+
const done = await driver.dismissKeyboard();
|
|
1075
|
+
return done.success ? { success: true, data: undefined } : done;
|
|
1076
|
+
}
|
|
1077
|
+
|
|
987
1078
|
// ../../packages/e2e/src/screen/selector.ts
|
|
988
1079
|
var VALUE_KEYS = ["id", "label", "text", "role", "value"];
|
|
989
1080
|
var KEY_ORDER = [...VALUE_KEYS, "editable", "nth"];
|
|
@@ -1534,7 +1625,7 @@ function describeExpression(expr) {
|
|
|
1534
1625
|
}
|
|
1535
1626
|
|
|
1536
1627
|
// ../../packages/e2e/src/screen/react-locator.ts
|
|
1537
|
-
async function
|
|
1628
|
+
async function lookUpTestIdViaReact(facet, testId, view, options = {}) {
|
|
1538
1629
|
const treeOptions = {
|
|
1539
1630
|
...view.platform === "android" ? { pixelRatio: view.screen.scale } : {},
|
|
1540
1631
|
...options.screenName === undefined ? {} : { screen: options.screenName },
|
|
@@ -1544,12 +1635,13 @@ async function locateByTestIdViaReact(facet, testId, view, options = {}) {
|
|
|
1544
1635
|
try {
|
|
1545
1636
|
result = await facet.tree(treeOptions);
|
|
1546
1637
|
} catch {
|
|
1547
|
-
return
|
|
1638
|
+
return { answered: false };
|
|
1548
1639
|
}
|
|
1549
1640
|
if (!result.success)
|
|
1550
|
-
return
|
|
1641
|
+
return { answered: false };
|
|
1551
1642
|
const matches = [];
|
|
1552
|
-
|
|
1643
|
+
const { components } = result.data;
|
|
1644
|
+
for (const component of components) {
|
|
1553
1645
|
const { testID, rect } = component;
|
|
1554
1646
|
if (testID === undefined || rect === undefined || !matchesText(testID, testId))
|
|
1555
1647
|
continue;
|
|
@@ -1558,10 +1650,25 @@ async function locateByTestIdViaReact(facet, testId, view, options = {}) {
|
|
|
1558
1650
|
tapPoint: { x: rect.x + rect.width / 2, y: rect.y + rect.height / 2 },
|
|
1559
1651
|
source: "react",
|
|
1560
1652
|
testId: testID,
|
|
1561
|
-
name: component.name
|
|
1653
|
+
name: component.name,
|
|
1654
|
+
...textOf2(component, components)
|
|
1562
1655
|
});
|
|
1563
1656
|
}
|
|
1564
|
-
return matches;
|
|
1657
|
+
return { answered: true, matches };
|
|
1658
|
+
}
|
|
1659
|
+
function textOf2(component, components) {
|
|
1660
|
+
if (component.text !== undefined)
|
|
1661
|
+
return { text: component.text };
|
|
1662
|
+
const inside = new Set([component.id]);
|
|
1663
|
+
const texts = [];
|
|
1664
|
+
for (const other of components) {
|
|
1665
|
+
if (other.parent === null || !inside.has(other.parent))
|
|
1666
|
+
continue;
|
|
1667
|
+
inside.add(other.id);
|
|
1668
|
+
if (other.text !== undefined)
|
|
1669
|
+
texts.push(other.text);
|
|
1670
|
+
}
|
|
1671
|
+
return texts.length === 0 ? {} : { text: texts.join(" ") };
|
|
1565
1672
|
}
|
|
1566
1673
|
function reactTapPoint(match, position) {
|
|
1567
1674
|
if (position === undefined)
|
|
@@ -1569,18 +1676,58 @@ function reactTapPoint(match, position) {
|
|
|
1569
1676
|
return { x: match.frame.x + position.x, y: match.frame.y + position.y };
|
|
1570
1677
|
}
|
|
1571
1678
|
|
|
1679
|
+
// ../../packages/e2e/src/screen/react-merge.ts
|
|
1680
|
+
var EDGE_SLACK = 1.5;
|
|
1681
|
+
function sameRect(a, b) {
|
|
1682
|
+
if (a.width <= 0 || a.height <= 0 || b.width <= 0 || b.height <= 0)
|
|
1683
|
+
return false;
|
|
1684
|
+
return Math.abs(a.x - b.x) <= EDGE_SLACK && Math.abs(a.y - b.y) <= EDGE_SLACK && Math.abs(a.x + a.width - (b.x + b.width)) <= EDGE_SLACK && Math.abs(a.y + a.height - (b.y + b.height)) <= EDGE_SLACK;
|
|
1685
|
+
}
|
|
1686
|
+
function ownText(entry) {
|
|
1687
|
+
return normalizeText(entry.name ?? entry.value ?? "");
|
|
1688
|
+
}
|
|
1689
|
+
function nodeOfReactMatch(match, index) {
|
|
1690
|
+
const candidates = index.entries.filter((entry) => {
|
|
1691
|
+
const id = entry.element.identifier;
|
|
1692
|
+
return (id === undefined || id === match.testId) && sameRect(entry.element.frame, match.frame);
|
|
1693
|
+
});
|
|
1694
|
+
const text = match.text === undefined ? "" : normalizeText(match.text);
|
|
1695
|
+
const worded = text === "" ? [] : candidates.filter((entry) => ownText(entry) === text);
|
|
1696
|
+
const pool = worded.length > 0 ? worded : candidates;
|
|
1697
|
+
let best;
|
|
1698
|
+
for (const entry of pool)
|
|
1699
|
+
if (best === undefined || entry.depth > best.depth)
|
|
1700
|
+
best = entry;
|
|
1701
|
+
return best;
|
|
1702
|
+
}
|
|
1703
|
+
function mergeReactMatches(matches, index) {
|
|
1704
|
+
const entries = [];
|
|
1705
|
+
for (const match of matches) {
|
|
1706
|
+
const entry = nodeOfReactMatch(match, index);
|
|
1707
|
+
if (entry === undefined || entries.includes(entry))
|
|
1708
|
+
return;
|
|
1709
|
+
entries.push(entry);
|
|
1710
|
+
}
|
|
1711
|
+
return entries;
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1572
1714
|
// ../../packages/e2e/src/screen/locator.ts
|
|
1573
1715
|
var WAIT_STATES = ["attached", "detached", "visible", "hidden"];
|
|
1574
1716
|
var DEFAULT_MAX_SWIPES = 10;
|
|
1575
1717
|
var VERIFY_MS = 1000;
|
|
1576
1718
|
var CONFIRM_FLOOR_MS = 500;
|
|
1577
1719
|
var SCROLL_SETTLE = { quietMs: 150, timeoutMs: 1000 };
|
|
1720
|
+
var MAX_COVER_SCROLLS = 3;
|
|
1721
|
+
var QUICK_CHECK_WINDOW_MS = 2000;
|
|
1578
1722
|
function assertOptions(verb, options, allowed) {
|
|
1579
1723
|
const unknown = Object.keys(options).filter((key) => !allowed.includes(key));
|
|
1580
1724
|
if (unknown.length > 0) {
|
|
1581
1725
|
throw new E2eError2("INVALID_ARGUMENT", `${verb} does not take ${unknown.map((key) => `"${key}"`).join(", ")}; it takes ${allowed.join(", ")}`);
|
|
1582
1726
|
}
|
|
1583
1727
|
}
|
|
1728
|
+
function reactVisible(match, observed) {
|
|
1729
|
+
return match.frame.width > 0 && match.frame.height > 0 && isOnScreen(match.tapPoint, observed.snapshot.screen);
|
|
1730
|
+
}
|
|
1584
1731
|
var REACT_ONLY = "exists only in the React tree, not in the accessibility tree, so only tap() and boundingBox() work on it; " + "expose it to accessibility (accessible, accessibilityLabel) to use anything else";
|
|
1585
1732
|
function plainTestId(expr) {
|
|
1586
1733
|
if (expr.kind !== "query" || expr.scope !== undefined || expr.query.kind !== "testId")
|
|
@@ -1665,8 +1812,79 @@ class Locator {
|
|
|
1665
1812
|
const observed = await this.rt.observe(options);
|
|
1666
1813
|
return { entries: resolve(this.expr, observed.index), observed };
|
|
1667
1814
|
}
|
|
1668
|
-
|
|
1669
|
-
|
|
1815
|
+
async resolveIn(observed) {
|
|
1816
|
+
const entries = resolve(this.expr, observed.index);
|
|
1817
|
+
if (entries.length > 0)
|
|
1818
|
+
return { kind: "nodes", entries };
|
|
1819
|
+
const read = degradedRead(observed.snapshot);
|
|
1820
|
+
if (read === undefined)
|
|
1821
|
+
return { kind: "nodes", entries };
|
|
1822
|
+
const testId = plainTestId(this.expr);
|
|
1823
|
+
const lookup = testId === undefined ? undefined : await this.rt.reactLookup(testId, observed);
|
|
1824
|
+
if (lookup === undefined || !lookup.answered)
|
|
1825
|
+
return { kind: "unknown", read };
|
|
1826
|
+
if (lookup.matches.length === 0)
|
|
1827
|
+
return { kind: "nodes", entries };
|
|
1828
|
+
const merged = mergeReactMatches(lookup.matches, observed.index);
|
|
1829
|
+
return merged === undefined ? { kind: "react", matches: lookup.matches } : { kind: "nodes", entries: merged };
|
|
1830
|
+
}
|
|
1831
|
+
reactNote() {
|
|
1832
|
+
if (plainTestId(this.expr) === undefined)
|
|
1833
|
+
return "not-testid";
|
|
1834
|
+
return this.rt.hasReactTree() ? "unanswered" : "no-devtools";
|
|
1835
|
+
}
|
|
1836
|
+
degradedFailure(read, waitedMs, snapshot) {
|
|
1837
|
+
const screen = snapshot === undefined ? undefined : this.rt.excerpt(snapshot);
|
|
1838
|
+
return degradedFailure(this.toString(), read, waitedMs, this.reactNote(), screen);
|
|
1839
|
+
}
|
|
1840
|
+
async conclusive(options = {}) {
|
|
1841
|
+
const first = await this.look(options.reuse === undefined ? {} : { reuse: options.reuse });
|
|
1842
|
+
const settled = await this.settled(first.observed);
|
|
1843
|
+
if (settled !== undefined)
|
|
1844
|
+
return settled;
|
|
1845
|
+
const actionMs = this.rt.actionTimeout();
|
|
1846
|
+
const timeoutMs = options.quick === true ? Math.min(actionMs, QUICK_CHECK_WINDOW_MS) : actionMs;
|
|
1847
|
+
let last;
|
|
1848
|
+
const result = await this.rt.poll(async () => {
|
|
1849
|
+
const observation = await this.rt.tryObserve({ reuse: false });
|
|
1850
|
+
if (!observation.ok) {
|
|
1851
|
+
if (!observation.error.retryable)
|
|
1852
|
+
throw new E2eError2("DRIVER", `snapshot failed: ${observation.error.message}`);
|
|
1853
|
+
return { done: false, state: undefined };
|
|
1854
|
+
}
|
|
1855
|
+
const answer = await this.settled(observation.observed);
|
|
1856
|
+
if (answer !== undefined)
|
|
1857
|
+
return { done: true, value: answer };
|
|
1858
|
+
const read = degradedRead(observation.observed.snapshot);
|
|
1859
|
+
if (read !== undefined)
|
|
1860
|
+
last = { read, snapshot: observation.observed.snapshot };
|
|
1861
|
+
return { done: false, state: undefined };
|
|
1862
|
+
}, timeoutMs);
|
|
1863
|
+
if (result.status === "done")
|
|
1864
|
+
return result.value;
|
|
1865
|
+
const fallback = degradedRead(first.observed.snapshot);
|
|
1866
|
+
const read = last?.read ?? fallback;
|
|
1867
|
+
if (read === undefined)
|
|
1868
|
+
throw new E2eError2("TIMEOUT", `${this} could not be read within ${timeoutMs} ms`);
|
|
1869
|
+
throw this.degradedFailure(read, timeoutMs, last?.snapshot ?? first.observed.snapshot);
|
|
1870
|
+
}
|
|
1871
|
+
async settled(observed) {
|
|
1872
|
+
const resolved = await this.resolveIn(observed);
|
|
1873
|
+
if (resolved.kind === "unknown")
|
|
1874
|
+
return;
|
|
1875
|
+
if (resolved.kind === "react")
|
|
1876
|
+
return { entries: [], react: resolved.matches, observed };
|
|
1877
|
+
return { entries: resolved.entries, react: undefined, observed };
|
|
1878
|
+
}
|
|
1879
|
+
async matches() {
|
|
1880
|
+
const { entries, observed } = await this.conclusive();
|
|
1881
|
+
return { entries, observed };
|
|
1882
|
+
}
|
|
1883
|
+
reactOnly(matches, observed) {
|
|
1884
|
+
const [only, ...others] = matches;
|
|
1885
|
+
if (only !== undefined && others.length === 0)
|
|
1886
|
+
return only;
|
|
1887
|
+
throw new E2eError2("LOCATOR_AMBIGUOUS", `${this} matched ${matches.length} components in the React tree`, this.rt.excerpt(observed.snapshot), matches.length);
|
|
1670
1888
|
}
|
|
1671
1889
|
async single() {
|
|
1672
1890
|
const { entries, observed } = await this.matches();
|
|
@@ -1686,27 +1904,58 @@ class Locator {
|
|
|
1686
1904
|
return this.rt.span(`read.${name}`, run, { detail: () => ({ locator: this.toString() }) });
|
|
1687
1905
|
}
|
|
1688
1906
|
count() {
|
|
1689
|
-
return this.traced("count", async () =>
|
|
1907
|
+
return this.traced("count", async () => {
|
|
1908
|
+
const { entries, react } = await this.conclusive({ quick: true });
|
|
1909
|
+
return react?.length ?? entries.length;
|
|
1910
|
+
});
|
|
1690
1911
|
}
|
|
1691
1912
|
async all() {
|
|
1692
1913
|
const count = await this.count();
|
|
1693
1914
|
return Array.from({ length: count }, (_, index) => this.nth(index));
|
|
1694
1915
|
}
|
|
1695
1916
|
text() {
|
|
1696
|
-
return this.traced("text", async () =>
|
|
1917
|
+
return this.traced("text", async () => {
|
|
1918
|
+
const { entries, react, observed } = await this.conclusive();
|
|
1919
|
+
if (react !== undefined)
|
|
1920
|
+
return this.reactText(this.reactOnly(react, observed), observed);
|
|
1921
|
+
if (entries.length === 0 && (await this.reactCandidates(observed)).length > 0) {
|
|
1922
|
+
throw new E2eError2("NOT_ACTIONABLE", `${this} ${REACT_ONLY}`, this.rt.excerpt(observed.snapshot));
|
|
1923
|
+
}
|
|
1924
|
+
return textOf(this.exactlyOne(entries, observed));
|
|
1925
|
+
});
|
|
1697
1926
|
}
|
|
1698
1927
|
allTexts() {
|
|
1699
|
-
return this.traced("allTexts", async () =>
|
|
1928
|
+
return this.traced("allTexts", async () => {
|
|
1929
|
+
const { entries, react, observed } = await this.conclusive();
|
|
1930
|
+
if (react !== undefined)
|
|
1931
|
+
return react.map((match) => this.reactText(match, observed));
|
|
1932
|
+
return entries.map(textOf);
|
|
1933
|
+
});
|
|
1934
|
+
}
|
|
1935
|
+
reactText(match, observed) {
|
|
1936
|
+
if (match.text !== undefined)
|
|
1937
|
+
return normalizeText(match.text);
|
|
1938
|
+
throw new E2eError2("NOT_ACTIONABLE", `${this} ${REACT_ONLY}`, this.rt.excerpt(observed.snapshot));
|
|
1700
1939
|
}
|
|
1701
1940
|
value() {
|
|
1702
1941
|
return this.traced("value", async () => fieldValue((await this.single()).entry));
|
|
1703
1942
|
}
|
|
1704
1943
|
isVisible() {
|
|
1705
|
-
return this.traced("isVisible", async () =>
|
|
1944
|
+
return this.traced("isVisible", async () => {
|
|
1945
|
+
const { entries, react, observed } = await this.conclusive({ quick: true });
|
|
1946
|
+
if (react !== undefined)
|
|
1947
|
+
return reactVisible(this.reactOnly(react, observed), observed);
|
|
1948
|
+
return entries.length > 0 && this.exactlyOne(entries, observed).visible;
|
|
1949
|
+
});
|
|
1706
1950
|
}
|
|
1707
1951
|
read() {
|
|
1708
1952
|
return this.traced("read", async () => {
|
|
1709
|
-
const { entries, observed } = await this.
|
|
1953
|
+
const { entries, react, observed } = await this.conclusive();
|
|
1954
|
+
if (react !== undefined) {
|
|
1955
|
+
const match = this.reactOnly(react, observed);
|
|
1956
|
+
const visible = reactVisible(match, observed);
|
|
1957
|
+
return match.text === undefined ? { visible } : { visible, text: normalizeText(match.text) };
|
|
1958
|
+
}
|
|
1710
1959
|
if (entries.length === 0)
|
|
1711
1960
|
return { visible: false };
|
|
1712
1961
|
const entry = this.exactlyOne(entries, observed);
|
|
@@ -1718,11 +1967,13 @@ class Locator {
|
|
|
1718
1967
|
};
|
|
1719
1968
|
});
|
|
1720
1969
|
}
|
|
1721
|
-
|
|
1722
|
-
const
|
|
1723
|
-
if (
|
|
1970
|
+
async shownIn(observed) {
|
|
1971
|
+
const resolved = await this.resolveIn(observed);
|
|
1972
|
+
if (resolved.kind === "unknown")
|
|
1724
1973
|
return false;
|
|
1725
|
-
|
|
1974
|
+
if (resolved.kind === "react")
|
|
1975
|
+
return reactVisible(this.reactOnly(resolved.matches, observed), observed);
|
|
1976
|
+
return resolved.entries.length > 0 && this.exactlyOne(resolved.entries, observed).visible;
|
|
1726
1977
|
}
|
|
1727
1978
|
async isHidden() {
|
|
1728
1979
|
return !await this.isVisible();
|
|
@@ -1735,7 +1986,7 @@ class Locator {
|
|
|
1735
1986
|
}
|
|
1736
1987
|
boundingBox() {
|
|
1737
1988
|
return this.traced("boundingBox", async () => {
|
|
1738
|
-
const { entries, observed } = await this.
|
|
1989
|
+
const { entries, observed } = await this.conclusive({ reuse: false });
|
|
1739
1990
|
if (entries.length === 0) {
|
|
1740
1991
|
const [only, ...others] = await this.reactCandidates(observed);
|
|
1741
1992
|
if (only !== undefined && others.length === 0)
|
|
@@ -1762,14 +2013,19 @@ class Locator {
|
|
|
1762
2013
|
return { done: false, state: undefined };
|
|
1763
2014
|
}
|
|
1764
2015
|
const { observed } = observation;
|
|
1765
|
-
const
|
|
1766
|
-
|
|
1767
|
-
|
|
2016
|
+
const resolved = await this.resolveIn(observed);
|
|
2017
|
+
if (resolved.kind === "unknown")
|
|
2018
|
+
return { done: false, state: { snapshot: observed.snapshot, degraded: resolved.read } };
|
|
2019
|
+
const done = resolved.kind === "react" ? reachedByReact(state, reactVisible(this.reactOnly(resolved.matches, observed), observed)) : reached(state, resolved.entries.length === 0 ? undefined : this.exactlyOne(resolved.entries, observed));
|
|
2020
|
+
return done ? { done: true, value: true } : { done: false, state: { snapshot: observed.snapshot } };
|
|
1768
2021
|
}, timeoutMs);
|
|
1769
2022
|
if (result.status === "done")
|
|
1770
2023
|
return;
|
|
2024
|
+
const last = result.last;
|
|
2025
|
+
if (last?.degraded !== undefined)
|
|
2026
|
+
throw this.degradedFailure(last.degraded, timeoutMs, last.snapshot);
|
|
1771
2027
|
const message = `${this} did not become ${state} within ${timeoutMs} ms`;
|
|
1772
|
-
throw new E2eError2("TIMEOUT", message,
|
|
2028
|
+
throw new E2eError2("TIMEOUT", message, last === undefined ? undefined : this.rt.excerpt(last.snapshot));
|
|
1773
2029
|
}
|
|
1774
2030
|
act(verb, run, suffix = "") {
|
|
1775
2031
|
return this.rt.step(`${verb} ${this}${suffix}`, run);
|
|
@@ -1795,6 +2051,8 @@ class Locator {
|
|
|
1795
2051
|
}
|
|
1796
2052
|
if (seen?.kind === "driver")
|
|
1797
2053
|
throw new E2eError2("DRIVER", `snapshot kept failing: ${seen.message}`);
|
|
2054
|
+
if (seen?.kind === "degraded")
|
|
2055
|
+
throw this.degradedFailure(seen.read, timeoutMs, seen.snapshot);
|
|
1798
2056
|
throw new E2eError2("LOCATOR_NOT_FOUND", `${this} matched no elements within ${timeoutMs} ms`, seen === undefined ? undefined : this.rt.excerpt(seen.snapshot));
|
|
1799
2057
|
}
|
|
1800
2058
|
async actionableLook(spec, allowReact, anyFrame) {
|
|
@@ -1857,6 +2115,26 @@ class Locator {
|
|
|
1857
2115
|
return;
|
|
1858
2116
|
return { entry, observed };
|
|
1859
2117
|
}
|
|
2118
|
+
async clearOfKeyboard(found, spec, position, timeoutMs) {
|
|
2119
|
+
const coverAt = (at) => coverOf(at.entry, tapPointOf(at.entry, position), at.observed.snapshot);
|
|
2120
|
+
let current = found;
|
|
2121
|
+
let cover = coverAt(current);
|
|
2122
|
+
if (cover === undefined)
|
|
2123
|
+
return found;
|
|
2124
|
+
const { driver } = found.observed;
|
|
2125
|
+
for (let scrolls = 0;cover?.scroller !== undefined && scrolls < MAX_COVER_SCROLLS; scrolls += 1) {
|
|
2126
|
+
const path = scrollPath(cover.scroller, cover.shift);
|
|
2127
|
+
this.rt.drive("scrollIntoView", await driver.swipe(path.from, path.to, COVER_SCROLL_SWIPE));
|
|
2128
|
+
await driver.settle(SCROLL_SETTLE);
|
|
2129
|
+
current = await this.actionable(spec, timeoutMs);
|
|
2130
|
+
cover = coverAt(current);
|
|
2131
|
+
}
|
|
2132
|
+
if (cover === undefined)
|
|
2133
|
+
return current;
|
|
2134
|
+
this.rt.drive("dismissKeyboard", await dismissKeyboard(driver));
|
|
2135
|
+
await driver.settle(SCROLL_SETTLE);
|
|
2136
|
+
return this.actionable(spec, timeoutMs);
|
|
2137
|
+
}
|
|
1860
2138
|
async tap(options = {}) {
|
|
1861
2139
|
assertOptions("tap", options, ["position", "timeout", "until", "retries", "windowMs", "pollMs"]);
|
|
1862
2140
|
const retry = retryPlan(options);
|
|
@@ -1866,7 +2144,7 @@ class Locator {
|
|
|
1866
2144
|
if (retry === undefined)
|
|
1867
2145
|
return;
|
|
1868
2146
|
for (let resent = 0;resent < retry.retries; resent += 1) {
|
|
1869
|
-
const outcome = await this.arrivedWithin(retry,
|
|
2147
|
+
const outcome = await this.arrivedWithin(retry, before);
|
|
1870
2148
|
if (outcome !== "press-again")
|
|
1871
2149
|
return;
|
|
1872
2150
|
before = await this.tapOnce(options);
|
|
@@ -1876,19 +2154,31 @@ class Locator {
|
|
|
1876
2154
|
async tapOnce(options) {
|
|
1877
2155
|
const withPosition = options.position !== undefined;
|
|
1878
2156
|
const spec = { enabled: !withPosition, tapPointOnScreen: !withPosition };
|
|
1879
|
-
const
|
|
2157
|
+
const timeoutMs = options.timeout ?? this.rt.actionTimeout();
|
|
2158
|
+
const look = (options.until === undefined ? await this.aimFromKept(spec, options.position) : undefined) ?? await this.actionable(spec, timeoutMs, true);
|
|
2159
|
+
const found = "entry" in look ? await this.clearOfKeyboard(look, spec, options.position, timeoutMs) : look;
|
|
1880
2160
|
const point = "react" in found ? reactTapPoint(found.react, options.position) : tapPointOf(found.entry, options.position);
|
|
1881
2161
|
const { driver } = found.observed;
|
|
2162
|
+
const aimed = {
|
|
2163
|
+
snapshot: found.observed.snapshot,
|
|
2164
|
+
target: "entry" in found ? found.entry.element : undefined
|
|
2165
|
+
};
|
|
1882
2166
|
if (!withPosition && "entry" in found && canActOnElements(driver)) {
|
|
1883
2167
|
if (this.rt.drive("tap", await driver.pressElement(found.entry.element)))
|
|
1884
|
-
return
|
|
2168
|
+
return aimed;
|
|
1885
2169
|
}
|
|
1886
2170
|
this.rt.drive("tap", await driver.tap(point));
|
|
1887
|
-
return
|
|
2171
|
+
return aimed;
|
|
1888
2172
|
}
|
|
1889
|
-
async arrivedWithin(plan,
|
|
2173
|
+
async arrivedWithin(plan, aimed) {
|
|
1890
2174
|
const { until } = plan;
|
|
1891
|
-
const watch = {
|
|
2175
|
+
const watch = {
|
|
2176
|
+
before: new Set(aimed.snapshot.flat.map(elementKey)),
|
|
2177
|
+
target: aimed.target === undefined ? undefined : elementKey(aimed.target),
|
|
2178
|
+
started: this.rt.clock.now(),
|
|
2179
|
+
changed: false,
|
|
2180
|
+
unchanged: 0
|
|
2181
|
+
};
|
|
1892
2182
|
const result = await this.rt.poll(async (attempt) => {
|
|
1893
2183
|
if (typeof until !== "function")
|
|
1894
2184
|
return this.checkLocatorUntil(until, watch, attempt);
|
|
@@ -1909,9 +2199,9 @@ class Locator {
|
|
|
1909
2199
|
watch.unchanged = 0;
|
|
1910
2200
|
return { done: false, state: undefined };
|
|
1911
2201
|
}
|
|
1912
|
-
if (until.
|
|
2202
|
+
if (await until.shownIn(observation.observed))
|
|
1913
2203
|
return { done: true, value: "arrived" };
|
|
1914
|
-
if (
|
|
2204
|
+
if (!reacted(observation.observed.snapshot, watch))
|
|
1915
2205
|
watch.unchanged += 1;
|
|
1916
2206
|
else {
|
|
1917
2207
|
watch.changed = true;
|
|
@@ -1923,7 +2213,9 @@ class Locator {
|
|
|
1923
2213
|
async longPress(options = {}) {
|
|
1924
2214
|
assertOptions("longPress", options, ["durationMs", "timeout"]);
|
|
1925
2215
|
return this.act("longPress", async () => {
|
|
1926
|
-
const
|
|
2216
|
+
const spec = { enabled: true, tapPointOnScreen: true };
|
|
2217
|
+
const timeoutMs = options.timeout ?? this.rt.actionTimeout();
|
|
2218
|
+
const { entry, observed } = await this.clearOfKeyboard(await this.actionable(spec, timeoutMs), spec, undefined, timeoutMs);
|
|
1927
2219
|
const opts = options.durationMs === undefined ? undefined : { durationMs: options.durationMs };
|
|
1928
2220
|
this.rt.drive("longPress", await observed.driver.longPress(tapPointOf(entry), opts));
|
|
1929
2221
|
});
|
|
@@ -1934,18 +2226,18 @@ class Locator {
|
|
|
1934
2226
|
return this.act("fill", async () => {
|
|
1935
2227
|
const budget = this.rt.budget(options.timeout);
|
|
1936
2228
|
let target = await this.actionable({ enabled: true, tapPointOnScreen: false }, budget.remaining());
|
|
2229
|
+
const verify = !target.entry.secure;
|
|
1937
2230
|
let firstHeld;
|
|
1938
2231
|
for (let attempt = 1;; attempt += 1) {
|
|
1939
2232
|
const point = focusPoint(target.entry.element.frame);
|
|
1940
2233
|
this.rt.drive("fill", await focusClearType(target.observed.driver, this.pause, point, target.entry, text, clearMethodFor(attempt), process.env, viaFor(attempt, via)));
|
|
1941
|
-
if (
|
|
2234
|
+
if (!verify)
|
|
1942
2235
|
return;
|
|
1943
2236
|
const outcome = await this.confirm(target, text, budget);
|
|
1944
2237
|
if (outcome.ok)
|
|
1945
2238
|
return;
|
|
1946
2239
|
if (attempt === 2) {
|
|
1947
|
-
|
|
1948
|
-
throw new E2eError2("ACTION_FAILED", `${this}: fill did not take: expected ${JSON.stringify(text)} but the field holds ${JSON.stringify(outcome.held)}${earlier}`, outcome.screen);
|
|
2240
|
+
throw new E2eError2("ACTION_FAILED", `${this}: ${fillMismatch(text, target.entry.secure, outcome.held, firstHeld)}`, outcome.screen);
|
|
1949
2241
|
}
|
|
1950
2242
|
firstHeld = outcome.held;
|
|
1951
2243
|
target = await this.actionable({ enabled: true, tapPointOnScreen: false }, budget.remaining());
|
|
@@ -2034,10 +2326,17 @@ class Locator {
|
|
|
2034
2326
|
return scrollUntilVisible(this.rt, this, options);
|
|
2035
2327
|
}
|
|
2036
2328
|
async reactStep(spec, observed, allowReact) {
|
|
2037
|
-
const
|
|
2329
|
+
const read = degradedRead(observed.snapshot);
|
|
2330
|
+
const testId = plainTestId(this.expr);
|
|
2331
|
+
const lookup = testId === undefined ? undefined : await this.rt.reactLookup(testId, observed);
|
|
2332
|
+
const found = lookup?.answered === true ? lookup.matches : [];
|
|
2038
2333
|
const [match, ...others] = found;
|
|
2039
|
-
if (match === undefined)
|
|
2334
|
+
if (match === undefined) {
|
|
2335
|
+
if (read !== undefined && lookup?.answered !== true) {
|
|
2336
|
+
return { done: false, state: { kind: "degraded", read, snapshot: observed.snapshot } };
|
|
2337
|
+
}
|
|
2040
2338
|
return { done: false, state: { kind: "none", snapshot: observed.snapshot } };
|
|
2339
|
+
}
|
|
2041
2340
|
if (!allowReact)
|
|
2042
2341
|
return { done: false, state: { kind: "react", snapshot: observed.snapshot, matches: found.length } };
|
|
2043
2342
|
if (others.length > 0) {
|
|
@@ -2061,7 +2360,11 @@ class Locator {
|
|
|
2061
2360
|
async confirm({ entry, observed }, text, budget) {
|
|
2062
2361
|
const span = Math.max(budget.remaining(), CONFIRM_FLOOR_MS);
|
|
2063
2362
|
let lastSeen;
|
|
2064
|
-
const readValue = (found) =>
|
|
2363
|
+
const readValue = (found) => {
|
|
2364
|
+
if (text === "")
|
|
2365
|
+
return fieldValue(found);
|
|
2366
|
+
return found.secure ? maskedReading(found, text) : rawValue(found);
|
|
2367
|
+
};
|
|
2065
2368
|
const outcome = await confirmFieldValue({
|
|
2066
2369
|
text,
|
|
2067
2370
|
now: this.rt.clock.now,
|
|
@@ -2119,6 +2422,16 @@ var TAP_WINDOW_MS = 4000;
|
|
|
2119
2422
|
var TAP_CHECK_MS = 250;
|
|
2120
2423
|
var UNCHANGED_CHECKS = 2;
|
|
2121
2424
|
var UNCHANGED_AFTER_MS = 750;
|
|
2425
|
+
function reacted(snapshot, watch) {
|
|
2426
|
+
const now = new Set;
|
|
2427
|
+
for (const element of snapshot.flat) {
|
|
2428
|
+
const key = elementKey(element);
|
|
2429
|
+
if (!watch.before.has(key))
|
|
2430
|
+
return true;
|
|
2431
|
+
now.add(key);
|
|
2432
|
+
}
|
|
2433
|
+
return watch.target !== undefined && !now.has(watch.target);
|
|
2434
|
+
}
|
|
2122
2435
|
function wholeNumber(name, value, atLeast) {
|
|
2123
2436
|
if (!Number.isInteger(value) || value < atLeast) {
|
|
2124
2437
|
throw new E2eError2("INVALID_ARGUMENT", `tap ${name} must be an integer of at least ${atLeast}, got ${String(value)}`);
|
|
@@ -2161,6 +2474,11 @@ function viaFor(attempt, via) {
|
|
|
2161
2474
|
function clearMethodFor(attempt) {
|
|
2162
2475
|
return attempt === 1 ? "auto" : "counted";
|
|
2163
2476
|
}
|
|
2477
|
+
function fillMismatch(text, secure, held, firstHeld) {
|
|
2478
|
+
const expected = secure ? `${graphemes(text)} characters (secure)` : JSON.stringify(text);
|
|
2479
|
+
const earlier = firstHeld === undefined ? "" : ` (first attempt held ${JSON.stringify(firstHeld)})`;
|
|
2480
|
+
return `fill did not take: expected ${expected} but the field holds ${JSON.stringify(held)}${earlier}`;
|
|
2481
|
+
}
|
|
2164
2482
|
function rawValue(entry) {
|
|
2165
2483
|
return entry.value ?? (entry.role === "textbox" || entry.role === "searchbox" ? entry.element.text ?? "" : "");
|
|
2166
2484
|
}
|
|
@@ -2176,6 +2494,18 @@ function reached(state, entry) {
|
|
|
2176
2494
|
return entry === undefined || !entry.visible;
|
|
2177
2495
|
}
|
|
2178
2496
|
}
|
|
2497
|
+
function reachedByReact(state, visible) {
|
|
2498
|
+
switch (state) {
|
|
2499
|
+
case "attached":
|
|
2500
|
+
return true;
|
|
2501
|
+
case "detached":
|
|
2502
|
+
return false;
|
|
2503
|
+
case "visible":
|
|
2504
|
+
return visible;
|
|
2505
|
+
case "hidden":
|
|
2506
|
+
return !visible;
|
|
2507
|
+
}
|
|
2508
|
+
}
|
|
2179
2509
|
function blockedReason(entry, spec, observed) {
|
|
2180
2510
|
if (!entry.visible)
|
|
2181
2511
|
return "is not visible";
|
|
@@ -2189,7 +2519,11 @@ function blockedReason(entry, spec, observed) {
|
|
|
2189
2519
|
function isOnScreenEntry(entry, observed) {
|
|
2190
2520
|
return entry?.visible === true && isOnScreen(tapPointOf(entry), observed.snapshot.screen);
|
|
2191
2521
|
}
|
|
2192
|
-
function scrollGaveUp(target, absent, effort,
|
|
2522
|
+
function scrollGaveUp(target, absent, effort, observed, waitedMs) {
|
|
2523
|
+
const read = absent ? degradedRead(observed.snapshot) : undefined;
|
|
2524
|
+
if (read !== undefined)
|
|
2525
|
+
return target.degradedFailure(read, waitedMs, observed.snapshot);
|
|
2526
|
+
const screen = target.runtime.excerpt(observed.snapshot);
|
|
2193
2527
|
if (absent)
|
|
2194
2528
|
return new E2eError2("LOCATOR_NOT_FOUND", `${target} was not found after ${effort}`, screen);
|
|
2195
2529
|
return new E2eError2("NOT_ACTIONABLE", `${target} is still not on screen after ${effort}`, screen);
|
|
@@ -2218,7 +2552,8 @@ async function scrollUntilVisible(rt, target, options = {}) {
|
|
|
2218
2552
|
if (isOnScreenEntry(entry, observed))
|
|
2219
2553
|
return;
|
|
2220
2554
|
if (swipes >= maxSwipes || budget.remaining() <= 0) {
|
|
2221
|
-
|
|
2555
|
+
const effort = `${swipes} swipes ${direction}`;
|
|
2556
|
+
throw scrollGaveUp(target, entry === undefined, effort, observed, budget.total);
|
|
2222
2557
|
}
|
|
2223
2558
|
await scrollOnce(rt, observed, direction, options.momentum);
|
|
2224
2559
|
await observed.driver.settle(SCROLL_SETTLE);
|
|
@@ -2288,7 +2623,7 @@ function describeException(details) {
|
|
|
2288
2623
|
return String(thrown.value);
|
|
2289
2624
|
return details.text ?? "JS exception";
|
|
2290
2625
|
}
|
|
2291
|
-
function
|
|
2626
|
+
function textOf3(data) {
|
|
2292
2627
|
if (typeof data === "string")
|
|
2293
2628
|
return data;
|
|
2294
2629
|
if (data instanceof ArrayBuffer)
|
|
@@ -2311,7 +2646,7 @@ function taggedBindingPayload(params) {
|
|
|
2311
2646
|
return tagged.success ? { requestId: tagged.data.requestId, payload } : undefined;
|
|
2312
2647
|
}
|
|
2313
2648
|
function parseEnvelope(data) {
|
|
2314
|
-
const text =
|
|
2649
|
+
const text = textOf3(data);
|
|
2315
2650
|
if (text === undefined)
|
|
2316
2651
|
return;
|
|
2317
2652
|
let json;
|
|
@@ -4346,6 +4681,14 @@ async function captureNativeLog(target, deps, win) {
|
|
|
4346
4681
|
}
|
|
4347
4682
|
}
|
|
4348
4683
|
|
|
4684
|
+
// ../../packages/e2e/src/driver/ax-wedge.ts
|
|
4685
|
+
function canRecoverAxWedge(driver) {
|
|
4686
|
+
return typeof driver.axWedge === "function";
|
|
4687
|
+
}
|
|
4688
|
+
function restartedWithin(state, windowMs) {
|
|
4689
|
+
return state.last?.outcome === "restarted" && state.last.agoMs <= windowMs;
|
|
4690
|
+
}
|
|
4691
|
+
|
|
4349
4692
|
// ../../packages/e2e/src/driver/ios/hid-recovery.ts
|
|
4350
4693
|
import { existsSync as existsSync2 } from "fs";
|
|
4351
4694
|
|
|
@@ -4421,19 +4764,133 @@ function driverErr(code, message, extra) {
|
|
|
4421
4764
|
}
|
|
4422
4765
|
|
|
4423
4766
|
// ../../packages/e2e/src/driver/ios/simd-protocol.ts
|
|
4767
|
+
import { z as z7 } from "zod";
|
|
4768
|
+
|
|
4769
|
+
// ../../packages/e2e/src/driver/bridge-stall.ts
|
|
4424
4770
|
import { z as z6 } from "zod";
|
|
4771
|
+
var bridgeStallSchema = z6.looseObject({
|
|
4772
|
+
stage: z6.string(),
|
|
4773
|
+
readMs: z6.number().optional(),
|
|
4774
|
+
deadlineMs: z6.number().optional(),
|
|
4775
|
+
guestAgeMs: z6.number().optional(),
|
|
4776
|
+
cold: z6.boolean(),
|
|
4777
|
+
cooldownRemainingMs: z6.number().optional(),
|
|
4778
|
+
probes: z6.number(),
|
|
4779
|
+
recycles: z6.number().optional(),
|
|
4780
|
+
recyclesExhausted: z6.boolean().optional(),
|
|
4781
|
+
axWedge: z6.string().optional(),
|
|
4782
|
+
axWedgeRestarts: z6.number().optional()
|
|
4783
|
+
});
|
|
4784
|
+
var SPAN_FIELDS = [
|
|
4785
|
+
["stallStage", "stage"],
|
|
4786
|
+
["stallReadMs", "readMs"],
|
|
4787
|
+
["stallDeadlineMs", "deadlineMs"],
|
|
4788
|
+
["stallGuestAgeMs", "guestAgeMs"],
|
|
4789
|
+
["stallCold", "cold"],
|
|
4790
|
+
["stallCooldownMs", "cooldownRemainingMs"],
|
|
4791
|
+
["stallProbes", "probes"],
|
|
4792
|
+
["stallRecycles", "recycles"],
|
|
4793
|
+
["stallRecyclesExhausted", "recyclesExhausted"],
|
|
4794
|
+
["stallAxWedge", "axWedge"],
|
|
4795
|
+
["stallAxWedgeRestarts", "axWedgeRestarts"]
|
|
4796
|
+
];
|
|
4797
|
+
function toBridgeStall(wire) {
|
|
4798
|
+
const stall = { stage: wire.stage, cold: wire.cold, probes: wire.probes };
|
|
4799
|
+
if (wire.readMs !== undefined)
|
|
4800
|
+
stall.readMs = wire.readMs;
|
|
4801
|
+
if (wire.deadlineMs !== undefined)
|
|
4802
|
+
stall.deadlineMs = wire.deadlineMs;
|
|
4803
|
+
if (wire.guestAgeMs !== undefined)
|
|
4804
|
+
stall.guestAgeMs = wire.guestAgeMs;
|
|
4805
|
+
if (wire.cooldownRemainingMs !== undefined)
|
|
4806
|
+
stall.cooldownRemainingMs = wire.cooldownRemainingMs;
|
|
4807
|
+
if (wire.recycles !== undefined)
|
|
4808
|
+
stall.recycles = wire.recycles;
|
|
4809
|
+
if (wire.recyclesExhausted !== undefined)
|
|
4810
|
+
stall.recyclesExhausted = wire.recyclesExhausted;
|
|
4811
|
+
if (wire.axWedge !== undefined)
|
|
4812
|
+
stall.axWedge = wire.axWedge;
|
|
4813
|
+
if (wire.axWedgeRestarts !== undefined)
|
|
4814
|
+
stall.axWedgeRestarts = wire.axWedgeRestarts;
|
|
4815
|
+
return stall;
|
|
4816
|
+
}
|
|
4817
|
+
function noteBridgeStall(stall) {
|
|
4818
|
+
for (const [key, field] of SPAN_FIELDS) {
|
|
4819
|
+
const value = stall[field];
|
|
4820
|
+
if (value !== undefined)
|
|
4821
|
+
noteSpanDetail(key, value);
|
|
4822
|
+
}
|
|
4823
|
+
}
|
|
4824
|
+
function bridgeStallFromSpan(detail) {
|
|
4825
|
+
const stage = detail?.stallStage;
|
|
4826
|
+
if (detail === undefined || typeof stage !== "string")
|
|
4827
|
+
return;
|
|
4828
|
+
const stall = {
|
|
4829
|
+
stage,
|
|
4830
|
+
cold: detail.stallCold === true,
|
|
4831
|
+
probes: typeof detail.stallProbes === "number" ? detail.stallProbes : 0
|
|
4832
|
+
};
|
|
4833
|
+
const numbers = [
|
|
4834
|
+
["stallReadMs", "readMs"],
|
|
4835
|
+
["stallDeadlineMs", "deadlineMs"],
|
|
4836
|
+
["stallGuestAgeMs", "guestAgeMs"],
|
|
4837
|
+
["stallCooldownMs", "cooldownRemainingMs"],
|
|
4838
|
+
["stallRecycles", "recycles"],
|
|
4839
|
+
["stallAxWedgeRestarts", "axWedgeRestarts"]
|
|
4840
|
+
];
|
|
4841
|
+
for (const [key, field] of numbers) {
|
|
4842
|
+
const value = detail[key];
|
|
4843
|
+
if (typeof value === "number")
|
|
4844
|
+
stall[field] = value;
|
|
4845
|
+
}
|
|
4846
|
+
if (typeof detail.stallRecyclesExhausted === "boolean")
|
|
4847
|
+
stall.recyclesExhausted = detail.stallRecyclesExhausted;
|
|
4848
|
+
if (typeof detail.stallAxWedge === "string")
|
|
4849
|
+
stall.axWedge = detail.stallAxWedge;
|
|
4850
|
+
return stall;
|
|
4851
|
+
}
|
|
4852
|
+
var seconds = (ms) => `${(ms / 1000).toFixed(1)} s`;
|
|
4853
|
+
function axWedgeParts(stall) {
|
|
4854
|
+
const parts = [];
|
|
4855
|
+
if (stall.axWedge === "checking")
|
|
4856
|
+
parts.push("both readers silent: checking for a wedged accessibility service");
|
|
4857
|
+
if (stall.axWedgeRestarts !== undefined) {
|
|
4858
|
+
parts.push(`backboardd restarted ${stall.axWedgeRestarts} time${stall.axWedgeRestarts === 1 ? "" : "s"}`);
|
|
4859
|
+
}
|
|
4860
|
+
return parts;
|
|
4861
|
+
}
|
|
4862
|
+
function formatBridgeStall(stall) {
|
|
4863
|
+
const parts = [`stage ${stall.stage}`];
|
|
4864
|
+
if (stall.readMs !== undefined) {
|
|
4865
|
+
parts.push(stall.deadlineMs === undefined ? `read ${stall.readMs} ms` : `read ${stall.readMs} ms of a ${stall.deadlineMs} ms deadline`);
|
|
4866
|
+
} else if (stall.deadlineMs !== undefined)
|
|
4867
|
+
parts.push(`${stall.deadlineMs} ms deadline`);
|
|
4868
|
+
parts.push(stall.cold ? "cold (first read)" : "warm");
|
|
4869
|
+
if (stall.guestAgeMs !== undefined)
|
|
4870
|
+
parts.push(`guest ${seconds(stall.guestAgeMs)} old`);
|
|
4871
|
+
if (stall.cooldownRemainingMs !== undefined)
|
|
4872
|
+
parts.push(`cooldown ${seconds(stall.cooldownRemainingMs)} left`);
|
|
4873
|
+
parts.push(`${stall.probes} failed bridge reads in a row`);
|
|
4874
|
+
if (stall.recycles !== undefined) {
|
|
4875
|
+
const guests = stall.recycles === 1 ? "guest" : "guests";
|
|
4876
|
+
parts.push(`${stall.recycles} stalled ${guests} recycled${stall.recyclesExhausted === true ? ", none left" : ""}`);
|
|
4877
|
+
}
|
|
4878
|
+
return [...parts, ...axWedgeParts(stall)].join(" \xB7 ");
|
|
4879
|
+
}
|
|
4880
|
+
|
|
4881
|
+
// ../../packages/e2e/src/driver/ios/simd-protocol.ts
|
|
4425
4882
|
var protocolVersion = 1;
|
|
4426
|
-
var rectSchema2 =
|
|
4427
|
-
x:
|
|
4428
|
-
y:
|
|
4429
|
-
width:
|
|
4430
|
-
height:
|
|
4883
|
+
var rectSchema2 = z7.object({
|
|
4884
|
+
x: z7.number(),
|
|
4885
|
+
y: z7.number(),
|
|
4886
|
+
width: z7.number(),
|
|
4887
|
+
height: z7.number()
|
|
4431
4888
|
});
|
|
4432
|
-
var screenInfoSchema =
|
|
4433
|
-
width:
|
|
4434
|
-
height:
|
|
4435
|
-
scale:
|
|
4436
|
-
rotation:
|
|
4889
|
+
var screenInfoSchema = z7.object({
|
|
4890
|
+
width: z7.number(),
|
|
4891
|
+
height: z7.number(),
|
|
4892
|
+
scale: z7.number(),
|
|
4893
|
+
rotation: z7.number().optional()
|
|
4437
4894
|
});
|
|
4438
4895
|
var ROLES = [
|
|
4439
4896
|
"button",
|
|
@@ -4460,29 +4917,29 @@ var ROLES = [
|
|
|
4460
4917
|
"application",
|
|
4461
4918
|
"other"
|
|
4462
4919
|
];
|
|
4463
|
-
var pointSchema =
|
|
4464
|
-
var wireElementSchema =
|
|
4465
|
-
id:
|
|
4466
|
-
role:
|
|
4467
|
-
rawType:
|
|
4468
|
-
label:
|
|
4469
|
-
identifier:
|
|
4470
|
-
value:
|
|
4471
|
-
text:
|
|
4472
|
-
placeholder:
|
|
4920
|
+
var pointSchema = z7.object({ x: z7.number(), y: z7.number() });
|
|
4921
|
+
var wireElementSchema = z7.lazy(() => z7.object({
|
|
4922
|
+
id: z7.string().nullish(),
|
|
4923
|
+
role: z7.enum(ROLES),
|
|
4924
|
+
rawType: z7.string(),
|
|
4925
|
+
label: z7.string().nullish(),
|
|
4926
|
+
identifier: z7.string().nullish(),
|
|
4927
|
+
value: z7.string().nullish(),
|
|
4928
|
+
text: z7.string().nullish(),
|
|
4929
|
+
placeholder: z7.string().nullish(),
|
|
4473
4930
|
frame: rectSchema2,
|
|
4474
4931
|
normFrame: rectSchema2.nullish(),
|
|
4475
|
-
traits:
|
|
4476
|
-
enabled:
|
|
4477
|
-
focused:
|
|
4478
|
-
hidden:
|
|
4479
|
-
selected:
|
|
4480
|
-
checked:
|
|
4481
|
-
hittable:
|
|
4932
|
+
traits: z7.array(z7.string()).nullish(),
|
|
4933
|
+
enabled: z7.boolean().nullish(),
|
|
4934
|
+
focused: z7.boolean().nullish(),
|
|
4935
|
+
hidden: z7.boolean().nullish(),
|
|
4936
|
+
selected: z7.boolean().nullish(),
|
|
4937
|
+
checked: z7.boolean().nullish(),
|
|
4938
|
+
hittable: z7.boolean().nullish(),
|
|
4482
4939
|
tapPoint: pointSchema.nullish(),
|
|
4483
|
-
pid:
|
|
4484
|
-
depth:
|
|
4485
|
-
children:
|
|
4940
|
+
pid: z7.number().nullish(),
|
|
4941
|
+
depth: z7.number().nullish(),
|
|
4942
|
+
children: z7.array(wireElementSchema).nullish()
|
|
4486
4943
|
}));
|
|
4487
4944
|
var ROLE_SET = new Set(ROLES);
|
|
4488
4945
|
var isBag = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -4520,33 +4977,33 @@ function wireElementProblem(value) {
|
|
|
4520
4977
|
const issue = parsed.error.issues[0];
|
|
4521
4978
|
return issue ? `${issue.path.join(".") || "(element)"}: ${issue.message}` : "invalid element";
|
|
4522
4979
|
}
|
|
4523
|
-
var wireErrorSchema =
|
|
4524
|
-
code:
|
|
4525
|
-
message:
|
|
4526
|
-
retryable:
|
|
4527
|
-
detail:
|
|
4980
|
+
var wireErrorSchema = z7.object({
|
|
4981
|
+
code: z7.string(),
|
|
4982
|
+
message: z7.string(),
|
|
4983
|
+
retryable: z7.boolean().optional(),
|
|
4984
|
+
detail: z7.unknown().optional()
|
|
4528
4985
|
});
|
|
4529
|
-
var readyEventSchema =
|
|
4530
|
-
event:
|
|
4531
|
-
protocol:
|
|
4532
|
-
version:
|
|
4533
|
-
udid:
|
|
4534
|
-
hid:
|
|
4535
|
-
ax:
|
|
4986
|
+
var readyEventSchema = z7.object({
|
|
4987
|
+
event: z7.literal("ready"),
|
|
4988
|
+
protocol: z7.number(),
|
|
4989
|
+
version: z7.string(),
|
|
4990
|
+
udid: z7.string(),
|
|
4991
|
+
hid: z7.object({ transport: z7.string() }),
|
|
4992
|
+
ax: z7.object({ default: z7.string(), bridge: z7.string() }),
|
|
4536
4993
|
screen: screenInfoSchema
|
|
4537
4994
|
});
|
|
4538
|
-
var logEventSchema =
|
|
4539
|
-
event:
|
|
4540
|
-
level:
|
|
4541
|
-
message:
|
|
4542
|
-
op:
|
|
4543
|
-
id:
|
|
4995
|
+
var logEventSchema = z7.object({
|
|
4996
|
+
event: z7.literal("log"),
|
|
4997
|
+
level: z7.enum(["debug", "info", "warn", "error"]),
|
|
4998
|
+
message: z7.string(),
|
|
4999
|
+
op: z7.string().optional(),
|
|
5000
|
+
id: z7.string().optional()
|
|
4544
5001
|
});
|
|
4545
|
-
var fatalEventSchema =
|
|
4546
|
-
var eventSchema =
|
|
4547
|
-
var responseSchema =
|
|
4548
|
-
|
|
4549
|
-
|
|
5002
|
+
var fatalEventSchema = z7.object({ event: z7.literal("fatal"), error: wireErrorSchema });
|
|
5003
|
+
var eventSchema = z7.discriminatedUnion("event", [readyEventSchema, logEventSchema, fatalEventSchema]);
|
|
5004
|
+
var responseSchema = z7.discriminatedUnion("ok", [
|
|
5005
|
+
z7.object({ id: z7.string(), ok: z7.literal(true), data: z7.unknown().optional() }),
|
|
5006
|
+
z7.object({ id: z7.string(), ok: z7.literal(false), error: wireErrorSchema })
|
|
4550
5007
|
]);
|
|
4551
5008
|
function parseLine(line) {
|
|
4552
5009
|
let json;
|
|
@@ -4562,74 +5019,81 @@ function parseLine(line) {
|
|
|
4562
5019
|
const response = responseSchema.safeParse(json);
|
|
4563
5020
|
return response.success ? { success: true, message: { kind: "response", response: response.data } } : { success: false, reason: `bad response: ${response.error.message}` };
|
|
4564
5021
|
}
|
|
4565
|
-
var healthDataSchema =
|
|
4566
|
-
version:
|
|
4567
|
-
udid:
|
|
4568
|
-
hid:
|
|
4569
|
-
ax:
|
|
4570
|
-
default:
|
|
4571
|
-
bridge:
|
|
4572
|
-
error:
|
|
4573
|
-
lastError:
|
|
5022
|
+
var healthDataSchema = z7.looseObject({
|
|
5023
|
+
version: z7.string(),
|
|
5024
|
+
udid: z7.string(),
|
|
5025
|
+
hid: z7.object({ transport: z7.string() }),
|
|
5026
|
+
ax: z7.object({
|
|
5027
|
+
default: z7.string(),
|
|
5028
|
+
bridge: z7.enum(["available", "degraded", "missing", "failed"]),
|
|
5029
|
+
error: z7.string().optional(),
|
|
5030
|
+
lastError: z7.object({ message: z7.string(), agoMs: z7.number() }).optional()
|
|
4574
5031
|
}),
|
|
4575
5032
|
screen: screenInfoSchema,
|
|
4576
|
-
socket:
|
|
4577
|
-
idb:
|
|
4578
|
-
xcode:
|
|
4579
|
-
builtWith:
|
|
4580
|
-
coreSimulatorVersion:
|
|
4581
|
-
frontmost:
|
|
4582
|
-
|
|
4583
|
-
|
|
5033
|
+
socket: z7.object({ path: z7.string(), clients: z7.number() }).optional(),
|
|
5034
|
+
idb: z7.string().optional(),
|
|
5035
|
+
xcode: z7.string().optional(),
|
|
5036
|
+
builtWith: z7.object({ xcode: z7.string().optional(), idb: z7.string().optional() }).optional(),
|
|
5037
|
+
coreSimulatorVersion: z7.string().nullish(),
|
|
5038
|
+
frontmost: z7.object({ unresolvedAgoMs: z7.number(), method: z7.string() }).optional(),
|
|
5039
|
+
bridgeRecycles: z7.object({ count: z7.number(), exhausted: z7.boolean() }).optional(),
|
|
5040
|
+
axWedge: z7.lazy(() => axWedgeDataSchema).optional(),
|
|
5041
|
+
state: z7.string().optional(),
|
|
5042
|
+
uptimeMs: z7.number().optional()
|
|
4584
5043
|
});
|
|
4585
|
-
var snapshotDataSchema =
|
|
4586
|
-
backend:
|
|
5044
|
+
var snapshotDataSchema = z7.looseObject({
|
|
5045
|
+
backend: z7.enum(["axbridge", "ax", "uiautomator"]),
|
|
4587
5046
|
screen: screenInfoSchema,
|
|
4588
|
-
modal:
|
|
4589
|
-
truncated:
|
|
4590
|
-
elements:
|
|
4591
|
-
ms:
|
|
4592
|
-
fallback:
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
5047
|
+
modal: z7.object({ pid: z7.number().optional(), description: z7.string().optional() }).nullish(),
|
|
5048
|
+
truncated: z7.boolean(),
|
|
5049
|
+
elements: z7.array(z7.custom(isWireElement, { error: (issue) => wireElementProblem(issue.input) })),
|
|
5050
|
+
ms: z7.number(),
|
|
5051
|
+
fallback: z7.object({
|
|
5052
|
+
from: z7.string(),
|
|
5053
|
+
to: z7.string(),
|
|
5054
|
+
reason: z7.string(),
|
|
5055
|
+
stall: bridgeStallSchema.optional()
|
|
5056
|
+
}).optional(),
|
|
5057
|
+
target: z7.object({ pid: z7.number(), source: z7.string() }).optional(),
|
|
5058
|
+
readMs: z7.number().optional(),
|
|
5059
|
+
nodes: z7.number().optional()
|
|
4596
5060
|
});
|
|
4597
5061
|
var elementAtPointDataSchema = wireElementSchema.nullable();
|
|
4598
|
-
var screenshotDataSchema =
|
|
4599
|
-
path:
|
|
4600
|
-
png:
|
|
4601
|
-
width:
|
|
4602
|
-
height:
|
|
5062
|
+
var screenshotDataSchema = z7.looseObject({
|
|
5063
|
+
path: z7.string().optional(),
|
|
5064
|
+
png: z7.string().optional(),
|
|
5065
|
+
width: z7.number().optional(),
|
|
5066
|
+
height: z7.number().optional()
|
|
4603
5067
|
});
|
|
4604
|
-
var settleDataSchema =
|
|
4605
|
-
quiet:
|
|
4606
|
-
waitedMs:
|
|
4607
|
-
via:
|
|
4608
|
-
reason:
|
|
4609
|
-
frontmostPid:
|
|
5068
|
+
var settleDataSchema = z7.looseObject({
|
|
5069
|
+
quiet: z7.boolean(),
|
|
5070
|
+
waitedMs: z7.number(),
|
|
5071
|
+
via: z7.enum(["stream", "snapshot"]).optional().catch(undefined),
|
|
5072
|
+
reason: z7.string().optional(),
|
|
5073
|
+
frontmostPid: z7.number().optional()
|
|
4610
5074
|
});
|
|
4611
|
-
var rebindDataSchema =
|
|
4612
|
-
bound:
|
|
4613
|
-
waitedMs:
|
|
4614
|
-
pid:
|
|
4615
|
-
restarts:
|
|
4616
|
-
recycled:
|
|
4617
|
-
reason:
|
|
5075
|
+
var rebindDataSchema = z7.looseObject({
|
|
5076
|
+
bound: z7.boolean(),
|
|
5077
|
+
waitedMs: z7.number(),
|
|
5078
|
+
pid: z7.number().optional(),
|
|
5079
|
+
restarts: z7.number().optional(),
|
|
5080
|
+
recycled: z7.boolean().optional(),
|
|
5081
|
+
reason: z7.string().optional()
|
|
4618
5082
|
});
|
|
4619
|
-
var alertButtonsDataSchema =
|
|
4620
|
-
title:
|
|
4621
|
-
message:
|
|
4622
|
-
buttons:
|
|
5083
|
+
var alertButtonsDataSchema = z7.object({
|
|
5084
|
+
title: z7.string().optional(),
|
|
5085
|
+
message: z7.string().optional(),
|
|
5086
|
+
buttons: z7.array(wireElementSchema)
|
|
4623
5087
|
}).nullable();
|
|
4624
|
-
var frontmostDataSchema =
|
|
4625
|
-
frontmost:
|
|
4626
|
-
pid:
|
|
4627
|
-
frontmostPid:
|
|
5088
|
+
var frontmostDataSchema = z7.looseObject({
|
|
5089
|
+
frontmost: z7.boolean(),
|
|
5090
|
+
pid: z7.number().optional(),
|
|
5091
|
+
frontmostPid: z7.number().optional()
|
|
4628
5092
|
});
|
|
4629
|
-
var launchDataSchema =
|
|
4630
|
-
var terminateDataSchema =
|
|
4631
|
-
var orientationDataSchema =
|
|
4632
|
-
orientation:
|
|
5093
|
+
var launchDataSchema = z7.looseObject({ pid: z7.number() });
|
|
5094
|
+
var terminateDataSchema = z7.looseObject({ terminated: z7.boolean() });
|
|
5095
|
+
var orientationDataSchema = z7.object({
|
|
5096
|
+
orientation: z7.enum([
|
|
4633
5097
|
"portrait",
|
|
4634
5098
|
"portraitUpsideDown",
|
|
4635
5099
|
"landscapeLeft",
|
|
@@ -4638,7 +5102,18 @@ var orientationDataSchema = z6.object({
|
|
|
4638
5102
|
"faceDown",
|
|
4639
5103
|
"unknown"
|
|
4640
5104
|
]),
|
|
4641
|
-
source:
|
|
5105
|
+
source: z7.enum(["device", "written", "assumed"]).optional()
|
|
5106
|
+
});
|
|
5107
|
+
var axWedgeDataSchema = z7.looseObject({
|
|
5108
|
+
restarts: z7.number(),
|
|
5109
|
+
maxRestarts: z7.number(),
|
|
5110
|
+
recovering: z7.boolean(),
|
|
5111
|
+
checked: z7.enum(["restarted", "not-wedged", "failed"]).optional(),
|
|
5112
|
+
last: z7.discriminatedUnion("outcome", [
|
|
5113
|
+
z7.looseObject({ outcome: z7.literal("restarted"), agoMs: z7.number(), ms: z7.number().optional() }),
|
|
5114
|
+
z7.looseObject({ outcome: z7.literal("not-wedged"), agoMs: z7.number() }),
|
|
5115
|
+
z7.looseObject({ outcome: z7.literal("failed"), agoMs: z7.number(), error: z7.string() })
|
|
5116
|
+
]).optional()
|
|
4642
5117
|
});
|
|
4643
5118
|
var opSchemas = {
|
|
4644
5119
|
health: healthDataSchema,
|
|
@@ -4651,7 +5126,8 @@ var opSchemas = {
|
|
|
4651
5126
|
frontmost: frontmostDataSchema,
|
|
4652
5127
|
orientation: orientationDataSchema,
|
|
4653
5128
|
launch: launchDataSchema,
|
|
4654
|
-
terminate: terminateDataSchema
|
|
5129
|
+
terminate: terminateDataSchema,
|
|
5130
|
+
axWedge: axWedgeDataSchema
|
|
4655
5131
|
};
|
|
4656
5132
|
function hasOpSchema(op) {
|
|
4657
5133
|
return Object.hasOwn(opSchemas, op);
|
|
@@ -5079,7 +5555,7 @@ async function createSimdClient(factory, options = {}) {
|
|
|
5079
5555
|
const transport = session.data.transport;
|
|
5080
5556
|
if (!transport)
|
|
5081
5557
|
return { success: false, error: driverError("E_INTERNAL", "warden-simd transport missing") };
|
|
5082
|
-
const timeoutMs = opts.timeoutMs ?? defaultTimeoutMs;
|
|
5558
|
+
const timeoutMs = Math.max(opts.timeoutMs ?? defaultTimeoutMs, opts.minTimeoutMs ?? 0);
|
|
5083
5559
|
const id = String(++seq);
|
|
5084
5560
|
const result = await new Promise((resolve) => {
|
|
5085
5561
|
const timer = setTimeout(() => {
|
|
@@ -5342,7 +5818,7 @@ function traceDriver(driver, options) {
|
|
|
5342
5818
|
}
|
|
5343
5819
|
|
|
5344
5820
|
// ../../packages/core/src/a11y/a11y.config.ts
|
|
5345
|
-
import { z as
|
|
5821
|
+
import { z as z8 } from "zod";
|
|
5346
5822
|
var A11Y_RULE_IDS = [
|
|
5347
5823
|
"missing-name",
|
|
5348
5824
|
"image-no-name",
|
|
@@ -5355,11 +5831,11 @@ var A11Y_RULE_IDS = [
|
|
|
5355
5831
|
"text-clipped",
|
|
5356
5832
|
"native"
|
|
5357
5833
|
];
|
|
5358
|
-
var ruleId =
|
|
5359
|
-
var a11yAuditConfigSchema =
|
|
5360
|
-
ignore:
|
|
5361
|
-
minSeverity:
|
|
5362
|
-
rules:
|
|
5834
|
+
var ruleId = z8.enum(A11Y_RULE_IDS);
|
|
5835
|
+
var a11yAuditConfigSchema = z8.object({
|
|
5836
|
+
ignore: z8.array(z8.object({ rule: ruleId, identifier: z8.string().min(1).optional() }).strict()).optional(),
|
|
5837
|
+
minSeverity: z8.enum(["error", "warning"]).optional(),
|
|
5838
|
+
rules: z8.array(ruleId).min(1).optional()
|
|
5363
5839
|
}).strict();
|
|
5364
5840
|
function parseA11yAuditEnv(raw) {
|
|
5365
5841
|
let json;
|
|
@@ -5375,7 +5851,7 @@ function parseA11yAuditEnv(raw) {
|
|
|
5375
5851
|
}
|
|
5376
5852
|
|
|
5377
5853
|
// ../../packages/core/src/conditions/conditions.config.ts
|
|
5378
|
-
import { z as
|
|
5854
|
+
import { z as z9 } from "zod";
|
|
5379
5855
|
|
|
5380
5856
|
// ../../packages/core/src/duration.ts
|
|
5381
5857
|
var UNIT_MS = { ms: 1, s: 1000, m: 60000, h: 3600000, d: 86400000 };
|
|
@@ -5417,6 +5893,10 @@ var TEXT_SIZE_NAMES = [
|
|
|
5417
5893
|
"accessibility-extra-extra-large",
|
|
5418
5894
|
"accessibility-extra-extra-extra-large"
|
|
5419
5895
|
];
|
|
5896
|
+
var KEYBOARD_MODES = ["software", "hardware"];
|
|
5897
|
+
var DEFAULT_KEYBOARD_MODE = "software";
|
|
5898
|
+
var IME_STASH_ENABLED = "warden_ime_enabled";
|
|
5899
|
+
var IME_STASH_DEFAULT = "warden_ime_default";
|
|
5420
5900
|
var CAMERA_FACINGS = ["back", "front"];
|
|
5421
5901
|
var CONDITION_AREAS = [
|
|
5422
5902
|
"network",
|
|
@@ -5426,7 +5906,8 @@ var CONDITION_AREAS = [
|
|
|
5426
5906
|
"locale",
|
|
5427
5907
|
"a11y",
|
|
5428
5908
|
"battery",
|
|
5429
|
-
"camera"
|
|
5909
|
+
"camera",
|
|
5910
|
+
"keyboard"
|
|
5430
5911
|
];
|
|
5431
5912
|
|
|
5432
5913
|
// ../../packages/core/src/conditions/network.presets.ts
|
|
@@ -5536,51 +6017,52 @@ function appliedA11y(op, applied) {
|
|
|
5536
6017
|
|
|
5537
6018
|
// ../../packages/core/src/conditions/conditions.config.ts
|
|
5538
6019
|
var NETWORK_WORDS = ["online", "offline", ...NETWORK_PRESET_NAMES];
|
|
5539
|
-
var nonNegative2 =
|
|
5540
|
-
var customNetworkSchema =
|
|
6020
|
+
var nonNegative2 = z9.number().nonnegative().finite();
|
|
6021
|
+
var customNetworkSchema = z9.object({
|
|
5541
6022
|
latencyMs: nonNegative2.optional(),
|
|
5542
6023
|
jitterMs: nonNegative2.optional(),
|
|
5543
6024
|
downKbps: nonNegative2.optional(),
|
|
5544
6025
|
upKbps: nonNegative2.optional(),
|
|
5545
|
-
lossPct:
|
|
6026
|
+
lossPct: z9.number().min(0).max(100).optional()
|
|
5546
6027
|
}).strict();
|
|
5547
|
-
var isoDate =
|
|
5548
|
-
var offsetDuration =
|
|
5549
|
-
var textSize =
|
|
5550
|
-
var a11yConfigSchema =
|
|
6028
|
+
var isoDate = z9.string().refine((v) => !Number.isNaN(Date.parse(v)), { message: "invalid date (use ISO 8601)" });
|
|
6029
|
+
var offsetDuration = z9.string().refine((v) => parseDuration(v).success, { message: "invalid duration (use e.g. 2d)" });
|
|
6030
|
+
var textSize = z9.union([z9.enum(TEXT_SIZE_NAMES), z9.number().positive().finite()]);
|
|
6031
|
+
var a11yConfigSchema = z9.object({
|
|
5551
6032
|
textSize: textSize.optional(),
|
|
5552
|
-
boldText:
|
|
5553
|
-
contrast:
|
|
5554
|
-
reduceMotion:
|
|
6033
|
+
boldText: z9.boolean().optional(),
|
|
6034
|
+
contrast: z9.enum(["increased", "normal"]).optional(),
|
|
6035
|
+
reduceMotion: z9.boolean().optional()
|
|
5555
6036
|
}).strict();
|
|
5556
|
-
var conditionsConfigSchema =
|
|
5557
|
-
network:
|
|
5558
|
-
location:
|
|
5559
|
-
|
|
5560
|
-
|
|
5561
|
-
lat:
|
|
5562
|
-
lon:
|
|
5563
|
-
altM:
|
|
6037
|
+
var conditionsConfigSchema = z9.object({
|
|
6038
|
+
network: z9.union([z9.enum(NETWORK_WORDS), customNetworkSchema]).optional(),
|
|
6039
|
+
location: z9.union([
|
|
6040
|
+
z9.enum(["city-run", "city-bike", "freeway"]),
|
|
6041
|
+
z9.object({
|
|
6042
|
+
lat: z9.number().min(-90).max(90),
|
|
6043
|
+
lon: z9.number().min(-180).max(180),
|
|
6044
|
+
altM: z9.number().finite().optional()
|
|
5564
6045
|
}).strict()
|
|
5565
6046
|
]).optional(),
|
|
5566
|
-
clock:
|
|
6047
|
+
clock: z9.union([
|
|
5567
6048
|
isoDate,
|
|
5568
|
-
|
|
5569
|
-
|
|
6049
|
+
z9.object({ at: isoDate, frozen: z9.boolean().optional() }).strict(),
|
|
6050
|
+
z9.object({ offset: offsetDuration }).strict()
|
|
5570
6051
|
]).optional(),
|
|
5571
|
-
permissions:
|
|
5572
|
-
locale:
|
|
5573
|
-
timezone:
|
|
5574
|
-
a11y: a11yConfigSchema.optional()
|
|
6052
|
+
permissions: z9.partialRecord(z9.enum(PERMISSION_NAMES), z9.enum(["granted", "denied", "unset"])).optional(),
|
|
6053
|
+
locale: z9.string().min(1).optional(),
|
|
6054
|
+
timezone: z9.string().min(1).optional(),
|
|
6055
|
+
a11y: a11yConfigSchema.optional(),
|
|
6056
|
+
keyboard: z9.enum(KEYBOARD_MODES).optional()
|
|
5575
6057
|
}).strict();
|
|
5576
|
-
var cameraFeedInput =
|
|
5577
|
-
|
|
5578
|
-
|
|
6058
|
+
var cameraFeedInput = z9.union([
|
|
6059
|
+
z9.string().min(1),
|
|
6060
|
+
z9.object({ path: z9.string().min(1), loop: z9.boolean().optional() }).strict()
|
|
5579
6061
|
]);
|
|
5580
|
-
var cameraConfigSchema =
|
|
5581
|
-
var cameraFeedsSchema =
|
|
5582
|
-
back:
|
|
5583
|
-
front:
|
|
6062
|
+
var cameraConfigSchema = z9.object({ back: cameraFeedInput.optional(), front: cameraFeedInput.optional() }).strict().refine((v) => v.back !== undefined || v.front !== undefined, { message: "camera needs a back or a front asset" });
|
|
6063
|
+
var cameraFeedsSchema = z9.object({
|
|
6064
|
+
back: z9.object({ path: z9.string().min(1), loop: z9.boolean() }).strict().optional(),
|
|
6065
|
+
front: z9.object({ path: z9.string().min(1), loop: z9.boolean() }).strict().optional()
|
|
5584
6066
|
}).strict();
|
|
5585
6067
|
function parseCameraFeedsEnv(raw) {
|
|
5586
6068
|
let json;
|
|
@@ -5638,6 +6120,8 @@ function normalizeConditions(config) {
|
|
|
5638
6120
|
conditions.locale = config.locale;
|
|
5639
6121
|
if (config.timezone !== undefined)
|
|
5640
6122
|
conditions.timezone = config.timezone;
|
|
6123
|
+
if (config.keyboard !== undefined)
|
|
6124
|
+
conditions.keyboard = { mode: config.keyboard };
|
|
5641
6125
|
if (config.a11y !== undefined) {
|
|
5642
6126
|
const a11y = { ...config.a11y };
|
|
5643
6127
|
if (config.a11y.textSize !== undefined) {
|
|
@@ -5662,7 +6146,7 @@ function parseConditionsEnv(raw) {
|
|
|
5662
6146
|
}
|
|
5663
6147
|
|
|
5664
6148
|
// ../../packages/e2e/src/env.ts
|
|
5665
|
-
import { z as
|
|
6149
|
+
import { z as z10 } from "zod";
|
|
5666
6150
|
var ENV_NAMES = {
|
|
5667
6151
|
udid: "WARDEN_UDID",
|
|
5668
6152
|
deviceName: "WARDEN_DEVICE_NAME",
|
|
@@ -5700,68 +6184,68 @@ var ENV_NAMES = {
|
|
|
5700
6184
|
var DEFAULT_TIMEOUT_MS5 = 120000;
|
|
5701
6185
|
var DEFAULT_ACTION_TIMEOUT_MS = 30000;
|
|
5702
6186
|
var DEFAULT_ASSERTION_TIMEOUT_MS = 5000;
|
|
5703
|
-
var toggle =
|
|
5704
|
-
var millis =
|
|
5705
|
-
var launchArgs =
|
|
6187
|
+
var toggle = z10.enum(["1", "true", "0", "false"]).transform((v) => v === "1" || v === "true");
|
|
6188
|
+
var millis = z10.string().regex(/^\d+$/, "must be a whole number of milliseconds").transform(Number).pipe(z10.number().int().positive());
|
|
6189
|
+
var launchArgs = z10.string().transform((raw, ctx) => {
|
|
5706
6190
|
try {
|
|
5707
|
-
const parsed =
|
|
6191
|
+
const parsed = z10.array(z10.string()).safeParse(JSON.parse(raw));
|
|
5708
6192
|
if (parsed.success)
|
|
5709
6193
|
return parsed.data;
|
|
5710
6194
|
} catch {}
|
|
5711
6195
|
ctx.addIssue({ code: "custom", message: "must be a JSON array of strings" });
|
|
5712
|
-
return
|
|
6196
|
+
return z10.NEVER;
|
|
5713
6197
|
});
|
|
5714
|
-
var conditions =
|
|
6198
|
+
var conditions = z10.string().transform((raw, ctx) => {
|
|
5715
6199
|
const parsed = parseConditionsEnv(raw);
|
|
5716
6200
|
if (parsed.success)
|
|
5717
6201
|
return parsed.data;
|
|
5718
6202
|
ctx.addIssue({ code: "custom", message: parsed.error });
|
|
5719
|
-
return
|
|
6203
|
+
return z10.NEVER;
|
|
5720
6204
|
});
|
|
5721
|
-
var a11yAudit =
|
|
6205
|
+
var a11yAudit = z10.string().transform((raw, ctx) => {
|
|
5722
6206
|
const parsed = parseA11yAuditEnv(raw);
|
|
5723
6207
|
if (parsed.success)
|
|
5724
6208
|
return parsed.data;
|
|
5725
6209
|
ctx.addIssue({ code: "custom", message: parsed.error });
|
|
5726
|
-
return
|
|
6210
|
+
return z10.NEVER;
|
|
5727
6211
|
});
|
|
5728
|
-
var cameraFeeds =
|
|
6212
|
+
var cameraFeeds = z10.string().transform((raw, ctx) => {
|
|
5729
6213
|
const parsed = parseCameraFeedsEnv(raw);
|
|
5730
6214
|
if (parsed.success)
|
|
5731
6215
|
return parsed.data;
|
|
5732
6216
|
ctx.addIssue({ code: "custom", message: parsed.error });
|
|
5733
|
-
return
|
|
6217
|
+
return z10.NEVER;
|
|
5734
6218
|
});
|
|
5735
6219
|
var REQUIRED = "required when WARDEN_UDID is set";
|
|
5736
|
-
var schema =
|
|
5737
|
-
udid:
|
|
5738
|
-
deviceName:
|
|
5739
|
-
platform:
|
|
5740
|
-
bundleId:
|
|
5741
|
-
appPath:
|
|
5742
|
-
metroUrl:
|
|
5743
|
-
devClientScheme:
|
|
6220
|
+
var schema = z10.object({
|
|
6221
|
+
udid: z10.string().min(1),
|
|
6222
|
+
deviceName: z10.string().optional(),
|
|
6223
|
+
platform: z10.enum(["ios", "android"]).default("ios"),
|
|
6224
|
+
bundleId: z10.string({ error: REQUIRED }).min(1, REQUIRED),
|
|
6225
|
+
appPath: z10.string().optional(),
|
|
6226
|
+
metroUrl: z10.string().optional(),
|
|
6227
|
+
devClientScheme: z10.string().min(1).optional(),
|
|
5744
6228
|
launchArgs: launchArgs.default([]),
|
|
5745
|
-
out:
|
|
6229
|
+
out: z10.string().optional(),
|
|
5746
6230
|
timeout: millis.default(DEFAULT_TIMEOUT_MS5),
|
|
5747
6231
|
actionTimeout: millis.default(DEFAULT_ACTION_TIMEOUT_MS),
|
|
5748
6232
|
assertionTimeout: millis.default(DEFAULT_ASSERTION_TIMEOUT_MS),
|
|
5749
|
-
specsDir:
|
|
5750
|
-
helperDir:
|
|
5751
|
-
simd:
|
|
5752
|
-
simdSocket:
|
|
5753
|
-
iosRunnerSocket:
|
|
6233
|
+
specsDir: z10.string().optional(),
|
|
6234
|
+
helperDir: z10.string().optional(),
|
|
6235
|
+
simd: z10.string().optional(),
|
|
6236
|
+
simdSocket: z10.string().optional(),
|
|
6237
|
+
iosRunnerSocket: z10.string().optional(),
|
|
5754
6238
|
logTimestamps: toggle.default(false),
|
|
5755
|
-
deviceKind:
|
|
6239
|
+
deviceKind: z10.enum(["physical"]).optional(),
|
|
5756
6240
|
iosRunnerRestart: launchArgs.optional(),
|
|
5757
6241
|
afterReboot: launchArgs.optional(),
|
|
5758
|
-
version:
|
|
5759
|
-
bin:
|
|
5760
|
-
perf:
|
|
5761
|
-
perfBudgets:
|
|
6242
|
+
version: z10.string().optional(),
|
|
6243
|
+
bin: z10.string().optional(),
|
|
6244
|
+
perf: z10.string().optional(),
|
|
6245
|
+
perfBudgets: z10.string().optional(),
|
|
5762
6246
|
conditions: conditions.optional(),
|
|
5763
|
-
leaseId:
|
|
5764
|
-
netproxyCommand: launchArgs.pipe(
|
|
6247
|
+
leaseId: z10.string().min(1).optional(),
|
|
6248
|
+
netproxyCommand: launchArgs.pipe(z10.array(z10.string()).min(1)).optional(),
|
|
5765
6249
|
biometrics: toggle.optional(),
|
|
5766
6250
|
a11yAudit: a11yAudit.optional(),
|
|
5767
6251
|
cameraFeeds: cameraFeeds.optional()
|
|
@@ -5771,7 +6255,7 @@ function defined2(values) {
|
|
|
5771
6255
|
}
|
|
5772
6256
|
function parseArgv(raw) {
|
|
5773
6257
|
try {
|
|
5774
|
-
const parsed =
|
|
6258
|
+
const parsed = z10.array(z10.string()).min(1).safeParse(JSON.parse(raw));
|
|
5775
6259
|
if (parsed.success)
|
|
5776
6260
|
return parsed.data;
|
|
5777
6261
|
} catch {}
|
|
@@ -5841,25 +6325,43 @@ function readEnv(env, opts = {}) {
|
|
|
5841
6325
|
// ../../packages/e2e/src/runner/results.ts
|
|
5842
6326
|
import { appendFileSync, mkdirSync as mkdirSync2 } from "fs";
|
|
5843
6327
|
import { dirname as dirname3 } from "path";
|
|
5844
|
-
import { z as
|
|
5845
|
-
var
|
|
5846
|
-
|
|
5847
|
-
|
|
5848
|
-
|
|
6328
|
+
import { z as z11 } from "zod";
|
|
6329
|
+
var bridgeReadSchema = z11.object({
|
|
6330
|
+
reason: z11.string().optional(),
|
|
6331
|
+
code: z11.string().optional(),
|
|
6332
|
+
stage: z11.string().optional(),
|
|
6333
|
+
readMs: z11.number().optional(),
|
|
6334
|
+
deadlineMs: z11.number().optional(),
|
|
6335
|
+
guestAgeMs: z11.number().optional(),
|
|
6336
|
+
cold: z11.boolean().optional(),
|
|
6337
|
+
cooldownRemainingMs: z11.number().optional(),
|
|
6338
|
+
probes: z11.number().optional(),
|
|
6339
|
+
recycles: z11.number().optional(),
|
|
6340
|
+
recyclesExhausted: z11.boolean().optional()
|
|
6341
|
+
});
|
|
6342
|
+
var testStatusSchema = z11.enum(["passed", "failed", "skipped", "timed-out"]);
|
|
6343
|
+
var testResultSchema = z11.object({
|
|
6344
|
+
file: z11.string(),
|
|
6345
|
+
title: z11.array(z11.string()),
|
|
5849
6346
|
status: testStatusSchema,
|
|
5850
|
-
durationMs:
|
|
5851
|
-
startedAt:
|
|
5852
|
-
only:
|
|
5853
|
-
error:
|
|
5854
|
-
message:
|
|
5855
|
-
code:
|
|
5856
|
-
stack:
|
|
6347
|
+
durationMs: z11.number(),
|
|
6348
|
+
startedAt: z11.number(),
|
|
6349
|
+
only: z11.boolean().optional(),
|
|
6350
|
+
error: z11.object({
|
|
6351
|
+
message: z11.string(),
|
|
6352
|
+
code: z11.string().optional(),
|
|
6353
|
+
stack: z11.string().optional()
|
|
5857
6354
|
}).optional(),
|
|
5858
|
-
artifacts:
|
|
5859
|
-
|
|
5860
|
-
|
|
5861
|
-
|
|
5862
|
-
|
|
6355
|
+
artifacts: z11.object({ dir: z11.string(), files: z11.array(z11.string()) }).optional(),
|
|
6356
|
+
bridge: z11.object({
|
|
6357
|
+
fallbacks: z11.number(),
|
|
6358
|
+
first: bridgeReadSchema,
|
|
6359
|
+
last: bridgeReadSchema
|
|
6360
|
+
}).optional(),
|
|
6361
|
+
device: z11.object({
|
|
6362
|
+
udid: z11.string(),
|
|
6363
|
+
platform: z11.enum(["ios", "android"]),
|
|
6364
|
+
name: z11.string().optional()
|
|
5863
6365
|
})
|
|
5864
6366
|
});
|
|
5865
6367
|
function openResults(path) {
|
|
@@ -5995,6 +6497,8 @@ ${read.error}`);
|
|
|
5995
6497
|
throw new E2eError2("SESSION", "not running under warden: WARDEN_UDID is not set");
|
|
5996
6498
|
return read.data;
|
|
5997
6499
|
}
|
|
6500
|
+
var AX_WEDGE_WAIT_MS = 60000;
|
|
6501
|
+
var AX_WEDGE_POLL_MS = 1000;
|
|
5998
6502
|
var WARM_TRIES = 15;
|
|
5999
6503
|
var WARM_INTERVAL_MS = 2000;
|
|
6000
6504
|
function createSession(opts = {}) {
|
|
@@ -6039,6 +6543,27 @@ function createSession(opts = {}) {
|
|
|
6039
6543
|
await sleep(WARM_INTERVAL_MS);
|
|
6040
6544
|
}
|
|
6041
6545
|
}
|
|
6546
|
+
let axWedgeRerunFor = 0;
|
|
6547
|
+
async function recoverAxWedge(attemptMs) {
|
|
6548
|
+
const driver = current;
|
|
6549
|
+
if (closing !== undefined || driver === undefined || !canRecoverAxWedge(driver))
|
|
6550
|
+
return false;
|
|
6551
|
+
const sleep = hid?.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
6552
|
+
let state = await driver.axWedge();
|
|
6553
|
+
let waited = 0;
|
|
6554
|
+
while (state.success && state.data.recovering && waited < AX_WEDGE_WAIT_MS) {
|
|
6555
|
+
await sleep(AX_WEDGE_POLL_MS);
|
|
6556
|
+
waited += AX_WEDGE_POLL_MS;
|
|
6557
|
+
state = await driver.axWedge();
|
|
6558
|
+
}
|
|
6559
|
+
if (!state.success || state.data.restarts <= axWedgeRerunFor)
|
|
6560
|
+
return false;
|
|
6561
|
+
if (!restartedWithin(state.data, attemptMs + waited))
|
|
6562
|
+
return false;
|
|
6563
|
+
axWedgeRerunFor = state.data.restarts;
|
|
6564
|
+
note(`sim ${resolved.deviceName ?? resolved.udid}: accessibility service wedged, warden-simd restarted backboardd (the app was quit); running the test again`);
|
|
6565
|
+
return true;
|
|
6566
|
+
}
|
|
6042
6567
|
async function recoverRunner(deps, name) {
|
|
6043
6568
|
if (hidRecovered) {
|
|
6044
6569
|
note(`iPhone ${name}: the runner connection was lost again after a restart; check the phone and retry`);
|
|
@@ -6116,6 +6641,7 @@ function createSession(opts = {}) {
|
|
|
6116
6641
|
fixtures: opts.fixtures ?? defaultFixtures,
|
|
6117
6642
|
nativeLog: opts.nativeLog ?? defaultNativeLog,
|
|
6118
6643
|
recoverHid: hid === undefined || resolved.platform !== "ios" ? undefined : recoverHid,
|
|
6644
|
+
recoverAxWedge: resolved.platform !== "ios" || resolved.deviceKind === "physical" ? undefined : recoverAxWedge,
|
|
6119
6645
|
resetDevice,
|
|
6120
6646
|
log: opts.log ?? writeLine,
|
|
6121
6647
|
get attempt() {
|
|
@@ -6254,6 +6780,9 @@ function attemptSession(session, binding) {
|
|
|
6254
6780
|
get recoverHid() {
|
|
6255
6781
|
return session.recoverHid;
|
|
6256
6782
|
},
|
|
6783
|
+
get recoverAxWedge() {
|
|
6784
|
+
return session.recoverAxWedge;
|
|
6785
|
+
},
|
|
6257
6786
|
resetDevice: () => session.resetDevice(),
|
|
6258
6787
|
get fixtures() {
|
|
6259
6788
|
return session.fixtures;
|
|
@@ -6884,15 +7413,15 @@ function runAudit(snapshot, platform, opts = {}) {
|
|
|
6884
7413
|
function failing(result, minSeverity = "error") {
|
|
6885
7414
|
return result.issues.filter((i) => SEVERITY_RANK[i.severity] >= SEVERITY_RANK[minSeverity]);
|
|
6886
7415
|
}
|
|
6887
|
-
function
|
|
7416
|
+
function elementKey2(i) {
|
|
6888
7417
|
return i.identifier ?? i.elementId;
|
|
6889
7418
|
}
|
|
6890
7419
|
function mergeIssues(base, extra, skipped = []) {
|
|
6891
7420
|
const issues = [...base.issues];
|
|
6892
7421
|
const added = [];
|
|
6893
7422
|
for (const e of extra) {
|
|
6894
|
-
const key =
|
|
6895
|
-
const idx = issues.findIndex((i) => key !== undefined &&
|
|
7423
|
+
const key = elementKey2(e);
|
|
7424
|
+
const idx = issues.findIndex((i) => key !== undefined && elementKey2(i) === key && (e.wcag !== undefined ? i.wcag === e.wcag : i.rule === e.rule && i.message === e.message));
|
|
6896
7425
|
const hit = idx === -1 ? undefined : issues[idx];
|
|
6897
7426
|
if (hit === undefined) {
|
|
6898
7427
|
added.push(e);
|
|
@@ -7235,6 +7764,12 @@ function createDeviceA11y(deps) {
|
|
|
7235
7764
|
const merged = await withA11yRead(driver, taken.data);
|
|
7236
7765
|
return renderA11yScreen(merged.success ? merged.data : taken.data);
|
|
7237
7766
|
},
|
|
7767
|
+
async keyboardVisible() {
|
|
7768
|
+
const state = await settled(() => driver.keyboardState(), "keyboardState", signal());
|
|
7769
|
+
if (!state.success)
|
|
7770
|
+
throw toE2eError(state.error, "read the keyboard");
|
|
7771
|
+
return state.data.visible;
|
|
7772
|
+
},
|
|
7238
7773
|
assertionTimeout: () => env.assertionTimeoutMs,
|
|
7239
7774
|
step: (label, fn) => deps.step === undefined ? fn() : deps.step(label, fn)
|
|
7240
7775
|
};
|
|
@@ -8030,6 +8565,7 @@ var KNOWN_AREAS = new Set([
|
|
|
8030
8565
|
"locale",
|
|
8031
8566
|
"a11y",
|
|
8032
8567
|
"battery",
|
|
8568
|
+
"keyboard",
|
|
8033
8569
|
"camera"
|
|
8034
8570
|
]);
|
|
8035
8571
|
|
|
@@ -8403,6 +8939,8 @@ function createDeviceConditions(deps) {
|
|
|
8403
8939
|
let localeDirty = false;
|
|
8404
8940
|
let a11yDirty = false;
|
|
8405
8941
|
let batteryDirty = false;
|
|
8942
|
+
let keyboardDirty = false;
|
|
8943
|
+
let currentKeyboard = DEFAULT_KEYBOARD_MODE;
|
|
8406
8944
|
let cameraDirty = false;
|
|
8407
8945
|
let currentCamera = {};
|
|
8408
8946
|
const permissionStates = new Map;
|
|
@@ -8779,6 +9317,28 @@ the app runs without the warden network dylib: declare \`conditions.network\` fo
|
|
|
8779
9317
|
emit({ area: "battery", value });
|
|
8780
9318
|
return state;
|
|
8781
9319
|
}
|
|
9320
|
+
const ownsDevice = recorder !== undefined && env.deviceKind !== "physical";
|
|
9321
|
+
const keyboardGoal = baseline.keyboard?.mode ?? DEFAULT_KEYBOARD_MODE;
|
|
9322
|
+
async function applyKeyboard(input) {
|
|
9323
|
+
const mode = input === "toggle" ? currentKeyboard === "software" ? "hardware" : "software" : input;
|
|
9324
|
+
if (!KEYBOARD_MODES.includes(mode)) {
|
|
9325
|
+
throw invalid2(`unknown keyboard mode "${String(input)}" (use software, hardware or toggle)`);
|
|
9326
|
+
}
|
|
9327
|
+
keyboardDirty = true;
|
|
9328
|
+
const run = settingOp("keyboard");
|
|
9329
|
+
const state = await call("keyboard", () => run(mode));
|
|
9330
|
+
currentKeyboard = mode;
|
|
9331
|
+
const value = {
|
|
9332
|
+
mode,
|
|
9333
|
+
...state.previousIme === undefined ? {} : { previousIme: state.previousIme }
|
|
9334
|
+
};
|
|
9335
|
+
if (mode === DEFAULT_KEYBOARD_MODE)
|
|
9336
|
+
recorder?.settle("keyboard", value, true);
|
|
9337
|
+
else
|
|
9338
|
+
recorder?.record("keyboard", value);
|
|
9339
|
+
emit({ area: "keyboard", value });
|
|
9340
|
+
return state;
|
|
9341
|
+
}
|
|
8782
9342
|
const home = deps.wardenHome ?? wardenHome(process.env);
|
|
8783
9343
|
async function applyCamera(next) {
|
|
8784
9344
|
cameraDirty = true;
|
|
@@ -8881,7 +9441,7 @@ the app runs without the warden network dylib: declare \`conditions.network\` fo
|
|
|
8881
9441
|
throw new E2eError2("ACTION_FAILED", `a network flap or timeline stopped on its own after failing: ${failures[0]}`);
|
|
8882
9442
|
}
|
|
8883
9443
|
}
|
|
8884
|
-
const declared = baseline.network !== undefined || baseline.location !== undefined || baseline.clock !== undefined || baseline.permissions !== undefined || baseline.locale !== undefined || baseline.timezone !== undefined || baseline.a11y !== undefined || env.cameraFeeds !== undefined;
|
|
9444
|
+
const declared = baseline.network !== undefined || baseline.location !== undefined || baseline.clock !== undefined || baseline.permissions !== undefined || baseline.locale !== undefined || baseline.timezone !== undefined || baseline.a11y !== undefined || baseline.keyboard !== undefined || ownsDevice || env.cameraFeeds !== undefined;
|
|
8885
9445
|
async function applyBaselinePermissions() {
|
|
8886
9446
|
if (baseline.permissions === undefined)
|
|
8887
9447
|
return;
|
|
@@ -8918,6 +9478,10 @@ the app runs without the warden network dylib: declare \`conditions.network\` fo
|
|
|
8918
9478
|
async () => {
|
|
8919
9479
|
if (env.cameraFeeds !== undefined)
|
|
8920
9480
|
await applyCamera(baselineCamera());
|
|
9481
|
+
},
|
|
9482
|
+
async () => {
|
|
9483
|
+
if (baseline.keyboard !== undefined || ownsDevice)
|
|
9484
|
+
await applyKeyboard(keyboardGoal);
|
|
8921
9485
|
}
|
|
8922
9486
|
];
|
|
8923
9487
|
const teardownSteps = [
|
|
@@ -8957,6 +9521,13 @@ the app runs without the warden network dylib: declare \`conditions.network\` fo
|
|
|
8957
9521
|
batteryDirty = false;
|
|
8958
9522
|
}
|
|
8959
9523
|
},
|
|
9524
|
+
{
|
|
9525
|
+
dirty: () => keyboardDirty,
|
|
9526
|
+
run: async () => {
|
|
9527
|
+
await applyKeyboard(keyboardGoal);
|
|
9528
|
+
keyboardDirty = false;
|
|
9529
|
+
}
|
|
9530
|
+
},
|
|
8960
9531
|
{
|
|
8961
9532
|
dirty: () => cameraDirty,
|
|
8962
9533
|
run: async () => {
|
|
@@ -8967,7 +9538,7 @@ the app runs without the warden network dylib: declare \`conditions.network\` fo
|
|
|
8967
9538
|
];
|
|
8968
9539
|
const internals = {
|
|
8969
9540
|
declared,
|
|
8970
|
-
dirty: () => networkDirty || locationDirty || clockDirty || permissionsDirty || localeDirty || a11yDirty || batteryDirty || cameraDirty || runs.size > 0,
|
|
9541
|
+
dirty: () => networkDirty || locationDirty || clockDirty || permissionsDirty || localeDirty || a11yDirty || batteryDirty || keyboardDirty || cameraDirty || runs.size > 0,
|
|
8971
9542
|
wantsInject: () => baseline.network !== undefined || baseline.clock !== undefined || currentNetwork.kind !== "online" || currentClock.kind !== "real",
|
|
8972
9543
|
async applyBaseline() {
|
|
8973
9544
|
for (const step of baselineSteps)
|
|
@@ -8980,6 +9551,7 @@ the app runs without the warden network dylib: declare \`conditions.network\` fo
|
|
|
8980
9551
|
localeDirty = false;
|
|
8981
9552
|
a11yDirty = false;
|
|
8982
9553
|
batteryDirty = false;
|
|
9554
|
+
keyboardDirty = false;
|
|
8983
9555
|
},
|
|
8984
9556
|
async teardown() {
|
|
8985
9557
|
const errors = [];
|
|
@@ -9008,6 +9580,7 @@ the app runs without the warden network dylib: declare \`conditions.network\` fo
|
|
|
9008
9580
|
locale: applyLocale,
|
|
9009
9581
|
a11y: applyA11y,
|
|
9010
9582
|
battery: applyBattery,
|
|
9583
|
+
keyboard: applyKeyboard,
|
|
9011
9584
|
camera: cameraApi,
|
|
9012
9585
|
memoryWarning: async (options) => {
|
|
9013
9586
|
const op = settingOp("memoryWarning");
|
|
@@ -9327,6 +9900,6 @@ function onlyWasSeen() {
|
|
|
9327
9900
|
|
|
9328
9901
|
// ../../packages/e2e/src/version.ts
|
|
9329
9902
|
var WORKSPACE_VERSION = "0.0.0-workspace";
|
|
9330
|
-
var WARDEN_E2E_VERSION2 = "0.
|
|
9903
|
+
var WARDEN_E2E_VERSION2 = "0.15.0";
|
|
9331
9904
|
|
|
9332
|
-
export { E2eError2, registerA11yTarget, a11yTargetOf, canActOnElements, noteSpanDetail, addSpanDetail, canObserve, canDetectAlerts, settleDetailOf, DEFAULT_SETTLE_QUIET_MS, DEFAULT_SETTLE_TIMEOUT_MS, settleByHash, snapshotHash, ok, err, swipeVector, screenRect, fieldValue, PINCH_MS, ROTATE_MS, PATH_MS, centreOf, pinchRadius, rotateRadius, requireGesture, assertScale, assertDegrees, isInteractiveRole, resolveRole, normalizeText, matchesText, indexSnapshot, testIdQuery, textQuery, roleQuery, selectorExpression, renderSnapshot, renderHeader,
|
|
9905
|
+
export { E2eError2, registerA11yTarget, a11yTargetOf, canActOnElements, noteSpanDetail, addSpanDetail, canObserve, canDetectAlerts, settleDetailOf, DEFAULT_SETTLE_QUIET_MS, DEFAULT_SETTLE_TIMEOUT_MS, hashFallbackQuietMs, settleByHash, snapshotHash, ok, err, swipeVector, screenRect, isOnScreen, fieldValue, PINCH_MS, ROTATE_MS, PATH_MS, centreOf, pinchRadius, rotateRadius, requireGesture, assertScale, assertDegrees, isInteractiveRole, resolveRole, normalizeText, matchesText, indexSnapshot, keyboardVisible, testIdQuery, textQuery, roleQuery, selectorExpression, renderSnapshot, renderHeader, lookUpTestIdViaReact, isLocator, Locator, pickOne, textOf, scrollUntilVisible, formatNativeTail, realNativeLogDeps, captureNativeLog, bunExec, execError, driverError, driverErr, bridgeStallSchema, toBridgeStall, noteBridgeStall, bridgeStallFromSpan, formatBridgeStall, logEventSchema, fatalEventSchema, responseSchema, mapWireError, createLineSplitter, encodeRequest, bunSpawn, stdioTransport, socketTransport, createSimdClient, spawnSimd, connectSimd, isRecoverableLoss, parseDuration, KEYBOARD_MODES, DEFAULT_KEYBOARD_MODE, IME_STASH_ENABLED, IME_STASH_DEFAULT, CAMERA_FACINGS, CONDITION_AREAS, resolveNetwork, resolveTextSize, androidFontScale, ENV_NAMES, DEFAULT_ASSERTION_TIMEOUT_MS, readEnv, runStep, setDefaultHidRecovery, setDefaultNativeLog, setDefaultDriverFactory, hasDefaultDriverFactory, setDefaultFixtureFactory, hasDefaultFixtureFactory, attemptSession, getSession, peekSession, accessibleName, announce, failing, formatIssues, toE2eError, settled, unwrap, parseUiadA11yEvents, parseInjectEvents, withA11yRead, renderA11yScreen, createDeviceA11y, IOS_DEVICE_CAPABILITIES, sniffCameraMedia, cameraFeedDir, clearCameraFeeds, isLeaseAlive, processAlive, describeOwner, isPhysicalIosId, wardenHome, defaultDbPath, ownerKey, openStore, callerFile, formatCoord, DEFAULT_SPEED_MPS, DEFAULT_INTERVAL_S, realTickerClock, startRouteTicker, startFlap, registerConditions, conditionsOf, conditionSinkFor, createDeviceConditions, parseBudgets, createDevicePerf, perfStartFor, spawnWarden, hash6, attemptDir, screenshotPath, specIdOf, BUN_TIMEOUT_MARGIN_MS2, ownHook, currentScope, createScope, runInScope, hooksFor, resolveUse, registrationDefaults, markOnly, onlyWasSeen, WORKSPACE_VERSION, WARDEN_E2E_VERSION2 };
|