agent-device 0.10.0 → 0.10.2
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/README.md +4 -607
- package/dist/src/331.js +3 -3
- package/dist/src/425.js +1 -0
- package/dist/src/bin.js +28 -28
- package/dist/src/core/dispatch.d.ts +2 -0
- package/dist/src/core/session-surface.d.ts +3 -0
- package/dist/src/core/settings-contract.d.ts +2 -1
- package/dist/src/daemon/android-system-dialog.d.ts +11 -0
- package/dist/src/daemon/app-log-ios.d.ts +2 -1
- package/dist/src/daemon/app-log-process.d.ts +1 -1
- package/dist/src/daemon/app-log.d.ts +1 -1
- package/dist/src/daemon/context.d.ts +2 -0
- package/dist/src/daemon/handlers/interaction-common.d.ts +30 -1
- package/dist/src/daemon/handlers/interaction-read.d.ts +14 -0
- package/dist/src/daemon/handlers/interaction-touch.d.ts +45 -0
- package/dist/src/daemon/handlers/interaction.d.ts +2 -0
- package/dist/src/daemon/handlers/record-trace-android.d.ts +18 -0
- package/dist/src/daemon/handlers/record-trace-ios.d.ts +52 -0
- package/dist/src/daemon/handlers/record-trace-recording.d.ts +32 -0
- package/dist/src/daemon/handlers/record-trace.d.ts +2 -7
- package/dist/src/daemon/handlers/snapshot-capture.d.ts +11 -4
- package/dist/src/daemon/record-trace-errors.d.ts +6 -0
- package/dist/src/daemon/recording-gestures.d.ts +3 -0
- package/dist/src/daemon/recording-telemetry.d.ts +20 -0
- package/dist/src/daemon/recording-timing.d.ts +24 -0
- package/dist/src/daemon/request-router.d.ts +6 -0
- package/dist/src/daemon/script-utils.d.ts +1 -0
- package/dist/src/daemon/snapshot-processing.d.ts +1 -0
- package/dist/src/daemon/touch-reference-frame.d.ts +7 -0
- package/dist/src/daemon/types.d.ts +65 -11
- package/dist/src/daemon.js +62 -36
- package/dist/src/platforms/android/index.d.ts +1 -1
- package/dist/src/platforms/android/input-actions.d.ts +5 -0
- package/dist/src/platforms/android/settings.d.ts +1 -1
- package/dist/src/platforms/ios/apps.d.ts +1 -1
- package/dist/src/platforms/ios/macos-helper.d.ts +69 -0
- package/dist/src/platforms/ios/runner-client.d.ts +2 -2
- package/dist/src/platforms/ios/runner-session.d.ts +5 -0
- package/dist/src/platforms/ios/runner-xctestrun.d.ts +3 -1
- package/dist/src/recording/overlay.d.ts +10 -0
- package/dist/src/utils/command-schema.d.ts +2 -0
- package/dist/src/utils/interactors.d.ts +8 -8
- package/dist/src/utils/snapshot-lines.d.ts +5 -2
- package/dist/src/utils/snapshot.d.ts +8 -1
- package/dist/src/utils/text-surface.d.ts +19 -0
- package/dist/src/utils/video.d.ts +9 -0
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandExecution.swift +196 -51
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Interaction.swift +133 -0
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Lifecycle.swift +1 -1
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Models.swift +33 -1
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+ScreenRecorder.swift +4 -6
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests.swift +1 -0
- package/ios-runner/AgentDeviceRunner/RecordingScripts/recording-overlay.swift +571 -0
- package/ios-runner/AgentDeviceRunner/RecordingScripts/recording-trim.swift +140 -0
- package/macos-helper/Package.swift +18 -0
- package/macos-helper/Sources/AgentDeviceMacOSHelper/SnapshotTraversal.swift +543 -0
- package/macos-helper/Sources/AgentDeviceMacOSHelper/main.swift +545 -0
- package/package.json +4 -1
- package/skills/agent-device/SKILL.md +25 -334
- package/skills/agent-device/references/bootstrap-install.md +167 -0
- package/skills/agent-device/references/coordinate-system.md +24 -4
- package/skills/agent-device/references/debugging.md +115 -0
- package/skills/agent-device/references/exploration.md +193 -0
- package/skills/agent-device/references/macos-desktop.md +55 -57
- package/skills/agent-device/references/remote-tenancy.md +56 -47
- package/skills/agent-device/references/verification.md +103 -0
- package/dist/src/274.js +0 -1
- package/dist/src/daemon/handlers/interaction-fill.d.ts +0 -3
- package/dist/src/daemon/handlers/interaction-press.d.ts +0 -3
- package/skills/agent-device/references/batching.md +0 -79
- package/skills/agent-device/references/logs-and-debug.md +0 -113
- package/skills/agent-device/references/perf-metrics.md +0 -53
- package/skills/agent-device/references/permissions.md +0 -70
- package/skills/agent-device/references/session-management.md +0 -101
- package/skills/agent-device/references/snapshot-refs.md +0 -102
- package/skills/agent-device/references/video-recording.md +0 -41
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { ensureAdb } from './adb.ts';
|
|
2
2
|
export { resolveAndroidApp, listAndroidApps, inferAndroidAppName, getAndroidAppState, openAndroidApp, isAmStartError, parseAndroidLaunchComponent, openAndroidDevice, closeAndroidApp, installAndroidInstallablePath, installAndroidInstallablePathAndResolvePackageName, installAndroidApp, reinstallAndroidApp, } from './app-lifecycle.ts';
|
|
3
|
-
export { pressAndroid, swipeAndroid, backAndroid, homeAndroid, appSwitcherAndroid, longPressAndroid, typeAndroid, focusAndroid, fillAndroid, scrollAndroid, scrollIntoViewAndroid, } from './input-actions.ts';
|
|
3
|
+
export { pressAndroid, swipeAndroid, backAndroid, homeAndroid, appSwitcherAndroid, longPressAndroid, typeAndroid, focusAndroid, fillAndroid, readAndroidTextAtPoint, scrollAndroid, scrollIntoViewAndroid, getAndroidScreenSize, } from './input-actions.ts';
|
|
4
4
|
export { type AndroidKeyboardState, getAndroidKeyboardState, dismissAndroidKeyboard, readAndroidClipboardText, writeAndroidClipboardText, } from './device-input-state.ts';
|
|
5
5
|
export { setAndroidSetting } from './settings.ts';
|
|
6
6
|
export { pushAndroidNotification } from './notifications.ts';
|
|
@@ -10,3 +10,8 @@ export declare function focusAndroid(device: DeviceInfo, x: number, y: number):
|
|
|
10
10
|
export declare function fillAndroid(device: DeviceInfo, x: number, y: number, text: string): Promise<void>;
|
|
11
11
|
export declare function scrollAndroid(device: DeviceInfo, direction: string, amount?: number): Promise<void>;
|
|
12
12
|
export declare function scrollIntoViewAndroid(device: DeviceInfo, text: string): Promise<void>;
|
|
13
|
+
export declare function getAndroidScreenSize(device: DeviceInfo): Promise<{
|
|
14
|
+
width: number;
|
|
15
|
+
height: number;
|
|
16
|
+
}>;
|
|
17
|
+
export declare function readAndroidTextAtPoint(device: DeviceInfo, x: number, y: number): Promise<string | null>;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { DeviceInfo } from '../../utils/device.ts';
|
|
2
2
|
import { type PermissionSettingOptions } from '../permission-utils.ts';
|
|
3
|
-
export declare function setAndroidSetting(device: DeviceInfo, setting: string, state: string, appPackage?: string, options?: PermissionSettingOptions): Promise<void>;
|
|
3
|
+
export declare function setAndroidSetting(device: DeviceInfo, setting: string, state: string, appPackage?: string, options?: PermissionSettingOptions): Promise<Record<string, unknown> | void>;
|
|
@@ -29,6 +29,6 @@ export declare function installIosInstallablePath(device: DeviceInfo, installabl
|
|
|
29
29
|
export declare function readIosClipboardText(device: DeviceInfo): Promise<string>;
|
|
30
30
|
export declare function writeIosClipboardText(device: DeviceInfo, text: string): Promise<void>;
|
|
31
31
|
export declare function pushIosNotification(device: DeviceInfo, bundleId: string, payload: Record<string, unknown>): Promise<void>;
|
|
32
|
-
export declare function setIosSetting(device: DeviceInfo, setting: string, state: string, appBundleId?: string, options?: PermissionSettingOptions): Promise<void>;
|
|
32
|
+
export declare function setIosSetting(device: DeviceInfo, setting: string, state: string, appBundleId?: string, options?: PermissionSettingOptions): Promise<Record<string, unknown> | void>;
|
|
33
33
|
export declare function listIosApps(device: DeviceInfo, filter?: 'user-installed' | 'all'): Promise<IosAppInfo[]>;
|
|
34
34
|
export declare function listSimulatorApps(device: DeviceInfo): Promise<IosAppInfo[]>;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { SessionSurface } from '../../core/session-surface.ts';
|
|
2
|
+
export type MacOsPermissionTarget = 'accessibility' | 'screen-recording' | 'input-monitoring';
|
|
3
|
+
export type MacOsSnapshotNode = {
|
|
4
|
+
index: number;
|
|
5
|
+
type?: string;
|
|
6
|
+
role?: string;
|
|
7
|
+
subrole?: string;
|
|
8
|
+
label?: string;
|
|
9
|
+
value?: string;
|
|
10
|
+
identifier?: string;
|
|
11
|
+
rect?: {
|
|
12
|
+
x: number;
|
|
13
|
+
y: number;
|
|
14
|
+
width: number;
|
|
15
|
+
height: number;
|
|
16
|
+
};
|
|
17
|
+
enabled?: boolean;
|
|
18
|
+
selected?: boolean;
|
|
19
|
+
hittable?: boolean;
|
|
20
|
+
depth?: number;
|
|
21
|
+
parentIndex?: number;
|
|
22
|
+
pid?: number;
|
|
23
|
+
bundleId?: string;
|
|
24
|
+
appName?: string;
|
|
25
|
+
windowTitle?: string;
|
|
26
|
+
surface?: string;
|
|
27
|
+
};
|
|
28
|
+
export declare function resolveMacOsHelperPackageRootFrom(modulePath: string): string;
|
|
29
|
+
export declare function resolveFrontmostMacOsApp(): Promise<{
|
|
30
|
+
bundleId?: string;
|
|
31
|
+
appName?: string;
|
|
32
|
+
pid?: number;
|
|
33
|
+
}>;
|
|
34
|
+
export declare function quitMacOsApp(bundleId: string): Promise<{
|
|
35
|
+
bundleId: string;
|
|
36
|
+
running: boolean;
|
|
37
|
+
terminated: boolean;
|
|
38
|
+
forceTerminated: boolean;
|
|
39
|
+
}>;
|
|
40
|
+
export declare function runMacOsPermissionAction(action: 'grant' | 'reset', target: MacOsPermissionTarget): Promise<{
|
|
41
|
+
target: MacOsPermissionTarget;
|
|
42
|
+
granted: boolean;
|
|
43
|
+
requested: boolean;
|
|
44
|
+
openedSettings: boolean;
|
|
45
|
+
action: 'grant' | 'reset';
|
|
46
|
+
message?: string;
|
|
47
|
+
}>;
|
|
48
|
+
export declare function runMacOsAlertAction(action: 'get' | 'accept' | 'dismiss', options?: {
|
|
49
|
+
bundleId?: string;
|
|
50
|
+
surface?: SessionSurface;
|
|
51
|
+
}): Promise<{
|
|
52
|
+
title?: string;
|
|
53
|
+
role?: string;
|
|
54
|
+
buttons?: string[];
|
|
55
|
+
action?: string;
|
|
56
|
+
bundleId?: string;
|
|
57
|
+
}>;
|
|
58
|
+
export declare function runMacOsSnapshotAction(surface: Exclude<SessionSurface, 'app'>): Promise<{
|
|
59
|
+
surface: Exclude<SessionSurface, 'app'>;
|
|
60
|
+
nodes: MacOsSnapshotNode[];
|
|
61
|
+
truncated: boolean;
|
|
62
|
+
backend: 'macos-helper';
|
|
63
|
+
}>;
|
|
64
|
+
export declare function runMacOsReadTextAction(x: number, y: number, options?: {
|
|
65
|
+
bundleId?: string;
|
|
66
|
+
surface?: SessionSurface;
|
|
67
|
+
}): Promise<{
|
|
68
|
+
text: string;
|
|
69
|
+
}>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { DeviceInfo } from '../../utils/device.ts';
|
|
2
2
|
import type { ClickButton } from '../../core/click-button.ts';
|
|
3
3
|
export type RunnerCommand = {
|
|
4
|
-
command: 'tap' | 'mouseClick' | 'tapSeries' | 'longPress' | 'drag' | 'dragSeries' | 'type' | 'swipe' | 'findText' | 'snapshot' | 'screenshot' | 'back' | 'home' | 'appSwitcher' | 'alert' | 'pinch' | 'recordStart' | 'recordStop' | 'shutdown';
|
|
4
|
+
command: 'tap' | 'mouseClick' | 'tapSeries' | 'longPress' | 'drag' | 'dragSeries' | 'type' | 'swipe' | 'findText' | 'readText' | 'snapshot' | 'screenshot' | 'back' | 'home' | 'appSwitcher' | 'alert' | 'pinch' | 'recordStart' | 'recordStop' | 'uptime' | 'shutdown';
|
|
5
5
|
appBundleId?: string;
|
|
6
6
|
text?: string;
|
|
7
7
|
action?: 'get' | 'accept' | 'dismiss';
|
|
@@ -35,4 +35,4 @@ export declare function runIosRunnerCommand(device: DeviceInfo, command: RunnerC
|
|
|
35
35
|
}): Promise<Record<string, unknown>>;
|
|
36
36
|
export { isRetryableRunnerError, shouldRetryRunnerConnectError, resolveRunnerEarlyExitHint, } from './runner-errors.ts';
|
|
37
37
|
export { resolveRunnerDestination, resolveRunnerBuildDestination, resolveRunnerMaxConcurrentDestinationsFlag, resolveRunnerSigningBuildSettings, resolveRunnerBundleBuildSettings, assertSafeDerivedCleanup, IOS_RUNNER_CONTAINER_BUNDLE_IDS, } from './runner-xctestrun.ts';
|
|
38
|
-
export { stopIosRunnerSession, abortAllIosRunnerSessions, stopAllIosRunnerSessions, } from './runner-session.ts';
|
|
38
|
+
export { getRunnerSessionSnapshot, stopIosRunnerSession, abortAllIosRunnerSessions, stopAllIosRunnerSessions, } from './runner-session.ts';
|
|
@@ -2,6 +2,7 @@ import { type ExecResult, type ExecBackgroundResult } from '../../utils/exec.ts'
|
|
|
2
2
|
import type { DeviceInfo } from '../../utils/device.ts';
|
|
3
3
|
import type { RunnerCommand } from './runner-client.ts';
|
|
4
4
|
export type RunnerSession = {
|
|
5
|
+
sessionId: string;
|
|
5
6
|
device: DeviceInfo;
|
|
6
7
|
deviceId: string;
|
|
7
8
|
port: number;
|
|
@@ -16,6 +17,10 @@ export declare function ensureRunnerSession(device: DeviceInfo, options: {
|
|
|
16
17
|
logPath?: string;
|
|
17
18
|
traceLogPath?: string;
|
|
18
19
|
}): Promise<RunnerSession>;
|
|
20
|
+
export declare function getRunnerSessionSnapshot(deviceId: string): {
|
|
21
|
+
sessionId: string;
|
|
22
|
+
alive: boolean;
|
|
23
|
+
} | null;
|
|
19
24
|
export declare function stopRunnerSession(session: RunnerSession): Promise<void>;
|
|
20
25
|
export declare function stopIosRunnerSession(deviceId: string): Promise<void>;
|
|
21
26
|
export declare function abortAllIosRunnerSessions(): Promise<void>;
|
|
@@ -4,7 +4,7 @@ export declare const runnerPrepProcesses: Set<import("child_process").ChildProce
|
|
|
4
4
|
export declare const IOS_RUNNER_CONTAINER_BUNDLE_IDS: string[];
|
|
5
5
|
type EnsureXctestrunDeps = {
|
|
6
6
|
findProjectRoot: () => string;
|
|
7
|
-
findXctestrun: (root: string) => string | null;
|
|
7
|
+
findXctestrun: (root: string, device?: DeviceInfo) => string | null;
|
|
8
8
|
xctestrunReferencesProjectRoot: (xctestrunPath: string, projectRoot: string) => boolean;
|
|
9
9
|
resolveExistingXctestrunProductPaths: (xctestrunPath: string) => string[] | null;
|
|
10
10
|
repairRunnerProductsIfNeeded: (device: DeviceInfo, productPaths: string[], xctestrunPath: string) => Promise<void>;
|
|
@@ -22,6 +22,8 @@ export declare function ensureXctestrun(device: DeviceInfo, options: {
|
|
|
22
22
|
traceLogPath?: string;
|
|
23
23
|
}, deps?: EnsureXctestrunDeps): Promise<string>;
|
|
24
24
|
export declare function shouldDeleteRunnerDerivedRootEntry(entryName: string): boolean;
|
|
25
|
+
export declare function findXctestrun(root: string, device?: DeviceInfo): string | null;
|
|
26
|
+
export declare function scoreXctestrunCandidate(candidatePath: string, device: DeviceInfo): number;
|
|
25
27
|
export declare function xctestrunReferencesProjectRoot(xctestrunPath: string, projectRoot: string): boolean;
|
|
26
28
|
export declare function prepareXctestrunWithEnv(xctestrunPath: string, envVars: Record<string, string>, suffix: string): Promise<{
|
|
27
29
|
xctestrunPath: string;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare function getRecordingOverlaySupportWarning(hostPlatform?: NodeJS.Platform): string | undefined;
|
|
2
|
+
export declare function trimRecordingStart(params: {
|
|
3
|
+
videoPath: string;
|
|
4
|
+
trimStartMs: number;
|
|
5
|
+
}): Promise<void>;
|
|
6
|
+
export declare function overlayRecordingTouches(params: {
|
|
7
|
+
videoPath: string;
|
|
8
|
+
telemetryPath: string;
|
|
9
|
+
targetLabel?: string;
|
|
10
|
+
}): Promise<void>;
|
|
@@ -54,6 +54,7 @@ export type CliFlags = {
|
|
|
54
54
|
appsFilter?: 'user-installed' | 'all';
|
|
55
55
|
count?: number;
|
|
56
56
|
fps?: number;
|
|
57
|
+
hideTouches?: boolean;
|
|
57
58
|
intervalMs?: number;
|
|
58
59
|
holdMs?: number;
|
|
59
60
|
jitterPx?: number;
|
|
@@ -66,6 +67,7 @@ export type CliFlags = {
|
|
|
66
67
|
saveScript?: boolean | string;
|
|
67
68
|
shutdown?: boolean;
|
|
68
69
|
relaunch?: boolean;
|
|
70
|
+
surface?: 'app' | 'frontmost-app' | 'desktop' | 'menubar';
|
|
69
71
|
headless?: boolean;
|
|
70
72
|
restart?: boolean;
|
|
71
73
|
noRecord?: boolean;
|
|
@@ -15,14 +15,14 @@ type Interactor = {
|
|
|
15
15
|
}): Promise<void>;
|
|
16
16
|
openDevice(): Promise<void>;
|
|
17
17
|
close(app: string): Promise<void>;
|
|
18
|
-
tap(x: number, y: number): Promise<void>;
|
|
19
|
-
doubleTap(x: number, y: number): Promise<void>;
|
|
20
|
-
swipe(x1: number, y1: number, x2: number, y2: number, durationMs?: number): Promise<void>;
|
|
21
|
-
longPress(x: number, y: number, durationMs?: number): Promise<void>;
|
|
22
|
-
focus(x: number, y: number): Promise<void>;
|
|
18
|
+
tap(x: number, y: number): Promise<Record<string, unknown> | void>;
|
|
19
|
+
doubleTap(x: number, y: number): Promise<Record<string, unknown> | void>;
|
|
20
|
+
swipe(x1: number, y1: number, x2: number, y2: number, durationMs?: number): Promise<Record<string, unknown> | void>;
|
|
21
|
+
longPress(x: number, y: number, durationMs?: number): Promise<Record<string, unknown> | void>;
|
|
22
|
+
focus(x: number, y: number): Promise<Record<string, unknown> | void>;
|
|
23
23
|
type(text: string): Promise<void>;
|
|
24
|
-
fill(x: number, y: number, text: string): Promise<void>;
|
|
25
|
-
scroll(direction: string, amount?: number): Promise<void>;
|
|
24
|
+
fill(x: number, y: number, text: string): Promise<Record<string, unknown> | void>;
|
|
25
|
+
scroll(direction: string, amount?: number): Promise<Record<string, unknown> | void>;
|
|
26
26
|
scrollIntoView(text: string): Promise<{
|
|
27
27
|
attempts?: number;
|
|
28
28
|
} | void>;
|
|
@@ -32,7 +32,7 @@ type Interactor = {
|
|
|
32
32
|
appSwitcher(): Promise<void>;
|
|
33
33
|
readClipboard(): Promise<string>;
|
|
34
34
|
writeClipboard(text: string): Promise<void>;
|
|
35
|
-
setSetting(setting: string, state: string, appId?: string, options?: PermissionSettingOptions): Promise<void>;
|
|
35
|
+
setSetting(setting: string, state: string, appId?: string, options?: PermissionSettingOptions): Promise<Record<string, unknown> | void>;
|
|
36
36
|
};
|
|
37
37
|
export declare function getInteractor(device: DeviceInfo, runnerContext: RunnerContext): Interactor;
|
|
38
38
|
export {};
|
|
@@ -5,8 +5,11 @@ type SnapshotDisplayLine = {
|
|
|
5
5
|
type: string;
|
|
6
6
|
text: string;
|
|
7
7
|
};
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
type SnapshotLineFormatOptions = {
|
|
9
|
+
summarizeTextSurfaces?: boolean;
|
|
10
|
+
};
|
|
11
|
+
export declare function buildSnapshotDisplayLines(nodes: SnapshotNode[], options?: SnapshotLineFormatOptions): SnapshotDisplayLine[];
|
|
12
|
+
export declare function formatSnapshotLine(node: SnapshotNode, depth: number, hiddenGroup: boolean, normalizedType?: string, options?: SnapshotLineFormatOptions): string;
|
|
10
13
|
export declare function displayLabel(node: SnapshotNode, type: string): string;
|
|
11
14
|
export declare function formatRole(type: string): string;
|
|
12
15
|
export {};
|
|
@@ -14,6 +14,8 @@ export type SnapshotOptions = {
|
|
|
14
14
|
export type RawSnapshotNode = {
|
|
15
15
|
index: number;
|
|
16
16
|
type?: string;
|
|
17
|
+
role?: string;
|
|
18
|
+
subrole?: string;
|
|
17
19
|
label?: string;
|
|
18
20
|
value?: string;
|
|
19
21
|
identifier?: string;
|
|
@@ -23,6 +25,11 @@ export type RawSnapshotNode = {
|
|
|
23
25
|
hittable?: boolean;
|
|
24
26
|
depth?: number;
|
|
25
27
|
parentIndex?: number;
|
|
28
|
+
pid?: number;
|
|
29
|
+
bundleId?: string;
|
|
30
|
+
appName?: string;
|
|
31
|
+
windowTitle?: string;
|
|
32
|
+
surface?: string;
|
|
26
33
|
};
|
|
27
34
|
export type SnapshotNode = RawSnapshotNode & {
|
|
28
35
|
ref: string;
|
|
@@ -31,7 +38,7 @@ export type SnapshotState = {
|
|
|
31
38
|
nodes: SnapshotNode[];
|
|
32
39
|
createdAt: number;
|
|
33
40
|
truncated?: boolean;
|
|
34
|
-
backend?: 'xctest' | 'android';
|
|
41
|
+
backend?: 'xctest' | 'android' | 'macos-helper';
|
|
35
42
|
};
|
|
36
43
|
export declare function attachRefs(nodes: RawSnapshotNode[]): SnapshotNode[];
|
|
37
44
|
export declare function normalizeRef(input: string): string | null;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
type TextSurfaceNode = {
|
|
2
|
+
type?: string;
|
|
3
|
+
label?: string;
|
|
4
|
+
value?: string;
|
|
5
|
+
identifier?: string;
|
|
6
|
+
role?: string;
|
|
7
|
+
subrole?: string;
|
|
8
|
+
};
|
|
9
|
+
export declare function extractReadableText(node: TextSurfaceNode): string;
|
|
10
|
+
export declare function isLargeTextSurface(node: TextSurfaceNode, displayType?: string): boolean;
|
|
11
|
+
export declare function buildTextPreview(text: string): string;
|
|
12
|
+
export declare function describeTextSurface(node: TextSurfaceNode, displayType?: string): {
|
|
13
|
+
text: string;
|
|
14
|
+
isLargeSurface: boolean;
|
|
15
|
+
shouldSummarize: boolean;
|
|
16
|
+
};
|
|
17
|
+
export declare function shouldSummarizeTextSurface(text: string): boolean;
|
|
18
|
+
export declare function trimText(value: unknown): string;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare function waitForStableFile(filePath: string, options?: {
|
|
2
|
+
pollMs?: number;
|
|
3
|
+
attempts?: number;
|
|
4
|
+
}): Promise<void>;
|
|
5
|
+
export declare function isPlayableVideo(filePath: string): Promise<boolean>;
|
|
6
|
+
export declare function waitForPlayableVideo(filePath: string, options?: {
|
|
7
|
+
pollMs?: number;
|
|
8
|
+
attempts?: number;
|
|
9
|
+
}): Promise<void>;
|
package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandExecution.swift
CHANGED
|
@@ -3,6 +3,16 @@ import XCTest
|
|
|
3
3
|
extension RunnerTests {
|
|
4
4
|
// MARK: - Main Thread Dispatch
|
|
5
5
|
|
|
6
|
+
private func currentUptimeMs() -> Double {
|
|
7
|
+
ProcessInfo.processInfo.systemUptime * 1000
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
private func measureGesture(_ action: () -> Void) -> (gestureStartUptimeMs: Double, gestureEndUptimeMs: Double) {
|
|
11
|
+
let gestureStartUptimeMs = currentUptimeMs()
|
|
12
|
+
action()
|
|
13
|
+
return (gestureStartUptimeMs, currentUptimeMs())
|
|
14
|
+
}
|
|
15
|
+
|
|
6
16
|
func execute(command: Command) throws -> Response {
|
|
7
17
|
if Thread.isMainThread {
|
|
8
18
|
return try executeOnMainSafely(command: command)
|
|
@@ -175,7 +185,7 @@ extension RunnerTests {
|
|
|
175
185
|
}
|
|
176
186
|
do {
|
|
177
187
|
let resolvedOutPath = resolveRecordingOutPath(requestedOutPath)
|
|
178
|
-
let fpsLabel = command.fps.map(String.init) ??
|
|
188
|
+
let fpsLabel = command.fps.map(String.init) ?? String(RunnerTests.defaultRecordingFps)
|
|
179
189
|
NSLog(
|
|
180
190
|
"AGENT_DEVICE_RUNNER_RECORD_START requestedOutPath=%@ resolvedOutPath=%@ fps=%@",
|
|
181
191
|
requestedOutPath,
|
|
@@ -204,26 +214,80 @@ extension RunnerTests {
|
|
|
204
214
|
activeRecording = nil
|
|
205
215
|
return Response(ok: false, error: ErrorPayload(message: "failed to stop recording: \(error.localizedDescription)"))
|
|
206
216
|
}
|
|
217
|
+
case .uptime:
|
|
218
|
+
return Response(
|
|
219
|
+
ok: true,
|
|
220
|
+
data: DataPayload(currentUptimeMs: currentUptimeMs())
|
|
221
|
+
)
|
|
207
222
|
case .tap:
|
|
208
223
|
if let text = command.text {
|
|
209
224
|
if let element = findElement(app: activeApp, text: text) {
|
|
210
|
-
|
|
211
|
-
|
|
225
|
+
let timing = measureGesture {
|
|
226
|
+
withTemporaryScrollIdleTimeoutIfSupported(activeApp) {
|
|
227
|
+
element.tap()
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return Response(
|
|
231
|
+
ok: true,
|
|
232
|
+
data: DataPayload(
|
|
233
|
+
message: "tapped",
|
|
234
|
+
gestureStartUptimeMs: timing.gestureStartUptimeMs,
|
|
235
|
+
gestureEndUptimeMs: timing.gestureEndUptimeMs
|
|
236
|
+
)
|
|
237
|
+
)
|
|
212
238
|
}
|
|
213
239
|
return Response(ok: false, error: ErrorPayload(message: "element not found"))
|
|
214
240
|
}
|
|
215
241
|
if let x = command.x, let y = command.y {
|
|
216
|
-
|
|
217
|
-
|
|
242
|
+
let touchFrame = resolvedTouchVisualizationFrame(app: activeApp, x: x, y: y)
|
|
243
|
+
let timing = measureGesture {
|
|
244
|
+
withTemporaryScrollIdleTimeoutIfSupported(activeApp) {
|
|
245
|
+
tapAt(app: activeApp, x: x, y: y)
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return Response(
|
|
249
|
+
ok: true,
|
|
250
|
+
data: DataPayload(
|
|
251
|
+
message: "tapped",
|
|
252
|
+
gestureStartUptimeMs: timing.gestureStartUptimeMs,
|
|
253
|
+
gestureEndUptimeMs: timing.gestureEndUptimeMs,
|
|
254
|
+
x: touchFrame.x,
|
|
255
|
+
y: touchFrame.y,
|
|
256
|
+
referenceWidth: touchFrame.referenceWidth,
|
|
257
|
+
referenceHeight: touchFrame.referenceHeight
|
|
258
|
+
)
|
|
259
|
+
)
|
|
218
260
|
}
|
|
219
261
|
return Response(ok: false, error: ErrorPayload(message: "tap requires text or x/y"))
|
|
220
262
|
case .mouseClick:
|
|
221
263
|
guard let x = command.x, let y = command.y else {
|
|
222
264
|
return Response(ok: false, error: ErrorPayload(message: "mouseClick requires x and y"))
|
|
223
265
|
}
|
|
266
|
+
let touchFrame = resolvedTouchVisualizationFrame(app: activeApp, x: x, y: y)
|
|
224
267
|
do {
|
|
225
|
-
|
|
226
|
-
|
|
268
|
+
var clickError: Error?
|
|
269
|
+
let timing = measureGesture {
|
|
270
|
+
do {
|
|
271
|
+
try mouseClickAt(app: activeApp, x: x, y: y, button: command.button ?? "primary")
|
|
272
|
+
} catch {
|
|
273
|
+
clickError = error
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
if let clickError {
|
|
277
|
+
throw clickError
|
|
278
|
+
}
|
|
279
|
+
return Response(
|
|
280
|
+
ok: true,
|
|
281
|
+
data: DataPayload(
|
|
282
|
+
message: "clicked",
|
|
283
|
+
gestureStartUptimeMs: timing.gestureStartUptimeMs,
|
|
284
|
+
gestureEndUptimeMs: timing.gestureEndUptimeMs,
|
|
285
|
+
x: touchFrame.x,
|
|
286
|
+
y: touchFrame.y,
|
|
287
|
+
referenceWidth: touchFrame.referenceWidth,
|
|
288
|
+
referenceHeight: touchFrame.referenceHeight
|
|
289
|
+
)
|
|
290
|
+
)
|
|
227
291
|
} catch {
|
|
228
292
|
return Response(ok: false, error: ErrorPayload(message: error.localizedDescription))
|
|
229
293
|
}
|
|
@@ -234,32 +298,95 @@ extension RunnerTests {
|
|
|
234
298
|
let count = max(Int(command.count ?? 1), 1)
|
|
235
299
|
let intervalMs = max(command.intervalMs ?? 0, 0)
|
|
236
300
|
let doubleTap = command.doubleTap ?? false
|
|
301
|
+
let touchFrame = resolvedTouchVisualizationFrame(app: activeApp, x: x, y: y)
|
|
237
302
|
if doubleTap {
|
|
238
|
-
|
|
239
|
-
|
|
303
|
+
let timing = measureGesture {
|
|
304
|
+
withTemporaryScrollIdleTimeoutIfSupported(activeApp) {
|
|
305
|
+
runSeries(count: count, pauseMs: intervalMs) { _ in
|
|
306
|
+
doubleTapAt(app: activeApp, x: x, y: y)
|
|
307
|
+
}
|
|
308
|
+
}
|
|
240
309
|
}
|
|
241
|
-
return Response(
|
|
310
|
+
return Response(
|
|
311
|
+
ok: true,
|
|
312
|
+
data: DataPayload(
|
|
313
|
+
message: "tap series",
|
|
314
|
+
gestureStartUptimeMs: timing.gestureStartUptimeMs,
|
|
315
|
+
gestureEndUptimeMs: timing.gestureEndUptimeMs,
|
|
316
|
+
x: touchFrame.x,
|
|
317
|
+
y: touchFrame.y,
|
|
318
|
+
referenceWidth: touchFrame.referenceWidth,
|
|
319
|
+
referenceHeight: touchFrame.referenceHeight
|
|
320
|
+
)
|
|
321
|
+
)
|
|
242
322
|
}
|
|
243
|
-
|
|
244
|
-
|
|
323
|
+
let timing = measureGesture {
|
|
324
|
+
withTemporaryScrollIdleTimeoutIfSupported(activeApp) {
|
|
325
|
+
runSeries(count: count, pauseMs: intervalMs) { _ in
|
|
326
|
+
tapAt(app: activeApp, x: x, y: y)
|
|
327
|
+
}
|
|
328
|
+
}
|
|
245
329
|
}
|
|
246
|
-
return Response(
|
|
330
|
+
return Response(
|
|
331
|
+
ok: true,
|
|
332
|
+
data: DataPayload(
|
|
333
|
+
message: "tap series",
|
|
334
|
+
gestureStartUptimeMs: timing.gestureStartUptimeMs,
|
|
335
|
+
gestureEndUptimeMs: timing.gestureEndUptimeMs,
|
|
336
|
+
x: touchFrame.x,
|
|
337
|
+
y: touchFrame.y,
|
|
338
|
+
referenceWidth: touchFrame.referenceWidth,
|
|
339
|
+
referenceHeight: touchFrame.referenceHeight
|
|
340
|
+
)
|
|
341
|
+
)
|
|
247
342
|
case .longPress:
|
|
248
343
|
guard let x = command.x, let y = command.y else {
|
|
249
344
|
return Response(ok: false, error: ErrorPayload(message: "longPress requires x and y"))
|
|
250
345
|
}
|
|
251
346
|
let duration = (command.durationMs ?? 800) / 1000.0
|
|
252
|
-
|
|
253
|
-
|
|
347
|
+
let touchFrame = resolvedTouchVisualizationFrame(app: activeApp, x: x, y: y)
|
|
348
|
+
let timing = measureGesture {
|
|
349
|
+
withTemporaryScrollIdleTimeoutIfSupported(activeApp) {
|
|
350
|
+
longPressAt(app: activeApp, x: x, y: y, duration: duration)
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
return Response(
|
|
354
|
+
ok: true,
|
|
355
|
+
data: DataPayload(
|
|
356
|
+
message: "long pressed",
|
|
357
|
+
gestureStartUptimeMs: timing.gestureStartUptimeMs,
|
|
358
|
+
gestureEndUptimeMs: timing.gestureEndUptimeMs,
|
|
359
|
+
x: touchFrame.x,
|
|
360
|
+
y: touchFrame.y,
|
|
361
|
+
referenceWidth: touchFrame.referenceWidth,
|
|
362
|
+
referenceHeight: touchFrame.referenceHeight
|
|
363
|
+
)
|
|
364
|
+
)
|
|
254
365
|
case .drag:
|
|
255
366
|
guard let x = command.x, let y = command.y, let x2 = command.x2, let y2 = command.y2 else {
|
|
256
367
|
return Response(ok: false, error: ErrorPayload(message: "drag requires x, y, x2, and y2"))
|
|
257
368
|
}
|
|
258
369
|
let holdDuration = min(max((command.durationMs ?? 60) / 1000.0, 0.016), 10.0)
|
|
259
|
-
|
|
260
|
-
|
|
370
|
+
let dragFrame = resolvedDragVisualizationFrame(app: activeApp, x: x, y: y, x2: x2, y2: y2)
|
|
371
|
+
let timing = measureGesture {
|
|
372
|
+
withTemporaryScrollIdleTimeoutIfSupported(activeApp) {
|
|
373
|
+
dragAt(app: activeApp, x: x, y: y, x2: x2, y2: y2, holdDuration: holdDuration)
|
|
374
|
+
}
|
|
261
375
|
}
|
|
262
|
-
return Response(
|
|
376
|
+
return Response(
|
|
377
|
+
ok: true,
|
|
378
|
+
data: DataPayload(
|
|
379
|
+
message: "dragged",
|
|
380
|
+
gestureStartUptimeMs: timing.gestureStartUptimeMs,
|
|
381
|
+
gestureEndUptimeMs: timing.gestureEndUptimeMs,
|
|
382
|
+
x: dragFrame.x,
|
|
383
|
+
y: dragFrame.y,
|
|
384
|
+
x2: dragFrame.x2,
|
|
385
|
+
y2: dragFrame.y2,
|
|
386
|
+
referenceWidth: dragFrame.referenceWidth,
|
|
387
|
+
referenceHeight: dragFrame.referenceHeight
|
|
388
|
+
)
|
|
389
|
+
)
|
|
263
390
|
case .dragSeries:
|
|
264
391
|
guard let x = command.x, let y = command.y, let x2 = command.x2, let y2 = command.y2 else {
|
|
265
392
|
return Response(ok: false, error: ErrorPayload(message: "dragSeries requires x, y, x2, and y2"))
|
|
@@ -271,17 +398,26 @@ extension RunnerTests {
|
|
|
271
398
|
return Response(ok: false, error: ErrorPayload(message: "dragSeries pattern must be one-way or ping-pong"))
|
|
272
399
|
}
|
|
273
400
|
let holdDuration = min(max((command.durationMs ?? 60) / 1000.0, 0.016), 10.0)
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
401
|
+
let timing = measureGesture {
|
|
402
|
+
withTemporaryScrollIdleTimeoutIfSupported(activeApp) {
|
|
403
|
+
runSeries(count: count, pauseMs: pauseMs) { idx in
|
|
404
|
+
let reverse = pattern == "ping-pong" && (idx % 2 == 1)
|
|
405
|
+
if reverse {
|
|
406
|
+
dragAt(app: activeApp, x: x2, y: y2, x2: x, y2: y, holdDuration: holdDuration)
|
|
407
|
+
} else {
|
|
408
|
+
dragAt(app: activeApp, x: x, y: y, x2: x2, y2: y2, holdDuration: holdDuration)
|
|
409
|
+
}
|
|
281
410
|
}
|
|
282
411
|
}
|
|
283
412
|
}
|
|
284
|
-
return Response(
|
|
413
|
+
return Response(
|
|
414
|
+
ok: true,
|
|
415
|
+
data: DataPayload(
|
|
416
|
+
message: "drag series",
|
|
417
|
+
gestureStartUptimeMs: timing.gestureStartUptimeMs,
|
|
418
|
+
gestureEndUptimeMs: timing.gestureEndUptimeMs
|
|
419
|
+
)
|
|
420
|
+
)
|
|
285
421
|
case .type:
|
|
286
422
|
guard let text = command.text else {
|
|
287
423
|
return Response(ok: false, error: ErrorPayload(message: "type requires text"))
|
|
@@ -304,16 +440,36 @@ extension RunnerTests {
|
|
|
304
440
|
guard let direction = command.direction else {
|
|
305
441
|
return Response(ok: false, error: ErrorPayload(message: "swipe requires direction"))
|
|
306
442
|
}
|
|
307
|
-
|
|
308
|
-
|
|
443
|
+
let referenceFrame = resolvedGestureReferenceFrame(app: activeApp)
|
|
444
|
+
let timing = measureGesture {
|
|
445
|
+
withTemporaryScrollIdleTimeoutIfSupported(activeApp) {
|
|
446
|
+
swipe(app: activeApp, direction: direction)
|
|
447
|
+
}
|
|
309
448
|
}
|
|
310
|
-
return Response(
|
|
449
|
+
return Response(
|
|
450
|
+
ok: true,
|
|
451
|
+
data: DataPayload(
|
|
452
|
+
message: "swiped",
|
|
453
|
+
gestureStartUptimeMs: timing.gestureStartUptimeMs,
|
|
454
|
+
gestureEndUptimeMs: timing.gestureEndUptimeMs,
|
|
455
|
+
referenceWidth: referenceFrame.referenceWidth,
|
|
456
|
+
referenceHeight: referenceFrame.referenceHeight
|
|
457
|
+
)
|
|
458
|
+
)
|
|
311
459
|
case .findText:
|
|
312
460
|
guard let text = command.text else {
|
|
313
461
|
return Response(ok: false, error: ErrorPayload(message: "findText requires text"))
|
|
314
462
|
}
|
|
315
463
|
let found = findElement(app: activeApp, text: text) != nil
|
|
316
464
|
return Response(ok: true, data: DataPayload(found: found))
|
|
465
|
+
case .readText:
|
|
466
|
+
guard let x = command.x, let y = command.y else {
|
|
467
|
+
return Response(ok: false, error: ErrorPayload(message: "readText requires x and y"))
|
|
468
|
+
}
|
|
469
|
+
guard let text = readTextAt(app: activeApp, x: x, y: y) else {
|
|
470
|
+
return Response(ok: false, error: ErrorPayload(message: "readText did not resolve text"))
|
|
471
|
+
}
|
|
472
|
+
return Response(ok: true, data: DataPayload(text: text))
|
|
317
473
|
case .snapshot:
|
|
318
474
|
let options = SnapshotOptions(
|
|
319
475
|
interactiveOnly: command.interactiveOnly ?? false,
|
|
@@ -358,30 +514,15 @@ extension RunnerTests {
|
|
|
358
514
|
if tapNavigationBack(app: activeApp) {
|
|
359
515
|
return Response(ok: true, data: DataPayload(message: "back"))
|
|
360
516
|
}
|
|
361
|
-
#if os(macOS)
|
|
362
|
-
return Response(ok: false, error: ErrorPayload(message: "back button is not available on macOS"))
|
|
363
|
-
#else
|
|
364
517
|
performBackGesture(app: activeApp)
|
|
365
518
|
return Response(ok: true, data: DataPayload(message: "back"))
|
|
366
|
-
#endif
|
|
367
519
|
case .home:
|
|
368
|
-
#if os(macOS)
|
|
369
|
-
return Response(ok: false, error: ErrorPayload(message: "home is not supported on macOS"))
|
|
370
|
-
#else
|
|
371
520
|
pressHomeButton()
|
|
372
521
|
return Response(ok: true, data: DataPayload(message: "home"))
|
|
373
|
-
#endif
|
|
374
522
|
case .appSwitcher:
|
|
375
|
-
#if os(macOS)
|
|
376
|
-
return Response(ok: false, error: ErrorPayload(message: "appSwitcher is not supported on macOS"))
|
|
377
|
-
#else
|
|
378
523
|
performAppSwitcherGesture(app: activeApp)
|
|
379
524
|
return Response(ok: true, data: DataPayload(message: "appSwitcher"))
|
|
380
|
-
#endif
|
|
381
525
|
case .alert:
|
|
382
|
-
#if os(macOS)
|
|
383
|
-
return Response(ok: false, error: ErrorPayload(message: "alert is not supported on macOS"))
|
|
384
|
-
#else
|
|
385
526
|
let action = (command.action ?? "get").lowercased()
|
|
386
527
|
let alert = activeApp.alerts.firstMatch
|
|
387
528
|
if !alert.exists {
|
|
@@ -399,17 +540,21 @@ extension RunnerTests {
|
|
|
399
540
|
}
|
|
400
541
|
let buttonLabels = alert.buttons.allElementsBoundByIndex.map { $0.label }
|
|
401
542
|
return Response(ok: true, data: DataPayload(message: alert.label, items: buttonLabels))
|
|
402
|
-
#endif
|
|
403
543
|
case .pinch:
|
|
404
|
-
#if os(macOS)
|
|
405
|
-
return Response(ok: false, error: ErrorPayload(message: "pinch is not supported on macOS"))
|
|
406
|
-
#else
|
|
407
544
|
guard let scale = command.scale, scale > 0 else {
|
|
408
545
|
return Response(ok: false, error: ErrorPayload(message: "pinch requires scale > 0"))
|
|
409
546
|
}
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
547
|
+
let timing = measureGesture {
|
|
548
|
+
pinch(app: activeApp, scale: scale, x: command.x, y: command.y)
|
|
549
|
+
}
|
|
550
|
+
return Response(
|
|
551
|
+
ok: true,
|
|
552
|
+
data: DataPayload(
|
|
553
|
+
message: "pinched",
|
|
554
|
+
gestureStartUptimeMs: timing.gestureStartUptimeMs,
|
|
555
|
+
gestureEndUptimeMs: timing.gestureEndUptimeMs
|
|
556
|
+
)
|
|
557
|
+
)
|
|
413
558
|
}
|
|
414
559
|
}
|
|
415
560
|
}
|