@delacour/warden-e2e 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/LICENSE +21 -0
  2. package/chunk-e6pn5ygy.mjs +5832 -0
  3. package/index.d.ts +1 -0
  4. package/index.mjs +1047 -0
  5. package/package.json +49 -4
  6. package/preload.d.ts +1 -0
  7. package/preload.mjs +4788 -0
  8. package/types/core/src/allocate.d.ts +23 -0
  9. package/types/core/src/builds/app-option.schema.d.ts +37 -0
  10. package/types/core/src/builds/install.d.ts +95 -0
  11. package/types/core/src/builds/resolve.d.ts +74 -0
  12. package/types/core/src/config.defaults.d.ts +25 -0
  13. package/types/core/src/exec.d.ts +18 -0
  14. package/types/core/src/liveness.d.ts +12 -0
  15. package/types/core/src/providers/android.d.ts +132 -0
  16. package/types/core/src/providers/health.d.ts +45 -0
  17. package/types/core/src/providers/provider.types.d.ts +56 -0
  18. package/types/core/src/store.d.ts +105 -0
  19. package/types/core/src/types.d.ts +126 -0
  20. package/types/e2e/src/alert-buttons.d.ts +41 -0
  21. package/types/e2e/src/alert-reader.d.ts +25 -0
  22. package/types/e2e/src/app.d.ts +62 -0
  23. package/types/e2e/src/device-devtools.d.ts +26 -0
  24. package/types/e2e/src/device-link.d.ts +43 -0
  25. package/types/e2e/src/device.d.ts +90 -0
  26. package/types/e2e/src/devtools/cdp.d.ts +65 -0
  27. package/types/e2e/src/devtools/console.d.ts +59 -0
  28. package/types/e2e/src/devtools/devtools.types.d.ts +169 -0
  29. package/types/e2e/src/devtools/facet.d.ts +28 -0
  30. package/types/e2e/src/devtools/inspector.d.ts +48 -0
  31. package/types/e2e/src/devtools/jsonl.d.ts +4 -0
  32. package/types/e2e/src/devtools/native-log.d.ts +50 -0
  33. package/types/e2e/src/devtools/network.d.ts +33 -0
  34. package/types/e2e/src/devtools/network.script.d.ts +24 -0
  35. package/types/e2e/src/devtools/session.d.ts +82 -0
  36. package/types/e2e/src/devtools/tree.d.ts +23 -0
  37. package/types/e2e/src/devtools/tree.script.d.ts +33 -0
  38. package/types/e2e/src/driver/alerts.d.ts +29 -0
  39. package/types/e2e/src/driver/android/adb-session.d.ts +52 -0
  40. package/types/e2e/src/driver/android/android-alert.d.ts +4 -0
  41. package/types/e2e/src/driver/android/android-animations.d.ts +29 -0
  42. package/types/e2e/src/driver/android/android-app.d.ts +32 -0
  43. package/types/e2e/src/driver/android/android-argv.d.ts +122 -0
  44. package/types/e2e/src/driver/android/android-device.d.ts +44 -0
  45. package/types/e2e/src/driver/android/android-driver.d.ts +60 -0
  46. package/types/e2e/src/driver/android/android-dump.d.ts +46 -0
  47. package/types/e2e/src/driver/android/android-state.d.ts +69 -0
  48. package/types/e2e/src/driver/android/uiad-backend.d.ts +63 -0
  49. package/types/e2e/src/driver/android/uiad-client.d.ts +60 -0
  50. package/types/e2e/src/driver/android/uiad-device.d.ts +70 -0
  51. package/types/e2e/src/driver/android/uiad-dex.generated.d.ts +8 -0
  52. package/types/e2e/src/driver/android/uiad-ops.d.ts +62 -0
  53. package/types/e2e/src/driver/android/uiad-protocol.d.ts +209 -0
  54. package/types/e2e/src/driver/android/uiautomator.d.ts +33 -0
  55. package/types/e2e/src/driver/driver.types.d.ts +231 -0
  56. package/types/e2e/src/driver/errors.d.ts +13 -0
  57. package/types/e2e/src/driver/gesture-geometry.d.ts +48 -0
  58. package/types/e2e/src/driver/ios/hid-recovery.d.ts +36 -0
  59. package/types/e2e/src/driver/ios/ios-driver.d.ts +63 -0
  60. package/types/e2e/src/driver/ios/ios-session.d.ts +30 -0
  61. package/types/e2e/src/driver/ios/locate-helper.d.ts +38 -0
  62. package/types/e2e/src/driver/ios/normalize.d.ts +11 -0
  63. package/types/e2e/src/driver/ios/simctl.d.ts +102 -0
  64. package/types/e2e/src/driver/ios/simd-client.d.ts +68 -0
  65. package/types/e2e/src/driver/ios/simd-protocol.d.ts +433 -0
  66. package/types/e2e/src/driver/ios/simd-transport.d.ts +43 -0
  67. package/types/e2e/src/driver/ios/type-budget.d.ts +6 -0
  68. package/types/e2e/src/driver/observation-cache.d.ts +66 -0
  69. package/types/e2e/src/driver/open.d.ts +29 -0
  70. package/types/e2e/src/driver/settle.d.ts +43 -0
  71. package/types/e2e/src/driver/span-detail.d.ts +12 -0
  72. package/types/e2e/src/driver/traced-driver.d.ts +14 -0
  73. package/types/e2e/src/driver/typing.d.ts +48 -0
  74. package/types/e2e/src/driver/unwrap.d.ts +13 -0
  75. package/types/e2e/src/env.d.ts +78 -0
  76. package/types/e2e/src/errors.d.ts +26 -0
  77. package/types/e2e/src/expect/expect.d.ts +27 -0
  78. package/types/e2e/src/expect/matchers.d.ts +72 -0
  79. package/types/e2e/src/fixture-session.d.ts +38 -0
  80. package/types/e2e/src/fixtures.d.ts +14 -0
  81. package/types/e2e/src/index.d.ts +11 -0
  82. package/types/e2e/src/perf/budgets.d.ts +33 -0
  83. package/types/e2e/src/perf/device-perf.d.ts +45 -0
  84. package/types/e2e/src/perf/perf.types.d.ts +59 -0
  85. package/types/e2e/src/preload.d.ts +29 -0
  86. package/types/e2e/src/runner/artifacts.d.ts +13 -0
  87. package/types/e2e/src/runner/attempt.d.ts +63 -0
  88. package/types/e2e/src/runner/callsite.d.ts +2 -0
  89. package/types/e2e/src/runner/failure.d.ts +40 -0
  90. package/types/e2e/src/runner/fixtures.types.d.ts +60 -0
  91. package/types/e2e/src/runner/results.d.ts +48 -0
  92. package/types/e2e/src/runner/scope.d.ts +64 -0
  93. package/types/e2e/src/runner/span.d.ts +40 -0
  94. package/types/e2e/src/runner/step.d.ts +7 -0
  95. package/types/e2e/src/runner/test.d.ts +59 -0
  96. package/types/e2e/src/runner/trace.d.ts +87 -0
  97. package/types/e2e/src/screen/actions.d.ts +93 -0
  98. package/types/e2e/src/screen/context.d.ts +134 -0
  99. package/types/e2e/src/screen/field.d.ts +121 -0
  100. package/types/e2e/src/screen/locator.d.ts +233 -0
  101. package/types/e2e/src/screen/nodes.d.ts +53 -0
  102. package/types/e2e/src/screen/poll.d.ts +57 -0
  103. package/types/e2e/src/screen/queries.d.ts +34 -0
  104. package/types/e2e/src/screen/query.d.ts +60 -0
  105. package/types/e2e/src/screen/react-locator.d.ts +30 -0
  106. package/types/e2e/src/screen/render.d.ts +42 -0
  107. package/types/e2e/src/screen/roles.d.ts +24 -0
  108. package/types/e2e/src/screen/screen.d.ts +61 -0
  109. package/types/e2e/src/screen/selector.d.ts +38 -0
  110. package/types/e2e/src/screen/text.d.ts +13 -0
  111. package/types/e2e/src/session.d.ts +124 -0
  112. package/types/e2e/src/traced-device.d.ts +9 -0
  113. package/types/e2e/src/version.d.ts +7 -0
  114. package/types/types/src/result.d.ts +12 -0
  115. package/README.md +0 -3
package/index.mjs ADDED
@@ -0,0 +1,1047 @@
1
+ // @bun
2
+ import {
3
+ E2eError2,
4
+ canObserve,
5
+ fieldValue,
6
+ normalizeText,
7
+ matchesText,
8
+ renderSnapshot,
9
+ renderHeader,
10
+ resolve,
11
+ isLocator,
12
+ pickOne,
13
+ textOf,
14
+ isHidLost,
15
+ runStep,
16
+ attemptSession,
17
+ getSession,
18
+ perfStartFor,
19
+ hash6,
20
+ attemptDir,
21
+ specIdOf,
22
+ captureNativeLog,
23
+ callerFile,
24
+ BUN_TIMEOUT_MARGIN_MS2,
25
+ ownHook,
26
+ currentScope,
27
+ createScope,
28
+ runInScope,
29
+ hooksFor,
30
+ resolveUse,
31
+ registrationDefaults,
32
+ markOnly,
33
+ WARDEN_E2E_VERSION2
34
+ } from "./chunk-e6pn5ygy.mjs";
35
+ // ../../packages/e2e/src/expect/expect.ts
36
+ import { expect as bunExpect } from "bun:test";
37
+
38
+ // ../../packages/e2e/src/expect/matchers.ts
39
+ var NEGATION_GRACE_MS = 1000;
40
+ var NEGATION_QUIET_MS = 250;
41
+ var NOT_FOUND = "element not found";
42
+ function missing() {
43
+ return { kind: "missing" };
44
+ }
45
+ function judged(matches, received) {
46
+ return { kind: "judged", matches, received };
47
+ }
48
+ function show(match) {
49
+ return typeof match === "string" ? JSON.stringify(match) : String(match);
50
+ }
51
+ function assertOptions(matcher, options, allowed) {
52
+ const unknown = Object.keys(options).filter((key) => !allowed.includes(key));
53
+ if (unknown.length > 0) {
54
+ throw new E2eError2("INVALID_ARGUMENT", `${matcher} does not take ${unknown.map((key) => `"${key}"`).join(", ")}; it takes ${allowed.join(", ")}`);
55
+ }
56
+ const bag = options;
57
+ for (const key of ["checked", "exact", "ignoreCase"]) {
58
+ if (bag[key] !== undefined && typeof bag[key] !== "boolean") {
59
+ throw new E2eError2("INVALID_ARGUMENT", `${matcher}: "${key}" must be a boolean`);
60
+ }
61
+ }
62
+ const timeout = bag.timeout;
63
+ if (timeout !== undefined && (typeof timeout !== "number" || !Number.isFinite(timeout) || timeout < 0)) {
64
+ throw new E2eError2("INVALID_ARGUMENT", `${matcher}: "timeout" must be a non-negative number of milliseconds`);
65
+ }
66
+ }
67
+ function failUnlessRetryable(error) {
68
+ if (!error.retryable)
69
+ throw new E2eError2("DRIVER", `snapshot failed: ${error.code}: ${error.message}`);
70
+ }
71
+ function regexTest(pattern, value, ignoreCase = false) {
72
+ const flags = pattern.flags.replaceAll(/[gy]/g, "");
73
+ return new RegExp(pattern.source, ignoreCase && !flags.includes("i") ? `${flags}i` : flags).test(value);
74
+ }
75
+ function containsText(value, expected, ignoreCase) {
76
+ if (typeof expected !== "string")
77
+ return regexTest(expected, normalizeText(value), ignoreCase);
78
+ const haystack = normalizeText(value);
79
+ const needle = normalizeText(expected);
80
+ return ignoreCase ? haystack.toLowerCase().includes(needle.toLowerCase()) : haystack.includes(needle);
81
+ }
82
+
83
+ class LocatorAssertions {
84
+ locator;
85
+ context;
86
+ constructor(locator, context) {
87
+ this.locator = locator;
88
+ this.context = context;
89
+ }
90
+ get not() {
91
+ return new LocatorAssertions(this.locator, { ...this.context, negate: !this.context.negate });
92
+ }
93
+ async toBeVisible(options = {}) {
94
+ assertOptions("toBeVisible", options, ["timeout"]);
95
+ return this.assert({
96
+ call: "toBeVisible()",
97
+ expected: "visible",
98
+ strict: true,
99
+ judge: (entry) => entry === undefined ? judged(false, "not found") : judged(entry.visible, entry.visible ? "visible" : "hidden")
100
+ }, options);
101
+ }
102
+ async toBeHidden(options = {}) {
103
+ assertOptions("toBeHidden", options, ["timeout"]);
104
+ return this.assert({
105
+ call: "toBeHidden()",
106
+ expected: "hidden",
107
+ strict: true,
108
+ judge: (entry) => entry === undefined ? judged(true, "not found") : judged(!entry.visible, entry.visible ? "visible" : "hidden")
109
+ }, options);
110
+ }
111
+ async toHaveText(expected, options = {}) {
112
+ assertOptions("toHaveText", options, ["exact", "timeout"]);
113
+ const exact = options.exact ?? true;
114
+ return this.assert({
115
+ call: `toHaveText(${show(expected)})`,
116
+ expected: exact ? show(expected) : `${show(expected)} (substring, ignoring case)`,
117
+ strict: true,
118
+ judge: (entry) => {
119
+ if (entry === undefined)
120
+ return missing();
121
+ const value = textOf(entry);
122
+ return judged(matchesText(value, expected, { exact }), JSON.stringify(value));
123
+ }
124
+ }, options);
125
+ }
126
+ async toContainText(expected, options = {}) {
127
+ assertOptions("toContainText", options, ["ignoreCase", "timeout"]);
128
+ const ignoreCase = options.ignoreCase ?? false;
129
+ return this.assert({
130
+ call: `toContainText(${show(expected)})`,
131
+ expected: `text containing ${show(expected)}`,
132
+ strict: true,
133
+ judge: (entry) => {
134
+ if (entry === undefined)
135
+ return missing();
136
+ const value = textOf(entry);
137
+ return judged(containsText(value, expected, ignoreCase), JSON.stringify(value));
138
+ }
139
+ }, options);
140
+ }
141
+ async toHaveValue(expected, options = {}) {
142
+ assertOptions("toHaveValue", options, ["timeout"]);
143
+ return this.assert({
144
+ call: `toHaveValue(${show(expected)})`,
145
+ expected: show(expected),
146
+ strict: true,
147
+ judge: (entry) => {
148
+ if (entry === undefined)
149
+ return missing();
150
+ if (entry.secure) {
151
+ throw new E2eError2("INVALID_ARGUMENT", "toHaveValue cannot judge a secure field: its value is withheld. Assert the outcome of the fill instead.");
152
+ }
153
+ const value = fieldValue(entry);
154
+ const matches = typeof expected === "string" ? value === expected : regexTest(expected, value);
155
+ return judged(matches, JSON.stringify(value));
156
+ }
157
+ }, options);
158
+ }
159
+ async toBeChecked(options = {}) {
160
+ assertOptions("toBeChecked", options, ["checked", "timeout"]);
161
+ const want = options.checked ?? true;
162
+ return this.assert({
163
+ call: options.checked === undefined ? "toBeChecked()" : `toBeChecked({ checked: ${want} })`,
164
+ expected: want ? "checked" : "unchecked",
165
+ strict: true,
166
+ judge: (entry) => {
167
+ if (entry === undefined)
168
+ return missing();
169
+ const checked = entry.states.checked ?? false;
170
+ return judged(checked === want, checked ? "checked" : "unchecked");
171
+ }
172
+ }, options);
173
+ }
174
+ async toBeEnabled(options = {}) {
175
+ assertOptions("toBeEnabled", options, ["timeout"]);
176
+ return this.assert({
177
+ call: "toBeEnabled()",
178
+ expected: "enabled",
179
+ strict: true,
180
+ judge: (entry) => entry === undefined ? missing() : judged(!entry.states.disabled, entry.states.disabled ? "disabled" : "enabled")
181
+ }, options);
182
+ }
183
+ async toBeDisabled(options = {}) {
184
+ assertOptions("toBeDisabled", options, ["timeout"]);
185
+ return this.assert({
186
+ call: "toBeDisabled()",
187
+ expected: "disabled",
188
+ strict: true,
189
+ judge: (entry) => entry === undefined ? missing() : judged(entry.states.disabled, entry.states.disabled ? "disabled" : "enabled")
190
+ }, options);
191
+ }
192
+ async toHaveCount(expected, options = {}) {
193
+ assertOptions("toHaveCount", options, ["timeout"]);
194
+ if (!Number.isInteger(expected) || expected < 0) {
195
+ throw new E2eError2("INVALID_ARGUMENT", `toHaveCount takes a non-negative integer, got ${String(expected)}`);
196
+ }
197
+ return this.assert({
198
+ call: `toHaveCount(${expected})`,
199
+ expected: String(expected),
200
+ strict: false,
201
+ judge: (_entry, count) => judged(count === expected, String(count))
202
+ }, options);
203
+ }
204
+ async assert(spec, options) {
205
+ const rt = this.locator.runtime;
206
+ const timeoutMs = options.timeout ?? rt.assertionTimeout();
207
+ try {
208
+ await rt.step(`expect ${this.locator}.${spec.call}`, () => this.poll(spec, timeoutMs));
209
+ } catch (error) {
210
+ if (this.context.soft !== undefined && error instanceof E2eError2 && error.code !== "TIMEOUT") {
211
+ this.context.soft(error);
212
+ return;
213
+ }
214
+ throw error;
215
+ }
216
+ }
217
+ async poll(spec, timeoutMs) {
218
+ const rt = this.locator.runtime;
219
+ const { negate } = this.context;
220
+ const hold = conditionHold(negate, Math.min(NEGATION_GRACE_MS, timeoutMs), rt.clock.now);
221
+ const result = await rt.poll(async (attempt) => {
222
+ const observation = await rt.tryObserve({ reuse: attempt === 1 });
223
+ if (!observation.ok) {
224
+ failUnlessRetryable(observation.error);
225
+ hold.broken();
226
+ return { done: false, state: { received: "(screen unreadable)" } };
227
+ }
228
+ const { observed } = observation;
229
+ const entries = resolve(this.locator.expr, observed.index);
230
+ if (spec.strict && entries.length > 1)
231
+ pickOne(rt, this.locator.expr, entries, observed);
232
+ const judgement = spec.judge(entries[0], entries.length);
233
+ const verdict = await hold.next(judgement.kind === "judged" && judgement.matches !== negate, observed.driver);
234
+ if (verdict === "pass")
235
+ return { done: true, value: true };
236
+ return {
237
+ done: false,
238
+ state: {
239
+ received: judgement.kind === "missing" ? NOT_FOUND : judgement.received,
240
+ snapshot: observed.snapshot
241
+ },
242
+ ...verdict.wakeMs === undefined ? {} : { maxWaitMs: verdict.wakeMs }
243
+ };
244
+ }, timeoutMs);
245
+ if (result.status === "done")
246
+ return;
247
+ throw this.failure(spec, result.last);
248
+ }
249
+ failure(spec, last) {
250
+ const { negate } = this.context;
251
+ const lines = [
252
+ `expect(${this.locator})${negate ? ".not" : ""}.${spec.call}`,
253
+ `Expected: ${negate ? "not " : ""}${spec.expected}`,
254
+ `Received: ${last?.received ?? NOT_FOUND}`
255
+ ];
256
+ const screen = last?.snapshot === undefined ? undefined : this.locator.runtime.excerpt(last.snapshot);
257
+ const message = screen === undefined ? lines.join(`
258
+ `) : `${lines.join(`
259
+ `)}
260
+
261
+ ${screen}`;
262
+ return new E2eError2("ASSERTION", message, screen);
263
+ }
264
+ }
265
+ function conditionHold(negate, holdMs, now) {
266
+ let since;
267
+ let asked = false;
268
+ let quiet = false;
269
+ const broken = () => {
270
+ since = undefined;
271
+ quiet = false;
272
+ };
273
+ return {
274
+ broken,
275
+ async next(holds, driver) {
276
+ if (!holds) {
277
+ broken();
278
+ return { wakeMs: undefined };
279
+ }
280
+ since ??= now();
281
+ if (!negate || quiet || now() - since >= holdMs)
282
+ return "pass";
283
+ if (!asked && driver.capabilities.nativeSettle && noRecentInput(driver)) {
284
+ asked = true;
285
+ quiet = await settledQuiet(driver, since + holdMs - now());
286
+ }
287
+ return { wakeMs: quiet ? 0 : since + holdMs - now() };
288
+ }
289
+ };
290
+ }
291
+ function noRecentInput(driver) {
292
+ const previous = canObserve(driver) ? driver.previous?.() : undefined;
293
+ if (previous === undefined)
294
+ return false;
295
+ return previous.sinceInputMs === undefined || previous.sinceInputMs >= NEGATION_GRACE_MS;
296
+ }
297
+ async function settledQuiet(driver, timeoutMs) {
298
+ if (timeoutMs <= 0)
299
+ return false;
300
+ const settled = await driver.settle({ quietMs: NEGATION_QUIET_MS, timeoutMs });
301
+ return settled.success && settled.data.quiet;
302
+ }
303
+
304
+ // ../../packages/e2e/src/expect/expect.ts
305
+ function softSinkFor(session) {
306
+ const failures = session.attempt?.softFailures;
307
+ return failures === undefined ? undefined : (error) => void failures.push(error);
308
+ }
309
+ function currentSoftSink() {
310
+ try {
311
+ return softSinkFor(getSession());
312
+ } catch {
313
+ return;
314
+ }
315
+ }
316
+ var valueExpect = bunExpect;
317
+ function asAssertion(error) {
318
+ if (error instanceof E2eError2)
319
+ return error;
320
+ return new E2eError2("ASSERTION", error instanceof Error ? error.message : String(error));
321
+ }
322
+ function isThenable(value) {
323
+ return typeof value === "object" && value !== null && "then" in value && typeof value.then === "function";
324
+ }
325
+ function softMatchers(target, sink) {
326
+ return new Proxy(target, {
327
+ get(object, property) {
328
+ const value = Reflect.get(object, property, object);
329
+ if (typeof value === "function") {
330
+ return (...args) => {
331
+ try {
332
+ const result = Reflect.apply(value, object, args);
333
+ return isThenable(result) ? Promise.resolve(result).catch((e) => sink(asAssertion(e))) : result;
334
+ } catch (error) {
335
+ sink(asAssertion(error));
336
+ return;
337
+ }
338
+ };
339
+ }
340
+ if (typeof value === "object" && value !== null)
341
+ return softMatchers(value, sink);
342
+ return value;
343
+ }
344
+ });
345
+ }
346
+ var NOT_STATIC = new Set(["length", "name", "prototype"]);
347
+ function createExpect(deps) {
348
+ const call = (actual, message) => isLocator(actual) ? new LocatorAssertions(actual, { negate: false }) : valueExpect(actual, message);
349
+ const soft = (actual, message) => {
350
+ const sink = deps.softSink();
351
+ if (isLocator(actual))
352
+ return new LocatorAssertions(actual, { negate: false, ...sink === undefined ? {} : { soft: sink } });
353
+ const matchers = valueExpect(actual, message);
354
+ return sink === undefined ? matchers : softMatchers(matchers, sink);
355
+ };
356
+ for (const key of Reflect.ownKeys(bunExpect)) {
357
+ if (NOT_STATIC.has(key))
358
+ continue;
359
+ Object.defineProperty(call, key, {
360
+ enumerable: true,
361
+ get() {
362
+ const value = Reflect.get(bunExpect, key);
363
+ return typeof value === "function" ? value.bind(bunExpect) : value;
364
+ }
365
+ });
366
+ }
367
+ Object.defineProperty(call, "soft", { enumerable: true, value: soft });
368
+ return call;
369
+ }
370
+ var expect = createExpect({ softSink: currentSoftSink });
371
+ // ../../packages/e2e/src/runner/test.ts
372
+ import { afterAll as bunAfterAll, beforeAll as bunBeforeAll, describe as bunDescribe, test as bunTest } from "bun:test";
373
+
374
+ // ../../packages/e2e/src/runner/attempt.ts
375
+ import { readdirSync } from "fs";
376
+ import { join as join2 } from "path";
377
+
378
+ // ../../packages/e2e/src/runner/failure.ts
379
+ import { mkdirSync, writeFileSync } from "fs";
380
+ import { join } from "path";
381
+ var FAILURE_PNG = "failure.png";
382
+ var SCREEN_AT_FAILURE = "screen-at-failure.txt";
383
+ var CONSOLE_JSONL = "console.jsonl";
384
+ var NETWORK_JSONL = "network.jsonl";
385
+ var CAPTURE_TIMEOUT_MS = 1e4;
386
+
387
+ class CaptureTimeout extends Error {
388
+ }
389
+ function limited(promise, ms) {
390
+ let timer;
391
+ const timeout = new Promise((_, reject) => {
392
+ timer = setTimeout(() => reject(new CaptureTimeout(`timed out after ${ms}ms`)), ms);
393
+ });
394
+ return Promise.race([promise, timeout]).finally(() => clearTimeout(timer));
395
+ }
396
+ var unlimited = () => Number.POSITIVE_INFINITY;
397
+ var SPENT = "skipped, the post-deadline budget is used up";
398
+ async function captureFailure(driver, dir, timeoutMs = CAPTURE_TIMEOUT_MS, budget = unlimited) {
399
+ const capture = { files: [], problems: [] };
400
+ try {
401
+ mkdirSync(dir, { recursive: true });
402
+ } catch (cause) {
403
+ capture.problems.push(`mkdir ${dir}: ${String(cause)}`);
404
+ return capture;
405
+ }
406
+ const shot = await captureScreenshot(driver, dir, Math.min(timeoutMs, budget()), capture);
407
+ if (shot === "unanswered") {
408
+ capture.problems.push("snapshot: skipped, the screenshot timed out (the device is not answering)");
409
+ return capture;
410
+ }
411
+ await captureSnapshot(driver, dir, Math.min(timeoutMs, budget()), capture);
412
+ return capture;
413
+ }
414
+ var message = (cause) => cause instanceof Error ? cause.message : String(cause);
415
+ async function captureScreenshot(driver, dir, ms, capture) {
416
+ if (ms <= 0) {
417
+ capture.problems.push(`screenshot: ${SPENT}`);
418
+ return "answered";
419
+ }
420
+ try {
421
+ const shot = await limited(driver.screenshot({ path: join(dir, FAILURE_PNG) }), ms);
422
+ if (!shot.success) {
423
+ capture.problems.push(`screenshot: ${shot.error.code}: ${shot.error.message}`);
424
+ return "answered";
425
+ }
426
+ if (shot.data.path === undefined && shot.data.png !== undefined) {
427
+ writeFileSync(join(dir, FAILURE_PNG), shot.data.png);
428
+ }
429
+ capture.files.push(FAILURE_PNG);
430
+ } catch (cause) {
431
+ capture.problems.push(`screenshot: ${message(cause)}`);
432
+ if (cause instanceof CaptureTimeout)
433
+ return "unanswered";
434
+ }
435
+ return "answered";
436
+ }
437
+ async function captureSnapshot(driver, dir, ms, capture) {
438
+ if (ms <= 0) {
439
+ capture.problems.push(`snapshot: ${SPENT}`);
440
+ return;
441
+ }
442
+ try {
443
+ const result = await limited(driver.snapshot(), ms);
444
+ if (!result.success) {
445
+ capture.problems.push(`snapshot: ${result.error.code}: ${result.error.message}`);
446
+ return;
447
+ }
448
+ const rendered = renderSnapshot(result.data, { interactiveOnly: false, refs: new Map });
449
+ const header = renderHeader({ snapshot: result.data, rendered, interactiveOnly: false });
450
+ writeFileSync(join(dir, SCREEN_AT_FAILURE), `${[header, ...rendered.lines].join(`
451
+ `)}
452
+ `);
453
+ capture.files.push(SCREEN_AT_FAILURE);
454
+ } catch (cause) {
455
+ capture.problems.push(`snapshot: ${message(cause)}`);
456
+ }
457
+ }
458
+ async function captureDevtoolsArtifacts(facet, dir, timeoutMs = CAPTURE_TIMEOUT_MS, budget = unlimited) {
459
+ const capture = { files: [], problems: [] };
460
+ const ms = Math.min(timeoutMs, budget());
461
+ if (ms <= 0) {
462
+ capture.problems.push(`devtools artifacts: ${SPENT}`);
463
+ return capture;
464
+ }
465
+ try {
466
+ const written = await limited(facet.attachArtifacts(dir), ms);
467
+ if (written.success)
468
+ capture.files.push(CONSOLE_JSONL, NETWORK_JSONL);
469
+ else
470
+ capture.problems.push(`devtools artifacts: ${written.error.code}: ${written.error.message}`);
471
+ } catch (cause) {
472
+ capture.problems.push(`devtools artifacts: ${cause instanceof Error ? cause.message : String(cause)}`);
473
+ }
474
+ return capture;
475
+ }
476
+ function nativeTarget(env) {
477
+ return env.platform === "ios" ? { platform: "ios", udid: env.udid, bundleId: env.bundleId } : { platform: "android", serial: env.udid, pkg: env.bundleId };
478
+ }
479
+ async function captureNativeArtifacts(target, deps, win, timeoutMs = CAPTURE_TIMEOUT_MS, budget = unlimited) {
480
+ const ms = Math.min(timeoutMs, budget());
481
+ if (ms <= 0)
482
+ return { files: [], problems: [`native log: ${SPENT}`] };
483
+ try {
484
+ return await limited(captureNativeLog(target, deps, { ...win, nowMs: Date.now(), timeoutMs: ms }), ms);
485
+ } catch (cause) {
486
+ return { files: [], problems: [`native log: ${message(cause)}`] };
487
+ }
488
+ }
489
+
490
+ // ../../packages/e2e/src/runner/trace.ts
491
+ import { appendFileSync, mkdirSync as mkdirSync2 } from "fs";
492
+ import { dirname } from "path";
493
+ import { z } from "zod";
494
+ var base = { ts: z.number(), kind: z.string(), label: z.string() };
495
+ var traceEventSchema = z.discriminatedUnion("type", [
496
+ z.object({ type: z.literal("step-start"), ...base }),
497
+ z.object({
498
+ type: z.literal("step-end"),
499
+ ...base,
500
+ status: z.enum(["passed", "failed"]),
501
+ ms: z.number(),
502
+ error: z.string().optional()
503
+ }),
504
+ z.object({ type: z.literal("screenshot"), ...base, path: z.string() }),
505
+ z.object({ type: z.literal("note"), ...base }),
506
+ z.object({
507
+ type: z.literal("span"),
508
+ ts: z.number(),
509
+ op: z.string(),
510
+ ms: z.number(),
511
+ ok: z.boolean(),
512
+ code: z.string().optional(),
513
+ detail: z.record(z.string(), z.union([z.string(), z.number(), z.boolean()])).optional()
514
+ })
515
+ ]);
516
+ var SPAN_BUFFER_MAX = 256;
517
+ function openTrace(path, mirror) {
518
+ let ready = false;
519
+ let pending = [];
520
+ const append = (lines) => {
521
+ if (!ready) {
522
+ mkdirSync2(dirname(path), { recursive: true });
523
+ ready = true;
524
+ }
525
+ appendFileSync(path, lines.join(""));
526
+ };
527
+ const flush = () => {
528
+ if (pending.length === 0)
529
+ return;
530
+ const lines = pending;
531
+ pending = [];
532
+ append(lines);
533
+ };
534
+ return {
535
+ path,
536
+ write(event) {
537
+ const line = `${JSON.stringify(event)}
538
+ `;
539
+ if (event.type === "span") {
540
+ pending.push(line);
541
+ if (pending.length >= SPAN_BUFFER_MAX)
542
+ flush();
543
+ } else {
544
+ const lines = pending;
545
+ pending = [];
546
+ append([...lines, line]);
547
+ }
548
+ mirror?.(event);
549
+ },
550
+ flush
551
+ };
552
+ }
553
+ var pad = (n, width = 2) => String(n).padStart(width, "0");
554
+ function clockOf(ts) {
555
+ const d = new Date(ts);
556
+ return `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}.${pad(d.getMilliseconds(), 3)}`;
557
+ }
558
+ function shortName(file) {
559
+ return (file.split("/").pop() ?? file).replace(/\.e2e\.[cm]?[jt]sx?$/, "").replace(/\.[cm]?[jt]sx?$/, "");
560
+ }
561
+ var deviceLabel = (device) => device.name ?? (device.udid.length > 9 ? `${device.udid.slice(0, 9)}\u2026` : device.udid);
562
+ var testName = (test) => `${test.file} \u203A ${test.title.join(" \u203A ")}`;
563
+ function lineOf(event, opts) {
564
+ const short = opts.short ?? "test";
565
+ const indent = " ".repeat((opts.depth ?? 0) + 1);
566
+ switch (event.type) {
567
+ case "test-start":
568
+ return { ts: event.test.startedAt, text: `\u25B6 test |${event.test.device.platform}| ${testName(event.test)}` };
569
+ case "test-end": {
570
+ const r = event.result;
571
+ return {
572
+ ts: r.startedAt + r.durationMs,
573
+ text: `\u25A0 test |${r.device.platform}| ${testName(r)} ${r.status} ${(r.durationMs / 1000).toFixed(2)}s on ${deviceLabel(r.device)}`
574
+ };
575
+ }
576
+ case "step-start":
577
+ return { ts: event.ts, text: `${indent}\u25B8 ${short} \u203A ${event.kind} ${event.label}` };
578
+ case "step-end": {
579
+ const error = event.error ? ` \u2014 ${event.error}` : "";
580
+ return {
581
+ ts: event.ts,
582
+ text: `${indent}\u25AA ${short} \u203A ${event.kind} ${event.label} ${event.status} ${event.ms}ms${error}`
583
+ };
584
+ }
585
+ default:
586
+ return;
587
+ }
588
+ }
589
+ function formatConsoleLine(event, opts) {
590
+ const line = lineOf(event, opts);
591
+ if (line === undefined)
592
+ return;
593
+ return opts.clock ? `[${clockOf(line.ts)}] ${line.text}` : line.text;
594
+ }
595
+
596
+ // ../../packages/e2e/src/runner/attempt.ts
597
+ var CLEANUP_TIMEOUT_MS = 1e4;
598
+ var POST_DEADLINE_RESERVE_MS = 5000;
599
+ var DEFAULT_TIMING = {
600
+ cleanupTimeoutMs: CLEANUP_TIMEOUT_MS,
601
+ captureTimeoutMs: CAPTURE_TIMEOUT_MS,
602
+ bunMarginMs: BUN_TIMEOUT_MARGIN_MS2,
603
+ reserveMs: POST_DEADLINE_RESERVE_MS
604
+ };
605
+ var timing = { ...DEFAULT_TIMING };
606
+ function runnerTiming() {
607
+ return timing;
608
+ }
609
+ function unavailable(name) {
610
+ return new Proxy({}, {
611
+ get(_target, prop) {
612
+ if (typeof prop === "symbol" || prop === "then")
613
+ return;
614
+ throw new E2eError2("SESSION", `the \`${name}\` fixture is not available: no fixture factory is registered on the session`);
615
+ }
616
+ });
617
+ }
618
+ async function buildBaseFixtures(session, signal, use) {
619
+ if (session.fixtures === undefined) {
620
+ return {
621
+ app: unavailable("app"),
622
+ device: unavailable("device"),
623
+ screen: unavailable("screen"),
624
+ platform: session.env.platform
625
+ };
626
+ }
627
+ return session.fixtures({
628
+ session,
629
+ signal,
630
+ launch: use.launch,
631
+ actionTimeoutMs: use.actionTimeoutMs,
632
+ assertionTimeoutMs: use.assertionTimeoutMs,
633
+ systemAlerts: use.systemAlerts
634
+ });
635
+ }
636
+ async function startFixture(entry, bag) {
637
+ let provide = () => {
638
+ return;
639
+ };
640
+ let fail = () => {
641
+ return;
642
+ };
643
+ let release = () => {
644
+ return;
645
+ };
646
+ let used = false;
647
+ const value = new Promise((resolve, reject) => {
648
+ provide = resolve;
649
+ fail = reject;
650
+ });
651
+ const released = new Promise((resolve) => {
652
+ release = resolve;
653
+ });
654
+ const finished = entry.setup(bag, async (v) => {
655
+ used = true;
656
+ provide(v);
657
+ await released;
658
+ }).then(() => {
659
+ if (!used)
660
+ fail(new E2eError2("INVALID_ARGUMENT", `fixture \`${entry.name}\` finished without calling use()`));
661
+ }, (error) => {
662
+ if (!used)
663
+ fail(error);
664
+ else
665
+ throw error;
666
+ });
667
+ finished.catch(() => {
668
+ return;
669
+ });
670
+ const resolved = await value;
671
+ return {
672
+ value: resolved,
673
+ teardown: async () => {
674
+ release();
675
+ await finished;
676
+ }
677
+ };
678
+ }
679
+ function cleanupDeadline(startedAt, timeoutMs, bunTimeoutMs) {
680
+ const t = runnerTiming();
681
+ return startedAt + Math.min(timeoutMs + t.bunMarginMs, bunTimeoutMs ?? Number.POSITIVE_INFINITY) - t.reserveMs;
682
+ }
683
+ function limited2(label, ms, fn) {
684
+ let timer;
685
+ const timeout = new Promise((_, reject) => {
686
+ timer = setTimeout(() => reject(new E2eError2("TIMEOUT", `${label} timed out after ${ms}ms`)), ms);
687
+ });
688
+ return Promise.race([fn(), timeout]).finally(() => clearTimeout(timer));
689
+ }
690
+ function listScreenshots(dir) {
691
+ try {
692
+ return readdirSync(join2(dir, "screenshots")).map((name) => `screenshots/${name}`);
693
+ } catch {
694
+ return [];
695
+ }
696
+ }
697
+ function errorInfo(error) {
698
+ if (error instanceof Error) {
699
+ return {
700
+ message: error.message,
701
+ ...error instanceof E2eError2 ? { code: error.code } : {},
702
+ ...error.stack !== undefined ? { stack: error.stack } : {}
703
+ };
704
+ }
705
+ return { message: String(error) };
706
+ }
707
+ async function runAttempt(session, spec) {
708
+ if (await runAttemptOnce(session, spec) === "again")
709
+ await runAttemptOnce(session, { ...spec });
710
+ }
711
+ async function runAttemptOnce(session, spec) {
712
+ const { env } = session;
713
+ const use = resolveUse(spec.scope, { timeoutMs: env.timeoutMs, actionTimeoutMs: env.actionTimeoutMs, assertionTimeoutMs: env.assertionTimeoutMs }, spec.timeout);
714
+ const startedAt = Date.now();
715
+ const cleanupBy = cleanupDeadline(startedAt, use.timeoutMs, spec.bunTimeoutMs);
716
+ const budget = () => cleanupBy - Date.now();
717
+ const device = { udid: env.udid, platform: env.platform };
718
+ const clock = !env.logTimestamps;
719
+ const short = shortName(spec.file);
720
+ const dir = attemptDir(session.out, specIdOf(spec.file, env.specsDir), spec.title, hash6(spec.file, ...spec.title));
721
+ const print = (line) => {
722
+ if (line !== undefined)
723
+ session.log(line);
724
+ };
725
+ const files = new Set;
726
+ let binding;
727
+ const trace = openTrace(join2(dir, "trace.jsonl"), (event) => {
728
+ files.add("trace.jsonl");
729
+ print(formatConsoleLine(event, { clock, short, depth: binding?.context.depth ?? 0 }));
730
+ });
731
+ const controller = new AbortController;
732
+ const ctx = {
733
+ signal: controller.signal,
734
+ dir,
735
+ short,
736
+ trace,
737
+ depth: 0,
738
+ files,
739
+ softFailures: []
740
+ };
741
+ binding = { context: ctx, finished: false };
742
+ const bound = attemptSession(session, () => binding ?? { context: ctx, finished: false });
743
+ print(formatConsoleLine({ type: "test-start", test: { file: spec.file, title: [...spec.title], startedAt, device } }, { clock }));
744
+ session.enterAttempt(ctx);
745
+ let deadlineHit = false;
746
+ const timer = setTimeout(() => {
747
+ deadlineHit = true;
748
+ controller.abort(new E2eError2("TIMEOUT", `test timed out after ${use.timeoutMs}ms`));
749
+ }, use.timeoutMs);
750
+ const deadline = new Promise((_, reject) => {
751
+ controller.signal.addEventListener("abort", () => reject(controller.signal.reason), { once: true });
752
+ });
753
+ deadline.catch(() => {
754
+ return;
755
+ });
756
+ const teardowns = [];
757
+ const state = {};
758
+ let failed = false;
759
+ let failure;
760
+ try {
761
+ await Promise.race([setUpAndRun(bound, spec, use, controller.signal, state, teardowns), deadline]);
762
+ } catch (error) {
763
+ failed = true;
764
+ failure = error;
765
+ } finally {
766
+ clearTimeout(timer);
767
+ }
768
+ if (!failed && ctx.softFailures.length > 0) {
769
+ failed = true;
770
+ failure = softFailureError(ctx.softFailures);
771
+ }
772
+ if (failed)
773
+ await captureFailureArtifacts(session, ctx, budget, startedAt);
774
+ const cleanupErrors = await runCleanup(session, spec, ctx, state.bag, teardowns, budget, (context) => {
775
+ binding = { context, finished: false };
776
+ });
777
+ const finished = new E2eError2("SESSION", "attempt finished");
778
+ if (!controller.signal.aborted)
779
+ controller.abort(finished);
780
+ binding = { context: { ...ctx, signal: AbortSignal.abort(finished), depth: 0 }, finished: true };
781
+ session.leaveAttempt(ctx);
782
+ trace.flush();
783
+ if (!failed && cleanupErrors.length > 0) {
784
+ failed = true;
785
+ failure = cleanupErrors[0];
786
+ }
787
+ for (const shot of listScreenshots(dir))
788
+ files.add(shot);
789
+ const outcome = failed ? { failed: true, error: failure, timedOut: deadlineHit } : { failed: false };
790
+ const result = buildResult(spec, ctx, { startedAt, device }, outcome);
791
+ print(formatConsoleLine({ type: "test-end", result }, { clock }));
792
+ if (failed && session.recoverHid !== undefined && isHidLost(failure) && await session.recoverHid()) {
793
+ return "again";
794
+ }
795
+ if (!failed || spec.final)
796
+ session.results.append(result);
797
+ if (failed)
798
+ throw failure instanceof Error ? failure : new Error(String(failure));
799
+ return;
800
+ }
801
+ function softFailureError(failures) {
802
+ if (failures.length === 1 && failures[0] !== undefined)
803
+ return failures[0];
804
+ const body = failures.map((failure, index) => `${index + 1}) ${failure.message}`).join(`
805
+
806
+ `);
807
+ return new E2eError2("ASSERTION", `${failures.length} soft assertions failed:
808
+
809
+ ${body}`, failures[0]?.screen);
810
+ }
811
+ function buildResult(spec, ctx, run, outcome) {
812
+ const status = !outcome.failed ? "passed" : outcome.timedOut ? "timed-out" : "failed";
813
+ return {
814
+ file: spec.file,
815
+ title: [...spec.title],
816
+ status,
817
+ durationMs: Date.now() - run.startedAt,
818
+ startedAt: run.startedAt,
819
+ ...spec.only ? { only: true } : {},
820
+ ...outcome.failed ? { error: errorInfo(outcome.error) } : {},
821
+ ...ctx.files.size > 0 ? { artifacts: { dir: ctx.dir, files: [...ctx.files].sort() } } : {},
822
+ device: run.device
823
+ };
824
+ }
825
+ async function setUpAndRun(session, spec, use, signal, state, teardowns) {
826
+ const base = await buildBaseFixtures(session, signal, use);
827
+ const built = { ...base };
828
+ state.bag = built;
829
+ const perfNames = session.env.perf;
830
+ if (perfNames !== undefined) {
831
+ const { perf } = base.device;
832
+ await session.step("perf", "perf.start", () => perf.start(perfStartFor(perfNames)));
833
+ teardowns.push({
834
+ name: "perf",
835
+ run: async () => {
836
+ if (perf.active)
837
+ await perf.stop();
838
+ }
839
+ });
840
+ }
841
+ if (use.launch === "fresh")
842
+ await session.step("app", "app.open", () => base.app.open());
843
+ for (const entry of spec.fixtures) {
844
+ const started = await session.step("fixture", entry.name, () => startFixture(entry, built));
845
+ built[entry.name] = started.value;
846
+ teardowns.push({ name: entry.name, run: started.teardown });
847
+ }
848
+ for (const hook of hooksFor(spec.scope, "beforeEach", spec.fixtures)) {
849
+ await session.step("hook", "beforeEach", async () => hook(built));
850
+ }
851
+ await spec.body(built);
852
+ }
853
+ async function captureFailureArtifacts(session, ctx, budget, startedAt) {
854
+ const driver = session.peekDriver();
855
+ const facet = session.peekDevtools();
856
+ const { captureTimeoutMs } = runnerTiming();
857
+ const captures = [
858
+ ...driver === undefined ? [] : [await captureFailure(driver, ctx.dir, captureTimeoutMs, budget)],
859
+ ...facet === undefined ? [] : [await captureDevtoolsArtifacts(facet, ctx.dir, captureTimeoutMs, budget)],
860
+ ...session.nativeLog === undefined ? [] : [
861
+ await captureNativeArtifacts(nativeTarget(session.env), session.nativeLog, { dir: ctx.dir, sinceMs: startedAt }, captureTimeoutMs, budget)
862
+ ]
863
+ ];
864
+ for (const capture of captures) {
865
+ for (const file of capture.files)
866
+ ctx.files.add(file);
867
+ for (const problem of capture.problems) {
868
+ ctx.trace.write({ type: "note", ts: Date.now(), kind: "note", label: `failure capture: ${problem}` });
869
+ }
870
+ }
871
+ }
872
+ async function runCleanup(session, spec, ctx, built, teardowns, budget, bind) {
873
+ const cleanup = new AbortController;
874
+ const cleanupCtx = { ...ctx, signal: cleanup.signal, depth: 0 };
875
+ session.enterAttempt(cleanupCtx, ctx);
876
+ bind(cleanupCtx);
877
+ const step = (kind, label, fn) => runStep(cleanupCtx, kind, label, fn);
878
+ const errors = [];
879
+ const attempt = async (label, fn) => {
880
+ try {
881
+ const left = budget();
882
+ if (left <= 0)
883
+ throw new E2eError2("TIMEOUT", `${label} skipped: the cleanup budget after the test is used up`);
884
+ await limited2(label, Math.min(timing.cleanupTimeoutMs, left), fn);
885
+ } catch (error) {
886
+ errors.push(error);
887
+ }
888
+ };
889
+ if (built !== undefined) {
890
+ for (const hook of hooksFor(spec.scope, "afterEach", spec.fixtures)) {
891
+ await attempt("afterEach", () => step("hook", "afterEach", async () => hook(built)));
892
+ }
893
+ }
894
+ for (const fixture of [...teardowns].reverse()) {
895
+ await attempt(`fixture ${fixture.name} teardown`, () => step("fixture", `${fixture.name} teardown`, fixture.run));
896
+ }
897
+ cleanup.abort(new E2eError2("SESSION", "attempt finished"));
898
+ session.leaveAttempt(cleanupCtx);
899
+ return errors;
900
+ }
901
+
902
+ // ../../packages/e2e/src/runner/fixtures.types.ts
903
+ var SYSTEM_ALERTS = ["accept", "dismiss", "off"];
904
+
905
+ // ../../packages/e2e/src/runner/test.ts
906
+ var registered = new WeakSet;
907
+ function markRegistered(scope) {
908
+ for (let s = scope;s !== undefined; s = s.parent)
909
+ registered.add(s);
910
+ }
911
+ function registerTest(fixtures, mode, title, second, third) {
912
+ const options = typeof second === "function" ? {} : second;
913
+ const body = typeof second === "function" ? second : third;
914
+ if (body === undefined) {
915
+ throw new E2eError2("INVALID_ARGUMENT", `test("${title}"): the test body is missing`);
916
+ }
917
+ const scope = currentScope();
918
+ const file = callerFile();
919
+ const path = [...scope.title, title];
920
+ const defaults = registrationDefaults();
921
+ const use = resolveUse(scope, defaults, options.timeout);
922
+ const retry = options.retry ?? 0;
923
+ const wrongPlatform = options.platforms !== undefined && defaults.platform !== undefined && !options.platforms.includes(defaults.platform);
924
+ const only = mode === "only" || options.only === true;
925
+ if (only)
926
+ markOnly();
927
+ markRegistered(scope);
928
+ const bunTimeoutMs = use.timeoutMs + runnerTiming().bunMarginMs;
929
+ let attempts = 0;
930
+ const run = async () => {
931
+ attempts += 1;
932
+ await runAttempt(getSession(), {
933
+ file,
934
+ title: path,
935
+ scope,
936
+ fixtures,
937
+ body,
938
+ ...options.timeout !== undefined ? { timeout: options.timeout } : {},
939
+ only,
940
+ final: attempts > retry,
941
+ bunTimeoutMs
942
+ });
943
+ };
944
+ const bunOptions = { timeout: bunTimeoutMs, retry };
945
+ if (mode === "skip" || options.skip === true || wrongPlatform)
946
+ bunTest.skip(title, run, bunOptions);
947
+ else if (only)
948
+ bunTest.only(title, run, bunOptions);
949
+ else
950
+ bunTest(title, run, bunOptions);
951
+ }
952
+ function eraseSetup(setup) {
953
+ return (base, use) => setup(base, use);
954
+ }
955
+ function eraseHook(hook) {
956
+ return (fixtures) => hook(fixtures);
957
+ }
958
+ function isKeyOf(object, key) {
959
+ return key in object;
960
+ }
961
+ function makeCallable(fixtures, mode) {
962
+ return (title, second, third) => registerTest(fixtures, mode, title, second, third);
963
+ }
964
+ function makeTest(fixtures) {
965
+ const base = makeCallable(fixtures, "run");
966
+ return Object.assign(base, {
967
+ skip: makeCallable(fixtures, "skip"),
968
+ only: makeCallable(fixtures, "only"),
969
+ todo(title) {
970
+ bunTest.todo(title, () => {
971
+ throw new E2eError2("INVALID_ARGUMENT", `test.todo("${title}") is not implemented`);
972
+ });
973
+ },
974
+ skipIf(condition) {
975
+ return makeCallable(fixtures, condition ? "skip" : "run");
976
+ },
977
+ beforeEach(hook) {
978
+ currentScope().beforeEach.push(ownHook(eraseHook(hook), fixtures));
979
+ },
980
+ afterEach(hook) {
981
+ currentScope().afterEach.push(ownHook(eraseHook(hook), fixtures));
982
+ },
983
+ use(options) {
984
+ if (options.systemAlerts !== undefined && !SYSTEM_ALERTS.includes(options.systemAlerts)) {
985
+ throw new E2eError2("INVALID_ARGUMENT", `test.use: systemAlerts must be one of ${SYSTEM_ALERTS.map((mode) => `"${mode}"`).join(", ")}, got ${JSON.stringify(options.systemAlerts)}`);
986
+ }
987
+ const scope = currentScope();
988
+ if (options.timeout !== undefined && registered.has(scope)) {
989
+ process.stderr.write(`warden: test.use({ timeout: ${options.timeout} }) comes after a test in the same scope; bun fixed that ` + `test's own timeout when it was registered, so put test.use above the tests it is for
990
+ `);
991
+ }
992
+ scope.use = { ...scope.use, ...options };
993
+ },
994
+ extend(defs) {
995
+ const added = [];
996
+ for (const name of Object.keys(defs)) {
997
+ if (isKeyOf(defs, name))
998
+ added.push({ name, setup: eraseSetup(defs[name]) });
999
+ }
1000
+ return makeTest([...fixtures, ...added]);
1001
+ }
1002
+ });
1003
+ }
1004
+ var test = makeTest([]);
1005
+ function describe(title, second, third) {
1006
+ const body = typeof second === "function" ? second : third;
1007
+ const options = typeof second === "function" ? {} : second;
1008
+ if (body === undefined)
1009
+ throw new E2eError2("INVALID_ARGUMENT", `describe("${title}"): the body is missing`);
1010
+ const scope = createScope(title, options.serial === true);
1011
+ bunDescribe(title, () => runInScope(scope, body));
1012
+ }
1013
+ function beforeEach(hook) {
1014
+ currentScope().beforeEach.push(hook);
1015
+ }
1016
+ function afterEach(hook) {
1017
+ currentScope().afterEach.push(hook);
1018
+ }
1019
+ async function sharedFixtures(scope) {
1020
+ const session = getSession();
1021
+ const defaults = session.env;
1022
+ const use = resolveUse(scope, {
1023
+ timeoutMs: defaults.timeoutMs,
1024
+ actionTimeoutMs: defaults.actionTimeoutMs,
1025
+ assertionTimeoutMs: defaults.assertionTimeoutMs
1026
+ });
1027
+ return buildBaseFixtures(session, session.signal, use);
1028
+ }
1029
+ function beforeAll(hook) {
1030
+ const scope = currentScope();
1031
+ bunBeforeAll(async () => hook(await sharedFixtures(scope)));
1032
+ }
1033
+ function afterAll(hook) {
1034
+ const scope = currentScope();
1035
+ bunAfterAll(async () => hook(await sharedFixtures(scope)));
1036
+ }
1037
+ export {
1038
+ E2eError2 as E2eError,
1039
+ WARDEN_E2E_VERSION2 as WARDEN_E2E_VERSION,
1040
+ afterAll,
1041
+ afterEach,
1042
+ beforeAll,
1043
+ beforeEach,
1044
+ describe,
1045
+ expect,
1046
+ test
1047
+ };