@delacour/warden-e2e 0.6.0 → 0.7.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-yb1hcjqm.mjs → chunk-empqr9n0.mjs} +1364 -53
- package/index.mjs +34 -4
- package/package.json +2 -2
- package/preload.mjs +4008 -335
- package/types/core/src/affected/affected.schema.d.ts +93 -0
- package/types/core/src/allocate.d.ts +2 -1
- package/types/core/src/android-keyguard.d.ts +34 -0
- package/types/core/src/batch/preset.schema.d.ts +1 -0
- package/types/core/src/biometrics.d.ts +15 -0
- package/types/core/src/builds/builds.defaults.d.ts +15 -0
- package/types/core/src/builds/config.d.ts +5 -1
- package/types/core/src/builds/config.schema.d.ts +182 -0
- package/types/core/src/builds/define-config.d.ts +104 -0
- package/types/core/src/builds/install-device.d.ts +37 -0
- package/types/core/src/builds/install.d.ts +17 -3
- package/types/core/src/builds/kinds.d.ts +21 -3
- package/types/core/src/builds/lock.d.ts +38 -0
- package/types/core/src/builds/resolve.d.ts +12 -2
- package/types/core/src/conditions/conditions.config.d.ts +117 -0
- package/types/core/src/conditions/conditions.d.ts +22 -0
- package/types/core/src/conditions/conditions.types.d.ts +141 -0
- package/types/core/src/conditions/inject-state.d.ts +13 -0
- package/types/core/src/conditions/launch-settings.d.ts +14 -0
- package/types/core/src/conditions/network.presets.d.ts +42 -0
- package/types/core/src/conditions/text-size.d.ts +19 -0
- package/types/core/src/golden/android-biometrics.d.ts +35 -0
- package/types/core/src/ios-runner/classify.d.ts +7 -0
- package/types/core/src/ios-runner/runner-build.types.d.ts +76 -0
- package/types/core/src/netproxy/control.d.ts +75 -0
- package/types/core/src/netproxy/decide.d.ts +22 -0
- package/types/core/src/netproxy/handle-file.d.ts +12 -0
- package/types/core/src/netproxy/lease-proxy.d.ts +53 -0
- package/types/core/src/netproxy/netproxy.main.d.ts +40 -0
- package/types/core/src/netproxy/pipe.d.ts +37 -0
- package/types/core/src/netproxy/proxy.d.ts +48 -0
- package/types/core/src/netproxy/shaping.d.ts +43 -0
- package/types/core/src/providers/android.d.ts +36 -1
- package/types/core/src/types.d.ts +47 -0
- package/types/e2e/src/alert-buttons.d.ts +9 -0
- package/types/e2e/src/app.d.ts +3 -1
- package/types/e2e/src/conditions/condition-events.d.ts +63 -0
- package/types/e2e/src/conditions/device-conditions.d.ts +170 -0
- package/types/e2e/src/conditions/geo.d.ts +15 -0
- package/types/e2e/src/conditions/gpx.d.ts +8 -0
- package/types/e2e/src/conditions/lease-recorder.d.ts +27 -0
- package/types/e2e/src/conditions/lease-store.d.ts +9 -0
- package/types/e2e/src/conditions/location-plan.d.ts +17 -0
- package/types/e2e/src/conditions/location-scenarios.data.d.ts +17 -0
- package/types/e2e/src/conditions/route-ticker.d.ts +53 -0
- package/types/e2e/src/conditions/scheduler.d.ts +83 -0
- package/types/e2e/src/device.d.ts +57 -3
- package/types/e2e/src/driver/android/android-app.d.ts +4 -0
- package/types/e2e/src/driver/android/android-argv.d.ts +19 -0
- package/types/e2e/src/driver/android/android-clock.d.ts +28 -0
- package/types/e2e/src/driver/android/android-device.d.ts +8 -0
- package/types/e2e/src/driver/android/android-driver.d.ts +17 -0
- package/types/e2e/src/driver/android/android-network.d.ts +66 -0
- package/types/e2e/src/driver/android/android-phone.d.ts +47 -0
- package/types/e2e/src/driver/android/android-settings.d.ts +34 -0
- package/types/e2e/src/driver/android/android-state.d.ts +10 -0
- package/types/e2e/src/driver/clock-recorder.d.ts +9 -0
- package/types/e2e/src/driver/device-settings.d.ts +10 -0
- package/types/e2e/src/driver/driver.types.d.ts +176 -1
- package/types/e2e/src/driver/ios/device/capabilities.d.ts +3 -0
- package/types/e2e/src/driver/ios/device/device-driver.d.ts +49 -0
- package/types/e2e/src/driver/ios/device/devicectl.d.ts +38 -0
- package/types/e2e/src/driver/ios/device/runner-protocol.d.ts +21 -0
- package/types/e2e/src/driver/ios/hid-recovery.d.ts +15 -0
- package/types/e2e/src/driver/ios/inject.d.ts +110 -0
- package/types/e2e/src/driver/ios/ios-driver.d.ts +29 -0
- package/types/e2e/src/driver/ios/ios-network.d.ts +46 -0
- package/types/e2e/src/driver/ios/ios-settings.d.ts +44 -0
- package/types/e2e/src/driver/ios/simctl.d.ts +41 -1
- package/types/e2e/src/driver/ios/type-budget.d.ts +6 -1
- package/types/e2e/src/driver/network-proxy.d.ts +52 -0
- package/types/e2e/src/driver/open.d.ts +20 -0
- package/types/e2e/src/driver/physical-conditions.d.ts +11 -0
- package/types/e2e/src/driver/requirements.d.ts +14 -0
- package/types/e2e/src/driver/typing.d.ts +1 -1
- package/types/e2e/src/env.d.ts +32 -0
- package/types/e2e/src/fixture-session.d.ts +4 -0
- package/types/e2e/src/fixtures.d.ts +6 -1
- package/types/e2e/src/index.d.ts +2 -0
- package/types/e2e/src/runner/scope.d.ts +1 -0
- package/types/e2e/src/runner/test.d.ts +6 -0
- package/types/e2e/src/traced-device.d.ts +2 -1
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import type { Exec } from "../../../../core/src/exec";
|
|
2
|
+
import type { ConditionSink } from "../../conditions/condition-events";
|
|
3
|
+
import { type TickerClock } from "../../conditions/route-ticker";
|
|
4
|
+
import type { ClockRecorder } from "../clock-recorder";
|
|
2
5
|
import type { Driver, DriverResult, Element, Logger, Point, ScreenInfo } from "../driver.types";
|
|
6
|
+
import type { NetworkProxyPort } from "../network-proxy";
|
|
3
7
|
import { type SpawnAdb } from "./adb-session";
|
|
4
8
|
import { type Clock } from "./android-device";
|
|
5
9
|
import { type DumpRoute } from "./android-dump";
|
|
@@ -23,6 +27,10 @@ export type AndroidDriverOptions = {
|
|
|
23
27
|
/** override how the resident shell is spawned (tests) */
|
|
24
28
|
spawn?: SpawnAdb;
|
|
25
29
|
log?: Logger;
|
|
30
|
+
/** the lease's shaping proxy: loss and per-host faults route through it (`createAndroidNetwork`); without it loss stays in `needsProxy` */
|
|
31
|
+
networkProxy?: NetworkProxyPort;
|
|
32
|
+
/** told about every successful `clock` op, so the lease records the `clock` area (`leaseClockRecorder`) */
|
|
33
|
+
clockRecorder?: ClockRecorder;
|
|
26
34
|
/** skip `wm size`/`wm density` */
|
|
27
35
|
screen?: ScreenInfo;
|
|
28
36
|
/** binary runner for screenshots, default {@link bunExecBinary} */
|
|
@@ -40,6 +48,11 @@ export type AndroidDriverOptions = {
|
|
|
40
48
|
state?: AndroidStateStore;
|
|
41
49
|
/** the app under test: `app.openUrl` is addressed to it (`am start -p`), so no other app or chooser takes the link */
|
|
42
50
|
appPackage?: string;
|
|
51
|
+
/**
|
|
52
|
+
* Packages besides `appPackage` that warden installed on this device (the `installs` table). On a physical phone
|
|
53
|
+
* only these may be uninstalled or have their data cleared; anything else is `E_REFUSED` (`physical_protected`).
|
|
54
|
+
*/
|
|
55
|
+
ownedPackages?: readonly string[];
|
|
43
56
|
/** how long to wait for `sys.boot_completed`, default 30 s */
|
|
44
57
|
bootTimeoutMs?: number;
|
|
45
58
|
/**
|
|
@@ -48,6 +61,10 @@ export type AndroidDriverOptions = {
|
|
|
48
61
|
* (a fake spawn, a fake dex push) a test replaces. API 26 and up only.
|
|
49
62
|
*/
|
|
50
63
|
uiad?: boolean | UiadOptions;
|
|
64
|
+
/** pacing of in-process location routes (default real time); tests pass a fake */
|
|
65
|
+
tickerClock?: TickerClock;
|
|
66
|
+
/** told about every network flap transition (the `condition` events of `results.jsonl`) */
|
|
67
|
+
conditionSink?: ConditionSink;
|
|
51
68
|
};
|
|
52
69
|
/** Deepest hittable element containing `point` (later in preorder wins ties: drawn on top); else deepest any. */
|
|
53
70
|
export declare function elementAt(flat: readonly Element[], point: Point): Element | null;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { NetworkCondition } from "../../../../core/src/conditions/conditions.types";
|
|
2
|
+
import type { Exec } from "../../../../core/src/exec";
|
|
3
|
+
import type { Driver, DriverResult, NetworkState } from "../driver.types";
|
|
4
|
+
import { type NetworkProxyPort } from "../network-proxy";
|
|
5
|
+
import type { AdbSession } from "./adb-session";
|
|
6
|
+
import type { Clock } from "./android-device";
|
|
7
|
+
/**
|
|
8
|
+
* Android native network conditions. Speed and delay go to the emulator console (`adb emu network ...`, host
|
|
9
|
+
* side); online/offline is airplane mode (`cmd connectivity`, falling back to `svc wifi|data`). Every switch is
|
|
10
|
+
* verified against what `dumpsys connectivity` reports, polled on the injected clock, before it returns.
|
|
11
|
+
* Loss, per-host faults and flapping are not native: loss is reported in `needsProxy` (COND-4), the rest never
|
|
12
|
+
* reach this driver.
|
|
13
|
+
*/
|
|
14
|
+
/** Longest wait for the OS to agree with one switch. */
|
|
15
|
+
export declare const NETWORK_VERIFY_MS = 10000;
|
|
16
|
+
export declare const DUMPSYS_CONNECTIVITY_ARGV: readonly string[];
|
|
17
|
+
/** Host argv: `adb -s <serial> emu network speed|delay <value>`. */
|
|
18
|
+
export declare function emuNetworkArgv(adb: string, serial: string, what: "speed" | "delay", value: string): string[];
|
|
19
|
+
/** Device shell argv. */
|
|
20
|
+
export declare function airplaneArgv(enable: boolean): string[];
|
|
21
|
+
/** Device shell argvs: the radios by hand, for images whose `cmd connectivity` has no airplane-mode. */
|
|
22
|
+
export declare function svcArgvs(enable: boolean): string[][];
|
|
23
|
+
/** The emulator console answers `KO: ...` with exit code 0 on a bad argument. */
|
|
24
|
+
export declare function emuReplyError(stdout: string): string | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* The default network out of `dumpsys connectivity`: its header line is `Active default network: 101` while a
|
|
27
|
+
* network is up and `Active default network: none` when airplane mode or disabled radios took it away. Anything
|
|
28
|
+
* else (a service that is not there, truncated output) is unreadable.
|
|
29
|
+
*/
|
|
30
|
+
export declare function parseActiveNetwork(dump: string): {
|
|
31
|
+
online: boolean;
|
|
32
|
+
netId?: number;
|
|
33
|
+
} | undefined;
|
|
34
|
+
export type AndroidNetworkDeps = {
|
|
35
|
+
session: AdbSession;
|
|
36
|
+
/** host runner for `adb emu` */
|
|
37
|
+
exec: Exec;
|
|
38
|
+
adb: string;
|
|
39
|
+
serial: string;
|
|
40
|
+
clock: Clock;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Put the emulator's network into `condition`. Offline: airplane mode (radios off by `svc` where the image has no
|
|
44
|
+
* airplane command). Anything else: back online first, then the preset's / custom speed and delay on the console.
|
|
45
|
+
* Physical devices have no console. `flap` is the scheduler's: the driver wraps this.
|
|
46
|
+
*/
|
|
47
|
+
export declare function applyAndroidNetwork(deps: AndroidNetworkDeps, condition: NetworkCondition): DriverResult<NetworkState>;
|
|
48
|
+
/** The emulator's alias for the host's loopback: a proxy bound to 127.0.0.1 on the host answers here. */
|
|
49
|
+
export declare const EMULATOR_HOST_ALIAS = "10.0.2.2";
|
|
50
|
+
/** Device shell argv: point the system HTTP(S) proxy at `value` (`host:port`, or `:0` to clear). */
|
|
51
|
+
export declare function httpProxyPutArgv(value: string): string[];
|
|
52
|
+
export declare const HTTP_PROXY_GET_ARGV: readonly string[];
|
|
53
|
+
/**
|
|
54
|
+
* Android network conditions, native first: speed, delay and offline stay on the emulator (`applyAndroidNetwork`).
|
|
55
|
+
* What the console cannot do, packet loss and per-host faults, goes through the lease's shaping proxy: the system
|
|
56
|
+
* `http_proxy` setting points at it (`10.0.2.2:<port>`) and the proxy applies only `lossPct` and the faults, so the
|
|
57
|
+
* native delay and bandwidth are not applied twice. Apps that honour the system proxy (OkHttp, HttpURLConnection,
|
|
58
|
+
* Chromium WebView, therefore React Native fetch) are covered; raw sockets and apps that set `Proxy.NO_PROXY` are not.
|
|
59
|
+
* Without a proxy port a loss stays in `needsProxy`.
|
|
60
|
+
*/
|
|
61
|
+
export declare function createAndroidNetwork(deps: AndroidNetworkDeps & {
|
|
62
|
+
proxy?: NetworkProxyPort;
|
|
63
|
+
}): {
|
|
64
|
+
network: Driver["network"];
|
|
65
|
+
networkFaults: Driver["networkFaults"];
|
|
66
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { DriverResult, Logger } from "../driver.types";
|
|
2
|
+
import type { AdbSession } from "./adb-session";
|
|
3
|
+
import type { Clock } from "./android-device";
|
|
4
|
+
import { type AndroidDeviceState, type AndroidStateStore } from "./android-state";
|
|
5
|
+
/** `ro.boot.qemu` / `ro.kernel.qemu` joined (empty on hardware), the power state and the keyguard, as the tail of the boot probe. */
|
|
6
|
+
export declare const PHONE_PROBE_TAIL: string;
|
|
7
|
+
/** `dumpsys window policy`'s keyguard lines: `showing=true`, `secure=true`. */
|
|
8
|
+
export declare const KEYGUARD_COMMAND = "dumpsys window policy | grep -E 'showing=|secure='";
|
|
9
|
+
/** What the phone-probe tail found out; every field is `undefined` when `dumpsys` printed nothing parseable. */
|
|
10
|
+
export type PhoneFacts = {
|
|
11
|
+
awake?: boolean;
|
|
12
|
+
keyguardShowing?: boolean;
|
|
13
|
+
keyguardSecure?: boolean;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* The phone facts in a boot probe's output, or `undefined` when the probe had no phone tail (a console emulator)
|
|
17
|
+
* or the qemu props say this is an emulator (`localhost:5555`, Genymotion): not a phone.
|
|
18
|
+
*/
|
|
19
|
+
export declare function parsePhoneFacts(stdout: string): PhoneFacts | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* Make an attached phone usable, never unlock it: a sleeping one gets `KEYCODE_WAKEUP`; a keyguard without a
|
|
22
|
+
* credential is dismissed (`wm dismiss-keyguard`); a secure keyguard is `E_DEVICE_LOCKED` (no swipe, no PIN).
|
|
23
|
+
*/
|
|
24
|
+
export declare function preparePhone(session: AdbSession, clock: Pick<Clock, "sleep">, facts: PhoneFacts, log?: Logger): DriverResult<void>;
|
|
25
|
+
export type SettingsGuard = {
|
|
26
|
+
/** before the first rotation write: save the current rotation settings (once); an error means do not write */
|
|
27
|
+
beforeRotate(): DriverResult<void>;
|
|
28
|
+
/** before the first `cmd uimode night` write: save the current mode (once) */
|
|
29
|
+
beforeAppearance(): DriverResult<void>;
|
|
30
|
+
/** put everything saved back (once); a failed restore leaves the originals in the state file for the next driver */
|
|
31
|
+
restore(): Promise<void>;
|
|
32
|
+
};
|
|
33
|
+
export type SettingsGuardDeps = {
|
|
34
|
+
session: AdbSession;
|
|
35
|
+
serial: string;
|
|
36
|
+
sdk: number | undefined;
|
|
37
|
+
/** originals a process that died left in the state file */
|
|
38
|
+
pending: Pick<AndroidDeviceState, "rotation" | "uimode">;
|
|
39
|
+
state?: AndroidStateStore;
|
|
40
|
+
log?: Logger;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Rotation (`accelerometer_rotation`, `user_rotation`) and dark mode (`cmd uimode night`) are settings that
|
|
44
|
+
* outlive the driver. On a phone the originals are read and written to the state file BEFORE the first change
|
|
45
|
+
* (so a crash cannot lose them) and restored when the driver closes, the way animation scales are.
|
|
46
|
+
*/
|
|
47
|
+
export declare function createSettingsGuard(deps: SettingsGuardDeps): Promise<SettingsGuard>;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { PermissionName, PermissionState } from "../../../../core/src/conditions/conditions.types";
|
|
2
|
+
import type { Driver } from "../driver.types";
|
|
3
|
+
import type { AdbSession } from "./adb-session";
|
|
4
|
+
/** Permission name to the manifest permissions it covers; a name absent here is skipped with the reason in `ANDROID_NO_PERMISSION`. */
|
|
5
|
+
export declare const ANDROID_PERMISSIONS: Partial<Record<PermissionName, readonly string[]>>;
|
|
6
|
+
export declare const ANDROID_NO_PERMISSION: Partial<Record<PermissionName, string>>;
|
|
7
|
+
export declare const permissionArgv: (state: PermissionState, pkg: string, permission: string) => string[];
|
|
8
|
+
/** `unset` also clears the flags a user choice sets, so the app is asked again. */
|
|
9
|
+
export declare const clearPermissionFlagsArgv: (pkg: string, permission: string) => string[];
|
|
10
|
+
export declare const setTimezoneArgv: (tz: string) => string[];
|
|
11
|
+
export declare const TIMEZONE_PROP_ARGV: readonly string[];
|
|
12
|
+
export declare const setAppLocaleArgv: (pkg: string, tag: string | null) => string[];
|
|
13
|
+
export declare const getAppLocaleArgv: (pkg: string) => string[];
|
|
14
|
+
export declare const settingGetArgv: (table: "system" | "secure", key: string) => string[];
|
|
15
|
+
export declare const BOLD_TEXT_ADJUSTMENT = 300;
|
|
16
|
+
export declare const trimMemoryArgv: (pkg: string) => string[];
|
|
17
|
+
export declare const BATTERY_RESET_ARGV: readonly string[];
|
|
18
|
+
export declare function batteryArgvs(op: {
|
|
19
|
+
level?: number;
|
|
20
|
+
charging?: boolean;
|
|
21
|
+
}): string[][];
|
|
22
|
+
export declare const APP_LOCALES_MIN_SDK = 33;
|
|
23
|
+
export type AndroidSettingsDeps = {
|
|
24
|
+
session: AdbSession;
|
|
25
|
+
serial: string;
|
|
26
|
+
/** the session's app package, used when an op names none */
|
|
27
|
+
appPackage?: string;
|
|
28
|
+
/** wait between a settings write and its read-back (default `Bun.sleep`) */
|
|
29
|
+
sleep?: (ms: number) => Promise<void>;
|
|
30
|
+
/** the host's timezone, restored on `timezone(null)` when no previous one is given (default: `Intl`) */
|
|
31
|
+
hostTimezone?: () => string;
|
|
32
|
+
};
|
|
33
|
+
export type AndroidSettings = Pick<Driver, "permissions" | "timezone" | "locale" | "a11y" | "battery" | "memoryWarning" | "push">;
|
|
34
|
+
export declare function createAndroidSettings(deps: AndroidSettingsDeps): AndroidSettings;
|
|
@@ -21,7 +21,17 @@ export type AndroidDeviceState = {
|
|
|
21
21
|
uiadUnsupported?: string | undefined;
|
|
22
22
|
/** the scales to put back (what they were before warden set them to 0), while warden's zeros are in place */
|
|
23
23
|
animations?: AnimationValues | undefined;
|
|
24
|
+
/** physical phones: the rotation settings to put back, while warden's pinned rotation is in place */
|
|
25
|
+
rotation?: RotationValues | undefined;
|
|
26
|
+
/** physical phones: the `cmd uimode night` mode to put back, while warden's override is in place */
|
|
27
|
+
uimode?: UimodeValue | undefined;
|
|
24
28
|
};
|
|
29
|
+
/** `settings get system` values as printed: a digit, or `null` when never set. */
|
|
30
|
+
export type RotationValues = {
|
|
31
|
+
accelerometer_rotation: string;
|
|
32
|
+
user_rotation: string;
|
|
33
|
+
};
|
|
34
|
+
export type UimodeValue = "yes" | "no" | "auto";
|
|
25
35
|
/** The slice of `node:fs/promises` the store uses (injected in tests). */
|
|
26
36
|
export type StateFs = {
|
|
27
37
|
readFile(path: string): Promise<string>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ClockCondition } from "../../../core/src/conditions/conditions.types";
|
|
2
|
+
import type { Store } from "../../../core/src/store";
|
|
3
|
+
/**
|
|
4
|
+
* Told about every successful `Driver.clock`, so the lease carries the `clock` area and release / gc undo it
|
|
5
|
+
* (COND-1). Drivers never touch the lease store: whoever opens the driver builds this (`leaseClockRecorder`).
|
|
6
|
+
*/
|
|
7
|
+
export type ClockRecorder = (condition: ClockCondition) => void;
|
|
8
|
+
/** `ClockRecorder` for one lease: a mocked clock is recorded, `real` forgets the area. */
|
|
9
|
+
export declare function leaseClockRecorder(store: Store, leaseId: string, now?: () => number): ClockRecorder;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { resolveTextSize } from "../../../core/src/conditions/text-size";
|
|
2
|
+
/** Checks shared by both platforms' device-setting ops; each returns a message, or undefined when fine. */
|
|
3
|
+
export declare function timezoneProblem(tz: string): string | undefined;
|
|
4
|
+
export declare function localeProblem(tag: string): string | undefined;
|
|
5
|
+
export declare function batteryProblem(level: number | undefined): string | undefined;
|
|
6
|
+
export declare const PUSH_MAX_BYTES = 4096;
|
|
7
|
+
export declare function pushProblem(payload: unknown): string | undefined;
|
|
8
|
+
/** `E_BAD_REQUEST` for a problem message, else undefined. */
|
|
9
|
+
export declare const badRequest: (problem: string | undefined) => import("../../../types/src/result").ErrorResult<import("./driver.types").DriverError> | undefined;
|
|
10
|
+
export { resolveTextSize };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ClockCondition, HostFault, LocationCondition, NetworkCondition, PermissionName, PermissionState, TextSize } from "../../../core/src/conditions/conditions.types";
|
|
1
2
|
import type { AsyncResult } from "../../../types/src/result";
|
|
2
3
|
/**
|
|
3
4
|
* The device driver contract every layer of warden-e2e builds on: the spec API (`screen`, `device`,
|
|
@@ -8,7 +9,7 @@ import type { AsyncResult } from "../../../types/src/result";
|
|
|
8
9
|
* Frozen interface — change it only together with `docs/plans/wdn-e2e.md`.
|
|
9
10
|
*/
|
|
10
11
|
export type Platform = "ios" | "android";
|
|
11
|
-
export type DriverErrorCode = "E_HELPER_MISSING" | "E_HELPER_CRASHED" | "E_TIMEOUT" | "E_NOT_BOOTED" | "E_UNKNOWN_DEVICE" | "E_AX_UNAVAILABLE" | "E_APP_UNAVAILABLE" | "E_APP_NOT_RESPONDING" | "E_NO_ELEMENT" | "E_STALE_REF" | "E_UNSUPPORTED" | "E_UNSUPPORTED_TEXT" | "E_HID" | "E_IO" | "E_BAD_REQUEST" | "E_INTERNAL";
|
|
12
|
+
export type DriverErrorCode = "E_HELPER_MISSING" | "E_HELPER_CRASHED" | "E_TIMEOUT" | "E_NOT_BOOTED" | "E_UNKNOWN_DEVICE" | "E_AX_UNAVAILABLE" | "E_APP_UNAVAILABLE" | "E_APP_NOT_RESPONDING" | "E_NO_ELEMENT" | "E_STALE_REF" | "E_UNSUPPORTED" | "E_UNSUPPORTED_TEXT" | "E_HID" | "E_IO" | "E_BAD_REQUEST" | "E_INTERNAL" | "E_DEVICE_LOCKED" | "E_REFUSED" | "E_NETWORK_RELAUNCH";
|
|
12
13
|
export type DriverError = {
|
|
13
14
|
code: DriverErrorCode;
|
|
14
15
|
message: string;
|
|
@@ -114,6 +115,13 @@ export type Orientation = "portrait" | "portraitUpsideDown" | "landscapeLeft" |
|
|
|
114
115
|
export type HardwareButton = "home" | "back" | "lock" | "volumeUp" | "volumeDown" | "shake";
|
|
115
116
|
export type KeyName = "Enter" | "Backspace" | "Tab" | "Space" | "Escape" | "Delete" | "Up" | "Down" | "Left" | "Right";
|
|
116
117
|
export type Modifier = "shift" | "cmd" | "alt" | "ctrl";
|
|
118
|
+
/** What `Driver.biometrics` does: read the enrollment, (un)enroll, or present a matching / non-matching biometric. */
|
|
119
|
+
export type BiometricOp = "status" | "enroll" | "unenroll" | "match" | "fail";
|
|
120
|
+
/** `modality`: what the device scans, iOS Face ID (`face`) or Touch ID and Android (`fingerprint`). */
|
|
121
|
+
export type BiometricState = {
|
|
122
|
+
enrolled: boolean;
|
|
123
|
+
modality: "face" | "fingerprint";
|
|
124
|
+
};
|
|
117
125
|
export type AlertInfo = {
|
|
118
126
|
title?: string;
|
|
119
127
|
message?: string;
|
|
@@ -136,6 +144,19 @@ export type LaunchOptions = {
|
|
|
136
144
|
env?: Record<string, string>;
|
|
137
145
|
/** iOS: `--terminate-running-process`; Android: `am force-stop` first. Default true. */
|
|
138
146
|
relaunch?: boolean;
|
|
147
|
+
/**
|
|
148
|
+
* iOS: load the warden network dylib even though no condition is set yet, so `network()` can take effect later
|
|
149
|
+
* without a relaunch (a suite that declares `conditions.network` launches this way). Conditions set before the
|
|
150
|
+
* launch inject on their own, a plain launch pays nothing. Ignored on Android.
|
|
151
|
+
*/
|
|
152
|
+
inject?: boolean;
|
|
153
|
+
};
|
|
154
|
+
/** Options of `Driver.network`. */
|
|
155
|
+
export type NetworkOptions = {
|
|
156
|
+
/** iOS: the app to check for the dylib (default: the last app this driver launched) */
|
|
157
|
+
bundleId?: string;
|
|
158
|
+
/** iOS: relaunch a running app that lacks the dylib instead of failing with `E_NETWORK_RELAUNCH` */
|
|
159
|
+
relaunch?: boolean;
|
|
139
160
|
};
|
|
140
161
|
export type DriverCapabilities = {
|
|
141
162
|
/** two-finger `pinch` and `rotate` */
|
|
@@ -225,7 +246,161 @@ export interface Driver {
|
|
|
225
246
|
alert(): DriverResult<AlertInfo | null>;
|
|
226
247
|
appearance(set?: "light" | "dark"): DriverResult<"light" | "dark">;
|
|
227
248
|
clipboard(set?: string): DriverResult<string>;
|
|
249
|
+
/**
|
|
250
|
+
* Put the device's network into `op` (offline / online / a preset / custom speed and latency) and return what
|
|
251
|
+
* is now in effect. Android emulator: native, verified through `dumpsys connectivity`. iOS: per app, the app
|
|
252
|
+
* is launched with a dylib that routes it through the lease proxy (`E_NETWORK_RELAUNCH` when it runs without).
|
|
253
|
+
* `flap` alternates the base with offline in this process (`conditions/scheduler.ts`) until the next network op
|
|
254
|
+
* or `close()` (Android; iOS goes through the scheduler directly).
|
|
255
|
+
*/
|
|
256
|
+
network(op: NetworkCondition, opts?: NetworkOptions): DriverResult<NetworkState>;
|
|
257
|
+
/**
|
|
258
|
+
* Replace the per-host faults (status / reset / delay / blackhole by host glob). Both platforms route the app
|
|
259
|
+
* through the lease's shaping proxy for this. `notes` carries what the proxy cannot do as written (an https://
|
|
260
|
+
* request matching a `status` fault is reset, the proxy does not decrypt TLS). `[]` clears them.
|
|
261
|
+
*/
|
|
262
|
+
networkFaults(faults: readonly HostFault[]): DriverResult<{
|
|
263
|
+
notes: readonly string[];
|
|
264
|
+
}>;
|
|
265
|
+
/**
|
|
266
|
+
* Simulated location: a point, a route (waypoints or GPX, played at `speedMps`), a bundled scenario, or clear.
|
|
267
|
+
* iOS: `simctl location` (the sim plays routes itself, no loop). Android: emulator `geo fix`, routes ticked by
|
|
268
|
+
* warden until the next op or `close()`; `clear` keeps the last fix. Physical devices: `E_UNSUPPORTED`.
|
|
269
|
+
*/
|
|
270
|
+
location(op: LocationCondition): DriverResult<void>;
|
|
271
|
+
/**
|
|
272
|
+
* Mock the device's wall clock (monotonic clocks are never touched). `offset` shifts it from the real time,
|
|
273
|
+
* `fixed` sets it to an instant and lets it run, `frozen` holds it there, `real` puts it back. Android emulator:
|
|
274
|
+
* system time (`settings put global auto_time 0` + `cmd alarm set-time`), device-wide; no `frozen`
|
|
275
|
+
* (`E_UNSUPPORTED`). iOS: the injected dylib shifts or holds the clocks of the injected app only (live, no
|
|
276
|
+
* relaunch); an app without the dylib gets it from the next launch (`appliesOnLaunch`), or from a relaunch with
|
|
277
|
+
* `relaunch: true`. Large offsets break TLS validity (HTTPS fails once the clock passes a certificate's dates).
|
|
278
|
+
*/
|
|
279
|
+
clock(op: ClockCondition, opts?: NetworkOptions): DriverResult<ClockState>;
|
|
280
|
+
/**
|
|
281
|
+
* Set runtime permissions of an app, per name (`location`, `camera`, `photos`, `notifications`, ...): `granted`,
|
|
282
|
+
* `denied` (revoked) or `unset` (back to asking). Names a platform has no service for are listed in `skipped`;
|
|
283
|
+
* when none applies it is `E_UNSUPPORTED`. iOS: `simctl privacy` (terminates a running app). Android: `pm
|
|
284
|
+
* grant|revoke` of the permissions the app declares. Apply before the first launch.
|
|
285
|
+
*/
|
|
286
|
+
permissions(grants: PermissionGrants, opts?: SettingOptions): DriverResult<PermissionsState>;
|
|
287
|
+
/**
|
|
288
|
+
* Device timezone (IANA name); `null` puts it back. Android: `cmd alarm set-timezone`, device-wide, live. iOS: a
|
|
289
|
+
* simulator has none per device, so it is staged and the app launched next gets it as `TZ` (`appliesOnLaunch`).
|
|
290
|
+
* `previous` is the Android timezone to restore on `null` (default: the host's).
|
|
291
|
+
*/
|
|
292
|
+
timezone(tz: string | null, opts?: {
|
|
293
|
+
previous?: string;
|
|
294
|
+
}): DriverResult<TimezoneState>;
|
|
295
|
+
/**
|
|
296
|
+
* Locale of an app (BCP 47, `fr-FR`); `null` puts it back. Android 13+: per-app locale (`cmd locale
|
|
297
|
+
* set-app-locales`), live. iOS: staged for the next launch as `-AppleLanguages` / `-AppleLocale`.
|
|
298
|
+
*/
|
|
299
|
+
locale(tag: string | null, opts?: SettingOptions): DriverResult<LocaleState>;
|
|
300
|
+
/**
|
|
301
|
+
* Accessibility settings: text size, bold text, contrast, reduce motion; `null` puts them back. iOS: `simctl ui
|
|
302
|
+
* content_size` / `increase_contrast` (no bold text or reduce motion: `skipped`). Android: `font_scale`,
|
|
303
|
+
* `font_weight_adjustment` (no contrast or reduce motion: `skipped`).
|
|
304
|
+
*/
|
|
305
|
+
a11y(op: A11yOp | null): DriverResult<A11yState>;
|
|
306
|
+
/**
|
|
307
|
+
* Battery level and charging; `null` puts the real battery back. Android emulator: `dumpsys battery`, seen by the
|
|
308
|
+
* OS and the app. iOS: status bar override only (`cosmetic: true`): the app still reads the real battery.
|
|
309
|
+
*/
|
|
310
|
+
battery(op: BatteryOp | null): DriverResult<BatteryState>;
|
|
311
|
+
/** Android: `am send-trim-memory RUNNING_CRITICAL` to the running app. iOS: `E_UNSUPPORTED` (no way verified to reach an app). */
|
|
312
|
+
memoryWarning(opts?: SettingOptions): DriverResult<void>;
|
|
313
|
+
/** iOS: `simctl push` of an APNs payload (needs `aps`) to the app. Android: `E_UNSUPPORTED`. */
|
|
314
|
+
push(payload: PushPayload, opts?: SettingOptions): DriverResult<void>;
|
|
315
|
+
/** Simulated biometrics (simulators / emulators only, else `E_UNSUPPORTED`); answers the state after the op. */
|
|
316
|
+
biometrics(op: BiometricOp): DriverResult<BiometricState>;
|
|
228
317
|
readonly app: AppCommands;
|
|
229
318
|
close(): Promise<void>;
|
|
230
319
|
}
|
|
320
|
+
/** Parts of a network condition no native mechanism does; the per-lease shaping proxy (COND-4) carries them. */
|
|
321
|
+
export type NetworkProxyFeature = "loss";
|
|
322
|
+
/**
|
|
323
|
+
* What `Driver.network` left on the device. `online` is what the OS reports (verified, not assumed);
|
|
324
|
+
* `needsProxy` lists the asked-for parts that were NOT applied because they need the shaping proxy.
|
|
325
|
+
*/
|
|
326
|
+
export type NetworkState = {
|
|
327
|
+
online: boolean;
|
|
328
|
+
condition: NetworkCondition;
|
|
329
|
+
needsProxy: readonly NetworkProxyFeature[];
|
|
330
|
+
/**
|
|
331
|
+
* iOS: the condition is staged (proxy configured, state written) but no running app carries the dylib yet; it
|
|
332
|
+
* reaches the app launched next. Absent = in effect now.
|
|
333
|
+
*/
|
|
334
|
+
appliesOnLaunch?: true;
|
|
335
|
+
};
|
|
336
|
+
/** The app a setting is for (default: the last app this driver launched, else the one the session names). */
|
|
337
|
+
export type SettingOptions = {
|
|
338
|
+
bundleId?: string;
|
|
339
|
+
};
|
|
340
|
+
export type PermissionGrants = Partial<Record<PermissionName, PermissionState>>;
|
|
341
|
+
export type PermissionsState = {
|
|
342
|
+
bundleId: string;
|
|
343
|
+
applied: readonly {
|
|
344
|
+
permission: PermissionName;
|
|
345
|
+
state: PermissionState;
|
|
346
|
+
}[];
|
|
347
|
+
/** names this platform has no service for, with why */
|
|
348
|
+
skipped: readonly {
|
|
349
|
+
permission: PermissionName;
|
|
350
|
+
reason: string;
|
|
351
|
+
}[];
|
|
352
|
+
};
|
|
353
|
+
export type TimezoneState = {
|
|
354
|
+
/** the timezone in effect (or staged), absent after a reset on iOS */
|
|
355
|
+
timezone?: string;
|
|
356
|
+
/** Android: the timezone before this change */
|
|
357
|
+
previous?: string;
|
|
358
|
+
/** iOS: staged for the next launch */
|
|
359
|
+
appliesOnLaunch?: true;
|
|
360
|
+
};
|
|
361
|
+
export type LocaleState = {
|
|
362
|
+
locale?: string;
|
|
363
|
+
bundleId?: string;
|
|
364
|
+
appliesOnLaunch?: true;
|
|
365
|
+
};
|
|
366
|
+
export type A11yOp = {
|
|
367
|
+
textSize?: TextSize;
|
|
368
|
+
boldText?: boolean;
|
|
369
|
+
contrast?: "increased" | "normal";
|
|
370
|
+
reduceMotion?: boolean;
|
|
371
|
+
};
|
|
372
|
+
export type A11yState = {
|
|
373
|
+
applied: readonly string[];
|
|
374
|
+
/** asked for but this platform has no mechanism that works, with why */
|
|
375
|
+
skipped: readonly {
|
|
376
|
+
setting: string;
|
|
377
|
+
reason: string;
|
|
378
|
+
}[];
|
|
379
|
+
/** the Android font scale was clamped to the platform's range */
|
|
380
|
+
notes: readonly string[];
|
|
381
|
+
};
|
|
382
|
+
export type BatteryOp = {
|
|
383
|
+
level?: number;
|
|
384
|
+
charging?: boolean;
|
|
385
|
+
};
|
|
386
|
+
export type BatteryState = {
|
|
387
|
+
level?: number;
|
|
388
|
+
charging?: boolean;
|
|
389
|
+
cosmetic: boolean;
|
|
390
|
+
};
|
|
391
|
+
/** An APNs payload: an object with an `aps` key (4096 bytes at most). */
|
|
392
|
+
export type PushPayload = {
|
|
393
|
+
aps: Record<string, unknown>;
|
|
394
|
+
} & Record<string, unknown>;
|
|
395
|
+
/** What `Driver.clock` left on the device. */
|
|
396
|
+
export type ClockState = {
|
|
397
|
+
condition: ClockCondition;
|
|
398
|
+
/** wall clock minus the host's, ms (0 for `real` and `frozen`) */
|
|
399
|
+
offsetMs: number;
|
|
400
|
+
frozen: boolean;
|
|
401
|
+
/** what the device's wall clock read when the op returned, ms since the epoch */
|
|
402
|
+
nowMs: number;
|
|
403
|
+
/** iOS: staged for the next launch of the app; absent = in effect now */
|
|
404
|
+
appliesOnLaunch?: true;
|
|
405
|
+
};
|
|
231
406
|
export type Logger = (line: string) => void;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { Exec } from "../../../../../core/src/exec";
|
|
2
|
+
import type { E2eEnv } from "../../../env";
|
|
3
|
+
import type { ProbesFrontmost, ReadsAlertOn } from "../../alerts";
|
|
4
|
+
import type { Driver, DriverResult, Logger } from "../../driver.types";
|
|
5
|
+
import { type SimdClient } from "../simd-client";
|
|
6
|
+
import { type Devicectl } from "./devicectl";
|
|
7
|
+
/**
|
|
8
|
+
* The `Driver` for a physical iPhone / iPad: input, accessibility and app lifecycle through `warden-runner` (the
|
|
9
|
+
* XCUITest server `warden __ios-runner-host` keeps running on the phone, spoken to over the host's unix socket in the
|
|
10
|
+
* `warden-simd` protocol, so the client, normalizer and alert reader are simd's), and `xcrun devicectl` for what the
|
|
11
|
+
* runner cannot do: install / uninstall / probe, orientation changes, appearance and pasteboard reads, the screenshot
|
|
12
|
+
* fallback. There is no select-all or paste (a field is cleared with counted Backspace), no native settle (snapshot
|
|
13
|
+
* hashes), no multi-touch, and nothing that changes a phone beyond the app under test: an uninstall or data wipe of
|
|
14
|
+
* a bundle warden did not install is `E_REFUSED`.
|
|
15
|
+
*/
|
|
16
|
+
export type IosDeviceEnv = Pick<E2eEnv, "iosRunnerSocket" | "appPath" | "bundleId">;
|
|
17
|
+
export type OpenIosDeviceDriverOptions = {
|
|
18
|
+
udid: string;
|
|
19
|
+
exec: Exec;
|
|
20
|
+
env?: Partial<IosDeviceEnv>;
|
|
21
|
+
/** the runner host's socket (wins over `env.iosRunnerSocket`) */
|
|
22
|
+
socketPath?: string;
|
|
23
|
+
/** an already connected client: used instead of connecting; the driver owns it from here */
|
|
24
|
+
client?: SimdClient;
|
|
25
|
+
/** `WARDEN_BUNDLE_ID`: the app under test, always allowed to be uninstalled / wiped */
|
|
26
|
+
bundleId?: string;
|
|
27
|
+
/** `WARDEN_APP_PATH`: what `clearData` reinstalls */
|
|
28
|
+
appPath?: string;
|
|
29
|
+
/** bundles warden installed on this phone earlier (the `installs` table) */
|
|
30
|
+
ownedBundles?: readonly string[];
|
|
31
|
+
log?: Logger;
|
|
32
|
+
now?: () => number;
|
|
33
|
+
sleep?: (ms: number) => Promise<void>;
|
|
34
|
+
defaultTimeoutMs?: number;
|
|
35
|
+
/** test seams */
|
|
36
|
+
devicectl?: Devicectl;
|
|
37
|
+
writeFile?: (path: string, bytes: Uint8Array) => Promise<void>;
|
|
38
|
+
};
|
|
39
|
+
export type IosDeviceDriver = Driver & ReadsAlertOn & ProbesFrontmost;
|
|
40
|
+
export declare function createIosDeviceDriver(client: SimdClient, devicectl: Devicectl, opts: Omit<OpenIosDeviceDriverOptions, "client" | "exec" | "devicectl" | "env" | "socketPath"> & {
|
|
41
|
+
now: () => number;
|
|
42
|
+
sleep: (ms: number) => Promise<void>;
|
|
43
|
+
write: (path: string, bytes: Uint8Array) => Promise<void>;
|
|
44
|
+
}): IosDeviceDriver;
|
|
45
|
+
/**
|
|
46
|
+
* Open the driver for the phone `udid` against its runner host's socket (`socketPath` / `env.iosRunnerSocket` /
|
|
47
|
+
* `client`). `E_HELPER_MISSING` without any: the host is started by `warden ui` / `warden e2e`, never by the driver.
|
|
48
|
+
*/
|
|
49
|
+
export declare function openIosDeviceDriver(o: OpenIosDeviceDriverOptions): DriverResult<Driver>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { Exec, ExecResult } from "../../../../../core/src/exec";
|
|
2
|
+
import type { DriverError, DriverResult, Orientation } from "../../driver.types";
|
|
3
|
+
export declare const installArgv: (udid: string, appPath: string, json: string) => string[];
|
|
4
|
+
export declare const uninstallArgv: (udid: string, bundleId: string, json: string) => string[];
|
|
5
|
+
/** `--include-default-apps`: system apps (Settings) are listed too, so a probe of one is not a false negative. */
|
|
6
|
+
export declare const appsArgv: (udid: string, bundleId: string, json: string) => string[];
|
|
7
|
+
export declare const appearanceArgv: (udid: string, json: string) => string[];
|
|
8
|
+
export declare const orientationArgv: (udid: string, orientation: Orientation) => string[];
|
|
9
|
+
export declare const screenshotArgv: (udid: string, destination: string) => string[];
|
|
10
|
+
export declare const pasteArgv: (udid: string) => string[];
|
|
11
|
+
export type DevicectlOptions = {
|
|
12
|
+
udid: string;
|
|
13
|
+
exec: Exec;
|
|
14
|
+
/** a fresh private directory for `--json-output` files and screenshots */
|
|
15
|
+
makeDir?: () => Promise<string>;
|
|
16
|
+
read?: (path: string) => Promise<string>;
|
|
17
|
+
readBytes?: (path: string) => Promise<Uint8Array>;
|
|
18
|
+
remove?: (dir: string) => Promise<void>;
|
|
19
|
+
};
|
|
20
|
+
export type Devicectl = {
|
|
21
|
+
install(appPath: string): DriverResult<{
|
|
22
|
+
bundleIds: string[];
|
|
23
|
+
}>;
|
|
24
|
+
uninstall(bundleId: string): DriverResult<void>;
|
|
25
|
+
isInstalled(bundleId: string): DriverResult<boolean>;
|
|
26
|
+
appearance(): DriverResult<"light" | "dark">;
|
|
27
|
+
setOrientation(orientation: Orientation): DriverResult<void>;
|
|
28
|
+
/** to `path` when given, else the PNG bytes */
|
|
29
|
+
screenshot(path?: string): DriverResult<{
|
|
30
|
+
path?: string;
|
|
31
|
+
png?: Uint8Array;
|
|
32
|
+
}>;
|
|
33
|
+
pasteboardCopy(text: string): DriverResult<void>;
|
|
34
|
+
pasteboardPaste(): DriverResult<string>;
|
|
35
|
+
};
|
|
36
|
+
/** A failed devicectl call: a locked phone cannot be retried, anything else is an I/O failure with devicectl's own words. */
|
|
37
|
+
export declare function devicectlError(what: string, result: ExecResult): DriverError;
|
|
38
|
+
export declare function createDevicectl(o: DevicectlOptions): Devicectl;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* What the `warden-runner` (XCUITest) wire adds to the `warden-simd` protocol of `simd-protocol.ts` (the shared
|
|
4
|
+
* schemas and `SimdClient` are reused unchanged): the `appState` op, and the facts about the runner the driver acts on.
|
|
5
|
+
* The runner's own contract is `native/ios-runner/AGENTS.md`.
|
|
6
|
+
*/
|
|
7
|
+
export declare const appStateSchema: z.ZodEnum<{
|
|
8
|
+
unknown: "unknown";
|
|
9
|
+
notRunning: "notRunning";
|
|
10
|
+
runningBackgroundSuspended: "runningBackgroundSuspended";
|
|
11
|
+
runningBackground: "runningBackground";
|
|
12
|
+
runningForeground: "runningForeground";
|
|
13
|
+
}>;
|
|
14
|
+
export type AppState = z.infer<typeof appStateSchema>;
|
|
15
|
+
export declare const appStateDataSchema: z.ZodObject<{
|
|
16
|
+
state: z.ZodString;
|
|
17
|
+
}, z.core.$loose>;
|
|
18
|
+
/** `appState` reply data, or undefined when it is not shaped like one. */
|
|
19
|
+
export declare function parseAppState(data: unknown): AppState | undefined;
|
|
20
|
+
/** The app is alive in some form (a suspended background app still has its process). */
|
|
21
|
+
export declare function isRunning(state: AppState): boolean;
|
|
@@ -23,6 +23,21 @@ export declare function stopResidentHelper(deps: HidRecoveryDeps, socketPath: st
|
|
|
23
23
|
* the next call then fails `E_NOT_BOOTED`.
|
|
24
24
|
*/
|
|
25
25
|
export declare function isHidLost(error: unknown): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* The error of a call on a physical iPhone whose runner host is gone (the host or the runner exited, or the socket
|
|
28
|
+
* closed): the driver's `E_HELPER_CRASHED`, however the error was wrapped.
|
|
29
|
+
*/
|
|
30
|
+
export declare function isRunnerLost(error: unknown): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Whether a failed attempt is worth one recovery and the test running again: a simulator that lost its HID connection
|
|
33
|
+
* (reboot), or a physical iPhone that lost its runner host (restart the host; the phone is never rebooted).
|
|
34
|
+
*/
|
|
35
|
+
export declare function isRecoverableLoss(error: unknown, deviceKind: "physical" | undefined): boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Restarts a physical iPhone's runner host (`WARDEN_IOS_RUNNER_RESTART`, `{udid}` replaced): stop the host, start a
|
|
38
|
+
* new one on the same socket and wait until it answers. Never touches the phone itself (no reboot, no `simctl`).
|
|
39
|
+
*/
|
|
40
|
+
export declare function restartRunnerHost(deps: HidRecoveryDeps, argv: readonly string[] | undefined, udid: string): AsyncResult<void>;
|
|
26
41
|
/**
|
|
27
42
|
* Shuts the sim down and boots it again: `bootstatus -b` boots a shut-down sim itself and waits for it, and opens
|
|
28
43
|
* no Simulator.app window (the same windowless path warden claims use). The caller reconnects the helper and
|