agent-device 0.12.3 → 0.12.5
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/dist/src/152.js +1 -1
- package/dist/src/320.js +1 -1
- package/dist/src/57.js +1 -1
- package/dist/src/641.js +38 -0
- package/dist/src/818.js +1 -1
- package/dist/src/974.js +2 -2
- package/dist/src/backend.d.ts +205 -0
- package/dist/src/backend.js +1 -0
- package/dist/src/bin.js +63 -63
- package/dist/src/commands/index.d.ts +908 -0
- package/dist/src/commands/index.js +1 -0
- package/dist/src/contracts.d.ts +1 -1
- package/dist/src/daemon.js +15 -15
- package/dist/src/index.d.ts +898 -3
- package/dist/src/index.js +3 -3
- package/dist/src/io.d.ts +85 -0
- package/dist/src/io.js +1 -0
- package/dist/src/metro-companion.js +1 -1
- package/dist/src/metro.d.ts +10 -0
- package/dist/src/metro.js +1 -1
- package/dist/src/selectors.js +1 -1
- package/dist/src/testing/conformance.d.ts +416 -0
- package/dist/src/testing/conformance.js +1 -0
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandExecution.swift +12 -3
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Models.swift +1 -0
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+ScreenRecorder.swift +24 -5
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests.swift +2 -0
- package/ios-runner/AgentDeviceRunner/RecordingScripts/recording-resize.swift +182 -0
- package/ios-runner/RUNNER_PROTOCOL.md +1 -1
- package/package.json +17 -1
- package/skills/agent-device/references/bootstrap-install.md +13 -0
- package/skills/agent-device/references/remote-tenancy.md +15 -0
- package/skills/agent-device/references/verification.md +1 -0
- package/dist/src/155.js +0 -38
- package/dist/src/940.js +0 -1
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
declare type AgentDeviceBackend = {
|
|
2
|
+
platform: AgentDeviceBackendPlatform;
|
|
3
|
+
capabilities?: BackendCapabilitySet;
|
|
4
|
+
escapeHatches?: BackendEscapeHatches;
|
|
5
|
+
captureSnapshot?(context: BackendCommandContext, options?: BackendSnapshotOptions): Promise<BackendSnapshotResult>;
|
|
6
|
+
captureScreenshot?(context: BackendCommandContext, outPath: string, options?: BackendScreenshotOptions): Promise<BackendScreenshotResult | void>;
|
|
7
|
+
readText?(context: BackendCommandContext, node: SnapshotNode): Promise<BackendReadTextResult>;
|
|
8
|
+
findText?(context: BackendCommandContext, text: string): Promise<BackendFindTextResult>;
|
|
9
|
+
tap?(context: BackendCommandContext, point: Point, options?: BackendTapOptions): Promise<BackendActionResult>;
|
|
10
|
+
fill?(context: BackendCommandContext, point: Point, text: string, options?: BackendFillOptions): Promise<BackendActionResult>;
|
|
11
|
+
typeText?(context: BackendCommandContext, text: string, options?: {
|
|
12
|
+
delayMs?: number;
|
|
13
|
+
}): Promise<BackendActionResult>;
|
|
14
|
+
pressKey?(context: BackendCommandContext, key: string, options?: {
|
|
15
|
+
modifiers?: string[];
|
|
16
|
+
}): Promise<BackendActionResult>;
|
|
17
|
+
openApp?(context: BackendCommandContext, target: BackendOpenTarget): Promise<BackendActionResult>;
|
|
18
|
+
closeApp?(context: BackendCommandContext, app?: string): Promise<BackendActionResult>;
|
|
19
|
+
installApp?(context: BackendCommandContext, target: BackendInstallTarget): Promise<BackendActionResult>;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
declare type AgentDeviceBackendPlatform = 'ios' | 'android' | 'macos' | 'linux';
|
|
23
|
+
|
|
24
|
+
declare type AgentDeviceRuntime = {
|
|
25
|
+
backend: AgentDeviceBackend;
|
|
26
|
+
artifacts: ArtifactAdapter;
|
|
27
|
+
sessions: CommandSessionStore;
|
|
28
|
+
policy: CommandPolicy;
|
|
29
|
+
diagnostics?: DiagnosticsSink;
|
|
30
|
+
clock?: CommandClock;
|
|
31
|
+
signal?: AbortSignal;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
declare type ArtifactAdapter = {
|
|
35
|
+
resolveInput(ref: FileInputRef, options: ResolveInputOptions): Promise<ResolvedInputFile>;
|
|
36
|
+
reserveOutput(ref: FileOutputRef | undefined, options: ReserveOutputOptions): Promise<ReservedOutputFile>;
|
|
37
|
+
createTempFile(options: CreateTempFileOptions): Promise<TemporaryFile>;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
declare type ArtifactDescriptor = {
|
|
41
|
+
kind: 'localPath';
|
|
42
|
+
field: string;
|
|
43
|
+
path: string;
|
|
44
|
+
fileName?: string;
|
|
45
|
+
metadata?: Record<string, unknown>;
|
|
46
|
+
} | {
|
|
47
|
+
kind: 'artifact';
|
|
48
|
+
field: string;
|
|
49
|
+
artifactId: string;
|
|
50
|
+
fileName?: string;
|
|
51
|
+
url?: string;
|
|
52
|
+
clientPath?: string;
|
|
53
|
+
metadata?: Record<string, unknown>;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export declare function assertCommandConformance(target: CommandConformanceTarget, options?: {
|
|
57
|
+
suites?: readonly CommandConformanceSuite[];
|
|
58
|
+
}): Promise<CommandConformanceReport>;
|
|
59
|
+
|
|
60
|
+
declare const BACKEND_CAPABILITY_NAMES: readonly ["android.shell", "ios.runnerCommand", "macos.desktopScreenshot"];
|
|
61
|
+
|
|
62
|
+
declare type BackendActionResult = Record<string, unknown> | void;
|
|
63
|
+
|
|
64
|
+
declare type BackendCapabilityName = (typeof BACKEND_CAPABILITY_NAMES)[number];
|
|
65
|
+
|
|
66
|
+
declare type BackendCapabilitySet = readonly BackendCapabilityName[];
|
|
67
|
+
|
|
68
|
+
declare type BackendCommandContext = {
|
|
69
|
+
session?: string;
|
|
70
|
+
requestId?: string;
|
|
71
|
+
appId?: string;
|
|
72
|
+
appBundleId?: string;
|
|
73
|
+
signal?: AbortSignal;
|
|
74
|
+
metadata?: Record<string, unknown>;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
declare type BackendEscapeHatches = {
|
|
78
|
+
androidShell?(context: BackendCommandContext, args: readonly string[]): Promise<BackendShellResult>;
|
|
79
|
+
iosRunnerCommand?(context: BackendCommandContext, command: BackendRunnerCommand): Promise<BackendActionResult>;
|
|
80
|
+
macosDesktopScreenshot?(context: BackendCommandContext, outPath: string, options?: BackendScreenshotOptions): Promise<BackendScreenshotResult | void>;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
declare type BackendFillOptions = {
|
|
84
|
+
delayMs?: number;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
declare type BackendFindTextResult = {
|
|
88
|
+
found: boolean;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
declare type BackendInstallTarget = {
|
|
92
|
+
app: string;
|
|
93
|
+
artifactPath: string;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
declare type BackendOpenTarget = {
|
|
97
|
+
app?: string;
|
|
98
|
+
url?: string;
|
|
99
|
+
activity?: string;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
declare type BackendReadTextResult = {
|
|
103
|
+
text: string;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
declare type BackendRunnerCommand = {
|
|
107
|
+
command: string;
|
|
108
|
+
args?: readonly string[];
|
|
109
|
+
payload?: Record<string, unknown>;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
declare type BackendScreenshotOptions = {
|
|
113
|
+
fullscreen?: boolean;
|
|
114
|
+
overlayRefs?: boolean;
|
|
115
|
+
surface?: 'app' | 'frontmost-app' | 'desktop' | 'menubar';
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
declare type BackendScreenshotResult = {
|
|
119
|
+
path?: string;
|
|
120
|
+
overlayRefs?: ScreenshotOverlayRef[];
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
declare type BackendShellResult = {
|
|
124
|
+
exitCode: number;
|
|
125
|
+
stdout: string;
|
|
126
|
+
stderr: string;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
declare type BackendSnapshotAnalysis = {
|
|
130
|
+
rawNodeCount?: number;
|
|
131
|
+
maxDepth?: number;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
declare type BackendSnapshotFreshness = {
|
|
135
|
+
action: string;
|
|
136
|
+
retryCount: number;
|
|
137
|
+
staleAfterRetries: boolean;
|
|
138
|
+
reason?: 'empty-interactive' | 'sharp-drop' | 'stuck-route';
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
declare type BackendSnapshotOptions = SnapshotOptions & {
|
|
142
|
+
outPath?: string;
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
declare type BackendSnapshotResult = {
|
|
146
|
+
nodes?: SnapshotNode[];
|
|
147
|
+
truncated?: boolean;
|
|
148
|
+
backend?: string;
|
|
149
|
+
snapshot?: SnapshotState;
|
|
150
|
+
analysis?: BackendSnapshotAnalysis;
|
|
151
|
+
freshness?: BackendSnapshotFreshness;
|
|
152
|
+
warnings?: string[];
|
|
153
|
+
appName?: string;
|
|
154
|
+
appBundleId?: string;
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
declare type BackendTapOptions = {
|
|
158
|
+
button?: 'primary' | 'secondary' | 'middle';
|
|
159
|
+
count?: number;
|
|
160
|
+
intervalMs?: number;
|
|
161
|
+
holdMs?: number;
|
|
162
|
+
jitterPx?: number;
|
|
163
|
+
doubleTap?: boolean;
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
export declare const captureConformanceSuite: CommandConformanceSuite;
|
|
167
|
+
|
|
168
|
+
declare type CommandClock = {
|
|
169
|
+
now(): number;
|
|
170
|
+
sleep(ms: number): Promise<void>;
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
export declare type CommandConformanceCase = {
|
|
174
|
+
name: string;
|
|
175
|
+
command: string;
|
|
176
|
+
run(runtime: AgentDeviceRuntime, fixtures: CommandConformanceFixtures): Promise<void>;
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
export declare type CommandConformanceCaseContext = {
|
|
180
|
+
suite: string;
|
|
181
|
+
caseName: string;
|
|
182
|
+
fixtures: CommandConformanceFixtures;
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
export declare type CommandConformanceFailure = {
|
|
186
|
+
suite: string;
|
|
187
|
+
caseName: string;
|
|
188
|
+
command: string;
|
|
189
|
+
error: unknown;
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
export declare type CommandConformanceFixtures = {
|
|
193
|
+
session: string;
|
|
194
|
+
visibleSelector: string;
|
|
195
|
+
visibleText: string;
|
|
196
|
+
editableTarget: InteractionTarget;
|
|
197
|
+
fillText: string;
|
|
198
|
+
point: Point;
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
export declare type CommandConformanceReport = {
|
|
202
|
+
target: string;
|
|
203
|
+
passed: number;
|
|
204
|
+
failed: number;
|
|
205
|
+
failures: CommandConformanceFailure[];
|
|
206
|
+
suites: CommandConformanceSuiteResult[];
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
export declare type CommandConformanceSuite = {
|
|
210
|
+
name: string;
|
|
211
|
+
cases: readonly CommandConformanceCase[];
|
|
212
|
+
run(target: CommandConformanceTarget): Promise<CommandConformanceSuiteResult>;
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
export declare type CommandConformanceSuiteResult = {
|
|
216
|
+
suite: string;
|
|
217
|
+
passed: number;
|
|
218
|
+
failed: number;
|
|
219
|
+
failures: CommandConformanceFailure[];
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
export declare const commandConformanceSuites: readonly CommandConformanceSuite[];
|
|
223
|
+
|
|
224
|
+
export declare type CommandConformanceTarget = {
|
|
225
|
+
name: string;
|
|
226
|
+
createRuntime: ConformanceRuntimeFactory;
|
|
227
|
+
fixtures?: Partial<CommandConformanceFixtures>;
|
|
228
|
+
beforeEach?(context: CommandConformanceCaseContext): void | Promise<void>;
|
|
229
|
+
afterEach?(context: CommandConformanceCaseContext): void | Promise<void>;
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
declare type CommandPolicy = {
|
|
233
|
+
allowLocalInputPaths: boolean;
|
|
234
|
+
allowLocalOutputPaths: boolean;
|
|
235
|
+
maxImagePixels: number;
|
|
236
|
+
allowNamedBackendCapabilities: readonly BackendCapabilityName[];
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
declare type CommandSessionRecord = {
|
|
240
|
+
name: string;
|
|
241
|
+
appId?: string;
|
|
242
|
+
appBundleId?: string;
|
|
243
|
+
appName?: string;
|
|
244
|
+
backendSessionId?: string;
|
|
245
|
+
snapshot?: SnapshotState;
|
|
246
|
+
metadata?: Record<string, unknown>;
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
declare type CommandSessionStore = {
|
|
250
|
+
get(name: string): CommandSessionRecord | undefined | Promise<CommandSessionRecord | undefined>;
|
|
251
|
+
set(record: CommandSessionRecord): void | Promise<void>;
|
|
252
|
+
delete?(name: string): void | Promise<void>;
|
|
253
|
+
list?(): readonly CommandSessionRecord[] | Promise<readonly CommandSessionRecord[]>;
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
export declare type ConformanceRuntimeFactory = () => AgentDeviceRuntime | Promise<AgentDeviceRuntime>;
|
|
257
|
+
|
|
258
|
+
declare type CreateTempFileOptions = {
|
|
259
|
+
prefix: string;
|
|
260
|
+
ext: string;
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
export declare const defaultCommandConformanceFixtures: CommandConformanceFixtures;
|
|
264
|
+
|
|
265
|
+
declare type DiagnosticsSink = {
|
|
266
|
+
emit(event: {
|
|
267
|
+
level: 'debug' | 'info' | 'warn' | 'error';
|
|
268
|
+
message: string;
|
|
269
|
+
data?: unknown;
|
|
270
|
+
}): void;
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
declare type ElementTarget = SelectorTarget | RefTarget;
|
|
274
|
+
|
|
275
|
+
declare type FileInputRef = {
|
|
276
|
+
kind: 'path';
|
|
277
|
+
path: string;
|
|
278
|
+
} | {
|
|
279
|
+
kind: 'uploadedArtifact';
|
|
280
|
+
id: string;
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
declare type FileOutputRef = {
|
|
284
|
+
kind: 'path';
|
|
285
|
+
path: string;
|
|
286
|
+
} | {
|
|
287
|
+
kind: 'downloadableArtifact';
|
|
288
|
+
clientPath?: string;
|
|
289
|
+
fileName?: string;
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
export declare const interactionConformanceSuite: CommandConformanceSuite;
|
|
293
|
+
|
|
294
|
+
declare type InteractionTarget = ElementTarget | PointTarget;
|
|
295
|
+
|
|
296
|
+
declare type OutputVisibility = 'client-visible' | 'internal';
|
|
297
|
+
|
|
298
|
+
declare type Point = {
|
|
299
|
+
x: number;
|
|
300
|
+
y: number;
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
declare type PointTarget = {
|
|
304
|
+
kind: 'point';
|
|
305
|
+
x: number;
|
|
306
|
+
y: number;
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
declare type RawSnapshotNode = {
|
|
310
|
+
index: number;
|
|
311
|
+
type?: string;
|
|
312
|
+
role?: string;
|
|
313
|
+
subrole?: string;
|
|
314
|
+
label?: string;
|
|
315
|
+
value?: string;
|
|
316
|
+
identifier?: string;
|
|
317
|
+
rect?: Rect;
|
|
318
|
+
enabled?: boolean;
|
|
319
|
+
selected?: boolean;
|
|
320
|
+
hittable?: boolean;
|
|
321
|
+
depth?: number;
|
|
322
|
+
parentIndex?: number;
|
|
323
|
+
pid?: number;
|
|
324
|
+
bundleId?: string;
|
|
325
|
+
appName?: string;
|
|
326
|
+
windowTitle?: string;
|
|
327
|
+
surface?: string;
|
|
328
|
+
hiddenContentAbove?: boolean;
|
|
329
|
+
hiddenContentBelow?: boolean;
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
declare type Rect = {
|
|
333
|
+
x: number;
|
|
334
|
+
y: number;
|
|
335
|
+
width: number;
|
|
336
|
+
height: number;
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
declare type RefTarget = {
|
|
340
|
+
kind: 'ref';
|
|
341
|
+
ref: string;
|
|
342
|
+
fallbackLabel?: string;
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
declare type ReservedOutputFile = {
|
|
346
|
+
path: string;
|
|
347
|
+
visibility: OutputVisibility;
|
|
348
|
+
publish: () => Promise<ArtifactDescriptor | undefined>;
|
|
349
|
+
cleanup?: () => Promise<void>;
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
declare type ReserveOutputOptions = {
|
|
353
|
+
field: string;
|
|
354
|
+
ext: string;
|
|
355
|
+
requestedClientPath?: string;
|
|
356
|
+
visibility?: OutputVisibility;
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
declare type ResolvedInputFile = {
|
|
360
|
+
path: string;
|
|
361
|
+
cleanup?: () => Promise<void>;
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
declare type ResolveInputOptions = {
|
|
365
|
+
usage: string;
|
|
366
|
+
field?: string;
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
export declare function runCommandConformance(target: CommandConformanceTarget, options?: {
|
|
370
|
+
suites?: readonly CommandConformanceSuite[];
|
|
371
|
+
}): Promise<CommandConformanceReport>;
|
|
372
|
+
|
|
373
|
+
declare type ScreenshotOverlayRef = {
|
|
374
|
+
ref: string;
|
|
375
|
+
label?: string;
|
|
376
|
+
rect: Rect;
|
|
377
|
+
overlayRect: Rect;
|
|
378
|
+
center: Point;
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
export declare const selectorConformanceSuite: CommandConformanceSuite;
|
|
382
|
+
|
|
383
|
+
declare type SelectorTarget = {
|
|
384
|
+
kind: 'selector';
|
|
385
|
+
selector: string;
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
declare type SnapshotBackend = 'xctest' | 'android' | 'macos-helper' | 'linux-atspi';
|
|
389
|
+
|
|
390
|
+
declare type SnapshotNode = RawSnapshotNode & {
|
|
391
|
+
ref: string;
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
declare type SnapshotOptions = {
|
|
395
|
+
interactiveOnly?: boolean;
|
|
396
|
+
compact?: boolean;
|
|
397
|
+
depth?: number;
|
|
398
|
+
scope?: string;
|
|
399
|
+
raw?: boolean;
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
declare type SnapshotState = {
|
|
403
|
+
nodes: SnapshotNode[];
|
|
404
|
+
createdAt: number;
|
|
405
|
+
truncated?: boolean;
|
|
406
|
+
backend?: SnapshotBackend;
|
|
407
|
+
comparisonSafe?: boolean;
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
declare type TemporaryFile = {
|
|
411
|
+
path: string;
|
|
412
|
+
visibility: 'internal';
|
|
413
|
+
cleanup: () => Promise<void>;
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
export { }
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import e from"node:assert/strict";import{commands as t,selector as s}from"../commands/index.js";let a={session:"default",visibleSelector:"label=Continue",visibleText:"Continue",editableTarget:s("label=Email"),fillText:"hello@example.com",point:{x:4,y:8}},n=m({name:"capture",cases:[{name:"captures screenshots through the backend primitive",command:"capture.screenshot",run:async(s,a)=>{let n=await t.capture.screenshot(s,{session:a.session});e.equal(typeof n.path,"string"),e.ok(n.path.length>0)}},{name:"captures snapshots with nodes",command:"capture.snapshot",run:async(s,a)=>{let n=await t.capture.snapshot(s,{session:a.session});e.ok(Array.isArray(n.nodes))}}]}),i=m({name:"selectors",cases:[{name:"finds visible text",command:"selectors.find",run:async(s,a)=>{let n=await t.selectors.find(s,{session:a.session,query:a.visibleText,action:"exists"});e.equal(n.kind,"found"),e.equal(n.found,!0)}},{name:"reads text from a selector",command:"selectors.getText",run:async(a,n)=>{let i=await t.selectors.getText(a,{session:n.session,target:s(n.visibleSelector)});e.equal(i.kind,"text"),e.equal(i.text,n.visibleText)}},{name:"checks selector visibility",command:"selectors.isVisible",run:async(a,n)=>{let i=await t.selectors.isVisible(a,{session:n.session,target:s(n.visibleSelector)});e.equal(i.pass,!0)}},{name:"waits for visible text",command:"selectors.waitForText",run:async(s,a)=>{let n=await t.selectors.waitForText(s,{session:a.session,text:a.visibleText,timeoutMs:1});e.equal(n.kind,"text"),e.equal(n.text,a.visibleText)}}]}),o=m({name:"interactions",cases:[{name:"clicks selector targets",command:"interactions.click",run:async(a,n)=>{let i=await t.interactions.click(a,{session:n.session,target:s(n.visibleSelector)});e.equal(i.kind,"selector")}},{name:"presses explicit points",command:"interactions.press",run:async(s,a)=>{let n=await t.interactions.press(s,{session:a.session,target:{kind:"point",...a.point}});e.deepEqual(n.point,a.point)}},{name:"fills editable targets",command:"interactions.fill",run:async(s,a)=>{let n=await t.interactions.fill(s,{session:a.session,target:a.editableTarget,text:a.fillText});e.equal(n.text,a.fillText)}},{name:"types text without a target",command:"interactions.typeText",run:async(s,a)=>{let n=await t.interactions.typeText(s,{session:a.session,text:a.fillText});e.equal(n.text,a.fillText)}}]}),r=[n,i,o];async function l(e,t={}){let s=t.suites??r,a=[];for(let t of s)a.push(await t.run(e));let n=a.flatMap(e=>e.failures);return{target:e.name,passed:a.reduce((e,t)=>e+t.passed,0),failed:a.reduce((e,t)=>e+t.failed,0),failures:n,suites:a}}async function c(e,t={}){let s=await l(e,t);if(s.failed>0)throw AggregateError(s.failures.map(e=>e.error),`${e.name} failed ${s.failed} agent-device conformance case${1===s.failed?"":"s"}`);return s}function m(e){return{name:e.name,cases:e.cases,run:async t=>{let s={...a,...t.fixtures},n=[],i=0;for(let a of e.cases){let o={suite:e.name,caseName:a.name,fixtures:s};try{await t.beforeEach?.(o);let e=await t.createRuntime();await a.run(e,s),i+=1}catch(t){n.push({suite:e.name,caseName:a.name,command:a.command,error:t})}finally{await t.afterEach?.(o)}}return{suite:e.name,passed:i,failed:n.length,failures:n}}}}export{c as assertCommandConformance,n as captureConformanceSuite,r as commandConformanceSuites,a as defaultCommandConformanceFixtures,o as interactionConformanceSuite,l as runCommandConformance,i as selectorConformanceSuite};
|
package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandExecution.swift
CHANGED
|
@@ -183,16 +183,25 @@ extension RunnerTests {
|
|
|
183
183
|
if let requestedFps = command.fps, (requestedFps < minRecordingFps || requestedFps > maxRecordingFps) {
|
|
184
184
|
return Response(ok: false, error: ErrorPayload(message: "recordStart fps must be between \(minRecordingFps) and \(maxRecordingFps)"))
|
|
185
185
|
}
|
|
186
|
+
if let requestedQuality = command.quality, (requestedQuality < minRecordingQuality || requestedQuality > maxRecordingQuality) {
|
|
187
|
+
return Response(ok: false, error: ErrorPayload(message: "recordStart quality must be between \(minRecordingQuality) and \(maxRecordingQuality)"))
|
|
188
|
+
}
|
|
186
189
|
do {
|
|
187
190
|
let resolvedOutPath = resolveRecordingOutPath(requestedOutPath)
|
|
188
191
|
let fpsLabel = command.fps.map(String.init) ?? String(RunnerTests.defaultRecordingFps)
|
|
192
|
+
let qualityLabel = command.quality.map(String.init) ?? "native"
|
|
189
193
|
NSLog(
|
|
190
|
-
"AGENT_DEVICE_RUNNER_RECORD_START requestedOutPath=%@ resolvedOutPath=%@ fps=%@",
|
|
194
|
+
"AGENT_DEVICE_RUNNER_RECORD_START requestedOutPath=%@ resolvedOutPath=%@ fps=%@ quality=%@",
|
|
191
195
|
requestedOutPath,
|
|
192
196
|
resolvedOutPath,
|
|
193
|
-
fpsLabel
|
|
197
|
+
fpsLabel,
|
|
198
|
+
qualityLabel
|
|
199
|
+
)
|
|
200
|
+
let recorder = ScreenRecorder(
|
|
201
|
+
outputPath: resolvedOutPath,
|
|
202
|
+
fps: command.fps.map { Int32($0) },
|
|
203
|
+
quality: command.quality
|
|
194
204
|
)
|
|
195
|
-
let recorder = ScreenRecorder(outputPath: resolvedOutPath, fps: command.fps.map { Int32($0) })
|
|
196
205
|
try recorder.start { [weak self] in
|
|
197
206
|
return self?.captureRunnerFrame()
|
|
198
207
|
}
|
package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+ScreenRecorder.swift
CHANGED
|
@@ -7,6 +7,7 @@ extension RunnerTests {
|
|
|
7
7
|
final class ScreenRecorder {
|
|
8
8
|
private let outputPath: String
|
|
9
9
|
private let fps: Int32?
|
|
10
|
+
private let quality: Int?
|
|
10
11
|
private var effectiveFps: Int32 {
|
|
11
12
|
max(1, fps ?? RunnerTests.defaultRecordingFps)
|
|
12
13
|
}
|
|
@@ -25,9 +26,10 @@ extension RunnerTests {
|
|
|
25
26
|
private var startedSession = false
|
|
26
27
|
private var startError: Error?
|
|
27
28
|
|
|
28
|
-
init(outputPath: String, fps: Int32?) {
|
|
29
|
+
init(outputPath: String, fps: Int32?, quality: Int?) {
|
|
29
30
|
self.outputPath = outputPath
|
|
30
31
|
self.fps = fps
|
|
32
|
+
self.quality = quality
|
|
31
33
|
}
|
|
32
34
|
|
|
33
35
|
func start(captureFrame: @escaping () -> RunnerImage?) throws {
|
|
@@ -48,7 +50,7 @@ extension RunnerTests {
|
|
|
48
50
|
while Date() < bootstrapDeadline {
|
|
49
51
|
if let image = captureFrame(), let cgImage = runnerCGImage(from: image) {
|
|
50
52
|
bootstrapImage = image
|
|
51
|
-
dimensions =
|
|
53
|
+
dimensions = scaledDimensions(width: cgImage.width, height: cgImage.height)
|
|
52
54
|
break
|
|
53
55
|
}
|
|
54
56
|
Thread.sleep(forTimeInterval: 0.05)
|
|
@@ -240,11 +242,13 @@ extension RunnerTests {
|
|
|
240
242
|
|
|
241
243
|
CVPixelBufferLockBaseAddress(pixelBuffer, [])
|
|
242
244
|
defer { CVPixelBufferUnlockBaseAddress(pixelBuffer, []) }
|
|
245
|
+
let width = CVPixelBufferGetWidth(pixelBuffer)
|
|
246
|
+
let height = CVPixelBufferGetHeight(pixelBuffer)
|
|
243
247
|
guard
|
|
244
248
|
let context = CGContext(
|
|
245
249
|
data: CVPixelBufferGetBaseAddress(pixelBuffer),
|
|
246
|
-
width:
|
|
247
|
-
height:
|
|
250
|
+
width: width,
|
|
251
|
+
height: height,
|
|
248
252
|
bitsPerComponent: 8,
|
|
249
253
|
bytesPerRow: CVPixelBufferGetBytesPerRow(pixelBuffer),
|
|
250
254
|
space: CGColorSpaceCreateDeviceRGB(),
|
|
@@ -253,8 +257,23 @@ extension RunnerTests {
|
|
|
253
257
|
else {
|
|
254
258
|
return nil
|
|
255
259
|
}
|
|
256
|
-
context.draw(image, in: CGRect(x: 0, y: 0, width:
|
|
260
|
+
context.draw(image, in: CGRect(x: 0, y: 0, width: width, height: height))
|
|
257
261
|
return pixelBuffer
|
|
258
262
|
}
|
|
263
|
+
|
|
264
|
+
private func scaledDimensions(width: Int, height: Int) -> CGSize {
|
|
265
|
+
guard let quality, quality < 10 else {
|
|
266
|
+
return CGSize(width: width, height: height)
|
|
267
|
+
}
|
|
268
|
+
let scale = Double(quality) / 10.0
|
|
269
|
+
return CGSize(
|
|
270
|
+
width: scaledEvenDimension(width, scale: scale),
|
|
271
|
+
height: scaledEvenDimension(height, scale: scale)
|
|
272
|
+
)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
private func scaledEvenDimension(_ value: Int, scale: Double) -> Int {
|
|
276
|
+
max(2, Int((Double(value) * scale / 2.0).rounded()) * 2)
|
|
277
|
+
}
|
|
259
278
|
}
|
|
260
279
|
}
|
|
@@ -48,6 +48,8 @@ final class RunnerTests: XCTestCase {
|
|
|
48
48
|
let tvRemoteDoublePressDelayDefault: TimeInterval = 0.0
|
|
49
49
|
let minRecordingFps = 1
|
|
50
50
|
let maxRecordingFps = 120
|
|
51
|
+
let minRecordingQuality = 5
|
|
52
|
+
let maxRecordingQuality = 10
|
|
51
53
|
var needsPostSnapshotInteractionDelay = false
|
|
52
54
|
var needsFirstInteractionDelay = false
|
|
53
55
|
var activeRecording: ScreenRecorder?
|