@delacour/warden-e2e 0.1.1 → 0.2.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-qtkmfjbq.mjs → chunk-7ns30pbp.mjs} +5482 -4235
- package/index.mjs +119 -70
- package/package.json +2 -2
- package/preload.mjs +215 -939
- package/types/core/src/providers/android.d.ts +8 -0
- package/types/core/src/store.d.ts +15 -1
- package/types/core/src/types.d.ts +20 -0
- package/types/e2e/src/app.d.ts +1 -1
- package/types/e2e/src/device.d.ts +9 -1
- package/types/e2e/src/devtools/native-log.d.ts +50 -0
- package/types/e2e/src/driver/android/uiad-backend.d.ts +1 -1
- package/types/e2e/src/driver/driver.types.d.ts +1 -1
- package/types/e2e/src/driver/ios/hid-recovery.d.ts +36 -0
- package/types/e2e/src/driver/ios/simctl.d.ts +15 -0
- package/types/e2e/src/driver/observation-cache.d.ts +1 -1
- package/types/e2e/src/env.d.ts +26 -9
- package/types/e2e/src/errors.d.ts +1 -1
- package/types/e2e/src/fixture-session.d.ts +5 -1
- package/types/e2e/src/index.d.ts +3 -0
- package/types/e2e/src/perf/budgets.d.ts +33 -0
- package/types/e2e/src/perf/device-perf.d.ts +45 -0
- package/types/e2e/src/perf/perf.types.d.ts +59 -0
- package/types/e2e/src/preload.d.ts +2 -2
- package/types/e2e/src/runner/artifacts.d.ts +5 -5
- package/types/e2e/src/runner/failure.d.ts +12 -0
- package/types/e2e/src/runner/fixtures.types.d.ts +1 -1
- package/types/e2e/src/runner/scope.d.ts +5 -3
- package/types/e2e/src/runner/test.d.ts +7 -0
- package/types/e2e/src/screen/actions.d.ts +1 -1
- package/types/e2e/src/session.d.ts +19 -0
package/index.mjs
CHANGED
|
@@ -1,33 +1,37 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
E2eError,
|
|
5
|
-
WARDEN_E2E_VERSION,
|
|
6
|
-
attemptDir,
|
|
7
|
-
attemptSession,
|
|
8
|
-
callerFile,
|
|
3
|
+
E2eError2,
|
|
9
4
|
canObserve,
|
|
10
|
-
createScope,
|
|
11
|
-
currentScope,
|
|
12
5
|
fieldValue,
|
|
13
|
-
flowIdOf,
|
|
14
|
-
getSession,
|
|
15
|
-
hash6,
|
|
16
|
-
hooksFor,
|
|
17
|
-
isLocator,
|
|
18
|
-
markOnly,
|
|
19
|
-
matchesText,
|
|
20
6
|
normalizeText,
|
|
21
|
-
|
|
22
|
-
registrationDefaults,
|
|
23
|
-
renderHeader,
|
|
7
|
+
matchesText,
|
|
24
8
|
renderSnapshot,
|
|
9
|
+
renderHeader,
|
|
25
10
|
resolve,
|
|
26
|
-
|
|
27
|
-
|
|
11
|
+
isLocator,
|
|
12
|
+
pickOne,
|
|
13
|
+
textOf,
|
|
14
|
+
isHidLost,
|
|
28
15
|
runStep,
|
|
29
|
-
|
|
30
|
-
|
|
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-7ns30pbp.mjs";
|
|
31
35
|
// ../../packages/e2e/src/expect/expect.ts
|
|
32
36
|
import { expect as bunExpect } from "bun:test";
|
|
33
37
|
|
|
@@ -47,22 +51,22 @@ function show(match) {
|
|
|
47
51
|
function assertOptions(matcher, options, allowed) {
|
|
48
52
|
const unknown = Object.keys(options).filter((key) => !allowed.includes(key));
|
|
49
53
|
if (unknown.length > 0) {
|
|
50
|
-
throw new
|
|
54
|
+
throw new E2eError2("INVALID_ARGUMENT", `${matcher} does not take ${unknown.map((key) => `"${key}"`).join(", ")}; it takes ${allowed.join(", ")}`);
|
|
51
55
|
}
|
|
52
56
|
const bag = options;
|
|
53
57
|
for (const key of ["checked", "exact", "ignoreCase"]) {
|
|
54
58
|
if (bag[key] !== undefined && typeof bag[key] !== "boolean") {
|
|
55
|
-
throw new
|
|
59
|
+
throw new E2eError2("INVALID_ARGUMENT", `${matcher}: "${key}" must be a boolean`);
|
|
56
60
|
}
|
|
57
61
|
}
|
|
58
62
|
const timeout = bag.timeout;
|
|
59
63
|
if (timeout !== undefined && (typeof timeout !== "number" || !Number.isFinite(timeout) || timeout < 0)) {
|
|
60
|
-
throw new
|
|
64
|
+
throw new E2eError2("INVALID_ARGUMENT", `${matcher}: "timeout" must be a non-negative number of milliseconds`);
|
|
61
65
|
}
|
|
62
66
|
}
|
|
63
67
|
function failUnlessRetryable(error) {
|
|
64
68
|
if (!error.retryable)
|
|
65
|
-
throw new
|
|
69
|
+
throw new E2eError2("DRIVER", `snapshot failed: ${error.code}: ${error.message}`);
|
|
66
70
|
}
|
|
67
71
|
function regexTest(pattern, value, ignoreCase = false) {
|
|
68
72
|
const flags = pattern.flags.replaceAll(/[gy]/g, "");
|
|
@@ -144,7 +148,7 @@ class LocatorAssertions {
|
|
|
144
148
|
if (entry === undefined)
|
|
145
149
|
return missing();
|
|
146
150
|
if (entry.secure) {
|
|
147
|
-
throw new
|
|
151
|
+
throw new E2eError2("INVALID_ARGUMENT", "toHaveValue cannot judge a secure field: its value is withheld. Assert the outcome of the fill instead.");
|
|
148
152
|
}
|
|
149
153
|
const value = fieldValue(entry);
|
|
150
154
|
const matches = typeof expected === "string" ? value === expected : regexTest(expected, value);
|
|
@@ -188,7 +192,7 @@ class LocatorAssertions {
|
|
|
188
192
|
async toHaveCount(expected, options = {}) {
|
|
189
193
|
assertOptions("toHaveCount", options, ["timeout"]);
|
|
190
194
|
if (!Number.isInteger(expected) || expected < 0) {
|
|
191
|
-
throw new
|
|
195
|
+
throw new E2eError2("INVALID_ARGUMENT", `toHaveCount takes a non-negative integer, got ${String(expected)}`);
|
|
192
196
|
}
|
|
193
197
|
return this.assert({
|
|
194
198
|
call: `toHaveCount(${expected})`,
|
|
@@ -203,7 +207,7 @@ class LocatorAssertions {
|
|
|
203
207
|
try {
|
|
204
208
|
await rt.step(`expect ${this.locator}.${spec.call}`, () => this.poll(spec, timeoutMs));
|
|
205
209
|
} catch (error) {
|
|
206
|
-
if (this.context.soft !== undefined && error instanceof
|
|
210
|
+
if (this.context.soft !== undefined && error instanceof E2eError2 && error.code !== "TIMEOUT") {
|
|
207
211
|
this.context.soft(error);
|
|
208
212
|
return;
|
|
209
213
|
}
|
|
@@ -255,7 +259,7 @@ class LocatorAssertions {
|
|
|
255
259
|
`)}
|
|
256
260
|
|
|
257
261
|
${screen}`;
|
|
258
|
-
return new
|
|
262
|
+
return new E2eError2("ASSERTION", message, screen);
|
|
259
263
|
}
|
|
260
264
|
}
|
|
261
265
|
function conditionHold(negate, holdMs, now) {
|
|
@@ -311,9 +315,9 @@ function currentSoftSink() {
|
|
|
311
315
|
}
|
|
312
316
|
var valueExpect = bunExpect;
|
|
313
317
|
function asAssertion(error) {
|
|
314
|
-
if (error instanceof
|
|
318
|
+
if (error instanceof E2eError2)
|
|
315
319
|
return error;
|
|
316
|
-
return new
|
|
320
|
+
return new E2eError2("ASSERTION", error instanceof Error ? error.message : String(error));
|
|
317
321
|
}
|
|
318
322
|
function isThenable(value) {
|
|
319
323
|
return typeof value === "object" && value !== null && "then" in value && typeof value.then === "function";
|
|
@@ -469,6 +473,19 @@ async function captureDevtoolsArtifacts(facet, dir, timeoutMs = CAPTURE_TIMEOUT_
|
|
|
469
473
|
}
|
|
470
474
|
return capture;
|
|
471
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
|
+
}
|
|
472
489
|
|
|
473
490
|
// ../../packages/e2e/src/runner/trace.ts
|
|
474
491
|
import { appendFileSync, mkdirSync as mkdirSync2 } from "fs";
|
|
@@ -582,7 +599,7 @@ var POST_DEADLINE_RESERVE_MS = 5000;
|
|
|
582
599
|
var DEFAULT_TIMING = {
|
|
583
600
|
cleanupTimeoutMs: CLEANUP_TIMEOUT_MS,
|
|
584
601
|
captureTimeoutMs: CAPTURE_TIMEOUT_MS,
|
|
585
|
-
bunMarginMs:
|
|
602
|
+
bunMarginMs: BUN_TIMEOUT_MARGIN_MS2,
|
|
586
603
|
reserveMs: POST_DEADLINE_RESERVE_MS
|
|
587
604
|
};
|
|
588
605
|
var timing = { ...DEFAULT_TIMING };
|
|
@@ -594,7 +611,7 @@ function unavailable(name) {
|
|
|
594
611
|
get(_target, prop) {
|
|
595
612
|
if (typeof prop === "symbol" || prop === "then")
|
|
596
613
|
return;
|
|
597
|
-
throw new
|
|
614
|
+
throw new E2eError2("SESSION", `the \`${name}\` fixture is not available: no fixture factory is registered on the session`);
|
|
598
615
|
}
|
|
599
616
|
});
|
|
600
617
|
}
|
|
@@ -627,12 +644,12 @@ async function startFixture(entry, bag) {
|
|
|
627
644
|
return;
|
|
628
645
|
};
|
|
629
646
|
let used = false;
|
|
630
|
-
const value = new Promise((
|
|
631
|
-
provide =
|
|
647
|
+
const value = new Promise((resolve, reject) => {
|
|
648
|
+
provide = resolve;
|
|
632
649
|
fail = reject;
|
|
633
650
|
});
|
|
634
|
-
const released = new Promise((
|
|
635
|
-
release =
|
|
651
|
+
const released = new Promise((resolve) => {
|
|
652
|
+
release = resolve;
|
|
636
653
|
});
|
|
637
654
|
const finished = entry.setup(bag, async (v) => {
|
|
638
655
|
used = true;
|
|
@@ -640,7 +657,7 @@ async function startFixture(entry, bag) {
|
|
|
640
657
|
await released;
|
|
641
658
|
}).then(() => {
|
|
642
659
|
if (!used)
|
|
643
|
-
fail(new
|
|
660
|
+
fail(new E2eError2("INVALID_ARGUMENT", `fixture \`${entry.name}\` finished without calling use()`));
|
|
644
661
|
}, (error) => {
|
|
645
662
|
if (!used)
|
|
646
663
|
fail(error);
|
|
@@ -666,7 +683,7 @@ function cleanupDeadline(startedAt, timeoutMs, bunTimeoutMs) {
|
|
|
666
683
|
function limited2(label, ms, fn) {
|
|
667
684
|
let timer;
|
|
668
685
|
const timeout = new Promise((_, reject) => {
|
|
669
|
-
timer = setTimeout(() => reject(new
|
|
686
|
+
timer = setTimeout(() => reject(new E2eError2("TIMEOUT", `${label} timed out after ${ms}ms`)), ms);
|
|
670
687
|
});
|
|
671
688
|
return Promise.race([fn(), timeout]).finally(() => clearTimeout(timer));
|
|
672
689
|
}
|
|
@@ -681,13 +698,17 @@ function errorInfo(error) {
|
|
|
681
698
|
if (error instanceof Error) {
|
|
682
699
|
return {
|
|
683
700
|
message: error.message,
|
|
684
|
-
...error instanceof
|
|
701
|
+
...error instanceof E2eError2 ? { code: error.code } : {},
|
|
685
702
|
...error.stack !== undefined ? { stack: error.stack } : {}
|
|
686
703
|
};
|
|
687
704
|
}
|
|
688
705
|
return { message: String(error) };
|
|
689
706
|
}
|
|
690
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) {
|
|
691
712
|
const { env } = session;
|
|
692
713
|
const use = resolveUse(spec.scope, { timeoutMs: env.timeoutMs, actionTimeoutMs: env.actionTimeoutMs, assertionTimeoutMs: env.assertionTimeoutMs }, spec.timeout);
|
|
693
714
|
const startedAt = Date.now();
|
|
@@ -696,7 +717,7 @@ async function runAttempt(session, spec) {
|
|
|
696
717
|
const device = { udid: env.udid, platform: env.platform };
|
|
697
718
|
const clock = !env.logTimestamps;
|
|
698
719
|
const short = shortName(spec.file);
|
|
699
|
-
const dir = attemptDir(session.out,
|
|
720
|
+
const dir = attemptDir(session.out, specIdOf(spec.file, env.specsDir), spec.title, hash6(spec.file, ...spec.title));
|
|
700
721
|
const print = (line) => {
|
|
701
722
|
if (line !== undefined)
|
|
702
723
|
session.log(line);
|
|
@@ -724,7 +745,7 @@ async function runAttempt(session, spec) {
|
|
|
724
745
|
let deadlineHit = false;
|
|
725
746
|
const timer = setTimeout(() => {
|
|
726
747
|
deadlineHit = true;
|
|
727
|
-
controller.abort(new
|
|
748
|
+
controller.abort(new E2eError2("TIMEOUT", `test timed out after ${use.timeoutMs}ms`));
|
|
728
749
|
}, use.timeoutMs);
|
|
729
750
|
const deadline = new Promise((_, reject) => {
|
|
730
751
|
controller.signal.addEventListener("abort", () => reject(controller.signal.reason), { once: true });
|
|
@@ -749,11 +770,11 @@ async function runAttempt(session, spec) {
|
|
|
749
770
|
failure = softFailureError(ctx.softFailures);
|
|
750
771
|
}
|
|
751
772
|
if (failed)
|
|
752
|
-
await captureFailureArtifacts(session, ctx, budget);
|
|
773
|
+
await captureFailureArtifacts(session, ctx, budget, startedAt);
|
|
753
774
|
const cleanupErrors = await runCleanup(session, spec, ctx, state.bag, teardowns, budget, (context) => {
|
|
754
775
|
binding = { context, finished: false };
|
|
755
776
|
});
|
|
756
|
-
const finished = new
|
|
777
|
+
const finished = new E2eError2("SESSION", "attempt finished");
|
|
757
778
|
if (!controller.signal.aborted)
|
|
758
779
|
controller.abort(finished);
|
|
759
780
|
binding = { context: { ...ctx, signal: AbortSignal.abort(finished), depth: 0 }, finished: true };
|
|
@@ -768,10 +789,14 @@ async function runAttempt(session, spec) {
|
|
|
768
789
|
const outcome = failed ? { failed: true, error: failure, timedOut: deadlineHit } : { failed: false };
|
|
769
790
|
const result = buildResult(spec, ctx, { startedAt, device }, outcome);
|
|
770
791
|
print(formatConsoleLine({ type: "test-end", result }, { clock }));
|
|
792
|
+
if (failed && session.recoverHid !== undefined && isHidLost(failure) && await session.recoverHid()) {
|
|
793
|
+
return "again";
|
|
794
|
+
}
|
|
771
795
|
if (!failed || spec.final)
|
|
772
796
|
session.results.append(result);
|
|
773
797
|
if (failed)
|
|
774
798
|
throw failure instanceof Error ? failure : new Error(String(failure));
|
|
799
|
+
return;
|
|
775
800
|
}
|
|
776
801
|
function softFailureError(failures) {
|
|
777
802
|
if (failures.length === 1 && failures[0] !== undefined)
|
|
@@ -779,7 +804,7 @@ function softFailureError(failures) {
|
|
|
779
804
|
const body = failures.map((failure, index) => `${index + 1}) ${failure.message}`).join(`
|
|
780
805
|
|
|
781
806
|
`);
|
|
782
|
-
return new
|
|
807
|
+
return new E2eError2("ASSERTION", `${failures.length} soft assertions failed:
|
|
783
808
|
|
|
784
809
|
${body}`, failures[0]?.screen);
|
|
785
810
|
}
|
|
@@ -798,28 +823,43 @@ function buildResult(spec, ctx, run, outcome) {
|
|
|
798
823
|
};
|
|
799
824
|
}
|
|
800
825
|
async function setUpAndRun(session, spec, use, signal, state, teardowns) {
|
|
801
|
-
const
|
|
802
|
-
const built = { ...
|
|
826
|
+
const base = await buildBaseFixtures(session, signal, use);
|
|
827
|
+
const built = { ...base };
|
|
803
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
|
+
}
|
|
804
841
|
if (use.launch === "fresh")
|
|
805
|
-
await session.step("app", "app.open", () =>
|
|
842
|
+
await session.step("app", "app.open", () => base.app.open());
|
|
806
843
|
for (const entry of spec.fixtures) {
|
|
807
844
|
const started = await session.step("fixture", entry.name, () => startFixture(entry, built));
|
|
808
845
|
built[entry.name] = started.value;
|
|
809
846
|
teardowns.push({ name: entry.name, run: started.teardown });
|
|
810
847
|
}
|
|
811
|
-
for (const hook of hooksFor(spec.scope, "beforeEach")) {
|
|
848
|
+
for (const hook of hooksFor(spec.scope, "beforeEach", spec.fixtures)) {
|
|
812
849
|
await session.step("hook", "beforeEach", async () => hook(built));
|
|
813
850
|
}
|
|
814
851
|
await spec.body(built);
|
|
815
852
|
}
|
|
816
|
-
async function captureFailureArtifacts(session, ctx, budget) {
|
|
853
|
+
async function captureFailureArtifacts(session, ctx, budget, startedAt) {
|
|
817
854
|
const driver = session.peekDriver();
|
|
818
855
|
const facet = session.peekDevtools();
|
|
819
856
|
const { captureTimeoutMs } = runnerTiming();
|
|
820
857
|
const captures = [
|
|
821
858
|
...driver === undefined ? [] : [await captureFailure(driver, ctx.dir, captureTimeoutMs, budget)],
|
|
822
|
-
...facet === undefined ? [] : [await captureDevtoolsArtifacts(facet, 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
|
+
]
|
|
823
863
|
];
|
|
824
864
|
for (const capture of captures) {
|
|
825
865
|
for (const file of capture.files)
|
|
@@ -840,21 +880,21 @@ async function runCleanup(session, spec, ctx, built, teardowns, budget, bind) {
|
|
|
840
880
|
try {
|
|
841
881
|
const left = budget();
|
|
842
882
|
if (left <= 0)
|
|
843
|
-
throw new
|
|
883
|
+
throw new E2eError2("TIMEOUT", `${label} skipped: the cleanup budget after the test is used up`);
|
|
844
884
|
await limited2(label, Math.min(timing.cleanupTimeoutMs, left), fn);
|
|
845
885
|
} catch (error) {
|
|
846
886
|
errors.push(error);
|
|
847
887
|
}
|
|
848
888
|
};
|
|
849
889
|
if (built !== undefined) {
|
|
850
|
-
for (const hook of hooksFor(spec.scope, "afterEach")) {
|
|
890
|
+
for (const hook of hooksFor(spec.scope, "afterEach", spec.fixtures)) {
|
|
851
891
|
await attempt("afterEach", () => step("hook", "afterEach", async () => hook(built)));
|
|
852
892
|
}
|
|
853
893
|
}
|
|
854
894
|
for (const fixture of [...teardowns].reverse()) {
|
|
855
895
|
await attempt(`fixture ${fixture.name} teardown`, () => step("fixture", `${fixture.name} teardown`, fixture.run));
|
|
856
896
|
}
|
|
857
|
-
cleanup.abort(new
|
|
897
|
+
cleanup.abort(new E2eError2("SESSION", "attempt finished"));
|
|
858
898
|
session.leaveAttempt(cleanupCtx);
|
|
859
899
|
return errors;
|
|
860
900
|
}
|
|
@@ -872,7 +912,7 @@ function registerTest(fixtures, mode, title, second, third) {
|
|
|
872
912
|
const options = typeof second === "function" ? {} : second;
|
|
873
913
|
const body = typeof second === "function" ? second : third;
|
|
874
914
|
if (body === undefined) {
|
|
875
|
-
throw new
|
|
915
|
+
throw new E2eError2("INVALID_ARGUMENT", `test("${title}"): the test body is missing`);
|
|
876
916
|
}
|
|
877
917
|
const scope = currentScope();
|
|
878
918
|
const file = callerFile();
|
|
@@ -910,7 +950,10 @@ function registerTest(fixtures, mode, title, second, third) {
|
|
|
910
950
|
bunTest(title, run, bunOptions);
|
|
911
951
|
}
|
|
912
952
|
function eraseSetup(setup) {
|
|
913
|
-
return (
|
|
953
|
+
return (base, use) => setup(base, use);
|
|
954
|
+
}
|
|
955
|
+
function eraseHook(hook) {
|
|
956
|
+
return (fixtures) => hook(fixtures);
|
|
914
957
|
}
|
|
915
958
|
function isKeyOf(object, key) {
|
|
916
959
|
return key in object;
|
|
@@ -919,21 +962,27 @@ function makeCallable(fixtures, mode) {
|
|
|
919
962
|
return (title, second, third) => registerTest(fixtures, mode, title, second, third);
|
|
920
963
|
}
|
|
921
964
|
function makeTest(fixtures) {
|
|
922
|
-
const
|
|
923
|
-
return Object.assign(
|
|
965
|
+
const base = makeCallable(fixtures, "run");
|
|
966
|
+
return Object.assign(base, {
|
|
924
967
|
skip: makeCallable(fixtures, "skip"),
|
|
925
968
|
only: makeCallable(fixtures, "only"),
|
|
926
969
|
todo(title) {
|
|
927
970
|
bunTest.todo(title, () => {
|
|
928
|
-
throw new
|
|
971
|
+
throw new E2eError2("INVALID_ARGUMENT", `test.todo("${title}") is not implemented`);
|
|
929
972
|
});
|
|
930
973
|
},
|
|
931
974
|
skipIf(condition) {
|
|
932
975
|
return makeCallable(fixtures, condition ? "skip" : "run");
|
|
933
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
|
+
},
|
|
934
983
|
use(options) {
|
|
935
984
|
if (options.systemAlerts !== undefined && !SYSTEM_ALERTS.includes(options.systemAlerts)) {
|
|
936
|
-
throw new
|
|
985
|
+
throw new E2eError2("INVALID_ARGUMENT", `test.use: systemAlerts must be one of ${SYSTEM_ALERTS.map((mode) => `"${mode}"`).join(", ")}, got ${JSON.stringify(options.systemAlerts)}`);
|
|
937
986
|
}
|
|
938
987
|
const scope = currentScope();
|
|
939
988
|
if (options.timeout !== undefined && registered.has(scope)) {
|
|
@@ -957,7 +1006,7 @@ function describe(title, second, third) {
|
|
|
957
1006
|
const body = typeof second === "function" ? second : third;
|
|
958
1007
|
const options = typeof second === "function" ? {} : second;
|
|
959
1008
|
if (body === undefined)
|
|
960
|
-
throw new
|
|
1009
|
+
throw new E2eError2("INVALID_ARGUMENT", `describe("${title}"): the body is missing`);
|
|
961
1010
|
const scope = createScope(title, options.serial === true);
|
|
962
1011
|
bunDescribe(title, () => runInScope(scope, body));
|
|
963
1012
|
}
|
|
@@ -986,13 +1035,13 @@ function afterAll(hook) {
|
|
|
986
1035
|
bunAfterAll(async () => hook(await sharedFixtures(scope)));
|
|
987
1036
|
}
|
|
988
1037
|
export {
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
describe,
|
|
992
|
-
beforeEach,
|
|
993
|
-
beforeAll,
|
|
994
|
-
afterEach,
|
|
1038
|
+
E2eError2 as E2eError,
|
|
1039
|
+
WARDEN_E2E_VERSION2 as WARDEN_E2E_VERSION,
|
|
995
1040
|
afterAll,
|
|
996
|
-
|
|
997
|
-
|
|
1041
|
+
afterEach,
|
|
1042
|
+
beforeAll,
|
|
1043
|
+
beforeEach,
|
|
1044
|
+
describe,
|
|
1045
|
+
expect,
|
|
1046
|
+
test
|
|
998
1047
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@delacour/warden-e2e",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.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.2.0"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"zod": "^4.4.3"
|