@delacour/warden-e2e 0.2.0 → 0.4.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-7ns30pbp.mjs → chunk-3874jt2j.mjs} +295 -172
- package/index.mjs +37 -37
- package/package.json +2 -2
- package/preload.mjs +103 -42
- package/types/core/src/exec.d.ts +2 -0
- package/types/core/src/providers/provider.types.d.ts +7 -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/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
|
@@ -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);
|
|
@@ -834,34 +856,34 @@ async function confirmFieldValue(request) {
|
|
|
834
856
|
const { now, sleep } = request;
|
|
835
857
|
const start = now();
|
|
836
858
|
const cap = request.maxMs === undefined ? Number.POSITIVE_INFINITY : start + request.maxMs;
|
|
837
|
-
|
|
838
|
-
let
|
|
839
|
-
let streak = 0;
|
|
859
|
+
const reads = request.reads ?? CONFIRM_READS;
|
|
860
|
+
let poll = { deadline: Math.min(start + request.budgetMs, cap), held: "(gone)", streak: 0 };
|
|
840
861
|
for (;; ) {
|
|
841
862
|
const read = await request.read();
|
|
842
863
|
if (!read.ok)
|
|
843
864
|
return { status: "failed", error: read.error };
|
|
844
|
-
|
|
845
|
-
if (
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
streak += 1;
|
|
850
|
-
if (streak >= (request.reads ?? CONFIRM_READS))
|
|
851
|
-
return { status: "confirmed" };
|
|
852
|
-
held = reading.value;
|
|
853
|
-
deadline = Math.max(deadline, Math.min(now() + 2 * FIELD_POLL_MS, cap));
|
|
854
|
-
} else {
|
|
855
|
-
streak = 0;
|
|
856
|
-
if (reading.value !== held)
|
|
857
|
-
deadline = Math.max(deadline, Math.min(now() + FIELD_PROGRESS_MS, cap));
|
|
858
|
-
held = reading.value;
|
|
859
|
-
}
|
|
860
|
-
if (now() >= deadline)
|
|
861
|
-
return { status: "mismatch", held };
|
|
865
|
+
poll = foldReading(poll, read.reading, { text: request.text, now: now(), cap, reads });
|
|
866
|
+
if (poll.streak > 0 && poll.streak >= reads)
|
|
867
|
+
return { status: "confirmed" };
|
|
868
|
+
if (now() >= poll.deadline)
|
|
869
|
+
return { status: "mismatch", held: poll.held };
|
|
862
870
|
await sleep(FIELD_POLL_MS);
|
|
863
871
|
}
|
|
864
872
|
}
|
|
873
|
+
function foldReading(poll, reading, at) {
|
|
874
|
+
if (reading.kind === "unreadable")
|
|
875
|
+
return { ...poll, streak: 0, held: reading.note };
|
|
876
|
+
if (reading.value === at.text) {
|
|
877
|
+
const streak = poll.streak + 1;
|
|
878
|
+
if (streak >= at.reads)
|
|
879
|
+
return { ...poll, streak };
|
|
880
|
+
const deadline = Math.max(poll.deadline, Math.min(at.now + 2 * FIELD_POLL_MS, at.cap));
|
|
881
|
+
return { deadline, held: reading.value, streak };
|
|
882
|
+
}
|
|
883
|
+
const progressed = reading.value !== poll.held;
|
|
884
|
+
const deadline = progressed ? Math.max(poll.deadline, Math.min(at.now + FIELD_PROGRESS_MS, at.cap)) : poll.deadline;
|
|
885
|
+
return { deadline, held: reading.value, streak: 0 };
|
|
886
|
+
}
|
|
865
887
|
async function hitTestField(driver, wanted) {
|
|
866
888
|
const { x, y, width, height } = wanted.element.frame;
|
|
867
889
|
const hit = await driver.elementAtPoint({ x: x + width / 2, y: y + height / 2 });
|
|
@@ -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
|
}
|
|
@@ -2201,6 +2247,32 @@ function textOf2(data) {
|
|
|
2201
2247
|
return new TextDecoder().decode(new Uint8Array(data.buffer, data.byteOffset, data.byteLength));
|
|
2202
2248
|
return;
|
|
2203
2249
|
}
|
|
2250
|
+
function taggedBindingPayload(params) {
|
|
2251
|
+
const call = bindingCalledSchema.safeParse(params);
|
|
2252
|
+
if (!call.success)
|
|
2253
|
+
return;
|
|
2254
|
+
let payload;
|
|
2255
|
+
try {
|
|
2256
|
+
payload = JSON.parse(call.data.payload);
|
|
2257
|
+
} catch {
|
|
2258
|
+
payload = undefined;
|
|
2259
|
+
}
|
|
2260
|
+
const tagged = bindingPayloadSchema.safeParse(payload);
|
|
2261
|
+
return tagged.success ? { requestId: tagged.data.requestId, payload } : undefined;
|
|
2262
|
+
}
|
|
2263
|
+
function parseEnvelope(data) {
|
|
2264
|
+
const text = textOf2(data);
|
|
2265
|
+
if (text === undefined)
|
|
2266
|
+
return;
|
|
2267
|
+
let json;
|
|
2268
|
+
try {
|
|
2269
|
+
json = JSON.parse(text);
|
|
2270
|
+
} catch {
|
|
2271
|
+
return;
|
|
2272
|
+
}
|
|
2273
|
+
const message = envelopeSchema.safeParse(json);
|
|
2274
|
+
return message.success ? message.data : undefined;
|
|
2275
|
+
}
|
|
2204
2276
|
function inspectorOrigin(wsUrl) {
|
|
2205
2277
|
if (!URL.canParse(wsUrl))
|
|
2206
2278
|
return;
|
|
@@ -2261,18 +2333,9 @@ async function openCdp(wsUrl, options = {}) {
|
|
|
2261
2333
|
};
|
|
2262
2334
|
const dispatch = (method, params) => {
|
|
2263
2335
|
if (method === "Runtime.bindingCalled") {
|
|
2264
|
-
const
|
|
2265
|
-
if (
|
|
2266
|
-
|
|
2267
|
-
try {
|
|
2268
|
-
payload = JSON.parse(call.data.payload);
|
|
2269
|
-
} catch {
|
|
2270
|
-
payload = undefined;
|
|
2271
|
-
}
|
|
2272
|
-
const tagged = bindingPayloadSchema.safeParse(payload);
|
|
2273
|
-
if (tagged.success)
|
|
2274
|
-
bindings.get(tagged.data.requestId)?.settle(ok(payload));
|
|
2275
|
-
}
|
|
2336
|
+
const tagged = taggedBindingPayload(params);
|
|
2337
|
+
if (tagged)
|
|
2338
|
+
bindings.get(tagged.requestId)?.settle(ok(tagged.payload));
|
|
2276
2339
|
}
|
|
2277
2340
|
for (const handler of [...anyHandlers])
|
|
2278
2341
|
handler(method, params);
|
|
@@ -2280,19 +2343,10 @@ async function openCdp(wsUrl, options = {}) {
|
|
|
2280
2343
|
handler(params);
|
|
2281
2344
|
};
|
|
2282
2345
|
ws.addEventListener("message", (event) => {
|
|
2283
|
-
const
|
|
2284
|
-
if (
|
|
2285
|
-
return;
|
|
2286
|
-
let json;
|
|
2287
|
-
try {
|
|
2288
|
-
json = JSON.parse(text);
|
|
2289
|
-
} catch {
|
|
2290
|
-
return;
|
|
2291
|
-
}
|
|
2292
|
-
const message = envelopeSchema.safeParse(json);
|
|
2293
|
-
if (!message.success)
|
|
2346
|
+
const message = parseEnvelope(event.data);
|
|
2347
|
+
if (!message)
|
|
2294
2348
|
return;
|
|
2295
|
-
const { id, error, result, method, params } = message
|
|
2349
|
+
const { id, error, result, method, params } = message;
|
|
2296
2350
|
if (id !== undefined) {
|
|
2297
2351
|
const request = pending.get(id);
|
|
2298
2352
|
if (!request)
|
|
@@ -2305,7 +2359,7 @@ async function openCdp(wsUrl, options = {}) {
|
|
|
2305
2359
|
if (method)
|
|
2306
2360
|
dispatch(method, params);
|
|
2307
2361
|
});
|
|
2308
|
-
ws.addEventListener("close", () => {
|
|
2362
|
+
ws.addEventListener("close", (event) => {
|
|
2309
2363
|
if (closed)
|
|
2310
2364
|
return;
|
|
2311
2365
|
closed = true;
|
|
@@ -2318,7 +2372,7 @@ async function openCdp(wsUrl, options = {}) {
|
|
|
2318
2372
|
binding.settle(devtoolsErr("cdp_closed", "CDP connection closed"));
|
|
2319
2373
|
if (!intentional)
|
|
2320
2374
|
for (const handler of [...closeHandlers])
|
|
2321
|
-
handler();
|
|
2375
|
+
handler({ code: event.code, reason: event.reason });
|
|
2322
2376
|
});
|
|
2323
2377
|
const subscribe = (set, handler) => {
|
|
2324
2378
|
set.add(handler);
|
|
@@ -2419,7 +2473,8 @@ var stackTraceSchema = z2.looseObject({
|
|
|
2419
2473
|
callFrames: z2.array(z2.looseObject({
|
|
2420
2474
|
url: z2.string().optional(),
|
|
2421
2475
|
lineNumber: z2.number().optional(),
|
|
2422
|
-
columnNumber: z2.number().optional()
|
|
2476
|
+
columnNumber: z2.number().optional(),
|
|
2477
|
+
functionName: z2.string().optional()
|
|
2423
2478
|
}))
|
|
2424
2479
|
});
|
|
2425
2480
|
var consoleCalledSchema = z2.looseObject({
|
|
@@ -2430,7 +2485,7 @@ var consoleCalledSchema = z2.looseObject({
|
|
|
2430
2485
|
});
|
|
2431
2486
|
var exceptionThrownSchema = z2.looseObject({
|
|
2432
2487
|
timestamp: z2.number().optional(),
|
|
2433
|
-
exceptionDetails: exceptionDetailsSchema
|
|
2488
|
+
exceptionDetails: exceptionDetailsSchema.extend({ stackTrace: stackTraceSchema.optional() })
|
|
2434
2489
|
});
|
|
2435
2490
|
var LEVELS = {
|
|
2436
2491
|
log: "log",
|
|
@@ -2455,6 +2510,20 @@ function formatPreview(object) {
|
|
|
2455
2510
|
const name = object.className && object.className !== "Object" ? `${object.className} ` : "";
|
|
2456
2511
|
return `${name}{${body}${more}}`;
|
|
2457
2512
|
}
|
|
2513
|
+
function formatObject(object) {
|
|
2514
|
+
if (object.subtype === "null")
|
|
2515
|
+
return "null";
|
|
2516
|
+
if (object.value !== undefined)
|
|
2517
|
+
return JSON.stringify(object.value);
|
|
2518
|
+
if (object.subtype === "error")
|
|
2519
|
+
return object.description ?? "Error";
|
|
2520
|
+
return formatPreview(object) ?? object.description ?? object.className ?? "Object";
|
|
2521
|
+
}
|
|
2522
|
+
function formatFunction(object) {
|
|
2523
|
+
const first = (object.description ?? "function").split(`
|
|
2524
|
+
`)[0] ?? "function";
|
|
2525
|
+
return first.length > MAX_FUNCTION_TEXT ? `${first.slice(0, MAX_FUNCTION_TEXT)}\u2026` : first;
|
|
2526
|
+
}
|
|
2458
2527
|
function formatValue2(object) {
|
|
2459
2528
|
switch (object.type) {
|
|
2460
2529
|
case "string":
|
|
@@ -2465,24 +2534,18 @@ function formatValue2(object) {
|
|
|
2465
2534
|
case "boolean":
|
|
2466
2535
|
case "bigint":
|
|
2467
2536
|
return object.unserializableValue ?? object.description ?? String(object.value);
|
|
2468
|
-
case "function":
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
return
|
|
2472
|
-
}
|
|
2473
|
-
case "object": {
|
|
2474
|
-
if (object.subtype === "null")
|
|
2475
|
-
return "null";
|
|
2476
|
-
if (object.value !== undefined)
|
|
2477
|
-
return JSON.stringify(object.value);
|
|
2478
|
-
if (object.subtype === "error")
|
|
2479
|
-
return object.description ?? "Error";
|
|
2480
|
-
return formatPreview(object) ?? object.description ?? object.className ?? "Object";
|
|
2481
|
-
}
|
|
2537
|
+
case "function":
|
|
2538
|
+
return formatFunction(object);
|
|
2539
|
+
case "object":
|
|
2540
|
+
return formatObject(object);
|
|
2482
2541
|
default:
|
|
2483
2542
|
return object.description ?? `[${object.type}]`;
|
|
2484
2543
|
}
|
|
2485
2544
|
}
|
|
2545
|
+
function formatNumeric(spec, arg) {
|
|
2546
|
+
const n = Number(arg.value ?? arg.description);
|
|
2547
|
+
return Number.isNaN(n) ? "NaN" : String(spec === "f" ? n : Math.trunc(n));
|
|
2548
|
+
}
|
|
2486
2549
|
function applyFormat(format, rest) {
|
|
2487
2550
|
let used = 0;
|
|
2488
2551
|
const text = format.replace(/%([sdifoOjc%])/g, (whole, spec) => {
|
|
@@ -2494,10 +2557,8 @@ function applyFormat(format, rest) {
|
|
|
2494
2557
|
used++;
|
|
2495
2558
|
if (spec === "c")
|
|
2496
2559
|
return "";
|
|
2497
|
-
if (spec === "d" || spec === "i" || spec === "f")
|
|
2498
|
-
|
|
2499
|
-
return Number.isNaN(n) ? "NaN" : String(spec === "f" ? n : Math.trunc(n));
|
|
2500
|
-
}
|
|
2560
|
+
if (spec === "d" || spec === "i" || spec === "f")
|
|
2561
|
+
return formatNumeric(spec, arg);
|
|
2501
2562
|
return formatValue2(arg);
|
|
2502
2563
|
});
|
|
2503
2564
|
return { text, used };
|
|
@@ -2515,6 +2576,20 @@ function sourceOf(url, line, col) {
|
|
|
2515
2576
|
return;
|
|
2516
2577
|
return { url, line: (line ?? 0) + 1, col: (col ?? 0) + 1 };
|
|
2517
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
|
+
}
|
|
2518
2593
|
var lineKey = (ts, text) => `${ts}\x00${text}`;
|
|
2519
2594
|
function createConsoleCapture(options = {}) {
|
|
2520
2595
|
const ring = options.ring ?? DEFAULT_RING;
|
|
@@ -2562,13 +2637,15 @@ function createConsoleCapture(options = {}) {
|
|
|
2562
2637
|
const formatted = formatConsoleArgs(args);
|
|
2563
2638
|
const frame = stackTrace?.callFrames[0];
|
|
2564
2639
|
const source = sourceOf(frame?.url, frame?.lineNumber, frame?.columnNumber);
|
|
2640
|
+
const stack = framesOf(stackTrace);
|
|
2565
2641
|
return {
|
|
2566
2642
|
ts: timestamp ?? now(),
|
|
2567
2643
|
level: LEVELS[type] ?? "log",
|
|
2568
2644
|
kind: "console",
|
|
2569
2645
|
text: formatted.text,
|
|
2570
2646
|
args: formatted.args,
|
|
2571
|
-
...source ? { source } : {}
|
|
2647
|
+
...source ? { source } : {},
|
|
2648
|
+
...stack ? { stack } : {}
|
|
2572
2649
|
};
|
|
2573
2650
|
};
|
|
2574
2651
|
const fromException = (params) => {
|
|
@@ -2577,12 +2654,14 @@ function createConsoleCapture(options = {}) {
|
|
|
2577
2654
|
return;
|
|
2578
2655
|
const { exceptionDetails: details, timestamp } = parsed.data;
|
|
2579
2656
|
const source = sourceOf(details.url, details.lineNumber, details.columnNumber);
|
|
2657
|
+
const stack = framesOf(details.stackTrace);
|
|
2580
2658
|
return {
|
|
2581
2659
|
ts: timestamp ?? now(),
|
|
2582
2660
|
level: "error",
|
|
2583
2661
|
kind: "exception",
|
|
2584
2662
|
text: describeException(details),
|
|
2585
|
-
...source ? { source } : {}
|
|
2663
|
+
...source ? { source } : {},
|
|
2664
|
+
...stack ? { stack } : {}
|
|
2586
2665
|
};
|
|
2587
2666
|
};
|
|
2588
2667
|
return {
|
|
@@ -3019,7 +3098,8 @@ async function connectDevtools(o) {
|
|
|
3019
3098
|
const cdpHandlers = new Map;
|
|
3020
3099
|
const eventHandlers = {
|
|
3021
3100
|
reconnected: new Set,
|
|
3022
|
-
lost: new Set
|
|
3101
|
+
lost: new Set,
|
|
3102
|
+
yielded: new Set
|
|
3023
3103
|
};
|
|
3024
3104
|
let unsubscribeCdp = [];
|
|
3025
3105
|
let connection = 0;
|
|
@@ -3069,60 +3149,78 @@ async function connectDevtools(o) {
|
|
|
3069
3149
|
for (const handler of handlers)
|
|
3070
3150
|
unsubscribeCdp.push(attached.cdp.on(method, handler));
|
|
3071
3151
|
}
|
|
3072
|
-
attached.cdp.onClose(() => {
|
|
3073
|
-
if (state.kind
|
|
3074
|
-
|
|
3152
|
+
attached.cdp.onClose((info) => {
|
|
3153
|
+
if (state.kind !== "up" || state.attached.cdp !== attached.cdp)
|
|
3154
|
+
return;
|
|
3155
|
+
if (o.yieldOnClose?.(info)) {
|
|
3156
|
+
state = { kind: "closed" };
|
|
3157
|
+
log(`devtools: yielded (${info.code} ${info.reason})`);
|
|
3158
|
+
for (const handler of [...eventHandlers.yielded])
|
|
3159
|
+
handler(info);
|
|
3160
|
+
return;
|
|
3161
|
+
}
|
|
3162
|
+
reconnect("socket closed");
|
|
3075
3163
|
});
|
|
3076
3164
|
attached.cdp.on("Runtime.executionContextsCleared", () => {
|
|
3077
3165
|
if (state.kind === "up" && state.attached.cdp === attached.cdp)
|
|
3078
3166
|
reconnect("JS context cleared");
|
|
3079
3167
|
});
|
|
3080
3168
|
}
|
|
3169
|
+
const closedError = () => devtoolsErr("cdp_closed", "devtools session is closed");
|
|
3170
|
+
function adopt(attached) {
|
|
3171
|
+
attach(attached);
|
|
3172
|
+
log(`devtools: reconnected to ${attached.target.id}`);
|
|
3173
|
+
for (const handler of [...eventHandlers.reconnected])
|
|
3174
|
+
handler(attached.target);
|
|
3175
|
+
}
|
|
3176
|
+
function adoptIfOpen(attached) {
|
|
3177
|
+
if (phase() === "closed") {
|
|
3178
|
+
attached.cdp.close();
|
|
3179
|
+
return closedError();
|
|
3180
|
+
}
|
|
3181
|
+
adopt(attached);
|
|
3182
|
+
return ok(attached);
|
|
3183
|
+
}
|
|
3184
|
+
function markLost(error) {
|
|
3185
|
+
if (phase() === "closed")
|
|
3186
|
+
return;
|
|
3187
|
+
state = { kind: "lost", error };
|
|
3188
|
+
for (const handler of [...eventHandlers.lost])
|
|
3189
|
+
handler(error);
|
|
3190
|
+
}
|
|
3191
|
+
async function reconnectLoop(previous) {
|
|
3192
|
+
previous.cdp.close();
|
|
3193
|
+
let last = { code: "devtools_unavailable", message: "reconnect failed" };
|
|
3194
|
+
const startedAt = now();
|
|
3195
|
+
let slept = 0;
|
|
3196
|
+
const leftMs = () => budgetMs - Math.max(now() - startedAt, slept);
|
|
3197
|
+
for (let attempt = 1;attempt <= maxAttempts; attempt++) {
|
|
3198
|
+
if (phase() === "closed")
|
|
3199
|
+
return closedError();
|
|
3200
|
+
const result = await establish();
|
|
3201
|
+
if (result.success)
|
|
3202
|
+
return adoptIfOpen(result.data);
|
|
3203
|
+
last = result.error;
|
|
3204
|
+
const left = leftMs();
|
|
3205
|
+
const cap = Number.isFinite(maxAttempts) ? `/${maxAttempts}` : "";
|
|
3206
|
+
log(`devtools: reconnect attempt ${attempt}${cap} failed: ${last.message}`);
|
|
3207
|
+
if (attempt >= maxAttempts || left <= 0)
|
|
3208
|
+
break;
|
|
3209
|
+
const pause = Math.min(backoffAfter(attempt), left);
|
|
3210
|
+
slept += pause;
|
|
3211
|
+
await sleep(pause);
|
|
3212
|
+
}
|
|
3213
|
+
markLost(last);
|
|
3214
|
+
return { success: false, error: last };
|
|
3215
|
+
}
|
|
3081
3216
|
function reconnect(reason) {
|
|
3082
3217
|
if (state.kind === "reconnecting")
|
|
3083
3218
|
return state.done;
|
|
3084
3219
|
if (state.kind !== "up")
|
|
3085
|
-
return Promise.resolve(
|
|
3220
|
+
return Promise.resolve(closedError());
|
|
3086
3221
|
const previous = state.attached;
|
|
3087
3222
|
log(`devtools: ${reason}, reconnecting`);
|
|
3088
|
-
const done = (
|
|
3089
|
-
previous.cdp.close();
|
|
3090
|
-
let last = { code: "devtools_unavailable", message: "reconnect failed" };
|
|
3091
|
-
const startedAt = now();
|
|
3092
|
-
let slept = 0;
|
|
3093
|
-
const leftMs = () => budgetMs - Math.max(now() - startedAt, slept);
|
|
3094
|
-
for (let attempt = 1;attempt <= maxAttempts; attempt++) {
|
|
3095
|
-
if (phase() === "closed")
|
|
3096
|
-
return devtoolsErr("cdp_closed", "devtools session is closed");
|
|
3097
|
-
const result = await establish();
|
|
3098
|
-
if (result.success) {
|
|
3099
|
-
if (phase() === "closed") {
|
|
3100
|
-
result.data.cdp.close();
|
|
3101
|
-
return devtoolsErr("cdp_closed", "devtools session is closed");
|
|
3102
|
-
}
|
|
3103
|
-
attach(result.data);
|
|
3104
|
-
log(`devtools: reconnected to ${result.data.target.id}`);
|
|
3105
|
-
for (const handler of [...eventHandlers.reconnected])
|
|
3106
|
-
handler(result.data.target);
|
|
3107
|
-
return result;
|
|
3108
|
-
}
|
|
3109
|
-
last = result.error;
|
|
3110
|
-
const left = leftMs();
|
|
3111
|
-
const cap = Number.isFinite(maxAttempts) ? `/${maxAttempts}` : "";
|
|
3112
|
-
log(`devtools: reconnect attempt ${attempt}${cap} failed: ${last.message}`);
|
|
3113
|
-
if (attempt >= maxAttempts || left <= 0)
|
|
3114
|
-
break;
|
|
3115
|
-
const pause = Math.min(backoffAfter(attempt), left);
|
|
3116
|
-
slept += pause;
|
|
3117
|
-
await sleep(pause);
|
|
3118
|
-
}
|
|
3119
|
-
if (phase() !== "closed") {
|
|
3120
|
-
state = { kind: "lost", error: last };
|
|
3121
|
-
for (const handler of [...eventHandlers.lost])
|
|
3122
|
-
handler(last);
|
|
3123
|
-
}
|
|
3124
|
-
return { success: false, error: last };
|
|
3125
|
-
})();
|
|
3223
|
+
const done = reconnectLoop(previous);
|
|
3126
3224
|
state = { kind: "reconnecting", done };
|
|
3127
3225
|
epoch++;
|
|
3128
3226
|
for (const wake of [...epochWaiters])
|
|
@@ -3136,16 +3234,13 @@ async function connectDevtools(o) {
|
|
|
3136
3234
|
if (phase() === "closed") {
|
|
3137
3235
|
if (result.success)
|
|
3138
3236
|
result.data.cdp.close();
|
|
3139
|
-
return
|
|
3237
|
+
return closedError();
|
|
3140
3238
|
}
|
|
3141
3239
|
if (!result.success) {
|
|
3142
3240
|
state = { kind: "lost", error: result.error };
|
|
3143
3241
|
return result;
|
|
3144
3242
|
}
|
|
3145
|
-
|
|
3146
|
-
log(`devtools: reconnected to ${result.data.target.id}`);
|
|
3147
|
-
for (const handler of [...eventHandlers.reconnected])
|
|
3148
|
-
handler(result.data.target);
|
|
3243
|
+
adopt(result.data);
|
|
3149
3244
|
return result;
|
|
3150
3245
|
})();
|
|
3151
3246
|
state = { kind: "reconnecting", done };
|
|
@@ -3160,6 +3255,44 @@ async function connectDevtools(o) {
|
|
|
3160
3255
|
return revive();
|
|
3161
3256
|
return devtoolsErr("cdp_closed", "devtools session is closed");
|
|
3162
3257
|
}
|
|
3258
|
+
async function requestReload(method, baseUrl) {
|
|
3259
|
+
if (method !== "http") {
|
|
3260
|
+
log(`devtools: reload via ${method}`);
|
|
3261
|
+
return;
|
|
3262
|
+
}
|
|
3263
|
+
log("devtools: reload via http");
|
|
3264
|
+
try {
|
|
3265
|
+
const res = await o.fetch(`${baseUrl.replace(/\/+$/, "")}/reload`, { method: "POST" });
|
|
3266
|
+
if (!res.ok) {
|
|
3267
|
+
return devtoolsErr("devtools_unavailable", `reload failed: POST /reload answered ${res.status}`);
|
|
3268
|
+
}
|
|
3269
|
+
} catch (error) {
|
|
3270
|
+
return devtoolsErr("devtools_unavailable", `reload failed: POST /reload threw ${String(error)}`);
|
|
3271
|
+
}
|
|
3272
|
+
return;
|
|
3273
|
+
}
|
|
3274
|
+
async function awaitReload(method, startedAt, waitMs) {
|
|
3275
|
+
if (epoch === startedAt)
|
|
3276
|
+
await waitForEpochChange(waitMs);
|
|
3277
|
+
if (epoch === startedAt && state.kind === "up") {
|
|
3278
|
+
log(`devtools: reload via ${method} not observed after ${waitMs} ms`);
|
|
3279
|
+
return devtoolsErr("reload_failed", `reload via ${method} acknowledged but the JS context did not restart within ${waitMs} ms`);
|
|
3280
|
+
}
|
|
3281
|
+
if (state.kind !== "reconnecting")
|
|
3282
|
+
return;
|
|
3283
|
+
const back = await state.done;
|
|
3284
|
+
return back.success ? undefined : back;
|
|
3285
|
+
}
|
|
3286
|
+
function waitForEpochChange(waitMs) {
|
|
3287
|
+
return new Promise((resolve) => {
|
|
3288
|
+
const wake = () => {
|
|
3289
|
+
epochWaiters.delete(wake);
|
|
3290
|
+
resolve();
|
|
3291
|
+
};
|
|
3292
|
+
epochWaiters.add(wake);
|
|
3293
|
+
sleep(waitMs).then(wake);
|
|
3294
|
+
});
|
|
3295
|
+
}
|
|
3163
3296
|
const first = await establish();
|
|
3164
3297
|
if (!first.success)
|
|
3165
3298
|
return first;
|
|
@@ -3197,39 +3330,8 @@ async function connectDevtools(o) {
|
|
|
3197
3330
|
return devtoolsErr(probe.error.code, `reload probe failed: ${probe.error.message}`);
|
|
3198
3331
|
}
|
|
3199
3332
|
const method = readReloadMethod(probe.data) ?? "http";
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
try {
|
|
3203
|
-
const res = await o.fetch(`${metroUrl.replace(/\/+$/, "")}/reload`, { method: "POST" });
|
|
3204
|
-
if (!res.ok) {
|
|
3205
|
-
return devtoolsErr("devtools_unavailable", `reload failed: POST /reload answered ${res.status}`);
|
|
3206
|
-
}
|
|
3207
|
-
} catch (error) {
|
|
3208
|
-
return devtoolsErr("devtools_unavailable", `reload failed: POST /reload threw ${String(error)}`);
|
|
3209
|
-
}
|
|
3210
|
-
} else {
|
|
3211
|
-
log(`devtools: reload via ${method}`);
|
|
3212
|
-
}
|
|
3213
|
-
if (epoch === startedAt) {
|
|
3214
|
-
await new Promise((resolve) => {
|
|
3215
|
-
const wake = () => {
|
|
3216
|
-
epochWaiters.delete(wake);
|
|
3217
|
-
resolve();
|
|
3218
|
-
};
|
|
3219
|
-
epochWaiters.add(wake);
|
|
3220
|
-
sleep(waitMs).then(wake);
|
|
3221
|
-
});
|
|
3222
|
-
}
|
|
3223
|
-
if (epoch === startedAt && state.kind === "up") {
|
|
3224
|
-
log(`devtools: reload via ${method} not observed after ${waitMs} ms`);
|
|
3225
|
-
return devtoolsErr("reload_failed", `reload via ${method} acknowledged but the JS context did not restart within ${waitMs} ms`);
|
|
3226
|
-
}
|
|
3227
|
-
if (state.kind === "reconnecting") {
|
|
3228
|
-
const back = await state.done;
|
|
3229
|
-
if (!back.success)
|
|
3230
|
-
return back;
|
|
3231
|
-
}
|
|
3232
|
-
return ok({ method });
|
|
3333
|
+
const failure = await requestReload(method, metroUrl) ?? await awaitReload(method, startedAt, waitMs);
|
|
3334
|
+
return failure ?? ok({ method });
|
|
3233
3335
|
},
|
|
3234
3336
|
get target() {
|
|
3235
3337
|
if (state.kind === "up")
|
|
@@ -3909,6 +4011,25 @@ function spawnFailure(cmd, cwd, error) {
|
|
|
3909
4011
|
return `cannot run \`${cmd.join(" ")}\`: no such directory ${cwd}`;
|
|
3910
4012
|
return `cannot run \`${cmd.join(" ")}\`: ${error instanceof Error ? error.message : String(error)}`;
|
|
3911
4013
|
}
|
|
4014
|
+
async function readLines(stream, onLine) {
|
|
4015
|
+
const decoder = new TextDecoder;
|
|
4016
|
+
let text = "";
|
|
4017
|
+
let pending = "";
|
|
4018
|
+
for await (const chunk of stream) {
|
|
4019
|
+
const piece = decoder.decode(chunk, { stream: true });
|
|
4020
|
+
text += piece;
|
|
4021
|
+
const lines = (pending + piece).split(/\r?\n/);
|
|
4022
|
+
pending = lines.pop() ?? "";
|
|
4023
|
+
for (const line of lines)
|
|
4024
|
+
onLine(line);
|
|
4025
|
+
}
|
|
4026
|
+
const rest = decoder.decode();
|
|
4027
|
+
text += rest;
|
|
4028
|
+
pending += rest;
|
|
4029
|
+
if (pending !== "")
|
|
4030
|
+
onLine(pending);
|
|
4031
|
+
return text;
|
|
4032
|
+
}
|
|
3912
4033
|
var bunExec = async (cmd, opts = {}) => {
|
|
3913
4034
|
try {
|
|
3914
4035
|
const proc = Bun.spawn([...cmd], {
|
|
@@ -3920,11 +4041,9 @@ var bunExec = async (cmd, opts = {}) => {
|
|
|
3920
4041
|
timeout: opts.timeoutMs,
|
|
3921
4042
|
...opts.signal !== undefined ? { signal: opts.signal } : {}
|
|
3922
4043
|
});
|
|
3923
|
-
const
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
proc.exited
|
|
3927
|
-
]);
|
|
4044
|
+
const { onLine } = opts;
|
|
4045
|
+
const read = (stream) => onLine ? readLines(stream, onLine) : new Response(stream).text();
|
|
4046
|
+
const [stdout, stderr, exitCode] = await Promise.all([read(proc.stdout), read(proc.stderr), proc.exited]);
|
|
3928
4047
|
return { exitCode, stdout, stderr };
|
|
3929
4048
|
} catch (error) {
|
|
3930
4049
|
return { exitCode: 127, stdout: "", stderr: spawnFailure(cmd, opts.cwd, error) };
|
|
@@ -4158,6 +4277,8 @@ var frontmostDataSchema = z6.looseObject({
|
|
|
4158
4277
|
pid: z6.number().optional(),
|
|
4159
4278
|
frontmostPid: z6.number().optional()
|
|
4160
4279
|
});
|
|
4280
|
+
var launchDataSchema = z6.looseObject({ pid: z6.number() });
|
|
4281
|
+
var terminateDataSchema = z6.looseObject({ terminated: z6.boolean() });
|
|
4161
4282
|
var orientationDataSchema = z6.object({
|
|
4162
4283
|
orientation: z6.enum([
|
|
4163
4284
|
"portrait",
|
|
@@ -4179,7 +4300,9 @@ var opSchemas = {
|
|
|
4179
4300
|
rebind: rebindDataSchema,
|
|
4180
4301
|
alertButtons: alertButtonsDataSchema,
|
|
4181
4302
|
frontmost: frontmostDataSchema,
|
|
4182
|
-
orientation: orientationDataSchema
|
|
4303
|
+
orientation: orientationDataSchema,
|
|
4304
|
+
launch: launchDataSchema,
|
|
4305
|
+
terminate: terminateDataSchema
|
|
4183
4306
|
};
|
|
4184
4307
|
function hasOpSchema(op) {
|
|
4185
4308
|
return Object.hasOwn(opSchemas, op);
|
|
@@ -5456,7 +5579,7 @@ function parseBudgets(raw) {
|
|
|
5456
5579
|
|
|
5457
5580
|
// ../../packages/e2e/src/perf/device-perf.ts
|
|
5458
5581
|
var PERF_DIR = "perf";
|
|
5459
|
-
function
|
|
5582
|
+
function parseEnvelope2(stdout) {
|
|
5460
5583
|
try {
|
|
5461
5584
|
const json = JSON.parse(stdout.trim().split(`
|
|
5462
5585
|
`).at(-1) ?? "");
|
|
@@ -5509,7 +5632,7 @@ function createDevicePerf(deps) {
|
|
|
5509
5632
|
if (deps.unavailable !== undefined)
|
|
5510
5633
|
throw new E2eError2("SESSION", `device.perf unavailable: ${deps.unavailable}`);
|
|
5511
5634
|
const { code, stdout, stderr } = await deps.exec(["perf", sub, "--json", "--udid", deps.udid, ...extra]);
|
|
5512
|
-
const env =
|
|
5635
|
+
const env = parseEnvelope2(stdout);
|
|
5513
5636
|
if (env === undefined || !env.ok) {
|
|
5514
5637
|
const why = env === undefined ? stderr.trim() || `exit ${code}` : `${env.error.code}: ${env.error.message}`;
|
|
5515
5638
|
throw new E2eError2("SESSION", `warden perf ${sub} failed: ${why}`);
|
|
@@ -5827,6 +5950,6 @@ function onlyWasSeen() {
|
|
|
5827
5950
|
|
|
5828
5951
|
// ../../packages/e2e/src/version.ts
|
|
5829
5952
|
var WORKSPACE_VERSION = "0.0.0-workspace";
|
|
5830
|
-
var WARDEN_E2E_VERSION2 = "0.
|
|
5953
|
+
var WARDEN_E2E_VERSION2 = "0.4.0";
|
|
5831
5954
|
|
|
5832
5955
|
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 };
|