@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.
- package/LICENSE +21 -0
- package/chunk-e6pn5ygy.mjs +5832 -0
- package/index.d.ts +1 -0
- package/index.mjs +1047 -0
- package/package.json +49 -4
- package/preload.d.ts +1 -0
- package/preload.mjs +4788 -0
- package/types/core/src/allocate.d.ts +23 -0
- package/types/core/src/builds/app-option.schema.d.ts +37 -0
- package/types/core/src/builds/install.d.ts +95 -0
- package/types/core/src/builds/resolve.d.ts +74 -0
- package/types/core/src/config.defaults.d.ts +25 -0
- package/types/core/src/exec.d.ts +18 -0
- package/types/core/src/liveness.d.ts +12 -0
- package/types/core/src/providers/android.d.ts +132 -0
- package/types/core/src/providers/health.d.ts +45 -0
- package/types/core/src/providers/provider.types.d.ts +56 -0
- package/types/core/src/store.d.ts +105 -0
- package/types/core/src/types.d.ts +126 -0
- package/types/e2e/src/alert-buttons.d.ts +41 -0
- package/types/e2e/src/alert-reader.d.ts +25 -0
- package/types/e2e/src/app.d.ts +62 -0
- package/types/e2e/src/device-devtools.d.ts +26 -0
- package/types/e2e/src/device-link.d.ts +43 -0
- package/types/e2e/src/device.d.ts +90 -0
- package/types/e2e/src/devtools/cdp.d.ts +65 -0
- package/types/e2e/src/devtools/console.d.ts +59 -0
- package/types/e2e/src/devtools/devtools.types.d.ts +169 -0
- package/types/e2e/src/devtools/facet.d.ts +28 -0
- package/types/e2e/src/devtools/inspector.d.ts +48 -0
- package/types/e2e/src/devtools/jsonl.d.ts +4 -0
- package/types/e2e/src/devtools/native-log.d.ts +50 -0
- package/types/e2e/src/devtools/network.d.ts +33 -0
- package/types/e2e/src/devtools/network.script.d.ts +24 -0
- package/types/e2e/src/devtools/session.d.ts +82 -0
- package/types/e2e/src/devtools/tree.d.ts +23 -0
- package/types/e2e/src/devtools/tree.script.d.ts +33 -0
- package/types/e2e/src/driver/alerts.d.ts +29 -0
- package/types/e2e/src/driver/android/adb-session.d.ts +52 -0
- package/types/e2e/src/driver/android/android-alert.d.ts +4 -0
- package/types/e2e/src/driver/android/android-animations.d.ts +29 -0
- package/types/e2e/src/driver/android/android-app.d.ts +32 -0
- package/types/e2e/src/driver/android/android-argv.d.ts +122 -0
- package/types/e2e/src/driver/android/android-device.d.ts +44 -0
- package/types/e2e/src/driver/android/android-driver.d.ts +60 -0
- package/types/e2e/src/driver/android/android-dump.d.ts +46 -0
- package/types/e2e/src/driver/android/android-state.d.ts +69 -0
- package/types/e2e/src/driver/android/uiad-backend.d.ts +63 -0
- package/types/e2e/src/driver/android/uiad-client.d.ts +60 -0
- package/types/e2e/src/driver/android/uiad-device.d.ts +70 -0
- package/types/e2e/src/driver/android/uiad-dex.generated.d.ts +8 -0
- package/types/e2e/src/driver/android/uiad-ops.d.ts +62 -0
- package/types/e2e/src/driver/android/uiad-protocol.d.ts +209 -0
- package/types/e2e/src/driver/android/uiautomator.d.ts +33 -0
- package/types/e2e/src/driver/driver.types.d.ts +231 -0
- package/types/e2e/src/driver/errors.d.ts +13 -0
- package/types/e2e/src/driver/gesture-geometry.d.ts +48 -0
- package/types/e2e/src/driver/ios/hid-recovery.d.ts +36 -0
- package/types/e2e/src/driver/ios/ios-driver.d.ts +63 -0
- package/types/e2e/src/driver/ios/ios-session.d.ts +30 -0
- package/types/e2e/src/driver/ios/locate-helper.d.ts +38 -0
- package/types/e2e/src/driver/ios/normalize.d.ts +11 -0
- package/types/e2e/src/driver/ios/simctl.d.ts +102 -0
- package/types/e2e/src/driver/ios/simd-client.d.ts +68 -0
- package/types/e2e/src/driver/ios/simd-protocol.d.ts +433 -0
- package/types/e2e/src/driver/ios/simd-transport.d.ts +43 -0
- package/types/e2e/src/driver/ios/type-budget.d.ts +6 -0
- package/types/e2e/src/driver/observation-cache.d.ts +66 -0
- package/types/e2e/src/driver/open.d.ts +29 -0
- package/types/e2e/src/driver/settle.d.ts +43 -0
- package/types/e2e/src/driver/span-detail.d.ts +12 -0
- package/types/e2e/src/driver/traced-driver.d.ts +14 -0
- package/types/e2e/src/driver/typing.d.ts +48 -0
- package/types/e2e/src/driver/unwrap.d.ts +13 -0
- package/types/e2e/src/env.d.ts +78 -0
- package/types/e2e/src/errors.d.ts +26 -0
- package/types/e2e/src/expect/expect.d.ts +27 -0
- package/types/e2e/src/expect/matchers.d.ts +72 -0
- package/types/e2e/src/fixture-session.d.ts +38 -0
- package/types/e2e/src/fixtures.d.ts +14 -0
- package/types/e2e/src/index.d.ts +11 -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 +29 -0
- package/types/e2e/src/runner/artifacts.d.ts +13 -0
- package/types/e2e/src/runner/attempt.d.ts +63 -0
- package/types/e2e/src/runner/callsite.d.ts +2 -0
- package/types/e2e/src/runner/failure.d.ts +40 -0
- package/types/e2e/src/runner/fixtures.types.d.ts +60 -0
- package/types/e2e/src/runner/results.d.ts +48 -0
- package/types/e2e/src/runner/scope.d.ts +64 -0
- package/types/e2e/src/runner/span.d.ts +40 -0
- package/types/e2e/src/runner/step.d.ts +7 -0
- package/types/e2e/src/runner/test.d.ts +59 -0
- package/types/e2e/src/runner/trace.d.ts +87 -0
- package/types/e2e/src/screen/actions.d.ts +93 -0
- package/types/e2e/src/screen/context.d.ts +134 -0
- package/types/e2e/src/screen/field.d.ts +121 -0
- package/types/e2e/src/screen/locator.d.ts +233 -0
- package/types/e2e/src/screen/nodes.d.ts +53 -0
- package/types/e2e/src/screen/poll.d.ts +57 -0
- package/types/e2e/src/screen/queries.d.ts +34 -0
- package/types/e2e/src/screen/query.d.ts +60 -0
- package/types/e2e/src/screen/react-locator.d.ts +30 -0
- package/types/e2e/src/screen/render.d.ts +42 -0
- package/types/e2e/src/screen/roles.d.ts +24 -0
- package/types/e2e/src/screen/screen.d.ts +61 -0
- package/types/e2e/src/screen/selector.d.ts +38 -0
- package/types/e2e/src/screen/text.d.ts +13 -0
- package/types/e2e/src/session.d.ts +124 -0
- package/types/e2e/src/traced-device.d.ts +9 -0
- package/types/e2e/src/version.d.ts +7 -0
- package/types/types/src/result.d.ts +12 -0
- package/README.md +0 -3
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { Driver, DriverResult, KeyName } from "./driver.types";
|
|
2
|
+
/**
|
|
3
|
+
* The one place that decides how text and keys are sent to a device (specs' `fill`/`type`/`clear` and `warden ui
|
|
4
|
+
* fill`/`type` all go through it).
|
|
5
|
+
*
|
|
6
|
+
* iOS: `warden-simd` holds, paces and spaces repeated keys itself (`WARDEN_SIMD_PACE_MS`, `WARDEN_SIMD_HOLD_MS`,
|
|
7
|
+
* `WARDEN_SIMD_REPEAT_GAP_MS`), so the whole string goes in one request and the client adds no gap. Before that
|
|
8
|
+
* the client sent one grapheme per request, 25 ms apart (set `KEY_PACE_MS` 25 and `SPLIT_TEXT` true to go back).
|
|
9
|
+
*/
|
|
10
|
+
/** Gap after each key on iOS. */
|
|
11
|
+
export declare const KEY_PACE_MS = 0;
|
|
12
|
+
/** iOS types one grapheme per request. */
|
|
13
|
+
export declare const SPLIT_TEXT = false;
|
|
14
|
+
export type Sleep = (ms: number) => Promise<void>;
|
|
15
|
+
/**
|
|
16
|
+
* Optional fast path a driver can offer next to the frozen `Driver` interface: `times` presses of `key` in one
|
|
17
|
+
* device round trip. Android needs it because every `input keyevent` is a process start (clearing a 20 character
|
|
18
|
+
* field one key at a time takes seconds); iOS paces keys itself.
|
|
19
|
+
*/
|
|
20
|
+
export type RepeatingKeys = {
|
|
21
|
+
pressKeyRepeat(key: KeyName, times: number): DriverResult<void>;
|
|
22
|
+
};
|
|
23
|
+
export declare function canRepeatKeys(driver: Driver): driver is Driver & RepeatingKeys;
|
|
24
|
+
/**
|
|
25
|
+
* Optional fast path next to the frozen `Driver`: select everything in the focused text field (iOS Cmd+A through
|
|
26
|
+
* `warden-simd`, Android Ctrl+A through `input keycombination` on API 33+), so one Backspace empties a field of any
|
|
27
|
+
* length instead of one per character. A field that refuses the shortcut is caught by the read-back after the fill.
|
|
28
|
+
*/
|
|
29
|
+
export type SelectsAll = {
|
|
30
|
+
selectAll(): DriverResult<void>;
|
|
31
|
+
};
|
|
32
|
+
export declare function canSelectAll(driver: Driver): driver is Driver & SelectsAll;
|
|
33
|
+
/**
|
|
34
|
+
* Optional fast path next to the frozen `Driver`: replace the whole text of the focused field in one call (Android
|
|
35
|
+
* `warden-uiad` `setText`, `ACTION_SET_TEXT`: the same action its `typeText` inserts with). `screen/field.ts` uses it,
|
|
36
|
+
* opt-in only (`ClearMethod` `setText`, or `WARDEN_ANDROID_SETTEXT=1`), for a `fill` over a field that holds text,
|
|
37
|
+
* instead of select-all then typing; the read-back checks it took. A refusal (no server, no focused input) is an error,
|
|
38
|
+
* and the caller types the old way. Opt-in because the server writes to the node its cached focus names, which may
|
|
39
|
+
* still be the field focused before the tap.
|
|
40
|
+
*/
|
|
41
|
+
export type SetsText = {
|
|
42
|
+
setText(text: string): DriverResult<void>;
|
|
43
|
+
};
|
|
44
|
+
export declare function canSetText(driver: Driver): driver is Driver & SetsText;
|
|
45
|
+
/** Types `text` at the caret. Android types the whole string in one call. */
|
|
46
|
+
export declare function typeText(driver: Driver, sleep: Sleep, text: string): DriverResult<void>;
|
|
47
|
+
/** Presses `key` `times` times, paced like typed characters. */
|
|
48
|
+
export declare function pressKeys(driver: Driver, sleep: Sleep, key: KeyName, times: number): DriverResult<void>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Result } from "../../../types/src/result";
|
|
2
|
+
import { E2eError } from "../errors";
|
|
3
|
+
import type { DriverError, DriverResult } from "./driver.types";
|
|
4
|
+
/** `TIMEOUT` for time limits, `DRIVER` when the helper / device is broken, else `ACTION_FAILED`. */
|
|
5
|
+
export declare function toE2eError(error: DriverError, action: string): E2eError;
|
|
6
|
+
/**
|
|
7
|
+
* Runs one driver call, racing it against `signal`: an already-aborted signal never starts the call,
|
|
8
|
+
* an abort while it runs rejects with `E2eError{TIMEOUT}`. Returns the raw `Result` (use {@link unwrap}
|
|
9
|
+
* when a failure should throw).
|
|
10
|
+
*/
|
|
11
|
+
export declare function settled<T>(call: () => DriverResult<T>, action: string, signal?: AbortSignal): Promise<Result<T, DriverError>>;
|
|
12
|
+
/** {@link settled}, throwing the mapped `E2eError` (see {@link toE2eError}) on a driver failure. */
|
|
13
|
+
export declare function unwrap<T>(call: () => DriverResult<T>, action: string, signal?: AbortSignal): Promise<T>;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { Result } from "../../types/src/result";
|
|
2
|
+
/**
|
|
3
|
+
* The env names `warden e2e` writes and the spec process reads (`apps/cli` imports these). `WARDEN_HEADLESS` and
|
|
4
|
+
* `WARDEN_WORKER` are also set for the spec process but are for the spec's own tools (`process.env`): nothing here
|
|
5
|
+
* reads them, so a bad value cannot fail spec-env validation.
|
|
6
|
+
*/
|
|
7
|
+
export declare const ENV_NAMES: {
|
|
8
|
+
readonly udid: "WARDEN_UDID";
|
|
9
|
+
readonly deviceName: "WARDEN_DEVICE_NAME";
|
|
10
|
+
readonly platform: "WARDEN_PLATFORM";
|
|
11
|
+
readonly bundleId: "WARDEN_BUNDLE_ID";
|
|
12
|
+
readonly appPath: "WARDEN_APP_PATH";
|
|
13
|
+
readonly metroUrl: "WARDEN_METRO_URL";
|
|
14
|
+
readonly launchArgs: "WARDEN_LAUNCH_ARGS";
|
|
15
|
+
readonly out: "WARDEN_E2E_OUT";
|
|
16
|
+
readonly timeout: "WARDEN_E2E_TIMEOUT";
|
|
17
|
+
readonly actionTimeout: "WARDEN_E2E_ACTION_TIMEOUT";
|
|
18
|
+
readonly assertionTimeout: "WARDEN_E2E_ASSERTION_TIMEOUT";
|
|
19
|
+
readonly specsDir: "WARDEN_E2E_SPECS_DIR";
|
|
20
|
+
readonly helperDir: "WARDEN_HELPER_DIR";
|
|
21
|
+
readonly simd: "WARDEN_SIMD";
|
|
22
|
+
readonly simdSocket: "WARDEN_SIMD_SOCKET";
|
|
23
|
+
readonly logTimestamps: "WARDEN_LOG_TIMESTAMPS";
|
|
24
|
+
/**
|
|
25
|
+
* JSON argv (`{udid}` is replaced) the session runs after it rebooted the sim following a lost HID connection;
|
|
26
|
+
* `warden` sets it for non-headless runs so the sim's viewer window comes back (packages/e2e cannot import apps/cli)
|
|
27
|
+
*/
|
|
28
|
+
readonly afterReboot: "WARDEN_AFTER_REBOOT";
|
|
29
|
+
/** the `warden` that started this process: the preload refuses to run when it is not this package's version */
|
|
30
|
+
readonly version: "WARDEN_VERSION";
|
|
31
|
+
/** JSON argv that re-runs the `warden` that started this process (`device.perf` shells out to it) */
|
|
32
|
+
readonly bin: "WARDEN_BIN";
|
|
33
|
+
/** `warden e2e --perf`: the capture flags (`all`, or a comma list like `react,js`) every attempt records */
|
|
34
|
+
readonly perf: "WARDEN_PERF";
|
|
35
|
+
/** JSON of the suite's `perf.budgets`; a breach fails the test with `PERF_BUDGET` */
|
|
36
|
+
readonly perfBudgets: "WARDEN_PERF_BUDGETS";
|
|
37
|
+
};
|
|
38
|
+
export declare const DEFAULT_TIMEOUT_MS = 120000;
|
|
39
|
+
export declare const DEFAULT_ACTION_TIMEOUT_MS = 30000;
|
|
40
|
+
export declare const DEFAULT_ASSERTION_TIMEOUT_MS = 5000;
|
|
41
|
+
export type E2eEnv = {
|
|
42
|
+
udid: string;
|
|
43
|
+
/** the leased device's name: pins devtools to its own Metro inspector page when several devices share one Metro */
|
|
44
|
+
deviceName?: string;
|
|
45
|
+
platform: "ios" | "android";
|
|
46
|
+
bundleId: string;
|
|
47
|
+
appPath?: string;
|
|
48
|
+
metroUrl?: string;
|
|
49
|
+
launchArgs: string[];
|
|
50
|
+
/** where `results.jsonl` and per-attempt artifacts go (may be relative to cwd) */
|
|
51
|
+
out: string;
|
|
52
|
+
timeoutMs: number;
|
|
53
|
+
actionTimeoutMs: number;
|
|
54
|
+
assertionTimeoutMs: number;
|
|
55
|
+
specsDir?: string;
|
|
56
|
+
helperDir?: string;
|
|
57
|
+
simd?: string;
|
|
58
|
+
simdSocket?: string;
|
|
59
|
+
/** warden already stamps the log lines: the spec reporter prints no clock of its own */
|
|
60
|
+
logTimestamps: boolean;
|
|
61
|
+
/** `WARDEN_AFTER_REBOOT`: argv run after a HID-loss reboot, `{udid}` not yet replaced (see `runAfterReboot`) */
|
|
62
|
+
afterReboot?: string[];
|
|
63
|
+
/** `WARDEN_VERSION`: the warden that started this run */
|
|
64
|
+
wardenVersion?: string;
|
|
65
|
+
/** `WARDEN_BIN`: argv of the `warden` that started this run */
|
|
66
|
+
wardenBin?: string[];
|
|
67
|
+
/** `WARDEN_PERF`: capture names every attempt records (`all`, `react`, `js`…) */
|
|
68
|
+
perf?: string[];
|
|
69
|
+
/** `WARDEN_PERF_BUDGETS`: raw JSON of the suite's budgets */
|
|
70
|
+
perfBudgets?: string;
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* Reads the spec env contract. `undefined` data = not running under warden (no `WARDEN_UDID`); an
|
|
74
|
+
* error lists every invalid variable by its env name. Empty values count as unset.
|
|
75
|
+
*/
|
|
76
|
+
export declare function readEnv(env: Readonly<Record<string, string | undefined>>, opts?: {
|
|
77
|
+
now?: () => Date;
|
|
78
|
+
}): Result<E2eEnv | undefined>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ErrorResult } from "../../types/src/result";
|
|
2
|
+
export type E2eErrorCode = "LOCATOR_NOT_FOUND" | "LOCATOR_AMBIGUOUS" | "INVALID_LOCATOR" | "ACTION_FAILED" | "TIMEOUT" | "NOT_ACTIONABLE" | "INVALID_ARGUMENT" | "SESSION" | "DRIVER" | "ASSERTION" | "PERF_BUDGET";
|
|
3
|
+
/** Plain shape of a spec error: what `e2eErr()` accepts and what a `Result` carries. */
|
|
4
|
+
export type E2eErrorShape = {
|
|
5
|
+
code: E2eErrorCode;
|
|
6
|
+
message: string;
|
|
7
|
+
/** text render of the accessibility tree at the moment of failure, when the caller had one */
|
|
8
|
+
screen?: string;
|
|
9
|
+
/** number of candidates for `LOCATOR_AMBIGUOUS` */
|
|
10
|
+
matches?: number;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Every failure a spec can see (`screen`, `expect`, `test`, session), discriminated by `code`.
|
|
14
|
+
* A thrown class so specs can `instanceof` it; `e2eErr()` wraps one in a failed `Result`.
|
|
15
|
+
*/
|
|
16
|
+
export declare class E2eError extends Error implements E2eErrorShape {
|
|
17
|
+
readonly code: E2eErrorCode;
|
|
18
|
+
readonly screen?: string;
|
|
19
|
+
readonly matches?: number;
|
|
20
|
+
constructor(code: E2eErrorCode, message: string, screen?: string, matches?: number);
|
|
21
|
+
static from(shape: E2eErrorShape): E2eError;
|
|
22
|
+
}
|
|
23
|
+
/** `err()` for results carrying an `E2eError`. */
|
|
24
|
+
export declare function e2eErr(error: E2eErrorShape): ErrorResult<E2eError>;
|
|
25
|
+
/** One line for logs and thrown messages: `CODE: message`. */
|
|
26
|
+
export declare function describeE2eError(error: E2eErrorShape): string;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type Expect as BunExpect, type Matchers } from "bun:test";
|
|
2
|
+
import { E2eError } from "../errors";
|
|
3
|
+
import { type Locator } from "../screen/locator";
|
|
4
|
+
import { type Session } from "../session";
|
|
5
|
+
import { LocatorAssertions } from "./matchers";
|
|
6
|
+
/** Receives a failure `expect.soft` kept instead of throwing. */
|
|
7
|
+
export type SoftSink = (error: E2eError) => void;
|
|
8
|
+
/** `expect.soft`: `expect` whose failures are kept on the running attempt, which fails once its body has settled. */
|
|
9
|
+
export type SoftExpect = {
|
|
10
|
+
(actual: Locator): LocatorAssertions;
|
|
11
|
+
<T = unknown>(actual: T, message?: string): Matchers<T>;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* The spec `expect`: `expect(locator)` gives the polling locator matchers, anything else is bun's own `expect`
|
|
15
|
+
* (all of its statics — `any`, `arrayContaining`, `extend`, `not`, … — are on it too).
|
|
16
|
+
*/
|
|
17
|
+
export type E2eExpect = ((actual: Locator) => LocatorAssertions) & BunExpect & {
|
|
18
|
+
readonly soft: SoftExpect;
|
|
19
|
+
};
|
|
20
|
+
export type ExpectDeps = {
|
|
21
|
+
/** where `expect.soft` keeps failures right now; undefined (outside an attempt) means throw as usual */
|
|
22
|
+
softSink: () => SoftSink | undefined;
|
|
23
|
+
};
|
|
24
|
+
/** The running attempt's collector (`AttemptContext.softFailures`), if a test is running. */
|
|
25
|
+
export declare function softSinkFor(session: Pick<Session, "attempt">): SoftSink | undefined;
|
|
26
|
+
export declare function createExpect(deps: ExpectDeps): E2eExpect;
|
|
27
|
+
export declare const expect: E2eExpect;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { E2eError } from "../errors";
|
|
2
|
+
import { type Locator } from "../screen/locator";
|
|
3
|
+
import { type TextMatch } from "../screen/text";
|
|
4
|
+
/**
|
|
5
|
+
* A negated matcher passes only after its condition held this long without a break (or the whole timeout, if shorter).
|
|
6
|
+
*
|
|
7
|
+
* It can end sooner: once the condition held on one poll, a driver with native settle (iOS `axbridge`, Android
|
|
8
|
+
* `warden-uiad`: both watch the app's own activity stream, ~250-300 ms) is asked once whether the screen is quiet
|
|
9
|
+
* (`NEGATION_QUIET_MS`, bounded by what is left of the hold). If it is, one fresh poll that still holds passes. A screen
|
|
10
|
+
* that is quiet but about to change by itself (a toast after a timer) can therefore pass before that change, where the
|
|
11
|
+
* full hold would have seen it. Without native settle the hold stays plain: there, knowing the screen is quiet means
|
|
12
|
+
* polled snapshots or a `uiautomator dump` (~1.9 s), longer than the window it would save.
|
|
13
|
+
*
|
|
14
|
+
* The settle is asked only when no input came within the last `NEGATION_GRACE_MS` (the observation cache's
|
|
15
|
+
* `sinceInputMs`; no input yet counts as none). A quiet app is not a settled one right after a tap: a submit waiting on
|
|
16
|
+
* the network is idle until the reply comes, so `submit.tap()` then `expect(error).not.toBeVisible()` keeps the full
|
|
17
|
+
* hold, which sees an error that comes within it. Without the cache the time of the last input is unknown: plain hold.
|
|
18
|
+
* Input is every driver call that is not a read, the matcher's own native settle included (so a second `.not` right
|
|
19
|
+
* after one that ended early holds the full time). Devtools calls (`device.devtools.evaluate`, `reloadJs`) go to Metro,
|
|
20
|
+
* not the driver, so they do not count: a `.not` right after one may end early. Tap, `wait` or assert something
|
|
21
|
+
* positive first.
|
|
22
|
+
*/
|
|
23
|
+
export declare const NEGATION_GRACE_MS = 1000;
|
|
24
|
+
/** The quiet window a negated matcher asks the driver for before it ends its hold early. */
|
|
25
|
+
export declare const NEGATION_QUIET_MS = 250;
|
|
26
|
+
export type AssertionOptions = {
|
|
27
|
+
timeout?: number;
|
|
28
|
+
};
|
|
29
|
+
export type TextAssertionOptions = AssertionOptions & {
|
|
30
|
+
exact?: boolean;
|
|
31
|
+
};
|
|
32
|
+
export type ContainAssertionOptions = AssertionOptions & {
|
|
33
|
+
ignoreCase?: boolean;
|
|
34
|
+
};
|
|
35
|
+
export type CheckedOptions = AssertionOptions & {
|
|
36
|
+
checked?: boolean;
|
|
37
|
+
};
|
|
38
|
+
export type AssertionContext = {
|
|
39
|
+
negate: boolean;
|
|
40
|
+
/** collects a failure instead of throwing it (`expect.soft`) */
|
|
41
|
+
soft?: (error: E2eError) => void;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* The locator matchers of `expect(locator)`. Each one polls (100 ms apart for the first second, then 250 ms) until its
|
|
45
|
+
* condition holds or the assertion timeout passes, then fails with `ASSERTION`. `.not` inverts the condition (and must
|
|
46
|
+
* see it hold for a grace window; the polls land exactly on its end).
|
|
47
|
+
* Except `toHaveCount`, a matcher needs exactly one match: none keeps polling, several fail at once.
|
|
48
|
+
*/
|
|
49
|
+
export declare class LocatorAssertions {
|
|
50
|
+
private readonly locator;
|
|
51
|
+
private readonly context;
|
|
52
|
+
constructor(locator: Locator, context: AssertionContext);
|
|
53
|
+
get not(): LocatorAssertions;
|
|
54
|
+
toBeVisible(options?: AssertionOptions): Promise<void>;
|
|
55
|
+
toBeHidden(options?: AssertionOptions): Promise<void>;
|
|
56
|
+
/** Whole-text after whitespace normalisation (`exact: false`: a case-insensitive substring); a RegExp as written. */
|
|
57
|
+
toHaveText(expected: TextMatch, options?: TextAssertionOptions): Promise<void>;
|
|
58
|
+
/** The (normalised) text contains the string, case-sensitively unless `ignoreCase`; a RegExp is tested as written. */
|
|
59
|
+
toContainText(expected: TextMatch, options?: ContainAssertionOptions): Promise<void>;
|
|
60
|
+
/** The field value as it is (no whitespace normalisation). Secure fields cannot be judged. */
|
|
61
|
+
toHaveValue(expected: TextMatch, options?: AssertionOptions): Promise<void>;
|
|
62
|
+
/** Checked by default; `{ checked: false }` expects unchecked. A node that is not checkable counts as unchecked. */
|
|
63
|
+
toBeChecked(options?: CheckedOptions): Promise<void>;
|
|
64
|
+
toBeEnabled(options?: AssertionOptions): Promise<void>;
|
|
65
|
+
toBeDisabled(options?: AssertionOptions): Promise<void>;
|
|
66
|
+
/** Exactly `expected` current matches (zero included). */
|
|
67
|
+
toHaveCount(expected: number, options?: AssertionOptions): Promise<void>;
|
|
68
|
+
private assert;
|
|
69
|
+
private poll;
|
|
70
|
+
/** The screen is rendered here, from the last snapshot read, because only a failure needs the text. */
|
|
71
|
+
private failure;
|
|
72
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { DevtoolsFacet, DevtoolsResult } from "./devtools/devtools.types";
|
|
2
|
+
import type { SpanRunner } from "./runner/span";
|
|
3
|
+
import type { TraceEvent } from "./runner/trace";
|
|
4
|
+
/**
|
|
5
|
+
* What the `app` / `device` fixtures need from the session. `Session` satisfies it structurally;
|
|
6
|
+
* the runner (T3) mutates `signal`, `attemptDir` and `trace` per attempt, so fixtures read them at
|
|
7
|
+
* call time, never at construction.
|
|
8
|
+
*/
|
|
9
|
+
export type FixtureSession = {
|
|
10
|
+
/** absolute `WARDEN_E2E_OUT` (screenshot fallback when no attempt is running) */
|
|
11
|
+
readonly out: string;
|
|
12
|
+
/** the running attempt's deadline / cancel signal; every driver call honours it */
|
|
13
|
+
readonly signal?: AbortSignal;
|
|
14
|
+
/** `<out>/<specId>/<title-slug>-<hash6>` of the running attempt */
|
|
15
|
+
readonly attemptDir?: string;
|
|
16
|
+
/** the running attempt's recorded files (`device.perf` adds its `perf/` ones) */
|
|
17
|
+
readonly attempt?: {
|
|
18
|
+
readonly files: Set<string>;
|
|
19
|
+
};
|
|
20
|
+
/** the running attempt's `trace.jsonl` */
|
|
21
|
+
readonly trace?: {
|
|
22
|
+
write(event: TraceEvent): void;
|
|
23
|
+
};
|
|
24
|
+
/** one reported step (`▸`/`▪` lines, `trace.jsonl`) that gives up when the attempt deadline aborts; absent = just run it */
|
|
25
|
+
step?<T>(kind: string, label: string, fn: () => Promise<T>): Promise<T>;
|
|
26
|
+
/** times one call as a trace-only `span` (`device.*`, `read.*`); absent = untraced */
|
|
27
|
+
span?: SpanRunner;
|
|
28
|
+
/** attaches the app's devtools on first call (the session shares one connection); absent = no devtools */
|
|
29
|
+
devtools?(): Promise<DevtoolsResult<DevtoolsFacet>>;
|
|
30
|
+
/** the facet if already attached; never attaches */
|
|
31
|
+
peekDevtools?(): DevtoolsFacet | undefined;
|
|
32
|
+
};
|
|
33
|
+
/** Injected time: tests drive a fake clock. */
|
|
34
|
+
export type Clock = {
|
|
35
|
+
now(): number;
|
|
36
|
+
sleep(ms: number): Promise<void>;
|
|
37
|
+
};
|
|
38
|
+
export declare const systemClock: Clock;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type Device } from "./device";
|
|
2
|
+
import type { BaseFixtures, FixtureContext } from "./runner/fixtures.types";
|
|
3
|
+
import type { ScreenHost } from "./screen/context";
|
|
4
|
+
/**
|
|
5
|
+
* What `screen` needs from one attempt: the live driver, its timeouts and deadline, and reported steps. With
|
|
6
|
+
* `systemAlerts` set (`test.use`) and a `device` to press with, reads and waits answer the alerts they see.
|
|
7
|
+
*/
|
|
8
|
+
export declare function screenHostFor(ctx: FixtureContext, device?: Pick<Device, "alert">): ScreenHost;
|
|
9
|
+
/**
|
|
10
|
+
* The `app` / `device` / `screen` / `platform` every test gets, over the session's driver. `device` calls and
|
|
11
|
+
* locator reads are trace-only spans (`device.*`, `read.*`); the driver's own calls are `driver.*` spans (the session
|
|
12
|
+
* traces its driver).
|
|
13
|
+
*/
|
|
14
|
+
export declare function defaultFixtureFactory(ctx: FixtureContext): Promise<BaseFixtures>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type { OpenOptions, OpenWaitUntil } from "./app";
|
|
2
|
+
export { E2eError } from "./errors";
|
|
3
|
+
export { expect } from "./expect/expect";
|
|
4
|
+
export type { PerfBreach, PerfBudgets } from "./perf/budgets";
|
|
5
|
+
export type { DevicePerf } from "./perf/device-perf";
|
|
6
|
+
export type { PerfMetrics, PerfReport, PerfStartOptions } from "./perf/perf.types";
|
|
7
|
+
export type { App, Device, Screen, SystemAlerts, TestFixtures } from "./runner/fixtures.types";
|
|
8
|
+
export { afterAll, afterEach, beforeAll, beforeEach, describe, test } from "./runner/test";
|
|
9
|
+
export type { Locator, LocatorReading, PinchOptions, RotateOptions, TapOptions, TapUntil } from "./screen/locator";
|
|
10
|
+
export type { ScreenGestures, ScreenPathOptions, ScreenPinchOptions, ScreenRotateOptions, } from "./screen/screen";
|
|
11
|
+
export { WARDEN_E2E_VERSION } from "./version";
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { PerfReport } from "./perf.types";
|
|
2
|
+
/** Ceilings a perf report must stay under; every field is optional and lower-is-better. */
|
|
3
|
+
export type PerfBudgets = {
|
|
4
|
+
/** cold start, ms */
|
|
5
|
+
startupColdMs?: number;
|
|
6
|
+
/** share of janky frames, percent */
|
|
7
|
+
jankPct?: number;
|
|
8
|
+
/** the slowest React commit, ms */
|
|
9
|
+
maxCommitMs?: number;
|
|
10
|
+
/** 95th percentile app CPU, percent */
|
|
11
|
+
p95CpuPct?: number;
|
|
12
|
+
/** peak memory (PSS on Android, RSS on iOS), MB */
|
|
13
|
+
peakMemoryMb?: number;
|
|
14
|
+
};
|
|
15
|
+
export declare const BUDGET_KEYS: readonly ["startupColdMs", "jankPct", "maxCommitMs", "p95CpuPct", "peakMemoryMb"];
|
|
16
|
+
export type BudgetKey = (typeof BUDGET_KEYS)[number];
|
|
17
|
+
/** One metric over its ceiling. */
|
|
18
|
+
export type PerfBreach = {
|
|
19
|
+
metric: BudgetKey;
|
|
20
|
+
value: number;
|
|
21
|
+
budget: number;
|
|
22
|
+
};
|
|
23
|
+
/** What the report measured for `metric`, or `undefined` when its capture did not run. */
|
|
24
|
+
export declare function measured(report: Pick<PerfReport, "metrics">, metric: BudgetKey): number | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* Budgets set in `overrides` replace the same key of `base` (config); a metric the report has no number for (its
|
|
27
|
+
* capture did not run) cannot breach.
|
|
28
|
+
*/
|
|
29
|
+
export declare function checkBudgets(report: Pick<PerfReport, "metrics">, base: PerfBudgets | undefined, overrides?: PerfBudgets): PerfBreach[];
|
|
30
|
+
/** The failure text: each breach, then where the full report is. */
|
|
31
|
+
export declare function describeBreaches(breaches: readonly PerfBreach[], reportPath: string | undefined): string;
|
|
32
|
+
/** Parses the `WARDEN_PERF_BUDGETS` JSON warden writes; unknown keys and non-numbers are dropped. */
|
|
33
|
+
export declare function parseBudgets(raw: string | undefined): PerfBudgets;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { type PerfBudgets } from "./budgets";
|
|
2
|
+
import type { PerfReport, PerfStartOptions } from "./perf.types";
|
|
3
|
+
/** One `warden` invocation: argv (without the warden command itself) in, exit code and streams out. */
|
|
4
|
+
export type PerfExec = (argv: readonly string[]) => Promise<{
|
|
5
|
+
code: number;
|
|
6
|
+
stdout: string;
|
|
7
|
+
stderr: string;
|
|
8
|
+
}>;
|
|
9
|
+
/** Subdirectory of the attempt dir a perf session's report is copied to. */
|
|
10
|
+
export declare const PERF_DIR = "perf";
|
|
11
|
+
export type DevicePerf = {
|
|
12
|
+
/** opens a perf session on this device; one per device at a time */
|
|
13
|
+
start(options?: PerfStartOptions): Promise<{
|
|
14
|
+
sessionId: string;
|
|
15
|
+
}>;
|
|
16
|
+
/** drops a label on the session timeline; commits, hangs and jank are tagged with the last mark before them */
|
|
17
|
+
mark(label: string): Promise<void>;
|
|
18
|
+
/**
|
|
19
|
+
* Ends the session and returns its report (`reportPath` is the copy under the attempt's `perf/`). Throws
|
|
20
|
+
* `PERF_BUDGET` when a metric is over the suite's budget (`warden.config.ts` `perf.budgets`) or `budgets`, which
|
|
21
|
+
* replaces the suite's per key.
|
|
22
|
+
*/
|
|
23
|
+
stop(options?: {
|
|
24
|
+
budgets?: PerfBudgets;
|
|
25
|
+
}): Promise<PerfReport>;
|
|
26
|
+
/** a session this device opened and has not stopped */
|
|
27
|
+
readonly active: boolean;
|
|
28
|
+
};
|
|
29
|
+
export type DevicePerfDeps = {
|
|
30
|
+
exec: PerfExec;
|
|
31
|
+
udid: string;
|
|
32
|
+
/** budgets of the suite (`WARDEN_PERF_BUDGETS`) */
|
|
33
|
+
budgets: PerfBudgets;
|
|
34
|
+
/** the running attempt's dir; absent outside an attempt (the report stays in the session dir) */
|
|
35
|
+
attemptDir: () => string | undefined;
|
|
36
|
+
/** records a file written into the attempt dir (relative path) so `results.jsonl` lists it */
|
|
37
|
+
addFile?: (relative: string) => void;
|
|
38
|
+
/** `warden` is not reachable (no `WARDEN_BIN`) */
|
|
39
|
+
unavailable?: string;
|
|
40
|
+
};
|
|
41
|
+
export declare function createDevicePerf(deps: DevicePerfDeps): DevicePerf;
|
|
42
|
+
/** `--perf=react,js` (or `all`) as the options `perf.start` takes; an unknown name is ignored, none named = warden's default set. */
|
|
43
|
+
export declare function perfStartFor(names: readonly string[]): PerfStartOptions;
|
|
44
|
+
/** Runs the `warden` that started the spec process (`WARDEN_BIN`). */
|
|
45
|
+
export declare function spawnWarden(bin: readonly string[] | undefined): PerfExec;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/** The capture switches `device.perf.start` takes (the `warden perf start` flags of the same name). */
|
|
2
|
+
export type PerfStartOptions = {
|
|
3
|
+
js?: boolean;
|
|
4
|
+
react?: boolean;
|
|
5
|
+
native?: boolean | "perfetto" | "simpleperf";
|
|
6
|
+
metrics?: boolean;
|
|
7
|
+
frames?: boolean;
|
|
8
|
+
logs?: boolean;
|
|
9
|
+
all?: boolean;
|
|
10
|
+
/** stop the app, arm the captures, launch it: startup and early JS/React are recorded */
|
|
11
|
+
relaunch?: boolean;
|
|
12
|
+
/** like `relaunch`, but wipe the app's data first */
|
|
13
|
+
reset?: boolean;
|
|
14
|
+
/** hard cap on the session; warden's default is 30 s, the maximum 5 min */
|
|
15
|
+
maxDurationMs?: number;
|
|
16
|
+
};
|
|
17
|
+
/** The numbers of a report, each lower-is-better (the `metrics` of `report.json`). */
|
|
18
|
+
export type PerfMetrics = {
|
|
19
|
+
startupColdMs?: number;
|
|
20
|
+
startupWarmMs?: number;
|
|
21
|
+
jankPct?: number;
|
|
22
|
+
fps?: number;
|
|
23
|
+
cpuP95Pct?: number;
|
|
24
|
+
peakPssKb?: number;
|
|
25
|
+
peakRssKb?: number;
|
|
26
|
+
/** the slowest React commit */
|
|
27
|
+
topCommitMs?: number;
|
|
28
|
+
hangCount?: number;
|
|
29
|
+
hangTotalMs?: number;
|
|
30
|
+
topFunctions: Array<{
|
|
31
|
+
name: string;
|
|
32
|
+
selfPct: number;
|
|
33
|
+
}>;
|
|
34
|
+
};
|
|
35
|
+
/** `report.json` of a perf session, as far as a spec reads it (the full document is at `reportPath`). */
|
|
36
|
+
export type PerfReport = {
|
|
37
|
+
version: 1;
|
|
38
|
+
sessionId: string;
|
|
39
|
+
app?: string;
|
|
40
|
+
durationMs: number;
|
|
41
|
+
stopReason?: string;
|
|
42
|
+
banners: string[];
|
|
43
|
+
summary: string[];
|
|
44
|
+
findings: Array<{
|
|
45
|
+
kind: string;
|
|
46
|
+
severity: string;
|
|
47
|
+
costMs: number;
|
|
48
|
+
evidence: string;
|
|
49
|
+
fix: string;
|
|
50
|
+
}>;
|
|
51
|
+
metrics: PerfMetrics;
|
|
52
|
+
marks: Array<{
|
|
53
|
+
label: string;
|
|
54
|
+
atMs: number;
|
|
55
|
+
}>;
|
|
56
|
+
next: string[];
|
|
57
|
+
/** where `report.json` is (inside the attempt's `perf/` dir when the spec runs under `warden e2e`) */
|
|
58
|
+
reportPath?: string;
|
|
59
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { BUN_TIMEOUT_MARGIN_MS } from "./runner/scope";
|
|
2
|
+
import { getSession } from "./session";
|
|
3
|
+
export { BUN_TIMEOUT_MARGIN_MS };
|
|
4
|
+
export type PreloadDeps = {
|
|
5
|
+
env: Readonly<Record<string, string | undefined>>;
|
|
6
|
+
setDefaultTimeout: (ms: number) => void;
|
|
7
|
+
afterAll: (fn: () => Promise<void>) => void;
|
|
8
|
+
createSession: typeof getSession;
|
|
9
|
+
/** this package's version (`WARDEN_E2E_VERSION`) */
|
|
10
|
+
version: string;
|
|
11
|
+
/** whether a `test.only` was collected */
|
|
12
|
+
onlySeen: () => boolean;
|
|
13
|
+
stderr: (text: string) => void;
|
|
14
|
+
exit: (code: number) => void;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* The protocol handshake: `warden e2e` and this package version in lock-step. A mismatch refuses the run (exit 2)
|
|
18
|
+
* with the install fix spelled out. Nothing to compare without `WARDEN_VERSION`
|
|
19
|
+
* (a by-hand run) or from the workspace sources (not a published build).
|
|
20
|
+
*/
|
|
21
|
+
export declare function versionMismatch(wardenVersion: string | undefined, e2eVersion: string): string | undefined;
|
|
22
|
+
/** `CI=1`, `CI=true`, … — anything but empty, `0` and `false`. */
|
|
23
|
+
export declare function isCi(env: Readonly<Record<string, string | undefined>>): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* `bun test --preload`: a no-op without `WARDEN_UDID` (safe in `bunfig.toml`); otherwise validates
|
|
26
|
+
* the env (exit 2 when invalid), raises bun's default timeout above the test timeout, opens the
|
|
27
|
+
* session eagerly and, in `afterAll`, closes it and (under `CI`) fails the run if a `.only` was collected.
|
|
28
|
+
*/
|
|
29
|
+
export declare function installPreload(deps: PreloadDeps): void;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Lowercase, runs of non-alphanumerics become `-`, trimmed, capped at 60 chars; `fallback` when empty. */
|
|
2
|
+
export declare function slug(text: string, fallback?: string): string;
|
|
3
|
+
/** First 6 hex chars of a sha1 over the parts: keeps equal slugs from colliding. */
|
|
4
|
+
export declare function hash6(...parts: string[]): string;
|
|
5
|
+
/** `<out>/<specId>/<title-slug>-<hash6>/` for one test attempt. */
|
|
6
|
+
export declare function attemptDir(out: string, specId: string, title: readonly string[], hash: string): string;
|
|
7
|
+
/** `<dir>/screenshots/NNN-name.png` (3-digit, 1-based order). */
|
|
8
|
+
export declare function screenshotPath(dir: string, n: number, name: string): string;
|
|
9
|
+
/**
|
|
10
|
+
* Stable per-file id: the path under `specsDir` without extension (`auth/sign-in.e2e.ts` →
|
|
11
|
+
* `auth/sign-in`), segments slugged. A file outside `specsDir` (or no `specsDir`) uses its basename.
|
|
12
|
+
*/
|
|
13
|
+
export declare function specIdOf(file: string, specsDir?: string): string;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { E2eError } from "../errors";
|
|
2
|
+
import { type Session } from "../session";
|
|
3
|
+
import type { BaseFixtures } from "./fixtures.types";
|
|
4
|
+
import { type ResolvedUse, type Scope } from "./scope";
|
|
5
|
+
/** Longest one hook or fixture teardown may take once the test itself is over. */
|
|
6
|
+
export declare const CLEANUP_TIMEOUT_MS = 10000;
|
|
7
|
+
/** Part of bun's margin the runner never spends after a deadline: the `■` line and the results line still need it. */
|
|
8
|
+
export declare const POST_DEADLINE_RESERVE_MS = 5000;
|
|
9
|
+
/** The runner's own time limits around a test body (see {@link setRunnerTiming}). */
|
|
10
|
+
export type RunnerTiming = {
|
|
11
|
+
/** longest one `afterEach` hook or fixture teardown may take ({@link CLEANUP_TIMEOUT_MS}) */
|
|
12
|
+
cleanupTimeoutMs: number;
|
|
13
|
+
/** longest one failure capture call may take (`CAPTURE_TIMEOUT_MS`) */
|
|
14
|
+
captureTimeoutMs: number;
|
|
15
|
+
/** what bun's own per-test timeout adds over warden's deadline (`BUN_TIMEOUT_MARGIN_MS`) */
|
|
16
|
+
bunMarginMs: number;
|
|
17
|
+
/** part of that margin left unspent ({@link POST_DEADLINE_RESERVE_MS}) */
|
|
18
|
+
reserveMs: number;
|
|
19
|
+
};
|
|
20
|
+
export declare function runnerTiming(): Readonly<RunnerTiming>;
|
|
21
|
+
/**
|
|
22
|
+
* Test seam: a runner fixture spec shrinks the limits (before it registers its tests) so a slow-cleanup case runs
|
|
23
|
+
* in milliseconds. `undefined` restores the defaults.
|
|
24
|
+
*/
|
|
25
|
+
export declare function setRunnerTiming(next: Partial<RunnerTiming> | undefined): void;
|
|
26
|
+
/** `test.extend` fixture: set up, `use(value)`, and everything after `use` is the teardown. */
|
|
27
|
+
export type FixtureSetup = (base: Record<string, unknown>, use: (value: unknown) => Promise<void>) => Promise<void>;
|
|
28
|
+
export type FixtureEntry = {
|
|
29
|
+
name: string;
|
|
30
|
+
setup: FixtureSetup;
|
|
31
|
+
};
|
|
32
|
+
export type AttemptSpec<F extends object> = {
|
|
33
|
+
/** absolute path of the spec file */
|
|
34
|
+
file: string;
|
|
35
|
+
/** describe titles, then the test title */
|
|
36
|
+
title: readonly string[];
|
|
37
|
+
scope: Scope;
|
|
38
|
+
fixtures: readonly FixtureEntry[];
|
|
39
|
+
body: (fixtures: F) => void | Promise<void>;
|
|
40
|
+
/** per-test `timeout` option (beats `test.use`) */
|
|
41
|
+
timeout?: number;
|
|
42
|
+
only: boolean;
|
|
43
|
+
/** no retries left: only a final (or passing) attempt appends to `results.jsonl` */
|
|
44
|
+
final: boolean;
|
|
45
|
+
/** bun's own timeout for this test, fixed when it was registered (default: the resolved timeout + bun's margin) */
|
|
46
|
+
bunTimeoutMs?: number;
|
|
47
|
+
};
|
|
48
|
+
/** `app`/`device`/`screen`/`platform` from the session's factory (throwing stand-ins when none is registered). */
|
|
49
|
+
export declare function buildBaseFixtures(session: Session, signal: AbortSignal, use: ResolvedUse): Promise<BaseFixtures>;
|
|
50
|
+
/**
|
|
51
|
+
* When the runner must be done with an attempt: bun's own timeout for the test (or the resolved timeout plus bun's
|
|
52
|
+
* margin, if that is sooner) minus the reserve. Failure capture, `afterEach` hooks and fixture teardowns all spend
|
|
53
|
+
* from this one budget, so bun never gives up on a test (and starts the next one) while its cleanup still runs.
|
|
54
|
+
*/
|
|
55
|
+
export declare function cleanupDeadline(startedAt: number, timeoutMs: number, bunTimeoutMs: number | undefined): number;
|
|
56
|
+
/**
|
|
57
|
+
* One attempt of one test: deadline (`AbortSignal`), fixtures, `beforeEach`, body, then — whatever
|
|
58
|
+
* happened — failure capture, `afterEach`, fixture teardown (reverse order), the `■` line and the
|
|
59
|
+
* `results.jsonl` line. Throws the test's own error so bun marks the test failed.
|
|
60
|
+
*/
|
|
61
|
+
export declare function runAttempt<F extends object>(session: Session, spec: AttemptSpec<F>): Promise<void>;
|
|
62
|
+
/** One error for every `expect.soft` failure of the attempt. */
|
|
63
|
+
export declare function softFailureError(failures: readonly E2eError[]): E2eError;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { DevtoolsFacet } from "../devtools/devtools.types";
|
|
2
|
+
import { type NativeLogDeps, type NativeTarget } from "../devtools/native-log";
|
|
3
|
+
import type { Driver } from "../driver/driver.types";
|
|
4
|
+
import type { E2eEnv } from "../env";
|
|
5
|
+
export declare const FAILURE_PNG = "failure.png";
|
|
6
|
+
export declare const SCREEN_AT_FAILURE = "screen-at-failure.txt";
|
|
7
|
+
export declare const CONSOLE_JSONL = "console.jsonl";
|
|
8
|
+
export declare const NETWORK_JSONL = "network.jsonl";
|
|
9
|
+
/** Longest a single capture call may take: a wedged device must not hold the test run. */
|
|
10
|
+
export declare const CAPTURE_TIMEOUT_MS = 10000;
|
|
11
|
+
/** What is left of the runner's post-deadline budget, in ms; the default never runs out. */
|
|
12
|
+
export type CaptureBudget = () => number;
|
|
13
|
+
export type FailureCapture = {
|
|
14
|
+
/** files written into `dir`, relative to it */
|
|
15
|
+
files: string[];
|
|
16
|
+
/** what could not be captured, for the trace */
|
|
17
|
+
problems: string[];
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Best-effort `failure.png` (`driver.screenshot`) and `screen-at-failure.txt` (`driver.snapshot`,
|
|
21
|
+
* rendered like `warden ui snapshot --all`) into `dir`. Never throws: the test's own error is
|
|
22
|
+
* the one worth reporting. Each call gets `timeoutMs` or what `budget` says is left, whichever is less; a
|
|
23
|
+
* screenshot that timed out skips the snapshot (the device is not answering).
|
|
24
|
+
*/
|
|
25
|
+
export declare function captureFailure(driver: Driver, dir: string, timeoutMs?: number, budget?: CaptureBudget): Promise<FailureCapture>;
|
|
26
|
+
/**
|
|
27
|
+
* `console.jsonl` and `network.jsonl` of the app's attached devtools into `dir`. Best effort like
|
|
28
|
+
* {@link captureFailure}: never throws, gives up after `timeoutMs` (the network drain talks to the page).
|
|
29
|
+
*/
|
|
30
|
+
export declare function captureDevtoolsArtifacts(facet: DevtoolsFacet, dir: string, timeoutMs?: number, budget?: CaptureBudget): Promise<FailureCapture>;
|
|
31
|
+
/** The app a failed attempt ran, from the run's env. */
|
|
32
|
+
export declare function nativeTarget(env: Pick<E2eEnv, "platform" | "udid" | "bundleId">): NativeTarget;
|
|
33
|
+
/**
|
|
34
|
+
* `native.log` (+ iOS `.ips` crash reports) of the failed attempt, see {@link captureNativeLog}. Best effort
|
|
35
|
+
* like {@link captureFailure}: never throws, limited by `timeoutMs` and what `budget` has left.
|
|
36
|
+
*/
|
|
37
|
+
export declare function captureNativeArtifacts(target: NativeTarget, deps: NativeLogDeps, win: {
|
|
38
|
+
dir: string;
|
|
39
|
+
sinceMs: number;
|
|
40
|
+
}, timeoutMs?: number, budget?: CaptureBudget): Promise<FailureCapture>;
|