@delacour/warden-e2e 0.3.1 → 0.5.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-8em9kgsk.mjs → chunk-j9b17ade.mjs} +200 -37
- package/index.mjs +1 -1
- package/package.json +2 -2
- package/preload.mjs +61 -10
- package/types/core/src/affected/affected.schema.d.ts +185 -0
- package/types/core/src/batch/preset.schema.d.ts +59 -0
- package/types/core/src/builds/app-option.schema.d.ts +9 -1
- package/types/core/src/builds/builds.defaults.d.ts +62 -0
- package/types/core/src/builds/config.d.ts +122 -0
- package/types/core/src/builds/config.schema.d.ts +392 -0
- package/types/core/src/builds/define-config.d.ts +719 -0
- package/types/core/src/builds/kinds.d.ts +56 -0
- package/types/core/src/builds/native-ids.d.ts +14 -0
- package/types/core/src/builds/resolve.d.ts +26 -0
- package/types/core/src/duration.d.ts +5 -0
- package/types/core/src/exec.d.ts +2 -0
- package/types/core/src/ports.d.ts +76 -0
- package/types/core/src/providers/provider.types.d.ts +7 -0
- package/types/core/src/sims/slim.profile.d.ts +54 -0
- package/types/e2e/src/devtools/cdp.d.ts +7 -2
- package/types/e2e/src/devtools/devtools.types.d.ts +13 -0
- package/types/e2e/src/devtools/facet.d.ts +2 -0
- package/types/e2e/src/devtools/logbox.d.ts +27 -0
- package/types/e2e/src/devtools/logbox.script.d.ts +17 -0
- package/types/e2e/src/devtools/session.d.ts +8 -1
- package/types/e2e/src/driver/ios/ios-driver.d.ts +7 -6
- package/types/e2e/src/driver/ios/simd-protocol.d.ts +23 -0
- package/types/e2e/src/driver/ios/type-budget.d.ts +8 -4
- package/types/e2e/src/driver/observation-cache.d.ts +3 -1
- package/types/e2e/src/driver/typing.d.ts +10 -0
- package/types/e2e/src/preload.d.ts +5 -0
- package/types/e2e/src/screen/field.d.ts +11 -1
- package/types/e2e/src/screen/locator.d.ts +24 -5
- package/types/e2e/src/session.d.ts +4 -1
|
@@ -64,10 +64,17 @@ var READS = new Set([
|
|
|
64
64
|
"previous",
|
|
65
65
|
"peek"
|
|
66
66
|
]);
|
|
67
|
+
var WAITS = new Set(["settle"]);
|
|
67
68
|
var READS_WITHOUT_ARGUMENT = new Set(["orientation", "appearance", "clipboard"]);
|
|
68
69
|
function isCall(value) {
|
|
69
70
|
return typeof value === "function";
|
|
70
71
|
}
|
|
72
|
+
function reportsQuiet(result) {
|
|
73
|
+
if (typeof result !== "object" || result === null || !("success" in result) || result.success !== true)
|
|
74
|
+
return false;
|
|
75
|
+
const data = "data" in result ? result.data : undefined;
|
|
76
|
+
return typeof data === "object" && data !== null && "quiet" in data && data.quiet === true;
|
|
77
|
+
}
|
|
71
78
|
function isThenable(value) {
|
|
72
79
|
return typeof value === "object" && value !== null && "then" in value && typeof value.then === "function";
|
|
73
80
|
}
|
|
@@ -137,21 +144,31 @@ function cacheObservations(driver, options = {}) {
|
|
|
137
144
|
return { success: true, data: reader.alertIn(taken.data) };
|
|
138
145
|
};
|
|
139
146
|
};
|
|
140
|
-
const
|
|
147
|
+
const edged = (fn, target, edge) => {
|
|
141
148
|
return (...args) => {
|
|
142
|
-
|
|
149
|
+
edge();
|
|
143
150
|
let result;
|
|
144
151
|
try {
|
|
145
152
|
result = Reflect.apply(fn, target, args);
|
|
146
153
|
} catch (error) {
|
|
147
|
-
|
|
154
|
+
edge();
|
|
148
155
|
throw error;
|
|
149
156
|
}
|
|
150
157
|
if (!isThenable(result)) {
|
|
151
|
-
|
|
158
|
+
edge();
|
|
152
159
|
return result;
|
|
153
160
|
}
|
|
154
|
-
return Promise.resolve(result).finally(
|
|
161
|
+
return Promise.resolve(result).finally(edge);
|
|
162
|
+
};
|
|
163
|
+
};
|
|
164
|
+
const asInput = (fn, target) => edged(fn, target, inputEdge);
|
|
165
|
+
const asWait = (fn, target) => {
|
|
166
|
+
const waited = edged(fn, target, moveOn);
|
|
167
|
+
return async (...args) => {
|
|
168
|
+
const result = await waited(...args);
|
|
169
|
+
if (reportsQuiet(result))
|
|
170
|
+
lastInputAt = undefined;
|
|
171
|
+
return result;
|
|
155
172
|
};
|
|
156
173
|
};
|
|
157
174
|
const wrapMember = (name, value, target) => {
|
|
@@ -159,6 +176,8 @@ function cacheObservations(driver, options = {}) {
|
|
|
159
176
|
return wrap(value, {});
|
|
160
177
|
if (!isCall(value) || READS.has(name))
|
|
161
178
|
return value;
|
|
179
|
+
if (WAITS.has(name))
|
|
180
|
+
return asWait(value, target);
|
|
162
181
|
const input = asInput(value, target);
|
|
163
182
|
if (!READS_WITHOUT_ARGUMENT.has(name))
|
|
164
183
|
return input;
|
|
@@ -271,6 +290,9 @@ function canSelectAll(driver) {
|
|
|
271
290
|
function canSetText(driver) {
|
|
272
291
|
return typeof driver.setText === "function";
|
|
273
292
|
}
|
|
293
|
+
function canPasteText(driver) {
|
|
294
|
+
return typeof driver.pasteText === "function";
|
|
295
|
+
}
|
|
274
296
|
var segmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" });
|
|
275
297
|
function graphemesOf(text) {
|
|
276
298
|
return Array.from(segmenter.segment(text), (part) => part.segment);
|
|
@@ -921,7 +943,7 @@ async function focusAndClear(driver, sleep, point, entry, method = "auto") {
|
|
|
921
943
|
return prepared;
|
|
922
944
|
return prepared.data.pending === "selection" ? pressKeys(driver, sleep, "Backspace", 1) : ok(undefined);
|
|
923
945
|
}
|
|
924
|
-
async function focusClearType(driver, sleep, point, entry, text, method = "auto", env = process.env) {
|
|
946
|
+
async function focusClearType(driver, sleep, point, entry, text, method = "auto", env = process.env, via = "paste") {
|
|
925
947
|
const wantsSetText = method === "setText" || method === "auto" && setTextOptedIn(env);
|
|
926
948
|
if (wantsSetText && entry !== undefined && fieldContent(entry).kind === "text" && canSetText(driver)) {
|
|
927
949
|
const focused = await driver.tap(point);
|
|
@@ -930,15 +952,20 @@ async function focusClearType(driver, sleep, point, entry, text, method = "auto"
|
|
|
930
952
|
const replaced = await driver.setText(text);
|
|
931
953
|
if (replaced.success)
|
|
932
954
|
return replaced;
|
|
933
|
-
return typeOver(driver, sleep, await prepareFocused(driver, sleep, entry, method), text);
|
|
955
|
+
return typeOver(driver, sleep, await prepareFocused(driver, sleep, entry, method), text, "type");
|
|
934
956
|
}
|
|
935
|
-
return typeOver(driver, sleep, await focusAndSelect(driver, sleep, point, entry, method), text);
|
|
957
|
+
return typeOver(driver, sleep, await focusAndSelect(driver, sleep, point, entry, method), text, via);
|
|
936
958
|
}
|
|
937
|
-
async function typeOver(driver, sleep, prepared, text) {
|
|
959
|
+
async function typeOver(driver, sleep, prepared, text, via) {
|
|
938
960
|
if (!prepared.success)
|
|
939
961
|
return prepared;
|
|
940
962
|
if (text === "")
|
|
941
963
|
return prepared.data.pending === "selection" ? pressKeys(driver, sleep, "Backspace", 1) : ok(undefined);
|
|
964
|
+
if (via === "paste" && canPasteText(driver)) {
|
|
965
|
+
const pasted = await driver.pasteText(text);
|
|
966
|
+
if (pasted.success)
|
|
967
|
+
return pasted;
|
|
968
|
+
}
|
|
942
969
|
return typeText(driver, sleep, text);
|
|
943
970
|
}
|
|
944
971
|
|
|
@@ -1750,10 +1777,20 @@ class Locator {
|
|
|
1750
1777
|
const reason = blockedReason(entry, spec, observed);
|
|
1751
1778
|
if (reason !== undefined)
|
|
1752
1779
|
return { done: false, state: { kind: "blocked", reason, snapshot: observed.snapshot } };
|
|
1753
|
-
if (!anyFrame && !this.heldStill(entry, observation.earlier))
|
|
1754
|
-
|
|
1780
|
+
if (!anyFrame && !this.heldStill(entry, observation.earlier)) {
|
|
1781
|
+
const compared = observation.earlier?.previous !== undefined;
|
|
1782
|
+
if (compared || !await this.stillByHitTest(entry, observed))
|
|
1783
|
+
return "moving";
|
|
1784
|
+
}
|
|
1755
1785
|
return { done: true, value: { entry, observed } };
|
|
1756
1786
|
}
|
|
1787
|
+
async stillByHitTest(entry, observed) {
|
|
1788
|
+
const { driver } = observed;
|
|
1789
|
+
if (driver.capabilities.cheapHitTest !== true || this.rt.host.answerSystemAlert !== undefined)
|
|
1790
|
+
return false;
|
|
1791
|
+
const hit = await driver.elementAtPoint(tapPointOf(entry));
|
|
1792
|
+
return hit.success && hit.data !== null && sameElement(hit.data, entry.element);
|
|
1793
|
+
}
|
|
1757
1794
|
heldStill(entry, earlier) {
|
|
1758
1795
|
if (earlier === undefined)
|
|
1759
1796
|
return true;
|
|
@@ -1851,14 +1888,15 @@ class Locator {
|
|
|
1851
1888
|
});
|
|
1852
1889
|
}
|
|
1853
1890
|
async fill(text, options = {}) {
|
|
1854
|
-
assertOptions("fill", options, ["timeout"]);
|
|
1891
|
+
assertOptions("fill", options, ["via", "timeout"]);
|
|
1892
|
+
const via = fillViaOf(this, options.via);
|
|
1855
1893
|
return this.act("fill", async () => {
|
|
1856
1894
|
const budget = this.rt.budget(options.timeout);
|
|
1857
1895
|
let target = await this.actionable({ enabled: true, tapPointOnScreen: false }, budget.remaining());
|
|
1858
1896
|
let firstHeld;
|
|
1859
1897
|
for (let attempt = 1;; attempt += 1) {
|
|
1860
1898
|
const point = focusPoint(target.entry.element.frame);
|
|
1861
|
-
this.rt.drive("fill", await focusClearType(target.observed.driver, this.pause, point, target.entry, text, clearMethodFor(attempt)));
|
|
1899
|
+
this.rt.drive("fill", await focusClearType(target.observed.driver, this.pause, point, target.entry, text, clearMethodFor(attempt), process.env, viaFor(attempt, via)));
|
|
1862
1900
|
if (target.entry.secure)
|
|
1863
1901
|
return;
|
|
1864
1902
|
const outcome = await this.confirm(target, text, budget);
|
|
@@ -2067,6 +2105,14 @@ function retryPlan(options) {
|
|
|
2067
2105
|
function describeUntil(until) {
|
|
2068
2106
|
return typeof until === "function" ? "check" : String(until);
|
|
2069
2107
|
}
|
|
2108
|
+
function fillViaOf(locator, via) {
|
|
2109
|
+
if (via === undefined || via === "paste" || via === "type")
|
|
2110
|
+
return via ?? "paste";
|
|
2111
|
+
throw new E2eError2("INVALID_ARGUMENT", `${locator}: fill via must be "paste" or "type", got ${JSON.stringify(via)}`);
|
|
2112
|
+
}
|
|
2113
|
+
function viaFor(attempt, via) {
|
|
2114
|
+
return attempt === 1 ? via : "type";
|
|
2115
|
+
}
|
|
2070
2116
|
function clearMethodFor(attempt) {
|
|
2071
2117
|
return attempt === 1 ? "auto" : "counted";
|
|
2072
2118
|
}
|
|
@@ -2313,7 +2359,7 @@ async function openCdp(wsUrl, options = {}) {
|
|
|
2313
2359
|
if (method)
|
|
2314
2360
|
dispatch(method, params);
|
|
2315
2361
|
});
|
|
2316
|
-
ws.addEventListener("close", () => {
|
|
2362
|
+
ws.addEventListener("close", (event) => {
|
|
2317
2363
|
if (closed)
|
|
2318
2364
|
return;
|
|
2319
2365
|
closed = true;
|
|
@@ -2326,7 +2372,7 @@ async function openCdp(wsUrl, options = {}) {
|
|
|
2326
2372
|
binding.settle(devtoolsErr("cdp_closed", "CDP connection closed"));
|
|
2327
2373
|
if (!intentional)
|
|
2328
2374
|
for (const handler of [...closeHandlers])
|
|
2329
|
-
handler();
|
|
2375
|
+
handler({ code: event.code, reason: event.reason });
|
|
2330
2376
|
});
|
|
2331
2377
|
const subscribe = (set, handler) => {
|
|
2332
2378
|
set.add(handler);
|
|
@@ -2427,7 +2473,8 @@ var stackTraceSchema = z2.looseObject({
|
|
|
2427
2473
|
callFrames: z2.array(z2.looseObject({
|
|
2428
2474
|
url: z2.string().optional(),
|
|
2429
2475
|
lineNumber: z2.number().optional(),
|
|
2430
|
-
columnNumber: z2.number().optional()
|
|
2476
|
+
columnNumber: z2.number().optional(),
|
|
2477
|
+
functionName: z2.string().optional()
|
|
2431
2478
|
}))
|
|
2432
2479
|
});
|
|
2433
2480
|
var consoleCalledSchema = z2.looseObject({
|
|
@@ -2438,7 +2485,7 @@ var consoleCalledSchema = z2.looseObject({
|
|
|
2438
2485
|
});
|
|
2439
2486
|
var exceptionThrownSchema = z2.looseObject({
|
|
2440
2487
|
timestamp: z2.number().optional(),
|
|
2441
|
-
exceptionDetails: exceptionDetailsSchema
|
|
2488
|
+
exceptionDetails: exceptionDetailsSchema.extend({ stackTrace: stackTraceSchema.optional() })
|
|
2442
2489
|
});
|
|
2443
2490
|
var LEVELS = {
|
|
2444
2491
|
log: "log",
|
|
@@ -2529,6 +2576,20 @@ function sourceOf(url, line, col) {
|
|
|
2529
2576
|
return;
|
|
2530
2577
|
return { url, line: (line ?? 0) + 1, col: (col ?? 0) + 1 };
|
|
2531
2578
|
}
|
|
2579
|
+
function framesOf(trace) {
|
|
2580
|
+
const frames = [];
|
|
2581
|
+
for (const frame of trace?.callFrames ?? []) {
|
|
2582
|
+
if (!frame.url)
|
|
2583
|
+
continue;
|
|
2584
|
+
frames.push({
|
|
2585
|
+
file: frame.url,
|
|
2586
|
+
line: (frame.lineNumber ?? 0) + 1,
|
|
2587
|
+
col: (frame.columnNumber ?? 0) + 1,
|
|
2588
|
+
...frame.functionName ? { fn: frame.functionName } : {}
|
|
2589
|
+
});
|
|
2590
|
+
}
|
|
2591
|
+
return frames.length > 0 ? frames : undefined;
|
|
2592
|
+
}
|
|
2532
2593
|
var lineKey = (ts, text) => `${ts}\x00${text}`;
|
|
2533
2594
|
function createConsoleCapture(options = {}) {
|
|
2534
2595
|
const ring = options.ring ?? DEFAULT_RING;
|
|
@@ -2576,13 +2637,15 @@ function createConsoleCapture(options = {}) {
|
|
|
2576
2637
|
const formatted = formatConsoleArgs(args);
|
|
2577
2638
|
const frame = stackTrace?.callFrames[0];
|
|
2578
2639
|
const source = sourceOf(frame?.url, frame?.lineNumber, frame?.columnNumber);
|
|
2640
|
+
const stack = framesOf(stackTrace);
|
|
2579
2641
|
return {
|
|
2580
2642
|
ts: timestamp ?? now(),
|
|
2581
2643
|
level: LEVELS[type] ?? "log",
|
|
2582
2644
|
kind: "console",
|
|
2583
2645
|
text: formatted.text,
|
|
2584
2646
|
args: formatted.args,
|
|
2585
|
-
...source ? { source } : {}
|
|
2647
|
+
...source ? { source } : {},
|
|
2648
|
+
...stack ? { stack } : {}
|
|
2586
2649
|
};
|
|
2587
2650
|
};
|
|
2588
2651
|
const fromException = (params) => {
|
|
@@ -2591,12 +2654,14 @@ function createConsoleCapture(options = {}) {
|
|
|
2591
2654
|
return;
|
|
2592
2655
|
const { exceptionDetails: details, timestamp } = parsed.data;
|
|
2593
2656
|
const source = sourceOf(details.url, details.lineNumber, details.columnNumber);
|
|
2657
|
+
const stack = framesOf(details.stackTrace);
|
|
2594
2658
|
return {
|
|
2595
2659
|
ts: timestamp ?? now(),
|
|
2596
2660
|
level: "error",
|
|
2597
2661
|
kind: "exception",
|
|
2598
2662
|
text: describeException(details),
|
|
2599
|
-
...source ? { source } : {}
|
|
2663
|
+
...source ? { source } : {},
|
|
2664
|
+
...stack ? { stack } : {}
|
|
2600
2665
|
};
|
|
2601
2666
|
};
|
|
2602
2667
|
return {
|
|
@@ -2620,6 +2685,71 @@ function createConsoleCapture(options = {}) {
|
|
|
2620
2685
|
};
|
|
2621
2686
|
}
|
|
2622
2687
|
|
|
2688
|
+
// ../../packages/e2e/src/devtools/logbox.script.ts
|
|
2689
|
+
var LOGBOX_SCRIPT = `function (g) {
|
|
2690
|
+
var req = g.__r;
|
|
2691
|
+
if (typeof req !== "function" || typeof req.getModules !== "function") return "no-require";
|
|
2692
|
+
var modules = req.getModules();
|
|
2693
|
+
if (!modules || typeof modules.forEach !== "function") return "no-require";
|
|
2694
|
+
var data = null;
|
|
2695
|
+
modules.forEach(function (mod) {
|
|
2696
|
+
if (data || !mod || typeof mod.verboseName !== "string" || !mod.isInitialized) return;
|
|
2697
|
+
var name = mod.verboseName.replace(/\\\\/g, "/");
|
|
2698
|
+
if (!/(^|\\/)react-native\\/Libraries\\/LogBox\\/Data\\/LogBoxData\\.js$/.test(name)) return;
|
|
2699
|
+
var exports = mod.publicModule && mod.publicModule.exports;
|
|
2700
|
+
if (exports && typeof exports.addIgnorePatterns === "function") data = exports;
|
|
2701
|
+
});
|
|
2702
|
+
if (!data) return "not-ready";
|
|
2703
|
+
data.addIgnorePatterns([/[\\s\\S]*/]);
|
|
2704
|
+
if (typeof data.setDisabled === "function") data.setDisabled(true);
|
|
2705
|
+
if (typeof data.setSelectedLog === "function") data.setSelectedLog(-1);
|
|
2706
|
+
return "off";
|
|
2707
|
+
}`;
|
|
2708
|
+
var LOGBOX_EXPRESSION = `(${LOGBOX_SCRIPT})(typeof globalThis !== "undefined" ? globalThis : (typeof global !== "undefined" ? global : this))`;
|
|
2709
|
+
|
|
2710
|
+
// ../../packages/e2e/src/devtools/logbox.ts
|
|
2711
|
+
var INTERVAL_MS = 250;
|
|
2712
|
+
var ATTEMPTS = 80;
|
|
2713
|
+
var timerSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
2714
|
+
function muteLogBox(session, options = {}) {
|
|
2715
|
+
const sleep = options.sleep ?? timerSleep;
|
|
2716
|
+
const intervalMs = options.intervalMs ?? INTERVAL_MS;
|
|
2717
|
+
const attempts = options.attempts ?? ATTEMPTS;
|
|
2718
|
+
let generation = 0;
|
|
2719
|
+
let stopped = false;
|
|
2720
|
+
async function tryOnce() {
|
|
2721
|
+
const reply = await session.evaluate(LOGBOX_EXPRESSION);
|
|
2722
|
+
if (!reply.success)
|
|
2723
|
+
return reply.error.message;
|
|
2724
|
+
return reply.data === "off" ? undefined : String(reply.data);
|
|
2725
|
+
}
|
|
2726
|
+
async function apply(mine) {
|
|
2727
|
+
const current = () => !stopped && mine === generation;
|
|
2728
|
+
let last = "no reply";
|
|
2729
|
+
for (let attempt = 1;attempt <= attempts && current(); attempt++) {
|
|
2730
|
+
last = await tryOnce();
|
|
2731
|
+
if (last === undefined)
|
|
2732
|
+
return;
|
|
2733
|
+
if (attempt < attempts)
|
|
2734
|
+
await sleep(intervalMs);
|
|
2735
|
+
}
|
|
2736
|
+
if (current())
|
|
2737
|
+
options.log?.(`devtools: LogBox left on (${last} after ${attempts} attempts)`);
|
|
2738
|
+
}
|
|
2739
|
+
const start = () => apply(++generation);
|
|
2740
|
+
const off = session.on("reconnected", () => {
|
|
2741
|
+
start();
|
|
2742
|
+
});
|
|
2743
|
+
const settled = start();
|
|
2744
|
+
return {
|
|
2745
|
+
settled,
|
|
2746
|
+
stop() {
|
|
2747
|
+
stopped = true;
|
|
2748
|
+
off();
|
|
2749
|
+
}
|
|
2750
|
+
};
|
|
2751
|
+
}
|
|
2752
|
+
|
|
2623
2753
|
// ../../packages/e2e/src/devtools/network.ts
|
|
2624
2754
|
import { z as z3 } from "zod";
|
|
2625
2755
|
|
|
@@ -2760,10 +2890,10 @@ var pageEntrySchema = z3.looseObject({
|
|
|
2760
2890
|
responseBodyTruncated: z3.boolean().optional(),
|
|
2761
2891
|
error: z3.string().optional()
|
|
2762
2892
|
});
|
|
2763
|
-
var
|
|
2893
|
+
var timerSleep2 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
2764
2894
|
async function attachNetwork(session, options = {}) {
|
|
2765
2895
|
const ring = options.ring ?? DEFAULT_RING2;
|
|
2766
|
-
const sleep = options.sleep ??
|
|
2896
|
+
const sleep = options.sleep ?? timerSleep2;
|
|
2767
2897
|
const pollMs = options.pollMs ?? DEFAULT_POLL_MS2;
|
|
2768
2898
|
const installExpression = networkInstallExpression({
|
|
2769
2899
|
ring,
|
|
@@ -3009,13 +3139,13 @@ var RECONNECT_BUDGET_MS = 30000;
|
|
|
3009
3139
|
var RELOAD_WAIT_MS = 5000;
|
|
3010
3140
|
var HANDSHAKE_OPTIONAL_TIMEOUT_MS = 3000;
|
|
3011
3141
|
var RELOAD_EXPRESSION = '(function(){var g=globalThis;if(g.expo&&typeof g.expo.reloadAppAsync==="function"){setTimeout(function(){g.expo.reloadAppAsync("warden e2e reloadJs")},0);return "expo"}var m=g.nativeModuleProxy&&g.nativeModuleProxy.DevSettings;if(m&&typeof m.reload==="function"){setTimeout(function(){m.reload()},0);return "devsettings"}return false})()';
|
|
3012
|
-
var
|
|
3142
|
+
var timerSleep3 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
3013
3143
|
async function connectDevtools(o) {
|
|
3014
3144
|
const { metroUrl } = o;
|
|
3015
3145
|
if (!metroUrl) {
|
|
3016
3146
|
return devtoolsErr("devtools_unavailable", "no Metro URL: release builds have no devtools (start a dev client)");
|
|
3017
3147
|
}
|
|
3018
|
-
const sleep = o.sleep ??
|
|
3148
|
+
const sleep = o.sleep ?? timerSleep3;
|
|
3019
3149
|
const log = o.log ?? (() => {
|
|
3020
3150
|
return;
|
|
3021
3151
|
});
|
|
@@ -3033,7 +3163,8 @@ async function connectDevtools(o) {
|
|
|
3033
3163
|
const cdpHandlers = new Map;
|
|
3034
3164
|
const eventHandlers = {
|
|
3035
3165
|
reconnected: new Set,
|
|
3036
|
-
lost: new Set
|
|
3166
|
+
lost: new Set,
|
|
3167
|
+
yielded: new Set
|
|
3037
3168
|
};
|
|
3038
3169
|
let unsubscribeCdp = [];
|
|
3039
3170
|
let connection = 0;
|
|
@@ -3083,9 +3214,17 @@ async function connectDevtools(o) {
|
|
|
3083
3214
|
for (const handler of handlers)
|
|
3084
3215
|
unsubscribeCdp.push(attached.cdp.on(method, handler));
|
|
3085
3216
|
}
|
|
3086
|
-
attached.cdp.onClose(() => {
|
|
3087
|
-
if (state.kind
|
|
3088
|
-
|
|
3217
|
+
attached.cdp.onClose((info) => {
|
|
3218
|
+
if (state.kind !== "up" || state.attached.cdp !== attached.cdp)
|
|
3219
|
+
return;
|
|
3220
|
+
if (o.yieldOnClose?.(info)) {
|
|
3221
|
+
state = { kind: "closed" };
|
|
3222
|
+
log(`devtools: yielded (${info.code} ${info.reason})`);
|
|
3223
|
+
for (const handler of [...eventHandlers.yielded])
|
|
3224
|
+
handler(info);
|
|
3225
|
+
return;
|
|
3226
|
+
}
|
|
3227
|
+
reconnect("socket closed");
|
|
3089
3228
|
});
|
|
3090
3229
|
attached.cdp.on("Runtime.executionContextsCleared", () => {
|
|
3091
3230
|
if (state.kind === "up" && state.attached.cdp === attached.cdp)
|
|
@@ -3782,7 +3921,7 @@ var scriptResultSchema = z5.union([
|
|
|
3782
3921
|
z5.object({ screen: z5.string().optional(), components: z5.array(componentSchema), truncated: z5.boolean() })
|
|
3783
3922
|
]);
|
|
3784
3923
|
var bindingPayloadSchema2 = z5.object({ requestId: z5.string(), result: z5.unknown() });
|
|
3785
|
-
var
|
|
3924
|
+
var timerSleep4 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
3786
3925
|
var requestCounter = 0;
|
|
3787
3926
|
function scriptError(message) {
|
|
3788
3927
|
if (message === "no-hook")
|
|
@@ -3845,7 +3984,7 @@ async function viaBinding(session, expression, requestId, timeoutMs) {
|
|
|
3845
3984
|
}
|
|
3846
3985
|
async function fetchTree(session, options = {}, deps = {}) {
|
|
3847
3986
|
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS2;
|
|
3848
|
-
const resolved = { sleep: deps.sleep ??
|
|
3987
|
+
const resolved = { sleep: deps.sleep ?? timerSleep4, now: deps.now ?? Date.now };
|
|
3849
3988
|
requestCounter += 1;
|
|
3850
3989
|
const requestId = `tree-${Date.now().toString(36)}-${requestCounter}`;
|
|
3851
3990
|
const config = {
|
|
@@ -3896,6 +4035,7 @@ async function openDevtoolsFacet(o) {
|
|
|
3896
4035
|
return attached;
|
|
3897
4036
|
}
|
|
3898
4037
|
const network = attached.data;
|
|
4038
|
+
const logbox = o.muteLogBox ? muteLogBox(session, { log: o.log }) : undefined;
|
|
3899
4039
|
return ok({
|
|
3900
4040
|
evaluate: (expression, options) => session.evaluate(expression, options),
|
|
3901
4041
|
reloadJs: () => session.reloadJs(),
|
|
@@ -3921,6 +4061,7 @@ async function openDevtoolsFacet(o) {
|
|
|
3921
4061
|
return ok(paths);
|
|
3922
4062
|
},
|
|
3923
4063
|
close() {
|
|
4064
|
+
logbox?.stop();
|
|
3924
4065
|
network.stop();
|
|
3925
4066
|
session.close();
|
|
3926
4067
|
}
|
|
@@ -3937,6 +4078,25 @@ function spawnFailure(cmd, cwd, error) {
|
|
|
3937
4078
|
return `cannot run \`${cmd.join(" ")}\`: no such directory ${cwd}`;
|
|
3938
4079
|
return `cannot run \`${cmd.join(" ")}\`: ${error instanceof Error ? error.message : String(error)}`;
|
|
3939
4080
|
}
|
|
4081
|
+
async function readLines(stream, onLine) {
|
|
4082
|
+
const decoder = new TextDecoder;
|
|
4083
|
+
let text = "";
|
|
4084
|
+
let pending = "";
|
|
4085
|
+
for await (const chunk of stream) {
|
|
4086
|
+
const piece = decoder.decode(chunk, { stream: true });
|
|
4087
|
+
text += piece;
|
|
4088
|
+
const lines = (pending + piece).split(/\r?\n/);
|
|
4089
|
+
pending = lines.pop() ?? "";
|
|
4090
|
+
for (const line of lines)
|
|
4091
|
+
onLine(line);
|
|
4092
|
+
}
|
|
4093
|
+
const rest = decoder.decode();
|
|
4094
|
+
text += rest;
|
|
4095
|
+
pending += rest;
|
|
4096
|
+
if (pending !== "")
|
|
4097
|
+
onLine(pending);
|
|
4098
|
+
return text;
|
|
4099
|
+
}
|
|
3940
4100
|
var bunExec = async (cmd, opts = {}) => {
|
|
3941
4101
|
try {
|
|
3942
4102
|
const proc = Bun.spawn([...cmd], {
|
|
@@ -3948,11 +4108,9 @@ var bunExec = async (cmd, opts = {}) => {
|
|
|
3948
4108
|
timeout: opts.timeoutMs,
|
|
3949
4109
|
...opts.signal !== undefined ? { signal: opts.signal } : {}
|
|
3950
4110
|
});
|
|
3951
|
-
const
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
proc.exited
|
|
3955
|
-
]);
|
|
4111
|
+
const { onLine } = opts;
|
|
4112
|
+
const read = (stream) => onLine ? readLines(stream, onLine) : new Response(stream).text();
|
|
4113
|
+
const [stdout, stderr, exitCode] = await Promise.all([read(proc.stdout), read(proc.stderr), proc.exited]);
|
|
3956
4114
|
return { exitCode, stdout, stderr };
|
|
3957
4115
|
} catch (error) {
|
|
3958
4116
|
return { exitCode: 127, stdout: "", stderr: spawnFailure(cmd, opts.cwd, error) };
|
|
@@ -4186,6 +4344,8 @@ var frontmostDataSchema = z6.looseObject({
|
|
|
4186
4344
|
pid: z6.number().optional(),
|
|
4187
4345
|
frontmostPid: z6.number().optional()
|
|
4188
4346
|
});
|
|
4347
|
+
var launchDataSchema = z6.looseObject({ pid: z6.number() });
|
|
4348
|
+
var terminateDataSchema = z6.looseObject({ terminated: z6.boolean() });
|
|
4189
4349
|
var orientationDataSchema = z6.object({
|
|
4190
4350
|
orientation: z6.enum([
|
|
4191
4351
|
"portrait",
|
|
@@ -4207,7 +4367,9 @@ var opSchemas = {
|
|
|
4207
4367
|
rebind: rebindDataSchema,
|
|
4208
4368
|
alertButtons: alertButtonsDataSchema,
|
|
4209
4369
|
frontmost: frontmostDataSchema,
|
|
4210
|
-
orientation: orientationDataSchema
|
|
4370
|
+
orientation: orientationDataSchema,
|
|
4371
|
+
launch: launchDataSchema,
|
|
4372
|
+
terminate: terminateDataSchema
|
|
4211
4373
|
};
|
|
4212
4374
|
function hasOpSchema(op) {
|
|
4213
4375
|
return Object.hasOwn(opSchemas, op);
|
|
@@ -5108,7 +5270,8 @@ var openDevtools = (env) => openDevtoolsFacet({
|
|
|
5108
5270
|
WebSocket,
|
|
5109
5271
|
timeoutMs: 5000,
|
|
5110
5272
|
log: (line) => process.stderr.write(`[devtools] ${line}
|
|
5111
|
-
`)
|
|
5273
|
+
`),
|
|
5274
|
+
muteLogBox: true
|
|
5112
5275
|
});
|
|
5113
5276
|
var SESSION_KEY = Symbol.for("@delacour/warden-e2e/session");
|
|
5114
5277
|
var slot = () => globalThis;
|
|
@@ -5855,6 +6018,6 @@ function onlyWasSeen() {
|
|
|
5855
6018
|
|
|
5856
6019
|
// ../../packages/e2e/src/version.ts
|
|
5857
6020
|
var WORKSPACE_VERSION = "0.0.0-workspace";
|
|
5858
|
-
var WARDEN_E2E_VERSION2 = "0.
|
|
6021
|
+
var WARDEN_E2E_VERSION2 = "0.5.0";
|
|
5859
6022
|
|
|
5860
6023
|
export { E2eError2, 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, resolve, locateByTestIdViaReact, isLocator, Locator, pickOne, textOf, scrollUntilVisible, bunExec, execError, driverError, driverErr, logEventSchema, fatalEventSchema, responseSchema, mapWireError, encodeRequest, bunSpawn, stdioTransport, spawnSimd, connectSimd, isHidLost, ENV_NAMES, DEFAULT_ASSERTION_TIMEOUT_MS, readEnv, runStep, setDefaultHidRecovery, setDefaultNativeLog, setDefaultDriverFactory, hasDefaultDriverFactory, setDefaultFixtureFactory, hasDefaultFixtureFactory, attemptSession, getSession, parseBudgets, createDevicePerf, perfStartFor, spawnWarden, hash6, attemptDir, screenshotPath, specIdOf, realNativeLogDeps, captureNativeLog, callerFile, BUN_TIMEOUT_MARGIN_MS2, ownHook, currentScope, createScope, runInScope, hooksFor, resolveUse, registrationDefaults, markOnly, onlyWasSeen, WORKSPACE_VERSION, WARDEN_E2E_VERSION2 };
|
package/index.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@delacour/warden-e2e",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "TypeScript spec API for `warden e2e`: test, expect, screen, device, app",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"LICENSE"
|
|
38
38
|
],
|
|
39
39
|
"peerDependencies": {
|
|
40
|
-
"@delacour/warden": "0.
|
|
40
|
+
"@delacour/warden": "0.5.0"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"zod": "^4.4.3"
|
package/preload.mjs
CHANGED
|
@@ -68,7 +68,7 @@ import {
|
|
|
68
68
|
onlyWasSeen,
|
|
69
69
|
WORKSPACE_VERSION,
|
|
70
70
|
WARDEN_E2E_VERSION2
|
|
71
|
-
} from "./chunk-
|
|
71
|
+
} from "./chunk-j9b17ade.mjs";
|
|
72
72
|
|
|
73
73
|
// ../../packages/e2e/src/preload.ts
|
|
74
74
|
import { afterAll, setDefaultTimeout } from "bun:test";
|
|
@@ -3238,8 +3238,8 @@ function createSimctl(deps) {
|
|
|
3238
3238
|
|
|
3239
3239
|
// ../../packages/e2e/src/driver/ios/type-budget.ts
|
|
3240
3240
|
var TYPE_BASE_MS = 1e4;
|
|
3241
|
-
var TYPE_PER_CHAR_MS =
|
|
3242
|
-
var TYPE_PER_REPEAT_MS =
|
|
3241
|
+
var TYPE_PER_CHAR_MS = 100;
|
|
3242
|
+
var TYPE_PER_REPEAT_MS = 200;
|
|
3243
3243
|
function typeTextBudgetMs(text) {
|
|
3244
3244
|
const characters = Array.from(text);
|
|
3245
3245
|
let repeats = 0;
|
|
@@ -3257,6 +3257,8 @@ var SETTLE_MARGIN_MS = 2000;
|
|
|
3257
3257
|
var REBIND_TIMEOUT_MS = 3000;
|
|
3258
3258
|
var GESTURE_MARGIN_MS = 5000;
|
|
3259
3259
|
var FRONTMOST_TIMEOUT_MS = 3000;
|
|
3260
|
+
var LAUNCH_TIMEOUT_MS = 30000;
|
|
3261
|
+
var APP_OP_TIMEOUT_MS = 15000;
|
|
3260
3262
|
var KEY_USAGE = {
|
|
3261
3263
|
Enter: 40,
|
|
3262
3264
|
Escape: 41,
|
|
@@ -3348,6 +3350,9 @@ function restOf(error, text) {
|
|
|
3348
3350
|
function decodeBase64(png) {
|
|
3349
3351
|
return new Uint8Array(Buffer.from(png, "base64"));
|
|
3350
3352
|
}
|
|
3353
|
+
function predatesOp(error) {
|
|
3354
|
+
return error.code === "E_UNSUPPORTED" || error.code === "E_BAD_REQUEST" && /unknown (input )?op/.test(error.message);
|
|
3355
|
+
}
|
|
3351
3356
|
function createIosDriver(swap, simctl, opts) {
|
|
3352
3357
|
const { udid, now, sleep } = opts;
|
|
3353
3358
|
const { client } = swap;
|
|
@@ -3359,6 +3364,7 @@ function createIosDriver(swap, simctl, opts) {
|
|
|
3359
3364
|
let settleUnsupported = !nativeSettle;
|
|
3360
3365
|
let rebindUnsupported = false;
|
|
3361
3366
|
let gestureUnsupported = false;
|
|
3367
|
+
let appOpsOutdated = false;
|
|
3362
3368
|
const capabilities = {
|
|
3363
3369
|
pinch: true,
|
|
3364
3370
|
maxPointers: 2,
|
|
@@ -3426,6 +3432,30 @@ function createIosDriver(swap, simctl, opts) {
|
|
|
3426
3432
|
}
|
|
3427
3433
|
return result;
|
|
3428
3434
|
};
|
|
3435
|
+
async function appOp(op, p, timeoutMs) {
|
|
3436
|
+
if (appOpsOutdated) {
|
|
3437
|
+
noteSpanDetail("via", "simctl");
|
|
3438
|
+
return;
|
|
3439
|
+
}
|
|
3440
|
+
const result = await request(op, p, { timeoutMs });
|
|
3441
|
+
if (!result.success && predatesOp(result.error)) {
|
|
3442
|
+
appOpsOutdated = true;
|
|
3443
|
+
noteSpanDetail("via", "simctl");
|
|
3444
|
+
noteSpanDetail("reason", "helper_outdated");
|
|
3445
|
+
opts.log?.(`[ios] this warden-simd predates the \`${op}\` op (${result.error.code}: ${result.error.message}); app lifecycle goes through simctl until the helper is restarted (warden ui close --udid ${udid}) or rebuilt (bun run --cwd packages/e2e native:build)`);
|
|
3446
|
+
return;
|
|
3447
|
+
}
|
|
3448
|
+
noteSpanDetail("via", "simd");
|
|
3449
|
+
return result;
|
|
3450
|
+
}
|
|
3451
|
+
async function appOutcome(viaHelper, fallback, note) {
|
|
3452
|
+
if (viaHelper === undefined)
|
|
3453
|
+
return fallback();
|
|
3454
|
+
if (!viaHelper.success)
|
|
3455
|
+
return viaHelper;
|
|
3456
|
+
note(viaHelper.data);
|
|
3457
|
+
return okVoid;
|
|
3458
|
+
}
|
|
3429
3459
|
const fetchSnapshot = async (o) => {
|
|
3430
3460
|
const result = await request("snapshot", params({ interactiveOnly: o?.interactiveOnly, backend: o?.backend }));
|
|
3431
3461
|
if (!result.success)
|
|
@@ -3463,11 +3493,17 @@ function createIosDriver(swap, simctl, opts) {
|
|
|
3463
3493
|
const rest = restOf(typed.error, text);
|
|
3464
3494
|
if (rest === undefined)
|
|
3465
3495
|
return typed;
|
|
3466
|
-
const copied = await simctl.pbcopy(rest);
|
|
3467
|
-
if (!copied.success)
|
|
3468
|
-
return copied;
|
|
3469
3496
|
opts.log?.(`[ios] typeText: pasting ${rest.length} unmappable character(s)`);
|
|
3470
|
-
return
|
|
3497
|
+
return pasteText(rest);
|
|
3498
|
+
}
|
|
3499
|
+
async function pasteText(text) {
|
|
3500
|
+
if (text === "")
|
|
3501
|
+
return ok(undefined);
|
|
3502
|
+
const copied = await simctl.pbcopy(text);
|
|
3503
|
+
const result = copied.success ? await sendVoid("paste") : copied;
|
|
3504
|
+
if (!result.success)
|
|
3505
|
+
opts.log?.(`[ios] pasteText failed (${result.error.code}): ${result.error.message}`);
|
|
3506
|
+
return result;
|
|
3471
3507
|
}
|
|
3472
3508
|
async function hashSettle(o) {
|
|
3473
3509
|
try {
|
|
@@ -3631,6 +3667,7 @@ function createIosDriver(swap, simctl, opts) {
|
|
|
3631
3667
|
typeText,
|
|
3632
3668
|
key: (key, modifiers) => sendVoid("key", params({ usage: KEY_USAGE[key], modifiers: modifiers && [...modifiers] })),
|
|
3633
3669
|
selectAll: () => sendVoid("selectAll"),
|
|
3670
|
+
pasteText,
|
|
3634
3671
|
pressButton,
|
|
3635
3672
|
screenshot,
|
|
3636
3673
|
orientation,
|
|
@@ -3647,7 +3684,8 @@ function createIosDriver(swap, simctl, opts) {
|
|
|
3647
3684
|
},
|
|
3648
3685
|
app: {
|
|
3649
3686
|
launch: async (bundleId, o) => {
|
|
3650
|
-
const
|
|
3687
|
+
const viaHelper = await appOp("launch", params({ bundleId, args: o?.args && [...o.args], env: o?.env, terminateRunning: o?.relaunch !== false }), LAUNCH_TIMEOUT_MS);
|
|
3688
|
+
const launched = await appOutcome(viaHelper, () => simctl.launch(bundleId, o), (data) => noteSpanDetail("pid", data.pid));
|
|
3651
3689
|
if (launched.success) {
|
|
3652
3690
|
launchedBundle = bundleId;
|
|
3653
3691
|
if (await rebind(bundleId))
|
|
@@ -3656,12 +3694,18 @@ function createIosDriver(swap, simctl, opts) {
|
|
|
3656
3694
|
return launched;
|
|
3657
3695
|
},
|
|
3658
3696
|
terminate: async (bundleId) => {
|
|
3659
|
-
const
|
|
3697
|
+
const viaHelper = await appOp("terminate", { bundleId }, APP_OP_TIMEOUT_MS);
|
|
3698
|
+
const terminated = await appOutcome(viaHelper, () => simctl.terminate(bundleId), (data) => noteSpanDetail("terminated", data.terminated));
|
|
3660
3699
|
if (terminated.success && launchedBundle === bundleId)
|
|
3661
3700
|
launchedBundle = undefined;
|
|
3662
3701
|
return terminated;
|
|
3663
3702
|
},
|
|
3664
|
-
openUrl: (url) =>
|
|
3703
|
+
openUrl: async (url) => {
|
|
3704
|
+
const viaHelper = await appOp("openUrl", { url }, APP_OP_TIMEOUT_MS);
|
|
3705
|
+
return appOutcome(viaHelper, () => simctl.openUrl(url), () => {
|
|
3706
|
+
return;
|
|
3707
|
+
});
|
|
3708
|
+
},
|
|
3665
3709
|
install: (path) => simctl.install(path),
|
|
3666
3710
|
uninstall: (bundleId) => simctl.uninstall(bundleId),
|
|
3667
3711
|
isInstalled: (bundleId) => simctl.isInstalled(bundleId),
|
|
@@ -3939,6 +3983,8 @@ function createApp({ session, driver, env, log = stderrLog2, clock = systemClock
|
|
|
3939
3983
|
if (options.env !== undefined)
|
|
3940
3984
|
opts.env = options.env;
|
|
3941
3985
|
await unwrap(() => driver.app.launch(bundleId, opts), `launch ${bundleId}`, session.signal);
|
|
3986
|
+
if (env.metroUrl !== undefined)
|
|
3987
|
+
session.devtools?.();
|
|
3942
3988
|
if (options.url !== undefined) {
|
|
3943
3989
|
const url = options.url;
|
|
3944
3990
|
await unwrap(() => driver.app.openUrl(url), `open ${url}`, session.signal);
|
|
@@ -4773,6 +4819,10 @@ ${read.error}
|
|
|
4773
4819
|
}
|
|
4774
4820
|
});
|
|
4775
4821
|
}
|
|
4822
|
+
function scrubRunnerEnv(env) {
|
|
4823
|
+
delete env.BUN_BE_BUN;
|
|
4824
|
+
}
|
|
4825
|
+
scrubRunnerEnv(process.env);
|
|
4776
4826
|
if (!hasDefaultDriverFactory()) {
|
|
4777
4827
|
setDefaultDriverFactory(defaultDriverFactory);
|
|
4778
4828
|
setDefaultNativeLog(realNativeLogDeps(bunExec, androidTools(process.env).adb));
|
|
@@ -4796,5 +4846,6 @@ export {
|
|
|
4796
4846
|
BUN_TIMEOUT_MARGIN_MS2 as BUN_TIMEOUT_MARGIN_MS,
|
|
4797
4847
|
installPreload,
|
|
4798
4848
|
isCi,
|
|
4849
|
+
scrubRunnerEnv,
|
|
4799
4850
|
versionMismatch
|
|
4800
4851
|
};
|