@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
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type AsyncResult } from "../../../types/src/result";
|
|
2
|
+
import type { Exec, ExecResult } from "../exec";
|
|
3
|
+
import type { InstalledApp } from "./resolve";
|
|
4
|
+
/** The temp-file effects of a devicectl call. */
|
|
5
|
+
export type DeviceIo = {
|
|
6
|
+
/** a fresh private directory */
|
|
7
|
+
tempDir: () => Promise<string>;
|
|
8
|
+
read: (path: string) => Promise<string>;
|
|
9
|
+
remove: (dir: string) => Promise<void>;
|
|
10
|
+
};
|
|
11
|
+
export declare const nodeDeviceIo: DeviceIo;
|
|
12
|
+
export type PhoneDeps = {
|
|
13
|
+
exec: Exec;
|
|
14
|
+
io?: DeviceIo;
|
|
15
|
+
};
|
|
16
|
+
export declare const phoneInstallArgv: (udid: string, appPath: string, json: string) => string[];
|
|
17
|
+
export declare const phoneUninstallArgv: (udid: string, bundleId: string, json: string) => string[];
|
|
18
|
+
export declare const phoneAppsArgv: (udid: string, bundleId: string, json: string) => string[];
|
|
19
|
+
/**
|
|
20
|
+
* A failed devicectl call in words: a locked phone, a phone the provisioning profile does not list, or one of the
|
|
21
|
+
* classified signing / device states (`classifyRunnerOutput`), each with what to do; else devicectl's first line.
|
|
22
|
+
*/
|
|
23
|
+
export declare function describePhoneFailure(what: string, res: ExecResult): string;
|
|
24
|
+
/** Is `bundleId` on the phone, and which `CFBundleVersion` does it have? */
|
|
25
|
+
export declare function probePhone(deps: PhoneDeps, udid: string, bundleId: string): AsyncResult<InstalledApp>;
|
|
26
|
+
/** Install `appPath`, then confirm `bundleId` is there. */
|
|
27
|
+
export declare function installOnPhone(deps: PhoneDeps, udid: string, appPath: string, bundleId: string): AsyncResult<InstalledApp>;
|
|
28
|
+
/** Remove `bundleId` (the app under test) from the phone. */
|
|
29
|
+
export declare function uninstallFromPhone(deps: PhoneDeps, udid: string, bundleId: string): AsyncResult<void>;
|
|
30
|
+
/** `CFBundleVersion` of an `.app` (one `plutil` spawn); undefined when it cannot be read. */
|
|
31
|
+
export declare function appBundleVersion(exec: Exec, appDir: string): Promise<string | undefined>;
|
|
32
|
+
/**
|
|
33
|
+
* Is the app on the phone the cached build? The install record already named this build's key; this adds the
|
|
34
|
+
* `CFBundleVersion` the phone reports against the cached `.app`'s. Anything unreadable counts as a mismatch
|
|
35
|
+
* (a reinstall is the safe side).
|
|
36
|
+
*/
|
|
37
|
+
export declare function phoneAppMatches(exec: Exec, cachedApp: string, installed: InstalledApp): Promise<boolean>;
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { type AsyncResult } from "../../../types/src/result";
|
|
2
2
|
import { type Exec } from "../exec";
|
|
3
3
|
import type { Platform } from "../types";
|
|
4
|
+
import { type DeviceIo } from "./install-device";
|
|
4
5
|
import type { InstalledApp } from "./resolve";
|
|
5
6
|
/** Byte size of a file; undefined when it cannot be read. Injected so identity checks need no `stat` spawns. */
|
|
6
7
|
export type FileSize = (path: string) => Promise<number | undefined>;
|
|
7
8
|
export declare const nodeFileSize: FileSize;
|
|
9
|
+
/** Hex sha256 of a file; undefined when it cannot be read (or is a directory). Injected so tests need no real apk. */
|
|
10
|
+
export type FileDigest = (path: string) => Promise<string | undefined>;
|
|
11
|
+
export declare const nodeFileDigest: FileDigest;
|
|
8
12
|
/** `ino:mtime` of a file; undefined when it cannot be read. Injected so tests need no real container. */
|
|
9
13
|
export type FileStamp = (path: string) => Promise<string | undefined>;
|
|
10
14
|
export declare const nodeFileStamp: FileStamp;
|
|
@@ -18,12 +22,19 @@ export type InstallerDeps = {
|
|
|
18
22
|
env: Record<string, string | undefined>;
|
|
19
23
|
/** default: `node:fs` */
|
|
20
24
|
fileSize?: FileSize;
|
|
25
|
+
/** the temp files of `devicectl` calls on a physical iPhone (default: node:fs) */
|
|
26
|
+
deviceIo?: DeviceIo;
|
|
27
|
+
/** sha256 of the cached apk for the Android identity check (default: `node:fs`) */
|
|
28
|
+
fileDigest?: FileDigest;
|
|
21
29
|
};
|
|
22
|
-
/** A device to install onto: iOS simulator udid / Android emulator serial. */
|
|
30
|
+
/** A device to install onto: iOS simulator udid / Android emulator or phone serial. `physical`: a real phone (ABI check before installing). */
|
|
23
31
|
export type InstallTarget = {
|
|
24
32
|
platform: Platform;
|
|
25
33
|
deviceId: string;
|
|
34
|
+
physical?: boolean;
|
|
26
35
|
};
|
|
36
|
+
/** A real iPhone / iPad: installed, probed and removed with `devicectl`, not `simctl`. */
|
|
37
|
+
export declare const isPhoneIos: (target: InstallTarget) => boolean;
|
|
27
38
|
/** `xcrun simctl install <udid> <app>` / `adb -s <serial> install -r <apk>`. */
|
|
28
39
|
export declare function installArgv(target: InstallTarget, appPath: string, env: InstallerDeps["env"]): string[];
|
|
29
40
|
/** `xcrun simctl get_app_container <udid> <bundleId>` / `adb -s <serial> shell pm path <bundleId>`. */
|
|
@@ -47,8 +58,9 @@ export declare function appIdentity(deps: InstallerDeps, appDir: string): Promis
|
|
|
47
58
|
* Is the app on the device the cached `artifactPath` build? iOS: compare `appIdentity` of the
|
|
48
59
|
* installed container with the cached `.app`; pass `installedDir` (the container `probeApp` already
|
|
49
60
|
* found) to skip a second `simctl get_app_container`. Anything unreadable counts as a mismatch
|
|
50
|
-
* (reinstall is the safe side). Android:
|
|
51
|
-
*
|
|
61
|
+
* (reinstall is the safe side). Android: the sha256 of the installed `base.apk` (`pm path` + the device's
|
|
62
|
+
* `sha256sum`) against the cached apk's. The install record is only a hint, never the proof: an emulator
|
|
63
|
+
* restored from a snapshot, or installed onto by another tool, keeps the record while the app differs.
|
|
52
64
|
*/
|
|
53
65
|
export declare function appMatchesArtifact(deps: InstallerDeps, target: InstallTarget, bundleId: string, artifactPath: string, installedDir?: string): AsyncResult<boolean>;
|
|
54
66
|
/** `xcrun simctl uninstall <udid> <bundleId>` / `adb -s <serial> uninstall <bundleId>`. */
|
|
@@ -60,6 +72,8 @@ export declare function uninstallArgv(target: InstallTarget, bundleId: string, e
|
|
|
60
72
|
export declare function uninstallApp(deps: InstallerDeps, target: InstallTarget, bundleId: string): AsyncResult<void>;
|
|
61
73
|
/** Install `appPath`, then confirm `bundleId` is present. */
|
|
62
74
|
export declare function installApp(deps: InstallerDeps, target: InstallTarget, appPath: string, bundleId: string): AsyncResult<void>;
|
|
75
|
+
/** ABI directories under `lib/` in `unzip -Z1` output; empty for a pure-Java APK. */
|
|
76
|
+
export declare function apkAbis(entries: string): string[];
|
|
63
77
|
/** `installApp`, keeping what the confirming probe found (iOS: the new bundle container, 17-7). */
|
|
64
78
|
export declare function installAndProbe(deps: InstallerDeps, target: InstallTarget, appPath: string, bundleId: string): AsyncResult<InstalledApp>;
|
|
65
79
|
/** Filesystem ops the iOS data wipe needs (injected; tests pass fakes). */
|
|
@@ -10,6 +10,23 @@ import type { AppFramework } from "./native-ids";
|
|
|
10
10
|
*/
|
|
11
11
|
export type BuildKind = "dev" | "simulator" | "release";
|
|
12
12
|
export declare const BUILD_KINDS: readonly ["dev", "simulator", "release"];
|
|
13
|
+
/**
|
|
14
|
+
* What a build runs on, separate from its kind: `simulator` builds (every kind so far) run on simulators and
|
|
15
|
+
* emulators, `device` builds are signed for a physical iPhone. v1: only `dev` and `release` have a device build.
|
|
16
|
+
*/
|
|
17
|
+
export type BuildTarget = "simulator" | "device";
|
|
18
|
+
/**
|
|
19
|
+
* A device-target build's identity: the signing `team` and a `salt` (the team, plus a hash of the bundle id
|
|
20
|
+
* override when `projects[].ios.device.bundleIdentifier` is set) that goes into the cache key and the cache path.
|
|
21
|
+
*/
|
|
22
|
+
export type DeviceTarget = {
|
|
23
|
+
team: string;
|
|
24
|
+
salt: string;
|
|
25
|
+
};
|
|
26
|
+
/** The device target of `project` signed by `team`: a changed team or bundle id override is a different build. */
|
|
27
|
+
export declare function deviceTarget(project: Project, team: string): DeviceTarget;
|
|
28
|
+
/** `project` with the bundle id a device build installs under (`projects[].ios.device.bundleIdentifier`, else its own). */
|
|
29
|
+
export declare function withDeviceBundleId(project: Project): Project;
|
|
13
30
|
/** The kind `warden e2e` installs when the suite's `app` names none. */
|
|
14
31
|
export declare const DEFAULT_E2E_KIND: BuildKind;
|
|
15
32
|
/** The kind `warden ui open` / `warden dev` use when no `--kind` is given. */
|
|
@@ -48,9 +65,10 @@ export declare function kindBuildCommand(kind: BuildKind, platform: Platform, fr
|
|
|
48
65
|
* (`fingerprint.command`, `build.cache` and `build.prebuiltReactNative` still apply: they are not about the kind).
|
|
49
66
|
* JS inputs: `kinds.<kind>.jsInputs`, else `DEFAULT_JS_INPUTS` (all tracked files but `ios/`, `android/`, `node_modules`).
|
|
50
67
|
*/
|
|
51
|
-
export declare function resolveKind(project: Project, kind: BuildKind): KindSettings;
|
|
68
|
+
export declare function resolveKind(project: Project, kind: BuildKind, device?: DeviceTarget): KindSettings;
|
|
52
69
|
/**
|
|
53
70
|
* `project` as `kind` builds it: the build commands, configuration and env of `resolveKind`, and `jsInputs` set only
|
|
54
|
-
* when the kind's cache key covers the JS (`release`), so `runLocalBuild` / `computeCacheKey` run unchanged.
|
|
71
|
+
* when the kind's cache key covers the JS (`release`), so `runLocalBuild` / `computeCacheKey` run unchanged. With a
|
|
72
|
+
* `device` target the project builds for a physical iPhone: the signed `xcodebuild` command, the device bundle id.
|
|
55
73
|
*/
|
|
56
|
-
export declare function projectForKind(project: Project, kind: BuildKind): Project;
|
|
74
|
+
export declare function projectForKind(project: Project, kind: BuildKind, device?: DeviceTarget): Project;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type AsyncResult } from "../../../types/src/result";
|
|
2
|
+
import { type PidAlive } from "../liveness";
|
|
3
|
+
import type { Store } from "../store";
|
|
4
|
+
import { type BuildResource, type Lease, type Owner, type Platform } from "../types";
|
|
5
|
+
/** Lease key serialising work on one (project, platform, fingerprint). */
|
|
6
|
+
export declare function buildLockKey(projectKey: string, platform: Platform, hash: string): string;
|
|
7
|
+
/**
|
|
8
|
+
* Lease key serialising local builds in one checkout: a build writes the working tree (prebuild, Pods, build/), so two
|
|
9
|
+
* builds of any fingerprints in one `root` clobber each other, and a fingerprint read while one runs is of a half-written
|
|
10
|
+
* native dir. `root` should be a realpath, so two spellings of one checkout share it.
|
|
11
|
+
*/
|
|
12
|
+
export declare function buildTreeLockKey(root: string, platform: Platform): string;
|
|
13
|
+
export declare function buildResource(key: string): BuildResource;
|
|
14
|
+
export type BuildLockDeps = {
|
|
15
|
+
store: Store;
|
|
16
|
+
owner: Owner;
|
|
17
|
+
/** pid recorded on the lock — the lock lives while this process does */
|
|
18
|
+
pid: number;
|
|
19
|
+
now: () => number;
|
|
20
|
+
pidAlive: PidAlive;
|
|
21
|
+
sleep: (ms: number) => Promise<void>;
|
|
22
|
+
log?: (line: string) => void;
|
|
23
|
+
/** the one line logged when this starts waiting, given the holder (default: "another warden (…) is fetching/building this app — waiting") */
|
|
24
|
+
waitLine?: (holder: string) => string;
|
|
25
|
+
/** called with the holder's lease on every poll while another owner holds the lock (progress, log tailing) */
|
|
26
|
+
onWait?: (held: Lease) => void;
|
|
27
|
+
pollMs?: number;
|
|
28
|
+
waitMs?: number;
|
|
29
|
+
ttlMs?: number;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Run `fn` holding the build lease for `key`. While another live owner holds it, poll (up to
|
|
33
|
+
* `waitMs`); callers re-check the cache inside `fn`, so a waiter ends up with the holder's result
|
|
34
|
+
* instead of downloading/building again. The lease is always released.
|
|
35
|
+
*/
|
|
36
|
+
export declare function withBuildLock<T>(deps: BuildLockDeps, key: string, fn: () => AsyncResult<T>): AsyncResult<T>;
|
|
37
|
+
/** Keys of build leases currently alive (prune never deletes these). */
|
|
38
|
+
export declare function liveBuildLocks(store: Store, now: number, pidAlive: PidAlive): Set<string>;
|
|
@@ -41,10 +41,20 @@ export type EasStepOutcome = {
|
|
|
41
41
|
kind: "miss";
|
|
42
42
|
reason: string;
|
|
43
43
|
};
|
|
44
|
+
/** What the local build step produced: its own build, or (after waiting on the checkout) another warden's, from the cache. */
|
|
45
|
+
export type BuildStepOutcome = {
|
|
46
|
+
kind: "built";
|
|
47
|
+
path: string;
|
|
48
|
+
} | {
|
|
49
|
+
kind: "cached";
|
|
50
|
+
path: string;
|
|
51
|
+
};
|
|
44
52
|
/** What the device probe found: is the app there, and (iOS) its container so `matches` need not ask again. */
|
|
45
53
|
export type InstalledApp = {
|
|
46
54
|
present: boolean;
|
|
47
55
|
container?: string;
|
|
56
|
+
/** a physical iPhone: the `CFBundleVersion` devicectl reports for the installed app */
|
|
57
|
+
version?: string;
|
|
48
58
|
};
|
|
49
59
|
/** Device half of the resolver — absent when only the artifact is wanted. */
|
|
50
60
|
export type DeviceSteps = {
|
|
@@ -84,8 +94,8 @@ export type ResolveSteps = {
|
|
|
84
94
|
flightKey?: string;
|
|
85
95
|
/** EAS: download into the cache, or miss */
|
|
86
96
|
eas?: () => AsyncResult<EasStepOutcome>;
|
|
87
|
-
/** local build into the cache
|
|
88
|
-
build?: () => AsyncResult<
|
|
97
|
+
/** local build into the cache (or the build another warden just made in this checkout) */
|
|
98
|
+
build?: () => AsyncResult<BuildStepOutcome>;
|
|
89
99
|
log: (line: string) => void;
|
|
90
100
|
/** progress: where this resolve is (see `AppPhase`) */
|
|
91
101
|
onPhase?: (phase: AppPhase) => void;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { type Result } from "../../../types/src/result";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { type A11yCondition, type ClockCondition, type LocationCondition, type NetworkCondition, type NetworkPreset, type PermissionName, type PermissionState } from "./conditions.types";
|
|
4
|
+
/** `network: { latencyMs, ... }`: the same fields as a `custom` network condition, without its `kind`. */
|
|
5
|
+
declare const customNetworkSchema: z.ZodObject<{
|
|
6
|
+
latencyMs: z.ZodOptional<z.ZodNumber>;
|
|
7
|
+
jitterMs: z.ZodOptional<z.ZodNumber>;
|
|
8
|
+
downKbps: z.ZodOptional<z.ZodNumber>;
|
|
9
|
+
upKbps: z.ZodOptional<z.ZodNumber>;
|
|
10
|
+
lossPct: z.ZodOptional<z.ZodNumber>;
|
|
11
|
+
}, z.core.$strict>;
|
|
12
|
+
/**
|
|
13
|
+
* `conditions` (top level, or per `e2e.<suite>`): what every spec of the suite starts from. `network`, `location`
|
|
14
|
+
* and `clock` are applied before the app launches and put back after each spec, as are `permissions`, `locale`,
|
|
15
|
+
* `timezone` and `a11y`.
|
|
16
|
+
*/
|
|
17
|
+
export declare const conditionsConfigSchema: z.ZodObject<{
|
|
18
|
+
network: z.ZodOptional<z.ZodUnion<readonly [z.ZodEnum<{
|
|
19
|
+
edge: "edge";
|
|
20
|
+
"3g": "3g";
|
|
21
|
+
"4g": "4g";
|
|
22
|
+
lte: "lte";
|
|
23
|
+
wifi: "wifi";
|
|
24
|
+
slow: "slow";
|
|
25
|
+
lossy: "lossy";
|
|
26
|
+
online: "online";
|
|
27
|
+
offline: "offline";
|
|
28
|
+
}>, z.ZodObject<{
|
|
29
|
+
latencyMs: z.ZodOptional<z.ZodNumber>;
|
|
30
|
+
jitterMs: z.ZodOptional<z.ZodNumber>;
|
|
31
|
+
downKbps: z.ZodOptional<z.ZodNumber>;
|
|
32
|
+
upKbps: z.ZodOptional<z.ZodNumber>;
|
|
33
|
+
lossPct: z.ZodOptional<z.ZodNumber>;
|
|
34
|
+
}, z.core.$strict>]>>;
|
|
35
|
+
location: z.ZodOptional<z.ZodUnion<readonly [z.ZodEnum<{
|
|
36
|
+
"city-run": "city-run";
|
|
37
|
+
"city-bike": "city-bike";
|
|
38
|
+
freeway: "freeway";
|
|
39
|
+
}>, z.ZodObject<{
|
|
40
|
+
lat: z.ZodNumber;
|
|
41
|
+
lon: z.ZodNumber;
|
|
42
|
+
altM: z.ZodOptional<z.ZodNumber>;
|
|
43
|
+
}, z.core.$strict>]>>;
|
|
44
|
+
clock: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
|
|
45
|
+
at: z.ZodString;
|
|
46
|
+
frozen: z.ZodOptional<z.ZodBoolean>;
|
|
47
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
48
|
+
offset: z.ZodString;
|
|
49
|
+
}, z.core.$strict>]>>;
|
|
50
|
+
permissions: z.ZodOptional<z.ZodRecord<z.ZodEnum<{
|
|
51
|
+
location: "location";
|
|
52
|
+
locationAlways: "locationAlways";
|
|
53
|
+
camera: "camera";
|
|
54
|
+
photos: "photos";
|
|
55
|
+
photosAdd: "photosAdd";
|
|
56
|
+
contacts: "contacts";
|
|
57
|
+
contactsLimited: "contactsLimited";
|
|
58
|
+
notifications: "notifications";
|
|
59
|
+
microphone: "microphone";
|
|
60
|
+
calendar: "calendar";
|
|
61
|
+
reminders: "reminders";
|
|
62
|
+
motion: "motion";
|
|
63
|
+
mediaLibrary: "mediaLibrary";
|
|
64
|
+
siri: "siri";
|
|
65
|
+
}> & z.core.$partial, z.ZodEnum<{
|
|
66
|
+
granted: "granted";
|
|
67
|
+
denied: "denied";
|
|
68
|
+
unset: "unset";
|
|
69
|
+
}>>>;
|
|
70
|
+
locale: z.ZodOptional<z.ZodString>;
|
|
71
|
+
timezone: z.ZodOptional<z.ZodString>;
|
|
72
|
+
a11y: z.ZodOptional<z.ZodObject<{
|
|
73
|
+
textSize: z.ZodOptional<z.ZodUnion<readonly [z.ZodEnum<{
|
|
74
|
+
"extra-small": "extra-small";
|
|
75
|
+
small: "small";
|
|
76
|
+
medium: "medium";
|
|
77
|
+
large: "large";
|
|
78
|
+
"extra-large": "extra-large";
|
|
79
|
+
"extra-extra-large": "extra-extra-large";
|
|
80
|
+
"extra-extra-extra-large": "extra-extra-extra-large";
|
|
81
|
+
"accessibility-medium": "accessibility-medium";
|
|
82
|
+
"accessibility-large": "accessibility-large";
|
|
83
|
+
"accessibility-extra-large": "accessibility-extra-large";
|
|
84
|
+
"accessibility-extra-extra-large": "accessibility-extra-extra-large";
|
|
85
|
+
"accessibility-extra-extra-extra-large": "accessibility-extra-extra-extra-large";
|
|
86
|
+
}>, z.ZodNumber]>>;
|
|
87
|
+
boldText: z.ZodOptional<z.ZodBoolean>;
|
|
88
|
+
contrast: z.ZodOptional<z.ZodEnum<{
|
|
89
|
+
increased: "increased";
|
|
90
|
+
normal: "normal";
|
|
91
|
+
}>>;
|
|
92
|
+
reduceMotion: z.ZodOptional<z.ZodBoolean>;
|
|
93
|
+
}, z.core.$strict>>;
|
|
94
|
+
}, z.core.$strict>;
|
|
95
|
+
export type ConditionsConfig = z.input<typeof conditionsConfigSchema>;
|
|
96
|
+
/** What a spec's `device.network.set` takes: a condition, or its word. */
|
|
97
|
+
export type NetworkInput = NetworkCondition | "online" | "offline" | NetworkPreset;
|
|
98
|
+
/** A word, a custom shape (config) or a ready condition, as a `NetworkCondition`. */
|
|
99
|
+
export declare function toNetworkCondition(input: NetworkInput | z.infer<typeof customNetworkSchema>): Result<NetworkCondition>;
|
|
100
|
+
export type SuiteConditions = {
|
|
101
|
+
network?: NetworkCondition;
|
|
102
|
+
location?: LocationCondition;
|
|
103
|
+
clock?: ClockCondition;
|
|
104
|
+
permissions?: Partial<Record<PermissionName, PermissionState>>;
|
|
105
|
+
locale?: string;
|
|
106
|
+
timezone?: string;
|
|
107
|
+
a11y?: A11yCondition;
|
|
108
|
+
};
|
|
109
|
+
/** A config as conditions: shaped values, with a text size resolved. */
|
|
110
|
+
export declare function normalizeConditions(config: ConditionsConfig): Result<{
|
|
111
|
+
conditions: SuiteConditions;
|
|
112
|
+
}>;
|
|
113
|
+
/** The suite's `conditions` over the top-level ones, per area; undefined when neither sets any. */
|
|
114
|
+
export declare function mergeConditionsConfig(top: ConditionsConfig | undefined, own: ConditionsConfig | undefined): ConditionsConfig | undefined;
|
|
115
|
+
/** `WARDEN_CONDITIONS`: the JSON of a suite's config, checked again on the spec side. */
|
|
116
|
+
export declare function parseConditionsEnv(raw: string): Result<ConditionsConfig>;
|
|
117
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type Result } from "../../../types/src/result";
|
|
2
|
+
import type { Store } from "../store";
|
|
3
|
+
import { type ConditionArea, type ConditionRecord, type ConditionValues, type OwnedProcess } from "./conditions.types";
|
|
4
|
+
export type ConditionRow = {
|
|
5
|
+
area: ConditionArea;
|
|
6
|
+
json: string;
|
|
7
|
+
owned: string;
|
|
8
|
+
set_at: number;
|
|
9
|
+
};
|
|
10
|
+
export declare function toConditionRecord(row: ConditionRow): ConditionRecord;
|
|
11
|
+
/**
|
|
12
|
+
* Persist what a lease has set on its device so release, gc and a crashed process can undo it. One
|
|
13
|
+
* slot per area: recording again replaces the value. `owned` (processes undo must stop) is kept as
|
|
14
|
+
* is when omitted and replaced when given — a caller swapping a proxy kills the old one itself.
|
|
15
|
+
*/
|
|
16
|
+
export declare function recordCondition<A extends ConditionArea>(store: Store, leaseId: string, area: A, value: ConditionValues[A], now: number, owned?: readonly OwnedProcess[]): Result<ConditionRecord>;
|
|
17
|
+
/** Forget one area of a lease; false when it was not set. */
|
|
18
|
+
export declare function clearCondition(store: Store, leaseId: string, area: ConditionArea): boolean;
|
|
19
|
+
/** Forget every area of a lease; returns how many were set. */
|
|
20
|
+
export declare function clearConditions(store: Store, leaseId: string): number;
|
|
21
|
+
/** What a lease currently has set, in `CONDITION_AREAS` order. */
|
|
22
|
+
export declare function activeConditions(store: Store, leaseId: string): ConditionRecord[];
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
export type NetworkPreset = "edge" | "3g" | "4g" | "lte" | "wifi" | "slow" | "lossy";
|
|
2
|
+
export type NetworkCondition = {
|
|
3
|
+
kind: "online";
|
|
4
|
+
} | {
|
|
5
|
+
kind: "offline";
|
|
6
|
+
} | {
|
|
7
|
+
kind: "preset";
|
|
8
|
+
preset: NetworkPreset;
|
|
9
|
+
} | {
|
|
10
|
+
kind: "custom";
|
|
11
|
+
latencyMs?: number;
|
|
12
|
+
jitterMs?: number;
|
|
13
|
+
downKbps?: number;
|
|
14
|
+
upKbps?: number;
|
|
15
|
+
lossPct?: number;
|
|
16
|
+
} | {
|
|
17
|
+
kind: "flap";
|
|
18
|
+
upMs: number;
|
|
19
|
+
downMs: number;
|
|
20
|
+
jitterPct?: number;
|
|
21
|
+
base?: NetworkCondition;
|
|
22
|
+
};
|
|
23
|
+
/** A per-host fault the lease's shaping proxy applies; `match` is a host glob. */
|
|
24
|
+
export type HostFault = {
|
|
25
|
+
match: string;
|
|
26
|
+
action: {
|
|
27
|
+
kind: "status";
|
|
28
|
+
code: number;
|
|
29
|
+
} | {
|
|
30
|
+
kind: "reset";
|
|
31
|
+
} | {
|
|
32
|
+
kind: "delay";
|
|
33
|
+
ms: number;
|
|
34
|
+
} | {
|
|
35
|
+
kind: "blackhole";
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
export type LatLon = {
|
|
39
|
+
lat: number;
|
|
40
|
+
lon: number;
|
|
41
|
+
};
|
|
42
|
+
export type LocationScenario = "city-run" | "city-bike" | "freeway";
|
|
43
|
+
export type LocationCondition = {
|
|
44
|
+
kind: "point";
|
|
45
|
+
lat: number;
|
|
46
|
+
lon: number;
|
|
47
|
+
altM?: number;
|
|
48
|
+
} | {
|
|
49
|
+
kind: "route";
|
|
50
|
+
points: readonly LatLon[];
|
|
51
|
+
speedMps?: number;
|
|
52
|
+
intervalS?: number;
|
|
53
|
+
loop?: boolean;
|
|
54
|
+
} | {
|
|
55
|
+
kind: "gpx";
|
|
56
|
+
path: string;
|
|
57
|
+
speedMps?: number;
|
|
58
|
+
} | {
|
|
59
|
+
kind: "scenario";
|
|
60
|
+
name: LocationScenario;
|
|
61
|
+
} | {
|
|
62
|
+
kind: "clear";
|
|
63
|
+
};
|
|
64
|
+
export type ClockCondition = {
|
|
65
|
+
kind: "real";
|
|
66
|
+
} | {
|
|
67
|
+
kind: "offset";
|
|
68
|
+
ms: number;
|
|
69
|
+
} | {
|
|
70
|
+
kind: "fixed";
|
|
71
|
+
at: string;
|
|
72
|
+
} | {
|
|
73
|
+
kind: "frozen";
|
|
74
|
+
at: string;
|
|
75
|
+
};
|
|
76
|
+
export type PermissionState = "granted" | "denied" | "unset";
|
|
77
|
+
/** The permission names a spec, `warden ui permission` and a suite's `conditions.permissions` use; each maps to platform services in the driver. */
|
|
78
|
+
export declare const PERMISSION_NAMES: readonly ["location", "locationAlways", "camera", "photos", "photosAdd", "contacts", "contactsLimited", "notifications", "microphone", "calendar", "reminders", "motion", "mediaLibrary", "siri"];
|
|
79
|
+
export type PermissionName = (typeof PERMISSION_NAMES)[number];
|
|
80
|
+
export declare const isPermissionName: (v: string) => v is PermissionName;
|
|
81
|
+
/** Runtime permission grants applied to apps on the device. */
|
|
82
|
+
export type PermissionCondition = {
|
|
83
|
+
grants: readonly {
|
|
84
|
+
bundleId: string;
|
|
85
|
+
permission: PermissionName;
|
|
86
|
+
state: PermissionState;
|
|
87
|
+
}[];
|
|
88
|
+
};
|
|
89
|
+
/** Locale applies to one app (`bundleId`); timezone to the device (Android) or the next launch (iOS). */
|
|
90
|
+
export type LocaleCondition = {
|
|
91
|
+
locale?: string;
|
|
92
|
+
timezone?: string;
|
|
93
|
+
bundleId?: string;
|
|
94
|
+
/** Android: the timezone before the first change, put back on release */
|
|
95
|
+
previousTimezone?: string;
|
|
96
|
+
};
|
|
97
|
+
/** A named text size (iOS content size categories) or a font scale where 1 is the default. */
|
|
98
|
+
export type TextSize = (typeof TEXT_SIZE_NAMES)[number] | number;
|
|
99
|
+
export declare const TEXT_SIZE_NAMES: readonly ["extra-small", "small", "medium", "large", "extra-large", "extra-extra-large", "extra-extra-extra-large", "accessibility-medium", "accessibility-large", "accessibility-extra-large", "accessibility-extra-extra-large", "accessibility-extra-extra-extra-large"];
|
|
100
|
+
export type A11yCondition = {
|
|
101
|
+
textSize?: TextSize;
|
|
102
|
+
/** resolved font scale of `textSize` (1 = default) */
|
|
103
|
+
fontScale?: number;
|
|
104
|
+
reduceMotion?: boolean;
|
|
105
|
+
boldText?: boolean;
|
|
106
|
+
contrast?: "increased" | "normal";
|
|
107
|
+
};
|
|
108
|
+
/** `cosmetic`: the status bar says so, the OS and the app still see the real battery (iOS simulators) */
|
|
109
|
+
export type BatteryCondition = {
|
|
110
|
+
level?: number;
|
|
111
|
+
charging?: boolean;
|
|
112
|
+
cosmetic?: boolean;
|
|
113
|
+
};
|
|
114
|
+
/** Everything a lease can have set on its device, one slot per area. */
|
|
115
|
+
export type ConditionValues = {
|
|
116
|
+
network: NetworkCondition;
|
|
117
|
+
location: LocationCondition;
|
|
118
|
+
clock: ClockCondition;
|
|
119
|
+
permission: PermissionCondition;
|
|
120
|
+
locale: LocaleCondition;
|
|
121
|
+
a11y: A11yCondition;
|
|
122
|
+
battery: BatteryCondition;
|
|
123
|
+
};
|
|
124
|
+
export type ConditionArea = keyof ConditionValues;
|
|
125
|
+
/** Display/iteration order; also the order undo resets areas in. */
|
|
126
|
+
export declare const CONDITION_AREAS: readonly ConditionArea[];
|
|
127
|
+
/** A process warden spawned to hold a condition (shaping proxy, route ticker); undo kills it. */
|
|
128
|
+
export type OwnedProcess = {
|
|
129
|
+
kind: "pid";
|
|
130
|
+
pid: number;
|
|
131
|
+
};
|
|
132
|
+
/** One active condition on a lease, discriminated by `area`. */
|
|
133
|
+
export type ConditionRecord = {
|
|
134
|
+
[A in ConditionArea]: {
|
|
135
|
+
area: A;
|
|
136
|
+
value: ConditionValues[A];
|
|
137
|
+
/** processes undo must stop */
|
|
138
|
+
owned: readonly OwnedProcess[];
|
|
139
|
+
setAt: number;
|
|
140
|
+
};
|
|
141
|
+
}[ConditionArea];
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The state file an iOS app launched with `libwarden-inject.dylib` watches (offline flag, wall-clock offset /
|
|
3
|
+
* freeze). The e2e driver owns the full format (`packages/e2e/src/driver/ios/inject.ts`); core only knows where it
|
|
4
|
+
* lives and how to put it back to neutral on release, so a still-running injected app stops seeing the lease's
|
|
5
|
+
* offline or shifted clock.
|
|
6
|
+
*/
|
|
7
|
+
export declare function injectStatePath(wardenHome: string, udid: string): string;
|
|
8
|
+
/**
|
|
9
|
+
* Write `{ seq: previous + 1, offline: false, clockOffsetMs: 0 }` (no freeze) when a state file exists; the dylib
|
|
10
|
+
* applies any higher `seq`. No file = no injected app was ever launched on the device: nothing to do.
|
|
11
|
+
* Returns an error message, or undefined.
|
|
12
|
+
*/
|
|
13
|
+
export declare function neutralizeInjectState(path: string): string | undefined;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the next launch of an app on an iOS simulator should carry: a simulator has no per-device timezone or
|
|
3
|
+
* locale, so both ride on the app's launch (`TZ` env, `-AppleLanguages` / `-AppleLocale` args). `warden ui` sets
|
|
4
|
+
* them in one process and launches in another, so they are staged in a file per device; release removes it.
|
|
5
|
+
*/
|
|
6
|
+
export type LaunchSettings = {
|
|
7
|
+
timezone?: string;
|
|
8
|
+
locale?: string;
|
|
9
|
+
};
|
|
10
|
+
export declare function launchSettingsPath(wardenHome: string, udid: string): string;
|
|
11
|
+
/** The staged settings; none (or an unreadable file) is `{}`. */
|
|
12
|
+
export declare function readLaunchSettings(path: string): LaunchSettings;
|
|
13
|
+
/** Replace the staged settings; an empty one removes the file. Returns an error message, or undefined. */
|
|
14
|
+
export declare function writeLaunchSettings(path: string, settings: LaunchSettings): string | undefined;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { NetworkCondition, NetworkPreset } from "./conditions.types";
|
|
2
|
+
/**
|
|
3
|
+
* One table for every layer that applies a network condition: the Android emulator console (`emu network
|
|
4
|
+
* speed|delay`, COND-3), the per-lease shaping proxy (COND-4) and the iOS per-app routing (COND-5).
|
|
5
|
+
*
|
|
6
|
+
* Shaping units: `latencyMs` is the added round-trip time, `jitterMs` the +/- spread on it, kbps are
|
|
7
|
+
* kilobits per second (0 = unlimited), `lossPct` is 0-100. The emulator console's `delay` is per packet, so
|
|
8
|
+
* its `min:max` is half the round trip, widened by the jitter.
|
|
9
|
+
*
|
|
10
|
+
* Sources: Apple Network Link Conditioner profiles (edge, 3g, lte, wifi, lossy = "Very Bad Network"),
|
|
11
|
+
* Chrome DevTools "Slow 3G" (400 kbps both ways, 2000 ms), WebPageTest "4G" (9 Mbps, 170 ms RTT).
|
|
12
|
+
*/
|
|
13
|
+
export type NetworkShaping = {
|
|
14
|
+
latencyMs: number;
|
|
15
|
+
jitterMs: number;
|
|
16
|
+
downKbps: number;
|
|
17
|
+
upKbps: number;
|
|
18
|
+
lossPct: number;
|
|
19
|
+
};
|
|
20
|
+
/** Arguments for `adb emu network speed <speed>` and `... delay <delay>`. */
|
|
21
|
+
export type EmuNetwork = {
|
|
22
|
+
speed: string;
|
|
23
|
+
delay: string;
|
|
24
|
+
};
|
|
25
|
+
export type NetworkPresetSpec = {
|
|
26
|
+
emu: EmuNetwork;
|
|
27
|
+
shaping: NetworkShaping;
|
|
28
|
+
};
|
|
29
|
+
export declare const NETWORK_PRESET_NAMES: readonly NetworkPreset[];
|
|
30
|
+
export declare const NETWORK_PRESETS: Readonly<Record<NetworkPreset, NetworkPresetSpec>>;
|
|
31
|
+
/** What a condition means for a device: no network at all, or shaped (all zeros = unshaped) with the emulator args. */
|
|
32
|
+
export type NetworkPlan = {
|
|
33
|
+
online: false;
|
|
34
|
+
} | ({
|
|
35
|
+
online: true;
|
|
36
|
+
} & NetworkPresetSpec);
|
|
37
|
+
/**
|
|
38
|
+
* The effective parameters of a condition. `custom` names only what it changes (the rest is unshaped, a
|
|
39
|
+
* direction left out takes the other's rate for the emulator); `flap` resolves to its up phase, because the
|
|
40
|
+
* scheduler (COND-6) drives the transitions as plain online/offline conditions.
|
|
41
|
+
*/
|
|
42
|
+
export declare function resolveNetwork(condition: NetworkCondition): NetworkPlan;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type Result } from "../../../types/src/result";
|
|
2
|
+
import { TEXT_SIZE_NAMES, type TextSize } from "./conditions.types";
|
|
3
|
+
/** Approximate font scale of each iOS content size category (UIFontMetrics body text; 1 = `large`, the default). */
|
|
4
|
+
export declare const TEXT_SIZE_SCALE: Record<(typeof TEXT_SIZE_NAMES)[number], number>;
|
|
5
|
+
/** The largest `font_scale` Android's own settings offer; asking for more is clamped to it. */
|
|
6
|
+
export declare const ANDROID_MAX_FONT_SCALE = 2;
|
|
7
|
+
export declare const ANDROID_MIN_FONT_SCALE = 0.5;
|
|
8
|
+
export type ResolvedTextSize = {
|
|
9
|
+
/** iOS content size category (the nearest one for a number) */
|
|
10
|
+
category: (typeof TEXT_SIZE_NAMES)[number];
|
|
11
|
+
/** font scale (the category's for a name) */
|
|
12
|
+
scale: number;
|
|
13
|
+
};
|
|
14
|
+
/** A name or a scale as both: the category iOS needs and the scale Android needs. */
|
|
15
|
+
export declare function resolveTextSize(size: TextSize): Result<ResolvedTextSize>;
|
|
16
|
+
/** Android's `font_scale` for `scale`, clamped to what the platform takes. */
|
|
17
|
+
export declare const androidFontScale: (scale: number) => number;
|
|
18
|
+
/** The part of an a11y request the platform applied (a skipped setting is not on the device, so not recorded). */
|
|
19
|
+
export declare function appliedA11y<T extends object>(op: T, applied: readonly string[]): Partial<T>;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Android fingerprint enrollment for the golden build emulator: set a screen-lock PIN, open the system
|
|
3
|
+
* enrollment wizard, answer its screens (PIN entry, Next/Agree/Done) and present the virtual finger until
|
|
4
|
+
* `dumpsys fingerprint` reports one enrolled. Slow (tens of seconds), so it runs once in the snapshot build
|
|
5
|
+
* and claims start enrolled. Idempotent: an already enrolled device is left alone. Emulator serials only.
|
|
6
|
+
*/
|
|
7
|
+
import { type AsyncResult } from "../../../types/src/result";
|
|
8
|
+
import type { Exec } from "../exec";
|
|
9
|
+
/**
|
|
10
|
+
* Whether `dumpsys fingerprint` lists an enrolled fingerprint: the JSON `"prints":[{"id":0,"count":N,…}]` of Android 10+
|
|
11
|
+
* (`count` = fingerprints enrolled for that user) or the older `Enrolled fingerprints: N` / `prints: N` lines.
|
|
12
|
+
*/
|
|
13
|
+
export declare function parseFingerprintEnrolled(dump: string): boolean;
|
|
14
|
+
export type TapTarget = {
|
|
15
|
+
label: string;
|
|
16
|
+
x: number;
|
|
17
|
+
y: number;
|
|
18
|
+
};
|
|
19
|
+
/** Centre of the first wizard button in a `uiautomator dump`. */
|
|
20
|
+
export declare function findTapTarget(xml: string): TapTarget | undefined;
|
|
21
|
+
/** The lock-screen credential entry the wizard asks before it starts. */
|
|
22
|
+
export declare function isPinEntry(xml: string): boolean;
|
|
23
|
+
export type EnrollDeps = {
|
|
24
|
+
exec: Exec;
|
|
25
|
+
sleep: (ms: number) => Promise<void>;
|
|
26
|
+
/** the adb binary (`androidTools(env).adb`) */
|
|
27
|
+
adb: string;
|
|
28
|
+
/** wizard rounds before giving up (default 60) */
|
|
29
|
+
maxSteps?: number;
|
|
30
|
+
/** pause after each round (default 700 ms) */
|
|
31
|
+
stepMs?: number;
|
|
32
|
+
};
|
|
33
|
+
export declare function enrollFingerprint(deps: EnrollDeps, serial: string): AsyncResult<{
|
|
34
|
+
alreadyEnrolled: boolean;
|
|
35
|
+
}>;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { RunnerBuildError } from "./runner-build.types";
|
|
2
|
+
/** `Unlock Chris's iPhone to Continue`: xcodebuild waits for the passcode and never says it is locked any other way. */
|
|
3
|
+
export declare const UNLOCK_LINE: RegExp;
|
|
4
|
+
/** The known failure in xcodebuild's / devicectl's output, or undefined for one warden has no fix for. */
|
|
5
|
+
export declare function classifyRunnerOutput(text: string): RunnerBuildError | undefined;
|
|
6
|
+
/** The last `lines` lines of the output: what `build_failed` shows when nothing classifies. */
|
|
7
|
+
export declare function outputTail(text: string, lines?: number): string;
|