@delacour/warden-e2e 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/chunk-7ns30pbp.mjs +5832 -0
- package/index.d.ts +1 -0
- package/index.mjs +1047 -0
- package/package.json +49 -4
- package/preload.d.ts +1 -0
- package/preload.mjs +4788 -0
- package/types/core/src/allocate.d.ts +23 -0
- package/types/core/src/builds/app-option.schema.d.ts +37 -0
- package/types/core/src/builds/install.d.ts +95 -0
- package/types/core/src/builds/resolve.d.ts +74 -0
- package/types/core/src/config.defaults.d.ts +25 -0
- package/types/core/src/exec.d.ts +18 -0
- package/types/core/src/liveness.d.ts +12 -0
- package/types/core/src/providers/android.d.ts +132 -0
- package/types/core/src/providers/health.d.ts +45 -0
- package/types/core/src/providers/provider.types.d.ts +56 -0
- package/types/core/src/store.d.ts +105 -0
- package/types/core/src/types.d.ts +126 -0
- package/types/e2e/src/alert-buttons.d.ts +41 -0
- package/types/e2e/src/alert-reader.d.ts +25 -0
- package/types/e2e/src/app.d.ts +62 -0
- package/types/e2e/src/device-devtools.d.ts +26 -0
- package/types/e2e/src/device-link.d.ts +43 -0
- package/types/e2e/src/device.d.ts +90 -0
- package/types/e2e/src/devtools/cdp.d.ts +65 -0
- package/types/e2e/src/devtools/console.d.ts +59 -0
- package/types/e2e/src/devtools/devtools.types.d.ts +169 -0
- package/types/e2e/src/devtools/facet.d.ts +28 -0
- package/types/e2e/src/devtools/inspector.d.ts +48 -0
- package/types/e2e/src/devtools/jsonl.d.ts +4 -0
- package/types/e2e/src/devtools/native-log.d.ts +50 -0
- package/types/e2e/src/devtools/network.d.ts +33 -0
- package/types/e2e/src/devtools/network.script.d.ts +24 -0
- package/types/e2e/src/devtools/session.d.ts +82 -0
- package/types/e2e/src/devtools/tree.d.ts +23 -0
- package/types/e2e/src/devtools/tree.script.d.ts +33 -0
- package/types/e2e/src/driver/alerts.d.ts +29 -0
- package/types/e2e/src/driver/android/adb-session.d.ts +52 -0
- package/types/e2e/src/driver/android/android-alert.d.ts +4 -0
- package/types/e2e/src/driver/android/android-animations.d.ts +29 -0
- package/types/e2e/src/driver/android/android-app.d.ts +32 -0
- package/types/e2e/src/driver/android/android-argv.d.ts +122 -0
- package/types/e2e/src/driver/android/android-device.d.ts +44 -0
- package/types/e2e/src/driver/android/android-driver.d.ts +60 -0
- package/types/e2e/src/driver/android/android-dump.d.ts +46 -0
- package/types/e2e/src/driver/android/android-state.d.ts +69 -0
- package/types/e2e/src/driver/android/uiad-backend.d.ts +63 -0
- package/types/e2e/src/driver/android/uiad-client.d.ts +60 -0
- package/types/e2e/src/driver/android/uiad-device.d.ts +70 -0
- package/types/e2e/src/driver/android/uiad-dex.generated.d.ts +8 -0
- package/types/e2e/src/driver/android/uiad-ops.d.ts +62 -0
- package/types/e2e/src/driver/android/uiad-protocol.d.ts +209 -0
- package/types/e2e/src/driver/android/uiautomator.d.ts +33 -0
- package/types/e2e/src/driver/driver.types.d.ts +231 -0
- package/types/e2e/src/driver/errors.d.ts +13 -0
- package/types/e2e/src/driver/gesture-geometry.d.ts +48 -0
- package/types/e2e/src/driver/ios/hid-recovery.d.ts +36 -0
- package/types/e2e/src/driver/ios/ios-driver.d.ts +63 -0
- package/types/e2e/src/driver/ios/ios-session.d.ts +30 -0
- package/types/e2e/src/driver/ios/locate-helper.d.ts +38 -0
- package/types/e2e/src/driver/ios/normalize.d.ts +11 -0
- package/types/e2e/src/driver/ios/simctl.d.ts +102 -0
- package/types/e2e/src/driver/ios/simd-client.d.ts +68 -0
- package/types/e2e/src/driver/ios/simd-protocol.d.ts +433 -0
- package/types/e2e/src/driver/ios/simd-transport.d.ts +43 -0
- package/types/e2e/src/driver/ios/type-budget.d.ts +6 -0
- package/types/e2e/src/driver/observation-cache.d.ts +66 -0
- package/types/e2e/src/driver/open.d.ts +29 -0
- package/types/e2e/src/driver/settle.d.ts +43 -0
- package/types/e2e/src/driver/span-detail.d.ts +12 -0
- package/types/e2e/src/driver/traced-driver.d.ts +14 -0
- package/types/e2e/src/driver/typing.d.ts +48 -0
- package/types/e2e/src/driver/unwrap.d.ts +13 -0
- package/types/e2e/src/env.d.ts +78 -0
- package/types/e2e/src/errors.d.ts +26 -0
- package/types/e2e/src/expect/expect.d.ts +27 -0
- package/types/e2e/src/expect/matchers.d.ts +72 -0
- package/types/e2e/src/fixture-session.d.ts +38 -0
- package/types/e2e/src/fixtures.d.ts +14 -0
- package/types/e2e/src/index.d.ts +11 -0
- package/types/e2e/src/perf/budgets.d.ts +33 -0
- package/types/e2e/src/perf/device-perf.d.ts +45 -0
- package/types/e2e/src/perf/perf.types.d.ts +59 -0
- package/types/e2e/src/preload.d.ts +29 -0
- package/types/e2e/src/runner/artifacts.d.ts +13 -0
- package/types/e2e/src/runner/attempt.d.ts +63 -0
- package/types/e2e/src/runner/callsite.d.ts +2 -0
- package/types/e2e/src/runner/failure.d.ts +40 -0
- package/types/e2e/src/runner/fixtures.types.d.ts +60 -0
- package/types/e2e/src/runner/results.d.ts +48 -0
- package/types/e2e/src/runner/scope.d.ts +64 -0
- package/types/e2e/src/runner/span.d.ts +40 -0
- package/types/e2e/src/runner/step.d.ts +7 -0
- package/types/e2e/src/runner/test.d.ts +59 -0
- package/types/e2e/src/runner/trace.d.ts +87 -0
- package/types/e2e/src/screen/actions.d.ts +93 -0
- package/types/e2e/src/screen/context.d.ts +134 -0
- package/types/e2e/src/screen/field.d.ts +121 -0
- package/types/e2e/src/screen/locator.d.ts +233 -0
- package/types/e2e/src/screen/nodes.d.ts +53 -0
- package/types/e2e/src/screen/poll.d.ts +57 -0
- package/types/e2e/src/screen/queries.d.ts +34 -0
- package/types/e2e/src/screen/query.d.ts +60 -0
- package/types/e2e/src/screen/react-locator.d.ts +30 -0
- package/types/e2e/src/screen/render.d.ts +42 -0
- package/types/e2e/src/screen/roles.d.ts +24 -0
- package/types/e2e/src/screen/screen.d.ts +61 -0
- package/types/e2e/src/screen/selector.d.ts +38 -0
- package/types/e2e/src/screen/text.d.ts +13 -0
- package/types/e2e/src/session.d.ts +124 -0
- package/types/e2e/src/traced-device.d.ts +9 -0
- package/types/e2e/src/version.d.ts +7 -0
- package/types/types/src/result.d.ts +12 -0
- package/README.md +0 -3
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Logger } from "../driver.types";
|
|
2
|
+
import type { AdbSession } from "./adb-session";
|
|
3
|
+
import { type AnimationValues } from "./android-argv";
|
|
4
|
+
import { type AndroidStateStore } from "./android-state";
|
|
5
|
+
/**
|
|
6
|
+
* Animations off for the run (a flake source in e2e) and back on afterwards. The three scales are global
|
|
7
|
+
* settings that outlive the driver, so whatever warden changes it puts back when the driver closes, and the
|
|
8
|
+
* values it will put back are saved with the device's state (`android-state.ts`) before anything can go wrong:
|
|
9
|
+
* a process that dies without closing leaves the originals in the file, and the next driver opened on that
|
|
10
|
+
* emulator restores them when it closes.
|
|
11
|
+
*/
|
|
12
|
+
export type AnimationGuard = {
|
|
13
|
+
/** put the scales back (once); a restore that fails leaves the saved originals for the next driver */
|
|
14
|
+
restore(): Promise<void>;
|
|
15
|
+
};
|
|
16
|
+
export type AnimationDeps = {
|
|
17
|
+
session: AdbSession;
|
|
18
|
+
serial: string;
|
|
19
|
+
sdk: number | undefined;
|
|
20
|
+
/** originals a process that died left in the state file, if any */
|
|
21
|
+
pending: AnimationValues | undefined;
|
|
22
|
+
state?: AndroidStateStore;
|
|
23
|
+
log?: Logger;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* One shell round trip that reads the three scales and sets them to 0, then (when something has to be put
|
|
27
|
+
* back) saves the originals. Never fails the open: a device that refuses just keeps its animations.
|
|
28
|
+
*/
|
|
29
|
+
export declare function disableAnimations(deps: AnimationDeps): Promise<AnimationGuard>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Exec } from "../../../../core/src/exec";
|
|
2
|
+
import type { AppCommands, Logger } from "../driver.types";
|
|
3
|
+
import type { AdbSession } from "./adb-session";
|
|
4
|
+
export type AppDeps = {
|
|
5
|
+
session: AdbSession;
|
|
6
|
+
exec: Exec;
|
|
7
|
+
adb: string;
|
|
8
|
+
serial: string;
|
|
9
|
+
log?: Logger;
|
|
10
|
+
/** the app under test: `openUrl` is addressed to it (`am start -p`) so no other handler or chooser can take the link */
|
|
11
|
+
appPackage?: string;
|
|
12
|
+
/** how long `am start -W` may wait for the first frame (default `START_WAIT_TIMEOUT_MS`); tests shorten it */
|
|
13
|
+
startWaitTimeoutMs?: number;
|
|
14
|
+
/** how long the `pidof` after a timed-out start may take (default `START_PROBE_TIMEOUT_MS`); tests shorten it */
|
|
15
|
+
startProbeTimeoutMs?: number;
|
|
16
|
+
};
|
|
17
|
+
/** The component on the last non-empty line of `cmd package resolve-activity --brief`; undefined when there is none. */
|
|
18
|
+
export declare function parseLauncherComponent(stdout: string): string | undefined;
|
|
19
|
+
export type StartWait = {
|
|
20
|
+
status?: string;
|
|
21
|
+
launchState?: string;
|
|
22
|
+
totalTimeMs?: number;
|
|
23
|
+
error?: string;
|
|
24
|
+
};
|
|
25
|
+
/** What `am start -W` printed: `Status: ok`, `LaunchState: COLD` (API 29+), `TotalTime: 812`, or an `Error:` line. */
|
|
26
|
+
export declare function parseStartWait(stdout: string): StartWait;
|
|
27
|
+
/**
|
|
28
|
+
* `AppCommands` over `am` / `pm` (and `monkey` when the launcher cannot be resolved) in the session and host
|
|
29
|
+
* `adb install|uninstall` through `exec`. A launch is one `am start -W -S -a MAIN -c LAUNCHER -n <launcher>`: a binder call (no JVM start,
|
|
30
|
+
* where `monkey` is an `app_process` program) that returns once the first frame is drawn.
|
|
31
|
+
*/
|
|
32
|
+
export declare function createAppCommands(deps: AppDeps): AppCommands;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import type { Result } from "../../../../types/src/result";
|
|
2
|
+
import type { DriverError, HardwareButton, KeyName, Point } from "../driver.types";
|
|
3
|
+
import type { GesturePlan } from "../gesture-geometry";
|
|
4
|
+
/** POSIX single-quote a word for the device shell (`'` becomes `'\''`). Safe words stay bare. */
|
|
5
|
+
export declare function shellQuote(word: string): string;
|
|
6
|
+
/** One shell command line from an argv. */
|
|
7
|
+
export declare function shellCommand(argv: readonly string[]): string;
|
|
8
|
+
export declare function tapArgv(point: Point): string[];
|
|
9
|
+
export declare const DEFAULT_SWIPE_MS = 300;
|
|
10
|
+
export declare const DEFAULT_LONG_PRESS_MS = 600;
|
|
11
|
+
export declare function swipeArgv(from: Point, to: Point, durationMs?: number): string[];
|
|
12
|
+
export type MotionAction = "DOWN" | "MOVE" | "UP" | "CANCEL";
|
|
13
|
+
/** `input motionevent <action> x y` (Android 8 / sdk 26+): one touch event of a held gesture. */
|
|
14
|
+
export declare function motionEventArgv(action: MotionAction, point: Point): string[];
|
|
15
|
+
/** Lowest sdk with `input motionevent` (Android 8.0). */
|
|
16
|
+
export declare const MOTIONEVENT_MIN_SDK = 26;
|
|
17
|
+
/** `input keycombination` (and so select-all by Ctrl+A) exists from Android 13. */
|
|
18
|
+
export declare const SELECT_ALL_MIN_SDK = 33;
|
|
19
|
+
export declare const DRAG_STEPS = 8;
|
|
20
|
+
/** Fractional seconds for toybox `sleep` (`0.500`). */
|
|
21
|
+
export declare function sleepArgv(ms: number): string[];
|
|
22
|
+
export type DragTiming = {
|
|
23
|
+
pressMs: number;
|
|
24
|
+
durationMs: number;
|
|
25
|
+
releaseMs: number;
|
|
26
|
+
steps?: number;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* One shell line for a hold-then-move drag: DOWN at `from`, sleep `pressMs`, `steps` MOVEs interpolated over
|
|
30
|
+
* `durationMs`, sleep `releaseMs`, UP at `to`. Chained with `&&`; if anything fails after DOWN the finger is
|
|
31
|
+
* lifted with CANCEL and the line exits 1. Each `input` is a process start, so the sleeps are lower bounds.
|
|
32
|
+
*/
|
|
33
|
+
export declare function dragMotionCommand(from: Point, to: Point, timing: DragTiming): string;
|
|
34
|
+
/** Most MOVEs a one-finger gesture line sends: each `input motionevent` is a process start (~100+ ms). */
|
|
35
|
+
export declare const GESTURE_LINE_MAX_MOVES = 12;
|
|
36
|
+
/**
|
|
37
|
+
* One shell line for a one-finger `GesturePlan` (the fallback when `warden-uiad` is not there): DOWN at the first
|
|
38
|
+
* frame, sleep `holdMs`, at most `maxMoves` MOVEs at evenly spaced frame indices (the last frame always among
|
|
39
|
+
* them, the sleeps between sum to the plan's travel time), sleep `releaseMs`, UP at the last frame. Like
|
|
40
|
+
* `dragMotionCommand`, a failure after DOWN lifts the finger with CANCEL and exits 1. Needs one path.
|
|
41
|
+
*/
|
|
42
|
+
export declare function gestureMotionCommand(plan: GesturePlan, maxMoves?: number): string;
|
|
43
|
+
/** `input` has no long-press verb: a swipe that stays in place for the duration. */
|
|
44
|
+
export declare function longPressArgv(point: Point, durationMs?: number): string[];
|
|
45
|
+
export declare function keyeventArgv(keycode: number): string[];
|
|
46
|
+
/** Keycodes per `input keyevent` call; more are chunked into further calls. */
|
|
47
|
+
export declare const KEYEVENT_CHUNK = 32;
|
|
48
|
+
/** `times` presses of one keycode as `input keyevent` argvs (one process start per chunk instead of per key). */
|
|
49
|
+
export declare function keyRepeatArgvs(keycode: number, times: number): string[][];
|
|
50
|
+
/** `HardwareButton` to Android keycode; `shake` has no keycode. */
|
|
51
|
+
export declare function buttonKeycode(button: HardwareButton): number | undefined;
|
|
52
|
+
export declare function keyNameKeycode(key: KeyName): number;
|
|
53
|
+
/**
|
|
54
|
+
* `input text` argvs for `text`: printable ASCII only, anything else is `E_UNSUPPORTED_TEXT` (no unicode path
|
|
55
|
+
* without an IME or app helper). Spaces become `%s` (the one escape `input` understands) and a literal `%`
|
|
56
|
+
* before a space or `s` is typed by splitting into separate calls (see {@link splitForInputText}); quoting
|
|
57
|
+
* for the shell is {@link shellCommand}'s job. Empty text yields no argv.
|
|
58
|
+
*/
|
|
59
|
+
export declare function textArgvs(text: string): Result<string[][], DriverError>;
|
|
60
|
+
/** `am start` a VIEW intent for `url`; `packageName` (`-p`) pins it to the app under test instead of any handler. */
|
|
61
|
+
export declare function startUrlArgv(url: string, packageName?: string): string[];
|
|
62
|
+
/** Launch a package's launcher activity without knowing its activity name. */
|
|
63
|
+
export declare function monkeyLaunchArgv(packageName: string): string[];
|
|
64
|
+
/** The launcher activity of a package; `--brief` prints the component (`pkg/.Activity`) on its last line. */
|
|
65
|
+
export declare function resolveLauncherArgv(packageName: string): string[];
|
|
66
|
+
/**
|
|
67
|
+
* Start a known component and wait (`-W`) until its first frame is drawn; `stop` (`-S`) force-stops the app first, so
|
|
68
|
+
* a relaunch is one round trip. Prints `Status`, `LaunchState` and `TotalTime`. The intent is the launcher's own
|
|
69
|
+
* (MAIN + LAUNCHER, what `monkey` sends): it matches a running task's root, so without `-S` the app is brought to the
|
|
70
|
+
* front with its state, where a bare `-n` intent can stack a second instance on top.
|
|
71
|
+
*/
|
|
72
|
+
export declare function startComponentArgv(component: string, options: {
|
|
73
|
+
stop: boolean;
|
|
74
|
+
}): string[];
|
|
75
|
+
export declare function forceStopArgv(packageName: string): string[];
|
|
76
|
+
export declare function pmClearArgv(packageName: string): string[];
|
|
77
|
+
/** The three global settings that scale animations (flake source in e2e), in the order they are read and saved. */
|
|
78
|
+
export declare const ANIMATION_SETTINGS: readonly ["window_animation_scale", "transition_animation_scale", "animator_duration_scale"];
|
|
79
|
+
export type AnimationSetting = (typeof ANIMATION_SETTINGS)[number];
|
|
80
|
+
/** A value per animation setting as `settings get global` prints it: a number, or `null` when never set. */
|
|
81
|
+
export type AnimationValues = Record<AnimationSetting, string>;
|
|
82
|
+
/** Turn all three animation scales off. */
|
|
83
|
+
export declare function animationScaleArgvs(): string[][];
|
|
84
|
+
/** Read all three animation scales, one value per output line. */
|
|
85
|
+
export declare function animationReadArgvs(): string[][];
|
|
86
|
+
/** Put saved scales back: a value is `put` again, a setting that was never set (`null`) is deleted. */
|
|
87
|
+
export declare function animationRestoreArgvs(values: AnimationValues): string[][];
|
|
88
|
+
/** Host argv: `adb -s <serial> exec-out screencap -p` (binary PNG on stdout). */
|
|
89
|
+
export declare function screencapArgv(adb: string, serial: string): string[];
|
|
90
|
+
/** `getprop <key>` (boot flag, sdk level, model). */
|
|
91
|
+
export declare function getpropArgv(key: string): string[];
|
|
92
|
+
export declare const WM_SIZE_ARGV: readonly string[];
|
|
93
|
+
export declare const WM_DENSITY_ARGV: readonly string[];
|
|
94
|
+
export declare const DUMP_FILE_PATH = "/sdcard/warden-ui.xml";
|
|
95
|
+
/** Route 1: dump straight to the session's stdout. */
|
|
96
|
+
export declare function dumpStdoutArgv(): string[];
|
|
97
|
+
/** Route 2 (host argv): `exec-out` gives uiautomator a tty-less stdout it accepts as `/dev/tty`. */
|
|
98
|
+
export declare function dumpTtyHostArgv(adb: string, serial: string): string[];
|
|
99
|
+
/**
|
|
100
|
+
* Route 3: dump to a file on the device then `cat` it (works on every image). The file is removed first so a
|
|
101
|
+
* failed dump can never hand back the previous screen; uiautomator's own message (`ERROR: null root node…`)
|
|
102
|
+
* is echoed when no file appears.
|
|
103
|
+
*/
|
|
104
|
+
export declare function dumpFileCommand(path?: string): string;
|
|
105
|
+
/** Host argv: `adb -s <serial> install -r <apk>`. */
|
|
106
|
+
export declare function installArgv(adb: string, serial: string, apkPath: string): string[];
|
|
107
|
+
export declare function uninstallArgv(adb: string, serial: string, packageName: string): string[];
|
|
108
|
+
/** Exit 0 and `package:/…` when installed, exit 1 and nothing otherwise. */
|
|
109
|
+
export declare function pmPathArgv(packageName: string): string[];
|
|
110
|
+
/** Exit 0 and the pids when running (API 24+). */
|
|
111
|
+
export declare function pidofArgv(packageName: string): string[];
|
|
112
|
+
export type RotationValue = 0 | 1 | 2 | 3;
|
|
113
|
+
/** Lock auto-rotate off, then pin the rotation. */
|
|
114
|
+
export declare function rotationArgvs(rotation: RotationValue): string[][];
|
|
115
|
+
export declare function userRotationArgv(): string[];
|
|
116
|
+
export declare function uimodeGetArgv(): string[];
|
|
117
|
+
export declare function uimodeSetArgv(dark: boolean): string[];
|
|
118
|
+
/** `input keycombination` (API 33+): modifiers first, the key last. */
|
|
119
|
+
export declare function keycombinationArgv(keycodes: readonly number[]): string[];
|
|
120
|
+
/** Ctrl+A: select the whole text of the focused field (API 33+, see `SELECT_ALL_MIN_SDK`). */
|
|
121
|
+
export declare function selectAllArgv(): string[];
|
|
122
|
+
export declare function modifierKeycode(modifier: "shift" | "cmd" | "alt" | "ctrl"): number;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { DriverResult, Logger, Orientation, ScreenInfo } from "../driver.types";
|
|
2
|
+
import type { AdbSession } from "./adb-session";
|
|
3
|
+
import { type RotationValue } from "./android-argv";
|
|
4
|
+
/** Device facts read once at open: boot flag, screen size/density. Pure parsers + small session helpers. */
|
|
5
|
+
export declare const BOOT_TIMEOUT_MS = 30000;
|
|
6
|
+
export type Clock = {
|
|
7
|
+
now: () => number;
|
|
8
|
+
sleep: (ms: number) => Promise<void>;
|
|
9
|
+
};
|
|
10
|
+
export declare const realClock: Clock;
|
|
11
|
+
/** `wm size` prints `Physical size: WxH` and, when overridden, `Override size: WxH` (which wins). */
|
|
12
|
+
export declare function parseWmSize(text: string): {
|
|
13
|
+
width: number;
|
|
14
|
+
height: number;
|
|
15
|
+
} | undefined;
|
|
16
|
+
/** `wm density`: `Physical density: 420` / `Override density: 320`; scale = dpi / 160. */
|
|
17
|
+
export declare function parseWmDensity(text: string): number | undefined;
|
|
18
|
+
/** The sdk level on the probe's second line, if numeric. */
|
|
19
|
+
export declare function sdkFromProbe(stdout: string): number | undefined;
|
|
20
|
+
export type BootInfo = {
|
|
21
|
+
screen: ScreenInfo | undefined;
|
|
22
|
+
sdk: number | undefined;
|
|
23
|
+
};
|
|
24
|
+
/** The screen out of a boot probe's output, if `wm` answered. */
|
|
25
|
+
export declare function screenFromProbe(stdout: string): ScreenInfo | undefined;
|
|
26
|
+
/**
|
|
27
|
+
* Poll the boot probe until `sys.boot_completed` is `1`; `E_NOT_BOOTED` after `timeoutMs`. Returns the screen
|
|
28
|
+
* and sdk level from the same round trip (screen undefined when `wm` printed nothing parseable; use {@link readScreen}).
|
|
29
|
+
*/
|
|
30
|
+
export declare function waitForBoot(session: AdbSession, serial: string, clock: Clock, opts?: {
|
|
31
|
+
timeoutMs?: number;
|
|
32
|
+
log?: Logger;
|
|
33
|
+
}): DriverResult<BootInfo>;
|
|
34
|
+
/** Screen size + scale from `wm size` / `wm density`. */
|
|
35
|
+
export declare function readScreen(session: AdbSession, serial: string): DriverResult<ScreenInfo>;
|
|
36
|
+
/** Rotation reported by `<hierarchy rotation="N">` (0 when absent). */
|
|
37
|
+
export declare function hierarchyRotation(xml: string): number;
|
|
38
|
+
/** The screen as drawn: width/height swap on 90/270 degree rotations. */
|
|
39
|
+
export declare function rotatedScreen(base: ScreenInfo, rotation: number): ScreenInfo;
|
|
40
|
+
/** Android `user_rotation` to `Orientation` (0 portrait, 1 landscapeLeft, 2 upside down, 3 landscapeRight). */
|
|
41
|
+
export declare function rotationToOrientation(rotation: number): Orientation;
|
|
42
|
+
export declare function orientationToRotation(orientation: Orientation): RotationValue;
|
|
43
|
+
/** Run one argv inside the session; a non-zero exit becomes `failure` (default `E_HID`) with the output as message. */
|
|
44
|
+
export declare function runChecked(session: AdbSession, cmd: string, failure?: "E_HID" | "E_IO" | "E_APP_UNAVAILABLE" | "E_UNSUPPORTED" | "E_BAD_REQUEST", timeoutMs?: number): DriverResult<string>;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { Exec } from "../../../../core/src/exec";
|
|
2
|
+
import type { Driver, DriverResult, Element, Logger, Point, ScreenInfo } from "../driver.types";
|
|
3
|
+
import { type SpawnAdb } from "./adb-session";
|
|
4
|
+
import { type Clock } from "./android-device";
|
|
5
|
+
import { type DumpRoute } from "./android-dump";
|
|
6
|
+
import { type AndroidStateStore } from "./android-state";
|
|
7
|
+
import { type UiadOptions } from "./uiad-backend";
|
|
8
|
+
/** Binary-safe host process runner (screenshots); `Exec` is text-only. */
|
|
9
|
+
export type ExecBinary = (argv: readonly string[], opts?: {
|
|
10
|
+
timeoutMs?: number;
|
|
11
|
+
}) => Promise<{
|
|
12
|
+
exitCode: number;
|
|
13
|
+
stdout: Uint8Array;
|
|
14
|
+
}>;
|
|
15
|
+
/** Default {@link ExecBinary}: `Bun.spawn`, stderr dropped, never throws (a spawn failure is exit 127). */
|
|
16
|
+
export declare const bunExecBinary: ExecBinary;
|
|
17
|
+
export type AndroidDriverOptions = {
|
|
18
|
+
serial: string;
|
|
19
|
+
/** path of the `adb` binary (`androidTools(env).adb`) */
|
|
20
|
+
adb: string;
|
|
21
|
+
/** text-only host runner (`bunExec` in production): `adb install`, the `exec-out` dump route */
|
|
22
|
+
exec: Exec;
|
|
23
|
+
/** override how the resident shell is spawned (tests) */
|
|
24
|
+
spawn?: SpawnAdb;
|
|
25
|
+
log?: Logger;
|
|
26
|
+
/** skip `wm size`/`wm density` */
|
|
27
|
+
screen?: ScreenInfo;
|
|
28
|
+
/** binary runner for screenshots, default {@link bunExecBinary} */
|
|
29
|
+
execBinary?: ExecBinary;
|
|
30
|
+
/** injectable clock for boot polling and settle (default real time) */
|
|
31
|
+
clock?: Clock;
|
|
32
|
+
/** the dump route that worked last time (a `warden ui` call is a fresh process; skips probing the others) */
|
|
33
|
+
dumpRoute?: DumpRoute;
|
|
34
|
+
/** told when a dump route is first found to work or changes (`state`, when given, saves it too) */
|
|
35
|
+
onDumpRoute?: (route: DumpRoute) => void;
|
|
36
|
+
/**
|
|
37
|
+
* What warden remembers about this device between processes (`$WARDEN_HOME/android/<serial>.json`): the dump
|
|
38
|
+
* route that worked, and the animation scales to put back when the driver closes. Omit it and nothing is saved.
|
|
39
|
+
*/
|
|
40
|
+
state?: AndroidStateStore;
|
|
41
|
+
/** the app under test: `app.openUrl` is addressed to it (`am start -p`), so no other app or chooser takes the link */
|
|
42
|
+
appPackage?: string;
|
|
43
|
+
/** how long to wait for `sys.boot_completed`, default 30 s */
|
|
44
|
+
bootTimeoutMs?: number;
|
|
45
|
+
/**
|
|
46
|
+
* Use the resident UiAutomation server (`warden-uiad`) for snapshots, settle and input, with `uiautomator dump`
|
|
47
|
+
* and `input` as the fallback for any call it cannot do. Off unless given: `true` for the defaults, or the seams
|
|
48
|
+
* (a fake spawn, a fake dex push) a test replaces. API 26 and up only.
|
|
49
|
+
*/
|
|
50
|
+
uiad?: boolean | UiadOptions;
|
|
51
|
+
};
|
|
52
|
+
/** Deepest hittable element containing `point` (later in preorder wins ties: drawn on top); else deepest any. */
|
|
53
|
+
export declare function elementAt(flat: readonly Element[], point: Point): Element | null;
|
|
54
|
+
/**
|
|
55
|
+
* Android `Driver`: one resident `adb shell` (inputs, `am`, `pm`, dumps), host `adb` for install and
|
|
56
|
+
* `exec-out` (binary screenshots never touch the session). `settle` is implemented here over snapshots (one dump,
|
|
57
|
+
* or `settleByHash` for a long quiet window), so the TS layer needs no fallback (`nativeSettle: false`). The
|
|
58
|
+
* animation scales it turns off on open are put back by `close`.
|
|
59
|
+
*/
|
|
60
|
+
export declare function openAndroidDriver(options: AndroidDriverOptions): DriverResult<Driver>;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { Exec } from "../../../../core/src/exec";
|
|
2
|
+
import type { DriverResult, Logger } from "../driver.types";
|
|
3
|
+
import type { AdbSession } from "./adb-session";
|
|
4
|
+
/**
|
|
5
|
+
* Fetching a `uiautomator dump`. Images disagree on where the XML may go (on the Pixel 10 AVD
|
|
6
|
+
* `dump /dev/stdout` prints only the trailer, `exec-out … dump /dev/tty` works), so three routes are
|
|
7
|
+
* tried in order and the first that yields a `<hierarchy>` is remembered and tried first next time. A
|
|
8
|
+
* failed probe is a whole dump (~2 s), so the order is a guess worth getting right: see {@link routeOrder}.
|
|
9
|
+
*/
|
|
10
|
+
export type DumpRoute = "stdout" | "tty" | "file";
|
|
11
|
+
export declare const DUMP_ROUTES: readonly DumpRoute[];
|
|
12
|
+
/**
|
|
13
|
+
* From this API level `tty` is tried before `stdout` when nothing is remembered. Measured on the API 36 and 37
|
|
14
|
+
* emulator images, where `/dev/stdout` returns only the trailer; the cut-off at 30 is WDN-PERF's guess for the
|
|
15
|
+
* levels in between (a wrong guess costs one failed dump, once per device, then the route is remembered).
|
|
16
|
+
*/
|
|
17
|
+
export declare const TTY_FIRST_SDK = 30;
|
|
18
|
+
/**
|
|
19
|
+
* The order to try the routes in: `known` (a route that worked before) first, else `tty` first from
|
|
20
|
+
* {@link TTY_FIRST_SDK}, else `stdout`; the rest follow in that order.
|
|
21
|
+
*/
|
|
22
|
+
export declare function routeOrder(sdk: number | undefined, known?: DumpRoute): DumpRoute[];
|
|
23
|
+
export declare const DUMP_TIMEOUT_MS = 20000;
|
|
24
|
+
export type DumpDeps = {
|
|
25
|
+
session: AdbSession;
|
|
26
|
+
exec: Exec;
|
|
27
|
+
adb: string;
|
|
28
|
+
serial: string;
|
|
29
|
+
log?: Logger;
|
|
30
|
+
/** the device's API level: decides which route is tried first when none is remembered */
|
|
31
|
+
sdk?: number;
|
|
32
|
+
/** route to try first (remembered from an earlier process) */
|
|
33
|
+
initialRoute?: DumpRoute;
|
|
34
|
+
/** called whenever the working route changes (including the first discovery) */
|
|
35
|
+
onRoute?: (route: DumpRoute) => void;
|
|
36
|
+
};
|
|
37
|
+
export type Dumper = {
|
|
38
|
+
/** raw XML plus the route that produced it; `E_AX_UNAVAILABLE` when no route yields a hierarchy */
|
|
39
|
+
dump(): DriverResult<{
|
|
40
|
+
xml: string;
|
|
41
|
+
route: DumpRoute;
|
|
42
|
+
}>;
|
|
43
|
+
/** the route that last worked, if any */
|
|
44
|
+
route(): DumpRoute | undefined;
|
|
45
|
+
};
|
|
46
|
+
export declare function createDumper(deps: DumpDeps): Dumper;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { Logger } from "../driver.types";
|
|
2
|
+
import { type AnimationValues } from "./android-argv";
|
|
3
|
+
import type { DumpRoute } from "./android-dump";
|
|
4
|
+
/**
|
|
5
|
+
* What warden remembers about one Android device between processes (`$WARDEN_HOME/android/<serial>.json`):
|
|
6
|
+
* - the API level it was learned on (a serial is reused by whatever image the emulator runs next, so the other
|
|
7
|
+
* fields are trusted only on the same level);
|
|
8
|
+
* - the `uiautomator dump` route that worked (probing the wrong ones costs a whole dump each, ~2 s);
|
|
9
|
+
* - the build of `warden-uiad` that could not run here (an API level it does not support), so later processes go
|
|
10
|
+
* straight to the dump instead of paying a failed start each time;
|
|
11
|
+
* - the animation scales warden found before it turned them off, so a process that died without closing its
|
|
12
|
+
* driver does not leave the emulator animation-less for whatever runs next.
|
|
13
|
+
*
|
|
14
|
+
* Best effort throughout: a missing, unreadable or hand-edited file reads as empty and a failed write is logged,
|
|
15
|
+
* never thrown. The file is read back into shell commands, so every field is validated down to a number-shaped value.
|
|
16
|
+
*/
|
|
17
|
+
export type AndroidDeviceState = {
|
|
18
|
+
sdk?: number | undefined;
|
|
19
|
+
dumpRoute?: DumpRoute | undefined;
|
|
20
|
+
/** `uiadBuildId()` of a `warden-uiad` build that failed to start here for a reason that will not change */
|
|
21
|
+
uiadUnsupported?: string | undefined;
|
|
22
|
+
/** the scales to put back (what they were before warden set them to 0), while warden's zeros are in place */
|
|
23
|
+
animations?: AnimationValues | undefined;
|
|
24
|
+
};
|
|
25
|
+
/** The slice of `node:fs/promises` the store uses (injected in tests). */
|
|
26
|
+
export type StateFs = {
|
|
27
|
+
readFile(path: string): Promise<string>;
|
|
28
|
+
writeFile(path: string, data: string): Promise<void>;
|
|
29
|
+
rename(from: string, to: string): Promise<void>;
|
|
30
|
+
/** create the directory and its parents */
|
|
31
|
+
mkdir(path: string): Promise<void>;
|
|
32
|
+
};
|
|
33
|
+
export declare const nodeStateFs: StateFs;
|
|
34
|
+
export type AndroidStateStore = {
|
|
35
|
+
/** the saved state of `serial`; `{}` when there is none or it cannot be read */
|
|
36
|
+
read(serial: string): Promise<AndroidDeviceState>;
|
|
37
|
+
/** replace the state with `change(what is on disk now)`; never rejects, and writes nothing when nothing changed */
|
|
38
|
+
update(serial: string, change: (state: AndroidDeviceState) => AndroidDeviceState): Promise<void>;
|
|
39
|
+
};
|
|
40
|
+
/** `$WARDEN_HOME/android`. */
|
|
41
|
+
export declare function androidStateDir(home?: string): string;
|
|
42
|
+
/** `<dir>/<serial>.json`; anything in a serial that is not a plain file-name character becomes `_`. */
|
|
43
|
+
export declare function androidStateFile(dir: string, serial: string): string;
|
|
44
|
+
export type AndroidStateStoreOptions = {
|
|
45
|
+
dir: string;
|
|
46
|
+
fs?: StateFs;
|
|
47
|
+
log?: Logger;
|
|
48
|
+
};
|
|
49
|
+
export declare function createAndroidStateStore(options: AndroidStateStoreOptions): AndroidStateStore;
|
|
50
|
+
/** A store that lives in memory (tests, and callers that want no file). */
|
|
51
|
+
export declare function memoryAndroidState(): AndroidStateStore;
|
|
52
|
+
/** `state` when it was learned on this API level (`undefined` = unknown matches unknown), else nothing is known. */
|
|
53
|
+
export declare function forSdk(state: AndroidDeviceState, sdk: number | undefined): AndroidDeviceState;
|
|
54
|
+
/** An `update` callback: keep what was learned on this API level, record the level, apply `patch` (`undefined` removes a field). */
|
|
55
|
+
export declare function patchFor(sdk: number | undefined, patch: Omit<AndroidDeviceState, "sdk">): (state: AndroidDeviceState) => AndroidDeviceState;
|
|
56
|
+
/**
|
|
57
|
+
* The three values `settings get global` printed, one per line in {@link ANIMATION_SETTINGS} order. Lines that are
|
|
58
|
+
* not a value (an error message) are skipped; anything but exactly three values is unreadable (`undefined`).
|
|
59
|
+
*/
|
|
60
|
+
export declare function parseAnimationValues(stdout: string): AnimationValues | undefined;
|
|
61
|
+
/**
|
|
62
|
+
* What to put back when the driver closes, given the scales as found now (just before warden turns them off) and
|
|
63
|
+
* the originals a previous process saved and never restored (`pending`).
|
|
64
|
+
*
|
|
65
|
+
* A scale that reads `0` while `pending` has a value for it is warden's own leftover, so the saved value is the
|
|
66
|
+
* original; any other value was set by someone else since, and is kept as it is. `undefined` when every original is
|
|
67
|
+
* already `0` (animations were off before warden came: nothing to restore).
|
|
68
|
+
*/
|
|
69
|
+
export declare function planAnimations(current: AnimationValues, pending: AnimationValues | undefined): AnimationValues | undefined;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { DriverError, Logger } from "../driver.types";
|
|
2
|
+
import type { SimdSpawn } from "../ios/simd-transport";
|
|
3
|
+
import type { AdbSession } from "./adb-session";
|
|
4
|
+
import type { Clock } from "./android-device";
|
|
5
|
+
import { type UiadClient } from "./uiad-client";
|
|
6
|
+
import { launchUiad, type PushDex } from "./uiad-device";
|
|
7
|
+
/**
|
|
8
|
+
* The driver's hold on `warden-uiad`: started when the first call that can use it arrives (a spec that only opens
|
|
9
|
+
* links never pays for it), reused for every call after, and given up on, for this driver, when it cannot be made to
|
|
10
|
+
* work. "Given up" means the driver goes on with `uiautomator dump` and `input`: the server is an accelerator, never a
|
|
11
|
+
* requirement. The one failure that is not answered with a fallback is a busy UiAutomation slot, because the dump needs
|
|
12
|
+
* the same slot and would die with a bare "Killed": that is reported as what it is.
|
|
13
|
+
*/
|
|
14
|
+
/** Seams and switches for the resident server, from `AndroidDriverOptions.uiad`. */
|
|
15
|
+
export type UiadOptions = {
|
|
16
|
+
/** how the server's `adb shell` is spawned (tests); default a real `adb` child */
|
|
17
|
+
spawn?: SimdSpawn;
|
|
18
|
+
/** how the dex is written to the device (tests); default `adb shell cat > file` with the bytes on stdin */
|
|
19
|
+
push?: PushDex;
|
|
20
|
+
/** how long to wait for the server's `ready`, default 5 s */
|
|
21
|
+
readyTimeoutMs?: number;
|
|
22
|
+
/** how long a request may take before the server is judged hung, default 10 s */
|
|
23
|
+
requestTimeoutMs?: number;
|
|
24
|
+
};
|
|
25
|
+
export type UiadBackend = {
|
|
26
|
+
/** the server is switched on and has not been given up on */
|
|
27
|
+
wanted(): boolean;
|
|
28
|
+
/** the running server, started if need be; undefined means "use the dump and `input`" for this call */
|
|
29
|
+
server(): Promise<UiadClient | undefined>;
|
|
30
|
+
/** why UiAutomation is unavailable when something else holds it (the dump would fail the same way) */
|
|
31
|
+
blocked(): DriverError | undefined;
|
|
32
|
+
/** a call to the server failed: stop it so the dump can run; it is started again by the next `server()` unless it keeps failing */
|
|
33
|
+
failed(error: DriverError): Promise<void>;
|
|
34
|
+
/** stop the server, if there is one */
|
|
35
|
+
stop(): Promise<void>;
|
|
36
|
+
/** for `health`: "up", "idle", or why it is off */
|
|
37
|
+
status(): string;
|
|
38
|
+
};
|
|
39
|
+
export type UiadBackendDeps = {
|
|
40
|
+
session: AdbSession;
|
|
41
|
+
adb: string;
|
|
42
|
+
serial: string;
|
|
43
|
+
clock: Clock;
|
|
44
|
+
log?: Logger;
|
|
45
|
+
options: UiadOptions;
|
|
46
|
+
/** a build that this device is known not to run: the server is off from the start */
|
|
47
|
+
unsupportedBuild?: string;
|
|
48
|
+
/** told when a start failed for a reason that will not change, so the next process need not try */
|
|
49
|
+
onUnsupported?: (buildId: string) => void;
|
|
50
|
+
/** `launchUiad` (tests replace it) */
|
|
51
|
+
launch?: typeof launchUiad;
|
|
52
|
+
};
|
|
53
|
+
/** `WARDEN_ANDROID_UIAD=0` keeps the driver on `uiautomator dump` and `input` (the server is on otherwise). */
|
|
54
|
+
export declare const UIAD_ENV = "WARDEN_ANDROID_UIAD";
|
|
55
|
+
/** What a caller puts in `AndroidDriverOptions.uiad` for the environment it runs in: the server unless it is switched off. */
|
|
56
|
+
export declare function uiadFromEnv(env: Readonly<Record<string, string | undefined>>): true | undefined;
|
|
57
|
+
/**
|
|
58
|
+
* A start failure that retrying will not fix: the hidden API is missing, or the server cannot even load. An
|
|
59
|
+
* `E_UNSUPPORTED` is written down at once; a `connect_failed` only when {@link CONNECT_FAILURES_TO_PERSIST} starts in a
|
|
60
|
+
* row end with it, since the server cannot yet tell a missing API from a transient connect error.
|
|
61
|
+
*/
|
|
62
|
+
export declare function isPersistentStartFailure(error: DriverError): boolean;
|
|
63
|
+
export declare function createUiadBackend(deps: UiadBackendDeps): UiadBackend;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { DriverError, DriverResult, Logger } from "../driver.types";
|
|
2
|
+
import { type SimdSpawn, type TransportClose } from "../ios/simd-transport";
|
|
3
|
+
import { type UiadHealth, type UiadSettleData, type UiadSnapshotData, type UiadTextData } from "./uiad-protocol";
|
|
4
|
+
/**
|
|
5
|
+
* Client of `warden-uiad`, the resident UiAutomation server (`native/android/`), over the stdio of an `adb shell`
|
|
6
|
+
* that runs it. Starts it and waits for `ready`, sends one request at a time per op in order (the server answers in
|
|
7
|
+
* order), enforces a timeout per request, forwards `log` events, and maps `fatal`, a crash and a protocol mismatch to
|
|
8
|
+
* a `DriverError`. A timed-out request kills the server (what it was doing is unknowable); the driver starts a new one.
|
|
9
|
+
*/
|
|
10
|
+
export type UiadClient = {
|
|
11
|
+
/** what the server said about itself in `ready` */
|
|
12
|
+
readonly health: UiadHealth;
|
|
13
|
+
/** false once the server has exited, been killed, or timed out */
|
|
14
|
+
alive(): boolean;
|
|
15
|
+
request(op: string, params?: Record<string, unknown>, opts?: {
|
|
16
|
+
timeoutMs?: number;
|
|
17
|
+
}): DriverResult<unknown>;
|
|
18
|
+
snapshot(params?: SnapshotParams, opts?: {
|
|
19
|
+
timeoutMs?: number;
|
|
20
|
+
}): DriverResult<UiadSnapshotData>;
|
|
21
|
+
settle(params: {
|
|
22
|
+
quietMs: number;
|
|
23
|
+
timeoutMs: number;
|
|
24
|
+
}): DriverResult<UiadSettleData>;
|
|
25
|
+
typeText(text: string): DriverResult<UiadTextData>;
|
|
26
|
+
/** called once, if the server dies without `close()` having been asked for */
|
|
27
|
+
onDeath(handler: (info: TransportClose) => void): void;
|
|
28
|
+
/** `shutdown`, then close stdin, then kill when it lingers. Safe to call twice. */
|
|
29
|
+
close(): Promise<void>;
|
|
30
|
+
};
|
|
31
|
+
export type SnapshotParams = {
|
|
32
|
+
scope?: "active" | "all";
|
|
33
|
+
maxNodes?: number;
|
|
34
|
+
/** wait for this long without accessibility events before reading (the dump's built-in idle wait) */
|
|
35
|
+
idleMs?: number;
|
|
36
|
+
rootWaitMs?: number;
|
|
37
|
+
/** false keeps nodes the user cannot see, flagged `invisible` */
|
|
38
|
+
skipInvisible?: boolean;
|
|
39
|
+
/** false may answer from the server's node cache (2-3 ms), which can lag the app by an event */
|
|
40
|
+
fresh?: boolean;
|
|
41
|
+
windows?: boolean;
|
|
42
|
+
};
|
|
43
|
+
export type UiadStartOptions = {
|
|
44
|
+
/** the host command that runs the server on the device (`adb -s <serial> shell exec env CLASSPATH=... app_process / ...`) */
|
|
45
|
+
cmd: readonly string[];
|
|
46
|
+
spawn?: SimdSpawn;
|
|
47
|
+
log?: Logger;
|
|
48
|
+
/** how long to wait for `ready`, default 5 s (a start takes 80-450 ms, the long end on a busy host) */
|
|
49
|
+
readyTimeoutMs?: number;
|
|
50
|
+
/** default per-request timeout, default 10 s */
|
|
51
|
+
requestTimeoutMs?: number;
|
|
52
|
+
/** how long `close()` waits for the server to exit after closing stdin, default 1 s */
|
|
53
|
+
graceMs?: number;
|
|
54
|
+
};
|
|
55
|
+
/** Start the server and wait for `ready`; the failure says why (busy slot, protocol, crash, timeout). */
|
|
56
|
+
export declare function startUiad(options: UiadStartOptions): DriverResult<UiadClient>;
|
|
57
|
+
/** True when the request never reached the server (it was already gone), so the call can be made again without doubling its effect. */
|
|
58
|
+
export declare function neverSent(error: DriverError): boolean;
|
|
59
|
+
/** True when `error` is the server saying another UiAutomation client holds the device. */
|
|
60
|
+
export declare function isBusy(error: DriverError): boolean;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { DriverError, DriverResult, Logger } from "../driver.types";
|
|
2
|
+
import type { SimdSpawn } from "../ios/simd-transport";
|
|
3
|
+
import type { AdbSession } from "./adb-session";
|
|
4
|
+
import { type UiadClient } from "./uiad-client";
|
|
5
|
+
import { UIAD_DEX } from "./uiad-dex.generated";
|
|
6
|
+
/**
|
|
7
|
+
* Getting `warden-uiad` running on one device: the dex is written to `/data/local/tmp` (once per build, named by its
|
|
8
|
+
* hash), the server is started as the shell user with `app_process`, and a busy UiAutomation slot is dealt with: a
|
|
9
|
+
* stale server of ours is killed, anything else is named in the error and left alone. Everything here is argv and
|
|
10
|
+
* parsing around injected seams (`AdbSession`, `PushDex`, `SimdSpawn`), so it is tested without adb.
|
|
11
|
+
*/
|
|
12
|
+
export declare const UIAD_DEVICE_DIR = "/data/local/tmp";
|
|
13
|
+
/** The server is built for API 26 and up (`d8 --min-api 26`); an older device keeps the dump and `input`. */
|
|
14
|
+
export declare const UIAD_MIN_SDK = 26;
|
|
15
|
+
export type UiadDex = Pick<typeof UIAD_DEX, "bytes" | "dexSha256" | "base64">;
|
|
16
|
+
/** `/data/local/tmp/warden-uiad-<hash8>.dex`: a new build is a new file, so an old build is never started by mistake. */
|
|
17
|
+
export declare function uiadDevicePath(dex?: Pick<UiadDex, "dexSha256">): string;
|
|
18
|
+
/** The first eight characters of the dex hash: what the saved state keys "this build does not work here" on. */
|
|
19
|
+
export declare function uiadBuildId(dex?: Pick<UiadDex, "dexSha256">): string;
|
|
20
|
+
/** `exec env CLASSPATH=<dex> app_process / <Main>`: `exec` keeps the pipe on the server rather than on a wrapper shell. */
|
|
21
|
+
export declare function uiadLaunchCommand(devicePath: string): string;
|
|
22
|
+
/** The host command that runs the server and carries its stdio. */
|
|
23
|
+
export declare function uiadLaunchArgv(adb: string, serial: string, devicePath: string): string[];
|
|
24
|
+
/** Writes stdin to a temp name, makes it read-only, renames it into place: a half-written file is never `present`. */
|
|
25
|
+
export declare function uiadInstallCommand(devicePath: string): string;
|
|
26
|
+
export declare function uiadInstallArgv(adb: string, serial: string, devicePath: string): string[];
|
|
27
|
+
/** Prints `present` when the dex is on the device with the size it should have. */
|
|
28
|
+
export declare function uiadPresentCommand(devicePath: string, bytes: number): string;
|
|
29
|
+
/** Runs a host command with `bytes` on its stdin (`adb shell cat > file`). */
|
|
30
|
+
export type PushDex = (argv: readonly string[], bytes: Uint8Array) => Promise<{
|
|
31
|
+
exitCode: number;
|
|
32
|
+
output: string;
|
|
33
|
+
}>;
|
|
34
|
+
export declare const bunPushDex: PushDex;
|
|
35
|
+
export type EnsureDexDeps = {
|
|
36
|
+
session: AdbSession;
|
|
37
|
+
adb: string;
|
|
38
|
+
serial: string;
|
|
39
|
+
push?: PushDex;
|
|
40
|
+
dex?: UiadDex;
|
|
41
|
+
log?: Logger;
|
|
42
|
+
};
|
|
43
|
+
/** The dex is on the device (written now if it was not); the result is its path. */
|
|
44
|
+
export declare function ensureUiadDex(deps: EnsureDexDeps): DriverResult<string>;
|
|
45
|
+
export type Holder = {
|
|
46
|
+
pid: number;
|
|
47
|
+
args: string;
|
|
48
|
+
/** a `warden-uiad` process: a server of ours that nobody is talking to any more */
|
|
49
|
+
ours: boolean;
|
|
50
|
+
};
|
|
51
|
+
export declare const HOLDERS_COMMAND = "ps -A -o PID,ARGS";
|
|
52
|
+
/**
|
|
53
|
+
* The processes in `ps -A -o PID,ARGS` output that can hold the one UiAutomation slot: a `warden-uiad` server (ours),
|
|
54
|
+
* an `am instrument` run (Maestro's driver, agent-device's helper, any test runner) or a `uiautomator` command.
|
|
55
|
+
*/
|
|
56
|
+
export declare function parseHolders(ps: string): Holder[];
|
|
57
|
+
/** What the failure says when the slot is taken by something that is not ours. */
|
|
58
|
+
export declare function busyError(serial: string, holders: readonly Holder[], cause: DriverError): DriverError;
|
|
59
|
+
export type LaunchDeps = EnsureDexDeps & {
|
|
60
|
+
spawn?: SimdSpawn;
|
|
61
|
+
readyTimeoutMs?: number;
|
|
62
|
+
requestTimeoutMs?: number;
|
|
63
|
+
sleep?: (ms: number) => Promise<void>;
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* Start `warden-uiad` on `serial` and wait for it to be ready. A busy slot is the one failure handled here: servers of
|
|
67
|
+
* ours still running (a host that died without its pipe closing, a second warden process on the same device) are
|
|
68
|
+
* killed and the start retried once; a foreign holder is named in the error and left alone.
|
|
69
|
+
*/
|
|
70
|
+
export declare function launchUiad(deps: LaunchDeps): DriverResult<UiadClient>;
|