@delacour/warden-e2e 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/LICENSE +21 -0
  2. package/chunk-e6pn5ygy.mjs +5832 -0
  3. package/index.d.ts +1 -0
  4. package/index.mjs +1047 -0
  5. package/package.json +49 -4
  6. package/preload.d.ts +1 -0
  7. package/preload.mjs +4788 -0
  8. package/types/core/src/allocate.d.ts +23 -0
  9. package/types/core/src/builds/app-option.schema.d.ts +37 -0
  10. package/types/core/src/builds/install.d.ts +95 -0
  11. package/types/core/src/builds/resolve.d.ts +74 -0
  12. package/types/core/src/config.defaults.d.ts +25 -0
  13. package/types/core/src/exec.d.ts +18 -0
  14. package/types/core/src/liveness.d.ts +12 -0
  15. package/types/core/src/providers/android.d.ts +132 -0
  16. package/types/core/src/providers/health.d.ts +45 -0
  17. package/types/core/src/providers/provider.types.d.ts +56 -0
  18. package/types/core/src/store.d.ts +105 -0
  19. package/types/core/src/types.d.ts +126 -0
  20. package/types/e2e/src/alert-buttons.d.ts +41 -0
  21. package/types/e2e/src/alert-reader.d.ts +25 -0
  22. package/types/e2e/src/app.d.ts +62 -0
  23. package/types/e2e/src/device-devtools.d.ts +26 -0
  24. package/types/e2e/src/device-link.d.ts +43 -0
  25. package/types/e2e/src/device.d.ts +90 -0
  26. package/types/e2e/src/devtools/cdp.d.ts +65 -0
  27. package/types/e2e/src/devtools/console.d.ts +59 -0
  28. package/types/e2e/src/devtools/devtools.types.d.ts +169 -0
  29. package/types/e2e/src/devtools/facet.d.ts +28 -0
  30. package/types/e2e/src/devtools/inspector.d.ts +48 -0
  31. package/types/e2e/src/devtools/jsonl.d.ts +4 -0
  32. package/types/e2e/src/devtools/native-log.d.ts +50 -0
  33. package/types/e2e/src/devtools/network.d.ts +33 -0
  34. package/types/e2e/src/devtools/network.script.d.ts +24 -0
  35. package/types/e2e/src/devtools/session.d.ts +82 -0
  36. package/types/e2e/src/devtools/tree.d.ts +23 -0
  37. package/types/e2e/src/devtools/tree.script.d.ts +33 -0
  38. package/types/e2e/src/driver/alerts.d.ts +29 -0
  39. package/types/e2e/src/driver/android/adb-session.d.ts +52 -0
  40. package/types/e2e/src/driver/android/android-alert.d.ts +4 -0
  41. package/types/e2e/src/driver/android/android-animations.d.ts +29 -0
  42. package/types/e2e/src/driver/android/android-app.d.ts +32 -0
  43. package/types/e2e/src/driver/android/android-argv.d.ts +122 -0
  44. package/types/e2e/src/driver/android/android-device.d.ts +44 -0
  45. package/types/e2e/src/driver/android/android-driver.d.ts +60 -0
  46. package/types/e2e/src/driver/android/android-dump.d.ts +46 -0
  47. package/types/e2e/src/driver/android/android-state.d.ts +69 -0
  48. package/types/e2e/src/driver/android/uiad-backend.d.ts +63 -0
  49. package/types/e2e/src/driver/android/uiad-client.d.ts +60 -0
  50. package/types/e2e/src/driver/android/uiad-device.d.ts +70 -0
  51. package/types/e2e/src/driver/android/uiad-dex.generated.d.ts +8 -0
  52. package/types/e2e/src/driver/android/uiad-ops.d.ts +62 -0
  53. package/types/e2e/src/driver/android/uiad-protocol.d.ts +209 -0
  54. package/types/e2e/src/driver/android/uiautomator.d.ts +33 -0
  55. package/types/e2e/src/driver/driver.types.d.ts +231 -0
  56. package/types/e2e/src/driver/errors.d.ts +13 -0
  57. package/types/e2e/src/driver/gesture-geometry.d.ts +48 -0
  58. package/types/e2e/src/driver/ios/hid-recovery.d.ts +36 -0
  59. package/types/e2e/src/driver/ios/ios-driver.d.ts +63 -0
  60. package/types/e2e/src/driver/ios/ios-session.d.ts +30 -0
  61. package/types/e2e/src/driver/ios/locate-helper.d.ts +38 -0
  62. package/types/e2e/src/driver/ios/normalize.d.ts +11 -0
  63. package/types/e2e/src/driver/ios/simctl.d.ts +102 -0
  64. package/types/e2e/src/driver/ios/simd-client.d.ts +68 -0
  65. package/types/e2e/src/driver/ios/simd-protocol.d.ts +433 -0
  66. package/types/e2e/src/driver/ios/simd-transport.d.ts +43 -0
  67. package/types/e2e/src/driver/ios/type-budget.d.ts +6 -0
  68. package/types/e2e/src/driver/observation-cache.d.ts +66 -0
  69. package/types/e2e/src/driver/open.d.ts +29 -0
  70. package/types/e2e/src/driver/settle.d.ts +43 -0
  71. package/types/e2e/src/driver/span-detail.d.ts +12 -0
  72. package/types/e2e/src/driver/traced-driver.d.ts +14 -0
  73. package/types/e2e/src/driver/typing.d.ts +48 -0
  74. package/types/e2e/src/driver/unwrap.d.ts +13 -0
  75. package/types/e2e/src/env.d.ts +78 -0
  76. package/types/e2e/src/errors.d.ts +26 -0
  77. package/types/e2e/src/expect/expect.d.ts +27 -0
  78. package/types/e2e/src/expect/matchers.d.ts +72 -0
  79. package/types/e2e/src/fixture-session.d.ts +38 -0
  80. package/types/e2e/src/fixtures.d.ts +14 -0
  81. package/types/e2e/src/index.d.ts +11 -0
  82. package/types/e2e/src/perf/budgets.d.ts +33 -0
  83. package/types/e2e/src/perf/device-perf.d.ts +45 -0
  84. package/types/e2e/src/perf/perf.types.d.ts +59 -0
  85. package/types/e2e/src/preload.d.ts +29 -0
  86. package/types/e2e/src/runner/artifacts.d.ts +13 -0
  87. package/types/e2e/src/runner/attempt.d.ts +63 -0
  88. package/types/e2e/src/runner/callsite.d.ts +2 -0
  89. package/types/e2e/src/runner/failure.d.ts +40 -0
  90. package/types/e2e/src/runner/fixtures.types.d.ts +60 -0
  91. package/types/e2e/src/runner/results.d.ts +48 -0
  92. package/types/e2e/src/runner/scope.d.ts +64 -0
  93. package/types/e2e/src/runner/span.d.ts +40 -0
  94. package/types/e2e/src/runner/step.d.ts +7 -0
  95. package/types/e2e/src/runner/test.d.ts +59 -0
  96. package/types/e2e/src/runner/trace.d.ts +87 -0
  97. package/types/e2e/src/screen/actions.d.ts +93 -0
  98. package/types/e2e/src/screen/context.d.ts +134 -0
  99. package/types/e2e/src/screen/field.d.ts +121 -0
  100. package/types/e2e/src/screen/locator.d.ts +233 -0
  101. package/types/e2e/src/screen/nodes.d.ts +53 -0
  102. package/types/e2e/src/screen/poll.d.ts +57 -0
  103. package/types/e2e/src/screen/queries.d.ts +34 -0
  104. package/types/e2e/src/screen/query.d.ts +60 -0
  105. package/types/e2e/src/screen/react-locator.d.ts +30 -0
  106. package/types/e2e/src/screen/render.d.ts +42 -0
  107. package/types/e2e/src/screen/roles.d.ts +24 -0
  108. package/types/e2e/src/screen/screen.d.ts +61 -0
  109. package/types/e2e/src/screen/selector.d.ts +38 -0
  110. package/types/e2e/src/screen/text.d.ts +13 -0
  111. package/types/e2e/src/session.d.ts +124 -0
  112. package/types/e2e/src/traced-device.d.ts +9 -0
  113. package/types/e2e/src/version.d.ts +7 -0
  114. package/types/types/src/result.d.ts +12 -0
  115. package/README.md +0 -3
@@ -0,0 +1,48 @@
1
+ import type { Result } from "../../../types/src/result";
2
+ import type { DriverError, GestureOptions, Point, TouchPath, TwoFingerOptions } from "./driver.types";
3
+ /**
4
+ * Touch-path geometry shared by both drivers: `gesture`, `pinch` and `rotate` all become a `GesturePlan`, one
5
+ * frame per ~16 ms on a shared timeline, which `warden-simd` (`gesture` op) and `warden-uiad` (`gesture` op)
6
+ * only replay. Fingers go down together on the first frame and lift together after the last one (the iOS
7
+ * transports carry one phase for both contacts), so every path has the same frame count.
8
+ */
9
+ export declare const GESTURE_FRAME_MS = 16;
10
+ export declare const GESTURE_MAX_FRAMES = 1000;
11
+ export declare const GESTURE_MAX_MS = 60000;
12
+ export declare const GESTURE_MAX_POINTERS = 2;
13
+ export declare const DEFAULT_GESTURE_MS = 300;
14
+ export declare const DEFAULT_PINCH_MS = 400;
15
+ export declare const DEFAULT_ROTATE_MS = 500;
16
+ /** finger distance from the centre: points on iOS; Android multiplies by the screen scale */
17
+ export declare const DEFAULT_RADIUS = 100;
18
+ /** two-finger gestures rest at their end before lifting, so recognisers read ~0 velocity */
19
+ export declare const TWO_FINGER_RELEASE_MS = 50;
20
+ /** What the native servers replay: frame `k` of every path is `holdMs + k * frameMs` after touch-down. */
21
+ export type GesturePlan = {
22
+ paths: Point[][];
23
+ frameMs: number;
24
+ holdMs: number;
25
+ releaseMs: number;
26
+ totalMs: number;
27
+ };
28
+ export type TwoFingerSpec = {
29
+ radius: number;
30
+ angle?: number;
31
+ scale?: number;
32
+ degrees?: number;
33
+ };
34
+ /**
35
+ * Validates 1–2 finger paths and resamples them onto one timeline. Bad input is `E_BAD_REQUEST`; more fingers
36
+ * than the driver takes is `E_UNSUPPORTED`.
37
+ */
38
+ export declare function planGesture(paths: readonly TouchPath[], opts: GestureOptions | undefined, maxPointers: 0 | 1 | 2): Result<GesturePlan, DriverError>;
39
+ /**
40
+ * Two fingers `radius` either side of `center` on an axis at `angle` degrees (0 = horizontal; finger 1 on the
41
+ * negative side), spreading to `radius * scale` and turning `degrees` clockwise (screen space, y down) over
42
+ * `durationMs`. A turn is sampled every few degrees so the fingers follow the arc.
43
+ */
44
+ export declare function twoFingerPaths(center: Point, spec: TwoFingerSpec, durationMs: number): [TouchPath, TouchPath];
45
+ /** Two fingers spreading (`scale` > 1) or closing (< 1) around `center`. */
46
+ export declare function pinchPlan(center: Point, scale: number, opts: TwoFingerOptions | undefined, defaultRadius: number): Result<GesturePlan, DriverError>;
47
+ /** Two fingers turning `degrees` around `center`; positive is clockwise on screen. */
48
+ export declare function rotatePlan(center: Point, degrees: number, opts: TwoFingerOptions | undefined, defaultRadius: number): Result<GesturePlan, DriverError>;
@@ -0,0 +1,36 @@
1
+ import type { Exec } from "../../../../core/src/exec";
2
+ import { type AsyncResult } from "../../../../types/src/result";
3
+ import { connectSimd } from "./simd-client";
4
+ export type HidRecoveryDeps = {
5
+ exec: Exec;
6
+ log?: (line: string) => void;
7
+ /** attach to the resident helper's socket (default {@link connectSimd}) */
8
+ connect?: typeof connectSimd;
9
+ /** whether the socket file is still there (default: `existsSync`) */
10
+ socketExists?: (path: string) => boolean;
11
+ sleep?: (ms: number) => Promise<void>;
12
+ };
13
+ /**
14
+ * Stops the resident `warden-simd` daemon on `socketPath` (the warm one `warden e2e` started): it is bound to the
15
+ * boot that just ended, and the reopened driver would otherwise attach to it and hit `E_HID` again. Asks it to
16
+ * `shutdown` and waits for the socket to go; with no daemon there (a stdio helper dies with its driver) nothing
17
+ * is done. The next driver open spawns a helper on the new boot.
18
+ */
19
+ export declare function stopResidentHelper(deps: HidRecoveryDeps, socketPath: string): AsyncResult<"stopped" | "none">;
20
+ /**
21
+ * The error of a tap/swipe/type on a sim whose HID connection is gone (wrapped as `ACTION_FAILED` or `DRIVER`), or
22
+ * of a helper that finds the sim shut down: quitting Device Hub normally shuts down every booted sim (DHW-0 V5), and
23
+ * the next call then fails `E_NOT_BOOTED`.
24
+ */
25
+ export declare function isHidLost(error: unknown): boolean;
26
+ /**
27
+ * Shuts the sim down and boots it again: `bootstatus -b` boots a shut-down sim itself and waits for it, and opens
28
+ * no Simulator.app window (the same windowless path warden claims use). The caller reconnects the helper and
29
+ * relaunches the app.
30
+ */
31
+ export declare function rebootSim(deps: HidRecoveryDeps, udid: string): AsyncResult<void>;
32
+ /**
33
+ * Runs `WARDEN_AFTER_REBOOT` (`{udid}` replaced) once the sim is back — `warden` uses it to reopen the sim's Device
34
+ * Hub window and tile again. Best effort: a failure is logged, never raised, and never fails the test.
35
+ */
36
+ export declare function runAfterReboot(deps: HidRecoveryDeps, argv: readonly string[] | undefined, udid: string): Promise<void>;
@@ -0,0 +1,63 @@
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, KeyName, Logger } from "../driver.types";
5
+ import type { SelectsAll } from "../typing";
6
+ import { type SwappableClient } from "./ios-session";
7
+ import { type Simctl } from "./simctl";
8
+ import { type SimdClient } from "./simd-client";
9
+ import type { SimdSpawn } from "./simd-transport";
10
+ /**
11
+ * The iOS `Driver`: input and AX through `warden-simd` (socket when `WARDEN_SIMD_SOCKET` is set, else a
12
+ * helper spawned on stdio), app lifecycle / clipboard / appearance through `xcrun simctl`. Fallbacks:
13
+ * `typeText` pastes what the HID keyboard map cannot type, `settle` polls snapshot hashes when the
14
+ * helper has no native quiescence, `screenshot` uses `simctl io` when the helper cannot.
15
+ */
16
+ export type IosDriverEnv = Pick<E2eEnv, "simd" | "simdSocket" | "helperDir">;
17
+ /** The iOS driver is a `Driver` plus the optional capabilities of `driver/alerts.ts`. */
18
+ export type IosDriver = Driver & SelectsAll & ReadsAlertOn & ProbesFrontmost;
19
+ export type OpenIosDriverOptions = {
20
+ udid: string;
21
+ env?: IosDriverEnv;
22
+ exec: Exec;
23
+ spawn?: SimdSpawn;
24
+ log?: Logger;
25
+ /** explicit helper binary (skips {@link locateHelper}) */
26
+ helperPath?: string;
27
+ /** explicit helper socket (wins over `env.simdSocket`) */
28
+ socketPath?: string;
29
+ /**
30
+ * An already connected (handshaken) helper client: used instead of connecting, so a caller that just
31
+ * connected to learn the helper was up does not connect twice. The driver owns it from here (`close()`
32
+ * closes it); a restart still reconnects through the socket / spawn path.
33
+ */
34
+ client?: SimdClient;
35
+ now?: () => number;
36
+ sleep?: (ms: number) => Promise<void>;
37
+ /** for `locateHelper` / `$WARDEN_HOME/simd/<version>`; defaults read the process */
38
+ version?: string;
39
+ wardenHome?: string;
40
+ execPath?: string;
41
+ importDir?: string;
42
+ exists?: (path: string) => boolean;
43
+ realpath?: (path: string) => string;
44
+ /** per-request default in ms, 10 s */
45
+ defaultTimeoutMs?: number;
46
+ /** log each helper op and simctl call with its duration (`WARDEN_E2E_DEBUG=1`) */
47
+ debug?: boolean;
48
+ /** CoreSimulator devices directory for the host-side `AutomationEnabled` check (default `~/Library/Developer/CoreSimulator/Devices`) */
49
+ devicesRoot?: string;
50
+ };
51
+ /** USB HID keyboard-page usage ids for the keys `Driver.key` offers. */
52
+ export declare const KEY_USAGE: Readonly<Record<KeyName, number>>;
53
+ export declare function createIosDriver(swap: SwappableClient, simctl: Simctl, opts: {
54
+ udid: string;
55
+ now: () => number;
56
+ sleep: (ms: number) => Promise<void>;
57
+ log?: Logger;
58
+ }): IosDriver;
59
+ /**
60
+ * Open the iOS driver for `udid`. Writes `AutomationEnabled` (best effort) while the helper starts.
61
+ * `E_HELPER_MISSING` (with the `warden doctor` hint) when no helper binary can be found.
62
+ */
63
+ export declare function openIosDriver(o: OpenIosDriverOptions): DriverResult<Driver>;
@@ -0,0 +1,30 @@
1
+ import type { Result } from "../../../../types/src/result";
2
+ import type { DriverError, Logger } from "../driver.types";
3
+ import type { SimdClient } from "./simd-client";
4
+ /** The slice of {@link SimdClient} the iOS driver uses; the driver holds this, never a raw client. */
5
+ export type DriverClient = Pick<SimdClient, "request" | "ready" | "close">;
6
+ export type Reopen = () => Promise<Result<SimdClient, DriverError>>;
7
+ export type SwappableClient = {
8
+ client: DriverClient;
9
+ /** bumps on every restart, so a caller can tell "someone already restarted it" from "still the stale one" */
10
+ generation(): number;
11
+ /**
12
+ * Replace the helper: ask the current one to `shutdown`, drop it, `reopen()` (socket first, else a
13
+ * fresh stdio spawn). Single-flight; a caller whose `seenGeneration` is already stale just returns ok.
14
+ * `crashed`: the helper (or the connection to it) is already gone, so nothing is asked of it; asking
15
+ * would only make a stdio client respawn a helper to shut it down.
16
+ */
17
+ restart(seenGeneration: number, o?: {
18
+ crashed?: boolean;
19
+ }): Promise<Result<void, DriverError>>;
20
+ };
21
+ /**
22
+ * Wraps the helper client so it can be swapped at runtime (bridge-stale recovery) and so every
23
+ * request's latency can be logged (`debug`, driven by `WARDEN_E2E_DEBUG=1`): `[ios] op snapshot 31 ms`.
24
+ */
25
+ export declare function swappableClient(initial: SimdClient, o: {
26
+ now: () => number;
27
+ log?: Logger;
28
+ debug?: boolean;
29
+ reopen?: Reopen;
30
+ }): SwappableClient;
@@ -0,0 +1,38 @@
1
+ import type { Result } from "../../../../types/src/result";
2
+ import type { DriverError } from "../driver.types";
3
+ import type { OpenIosDriverOptions } from "./ios-driver";
4
+ export declare const HELPER_NAME = "warden-simd";
5
+ export type LocateHelperInput = {
6
+ /** `WARDEN_SIMD` (a file) and `WARDEN_HELPER_DIR` (a directory holding `warden-simd`) are read from here */
7
+ env: Readonly<Record<string, string | undefined>>;
8
+ /** `process.execPath`: a compiled `warden` ships the helper next to itself */
9
+ execPath: string;
10
+ /** directory of this module (`packages/e2e/src/driver/ios`), anchors the source-checkout build output */
11
+ importDir: string;
12
+ /** `$WARDEN_HOME` (resolved), where `warden doctor` / installs stage `simd/<version>/warden-simd` */
13
+ wardenHome: string;
14
+ /** warden version for the staged copy; without it that lookup step is skipped */
15
+ version?: string;
16
+ /** file existence, injected */
17
+ exists: (path: string) => boolean;
18
+ /** symlink resolution, injected; defaults to identity */
19
+ realpath?: (path: string) => string;
20
+ };
21
+ export type HelperLocation = {
22
+ path: string;
23
+ source: "WARDEN_SIMD" | "WARDEN_HELPER_DIR" | "execPath" | "source-build" | "warden-home";
24
+ };
25
+ export declare const DOCTOR_HINT = "run `warden doctor` to check the iOS driver helper, or set WARDEN_SIMD=/path/to/warden-simd";
26
+ /**
27
+ * Find the `warden-simd` binary. Order: `WARDEN_SIMD` (file) → `WARDEN_HELPER_DIR/warden-simd` →
28
+ * next to the real path of the running executable → `<packages/e2e>/native/dist/warden-simd` →
29
+ * `$WARDEN_HOME/simd/<version>/warden-simd`. An explicitly set env var that points at nothing is an
30
+ * error, not a fall-through (a stale override should not silently pick another binary).
31
+ */
32
+ export declare function locateHelper(input: LocateHelperInput): Result<HelperLocation, DriverError>;
33
+ /**
34
+ * The helper binary: the explicit `helperPath`, else `locateHelper` over the env / install locations. Lives here,
35
+ * not in `ios-driver.ts`, so a caller that only needs the path (`warden ui` spawning or replacing the helper)
36
+ * does not load the driver and its protocol schemas.
37
+ */
38
+ export declare function resolveHelper(o: OpenIosDriverOptions): Result<string, DriverError>;
@@ -0,0 +1,11 @@
1
+ import type { Element, ScreenInfo, Snapshot } from "../driver.types";
2
+ import type { SnapshotData, WireElement } from "./simd-protocol";
3
+ /** Wire → `Element`: strips `null`s, fills what the helper may omit (`id` as the preorder path, `depth`, `normFrame`, `traits`, `enabled`, `children`). */
4
+ export type Normalized = {
5
+ elements: Element[];
6
+ flat: Element[];
7
+ };
8
+ export declare function normalizeElements(wire: readonly WireElement[], screen: ScreenInfo, prefix?: string, depth?: number): Normalized;
9
+ /** One element out of `elementAtPoint` (no siblings, so depth/path are 0). */
10
+ export declare function normalizeElement(wire: WireElement, screen: ScreenInfo): Element;
11
+ export declare function toSnapshot(data: SnapshotData, takenAt: number): Snapshot;
@@ -0,0 +1,102 @@
1
+ import { type ResetFs } from "../../../../core/src/builds/install";
2
+ import { type Exec, type ExecResult } from "../../../../core/src/exec";
3
+ import type { DriverError, DriverResult, LaunchOptions } from "../driver.types";
4
+ export declare const SIMCTL_CHILD_PREFIX = "SIMCTL_CHILD_";
5
+ /** `xcrun simctl launch [--terminate-running-process] <udid> <bundle> <args…>` (env goes through `SIMCTL_CHILD_*`, see {@link launchEnv}). */
6
+ export declare function launchArgv(udid: string, bundleId: string, opts?: LaunchOptions): string[];
7
+ /** simctl forwards `SIMCTL_CHILD_<NAME>` from its own environment to the launched app as `<NAME>`. */
8
+ export declare function launchEnv(env: Record<string, string> | undefined): Record<string, string>;
9
+ export declare const terminateArgv: (udid: string, bundleId: string) => string[];
10
+ export declare const openUrlArgv: (udid: string, url: string) => string[];
11
+ export declare const installArgv: (udid: string, appPath: string) => string[];
12
+ export declare const uninstallArgv: (udid: string, bundleId: string) => string[];
13
+ /** Exit 0 and a path on stdout when installed; non-zero when not. */
14
+ export declare const appContainerArgv: (udid: string, bundleId: string) => string[];
15
+ /** Lists launchd jobs inside the simulator; a running app shows up as `UIKitApplication:<bundle>[…]` with a PID. */
16
+ export declare const launchctlListArgv: (udid: string) => string[];
17
+ export declare const pbpasteArgv: (udid: string) => string[];
18
+ export declare const appearanceArgv: (udid: string, set?: "light" | "dark") => string[];
19
+ export declare const screenshotArgv: (udid: string, path: string) => string[];
20
+ /** `AutomationEnabled` lets the AX bridge attach without the runner app; written once per open. */
21
+ export declare const automationArgv: (udid: string) => string[];
22
+ /** Where CoreSimulator keeps every simulator's data directory on the host. */
23
+ export declare const defaultDevicesRoot: () => string;
24
+ /**
25
+ * The simulator's Accessibility preferences, readable from the host without booting anything through `xcrun`
26
+ * (`simctl spawn` costs 300-500 ms, a `plutil` read about 10 ms).
27
+ */
28
+ export declare const automationPlistPath: (devicesRoot: string, udid: string) => string;
29
+ /** `plutil -extract AutomationEnabled raw -o - <plist>` prints `true` once the default is written. */
30
+ export declare const automationReadArgv: (plist: string) => string[];
31
+ export type StatusBarOverrides = {
32
+ time?: string;
33
+ batteryLevel?: number;
34
+ batteryState?: "charged" | "charging" | "discharging";
35
+ wifiBars?: number;
36
+ cellularBars?: number;
37
+ };
38
+ /** Clean 9:41 status bar by default (stable screenshots); pass `undefined` fields to drop them. */
39
+ export declare function statusBarOverrideArgv(udid: string, o?: StatusBarOverrides): string[];
40
+ export declare const statusBarClearArgv: (udid: string) => string[];
41
+ /**
42
+ * `simctl pbcopy` reads stdin and `Exec` has none, so the text rides as `$1` of a tiny `sh -c`
43
+ * (never interpolated into the script): `printf %s "$1" | xcrun simctl pbcopy "$2"`.
44
+ */
45
+ export declare function pbcopyArgv(udid: string, text: string): string[];
46
+ /**
47
+ * The same text through `devicectl device pasteboard copy`, the CoreDevice side of the simulator pasteboard.
48
+ * On Xcode 27.1 (iOS 26.5 runtime) `simctl pbcopy` exits 0 without changing the pasteboard, which then still
49
+ * holds whatever CoreDevice's `dtpasteboardd` last mirrored from the Mac; `devicectl` writes it in ~350 ms.
50
+ */
51
+ export declare function devicectlPbcopyArgv(udid: string, text: string): string[];
52
+ /** The channels {@link createSimctl}'s `pbcopy` tries, in order; each write is confirmed with `simctl pbpaste`. */
53
+ export declare const PBCOPY_CHANNELS: readonly {
54
+ name: string;
55
+ argv: (udid: string, text: string) => string[];
56
+ }[];
57
+ /** `launchctl list` rows are `PID STATUS LABEL`; PID is `-` for loaded-but-not-running jobs. */
58
+ export declare function parseRunning(launchctlOutput: string, bundleId: string): boolean;
59
+ /** Map a failed simctl run to a `DriverError`. `appCommand` marks launch-like calls where "not installed" means the app, not the device. */
60
+ export declare function simctlFailure(argv: readonly string[], res: ExecResult, appCommand?: boolean): DriverError;
61
+ export type SimctlDeps = {
62
+ udid: string;
63
+ exec: Exec;
64
+ /** forwarded to core's installer helpers (resolves `adb`; unused on iOS but part of their deps) */
65
+ env?: Record<string, string | undefined>;
66
+ /** app data wipe filesystem; defaults to node fs */
67
+ resetFs?: ResetFs;
68
+ /** where the screenshot fallback writes before reading back; defaults to a fresh `os.tmpdir()` dir */
69
+ scratchDir?: () => Promise<string>;
70
+ readFile?: (path: string) => Promise<Uint8Array>;
71
+ rmDir?: (path: string) => Promise<void>;
72
+ /** CoreSimulator's devices directory, for the host-side `AutomationEnabled` check; defaults to `~/Library/...` */
73
+ devicesRoot?: string;
74
+ };
75
+ export type Simctl = {
76
+ launch(bundleId: string, opts?: LaunchOptions): DriverResult<void>;
77
+ terminate(bundleId: string): DriverResult<void>;
78
+ openUrl(url: string): DriverResult<void>;
79
+ install(path: string): DriverResult<void>;
80
+ uninstall(bundleId: string): DriverResult<void>;
81
+ isInstalled(bundleId: string): DriverResult<boolean>;
82
+ isRunning(bundleId: string): DriverResult<boolean>;
83
+ clearData(bundleId: string, opts?: {
84
+ keychain?: boolean;
85
+ }): DriverResult<void>;
86
+ /**
87
+ * Puts `text` on the simulator pasteboard and confirms it with `pbpaste` before answering, trying every
88
+ * {@link PBCOPY_CHANNELS} entry: `E_IO` when none took, so a paste never lands other text.
89
+ */
90
+ pbcopy(text: string): DriverResult<void>;
91
+ pbpaste(): DriverResult<string>;
92
+ appearance(set?: "light" | "dark"): DriverResult<"light" | "dark">;
93
+ statusBar(overrides?: StatusBarOverrides | "clear"): DriverResult<void>;
94
+ /** `simctl io screenshot` to `path`, or (no path) to a scratch file read back as PNG bytes */
95
+ screenshot(path?: string): DriverResult<{
96
+ path?: string;
97
+ png?: Uint8Array;
98
+ }>;
99
+ /** `defaults write com.apple.Accessibility AutomationEnabled -bool true` inside the sim */
100
+ enableAutomation(): DriverResult<void>;
101
+ };
102
+ export declare function createSimctl(deps: SimctlDeps): Simctl;
@@ -0,0 +1,68 @@
1
+ import type { Result } from "../../../../types/src/result";
2
+ import type { DriverError, DriverResult, Logger } from "../driver.types";
3
+ import { type LogEvent, type OpData, type ReadyEvent } from "./simd-protocol";
4
+ import { type SimdSpawn, type TransportFactory } from "./simd-transport";
5
+ /**
6
+ * NDJSON client for `warden-simd`, over the helper's stdio (`spawnSimd`, by-hand runs) or its unix
7
+ * socket (`connectSimd`, the pre-warmed daemon). Multiplexes requests by id (responses may arrive out
8
+ * of order), enforces per-op timeouts, forwards `log` events, maps `fatal` to a `DriverError`, and in
9
+ * stdio mode respawns the helper on the call after a crash.
10
+ */
11
+ export type SimdEventMap = {
12
+ ready: ReadyEvent;
13
+ log: LogEvent;
14
+ /** the helper reported it cannot continue (also fails every in-flight request) */
15
+ fatal: DriverError;
16
+ /** the helper process / connection went away */
17
+ exit: {
18
+ exitCode: number | null;
19
+ expected: boolean;
20
+ };
21
+ };
22
+ export type RequestOptions = {
23
+ timeoutMs?: number;
24
+ };
25
+ export type SimdClient = {
26
+ /** the handshake of the current helper process (refreshed after a respawn) */
27
+ readonly ready: ReadyEvent;
28
+ request<Op extends string>(op: Op, params?: Record<string, unknown>, opts?: RequestOptions): DriverResult<OpData<Op>>;
29
+ /** subscribe; returns the unsubscribe function */
30
+ on<E extends keyof SimdEventMap>(event: E, handler: (payload: SimdEventMap[E]) => void): () => void;
31
+ close(): Promise<void>;
32
+ };
33
+ export type ClientOptions = {
34
+ log?: Logger;
35
+ /** default per-request timeout, 10 s */
36
+ defaultTimeoutMs?: number;
37
+ /** how long to wait for `ready`, 15 s */
38
+ readyTimeoutMs?: number;
39
+ /**
40
+ * respawns allowed in a row after unexpected exits (stdio default 1, socket 0). The budget refills
41
+ * as soon as the helper answers anything, so each crash gets one respawn but a helper that dies
42
+ * before answering is not retried forever.
43
+ */
44
+ maxRespawns?: number;
45
+ /** ask the helper to `shutdown` on close (stdio: yes; a shared socket daemon: no) */
46
+ shutdownOnClose?: boolean;
47
+ };
48
+ /** Shared client engine; the transport decides stdio vs socket. */
49
+ export declare function createSimdClient(factory: TransportFactory, options?: ClientOptions): Promise<Result<SimdClient, DriverError>>;
50
+ export type SpawnSimdOptions = ClientOptions & {
51
+ helperPath: string;
52
+ udid: string;
53
+ /** `--resources <dir>`: where the in-sim AX bridge guest lives */
54
+ resources?: string;
55
+ /** extra helper arguments after the standard ones (e.g. `--hid indigo`) */
56
+ args?: readonly string[];
57
+ /** injectable process spawn (tests); defaults to `Bun.spawn` */
58
+ spawn?: SimdSpawn;
59
+ /** grace period for `close()` before the helper is killed */
60
+ graceMs?: number;
61
+ };
62
+ /** Stdio mode: own a helper process for this client (by-hand runs). Crashes are retried by one respawn. */
63
+ export declare function spawnSimd(options: SpawnSimdOptions): Promise<Result<SimdClient, DriverError>>;
64
+ export type ConnectSimdOptions = ClientOptions & {
65
+ socketPath: string;
66
+ };
67
+ /** Socket mode: attach to a running (pre-warmed) helper daemon. `close()` only disconnects; the daemon idles out. */
68
+ export declare function connectSimd(options: ConnectSimdOptions): Promise<Result<SimdClient, DriverError>>;