agent-device 0.12.5 → 0.12.6

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.
@@ -1,3 +1,5 @@
1
+ export declare const adminConformanceSuite: CommandConformanceSuite;
2
+
1
3
  declare type AgentDeviceBackend = {
2
4
  platform: AgentDeviceBackendPlatform;
3
5
  capabilities?: BackendCapabilitySet;
@@ -11,12 +13,44 @@ declare type AgentDeviceBackend = {
11
13
  typeText?(context: BackendCommandContext, text: string, options?: {
12
14
  delayMs?: number;
13
15
  }): Promise<BackendActionResult>;
16
+ focus?(context: BackendCommandContext, point: Point): Promise<BackendActionResult>;
17
+ longPress?(context: BackendCommandContext, point: Point, options?: BackendLongPressOptions): Promise<BackendActionResult>;
18
+ swipe?(context: BackendCommandContext, from: Point, to: Point, options?: BackendSwipeOptions): Promise<BackendActionResult>;
19
+ scroll?(context: BackendCommandContext, target: BackendScrollTarget, options: BackendScrollOptions): Promise<BackendActionResult>;
20
+ pinch?(context: BackendCommandContext, options: BackendPinchOptions): Promise<BackendActionResult>;
14
21
  pressKey?(context: BackendCommandContext, key: string, options?: {
15
22
  modifiers?: string[];
16
23
  }): Promise<BackendActionResult>;
17
- openApp?(context: BackendCommandContext, target: BackendOpenTarget): Promise<BackendActionResult>;
24
+ pressBack?(context: BackendCommandContext, options?: BackendBackOptions): Promise<BackendActionResult>;
25
+ pressHome?(context: BackendCommandContext): Promise<BackendActionResult>;
26
+ rotate?(context: BackendCommandContext, orientation: BackendDeviceOrientation): Promise<BackendActionResult>;
27
+ setKeyboard?(context: BackendCommandContext, options: BackendKeyboardOptions): Promise<BackendKeyboardResult | BackendActionResult>;
28
+ getClipboard?(context: BackendCommandContext): Promise<string | BackendClipboardTextResult>;
29
+ setClipboard?(context: BackendCommandContext, text: string): Promise<BackendActionResult>;
30
+ openSettings?(context: BackendCommandContext, target?: string): Promise<BackendActionResult>;
31
+ handleAlert?(context: BackendCommandContext, action: BackendAlertAction, options?: {
32
+ timeoutMs?: number;
33
+ }): Promise<BackendAlertResult>;
34
+ openAppSwitcher?(context: BackendCommandContext): Promise<BackendActionResult>;
35
+ openApp?(context: BackendCommandContext, target: BackendOpenTarget, options?: BackendOpenOptions): Promise<BackendActionResult>;
18
36
  closeApp?(context: BackendCommandContext, app?: string): Promise<BackendActionResult>;
19
- installApp?(context: BackendCommandContext, target: BackendInstallTarget): Promise<BackendActionResult>;
37
+ listApps?(context: BackendCommandContext, filter?: BackendAppListFilter): Promise<readonly BackendAppInfo[]>;
38
+ getAppState?(context: BackendCommandContext, app: string): Promise<BackendAppState>;
39
+ pushFile?(context: BackendCommandContext, input: BackendPushInput, target: string): Promise<BackendActionResult>;
40
+ triggerAppEvent?(context: BackendCommandContext, event: BackendAppEvent): Promise<BackendActionResult>;
41
+ listDevices?(context: BackendCommandContext, filter?: BackendDeviceFilter): Promise<readonly BackendDeviceInfo[]>;
42
+ bootDevice?(context: BackendCommandContext, target?: BackendDeviceTarget): Promise<BackendActionResult>;
43
+ ensureSimulator?(context: BackendCommandContext, options: BackendEnsureSimulatorOptions): Promise<BackendEnsureSimulatorResult>;
44
+ resolveInstallSource?(context: BackendCommandContext, source: BackendInstallSource): Promise<BackendInstallSource>;
45
+ installApp?(context: BackendCommandContext, target: BackendInstallTarget): Promise<BackendInstallResult>;
46
+ reinstallApp?(context: BackendCommandContext, target: BackendInstallTarget): Promise<BackendInstallResult>;
47
+ startRecording?(context: BackendCommandContext, options?: BackendRecordingOptions): Promise<BackendRecordingResult>;
48
+ stopRecording?(context: BackendCommandContext, options?: BackendRecordingOptions): Promise<BackendRecordingResult>;
49
+ startTrace?(context: BackendCommandContext, options?: BackendTraceOptions): Promise<BackendTraceResult>;
50
+ stopTrace?(context: BackendCommandContext, options?: BackendTraceOptions): Promise<BackendTraceResult>;
51
+ readLogs?(context: BackendCommandContext, options?: BackendReadLogsOptions): Promise<BackendReadLogsResult>;
52
+ dumpNetwork?(context: BackendCommandContext, options?: BackendDumpNetworkOptions): Promise<BackendDumpNetworkResult>;
53
+ measurePerf?(context: BackendCommandContext, options?: BackendMeasurePerfOptions): Promise<BackendMeasurePerfResult>;
20
54
  };
21
55
 
22
56
  declare type AgentDeviceBackendPlatform = 'ios' | 'android' | 'macos' | 'linux';
@@ -31,6 +65,8 @@ declare type AgentDeviceRuntime = {
31
65
  signal?: AbortSignal;
32
66
  };
33
67
 
68
+ export declare const appsConformanceSuite: CommandConformanceSuite;
69
+
34
70
  declare type ArtifactAdapter = {
35
71
  resolveInput(ref: FileInputRef, options: ResolveInputOptions): Promise<ResolvedInputFile>;
36
72
  reserveOutput(ref: FileOutputRef | undefined, options: ReserveOutputOptions): Promise<ReservedOutputFile>;
@@ -61,10 +97,65 @@ declare const BACKEND_CAPABILITY_NAMES: readonly ["android.shell", "ios.runnerCo
61
97
 
62
98
  declare type BackendActionResult = Record<string, unknown> | void;
63
99
 
100
+ declare type BackendAlertAction = 'get' | 'accept' | 'dismiss' | 'wait';
101
+
102
+ declare type BackendAlertInfo = {
103
+ title?: string;
104
+ message?: string;
105
+ buttons?: string[];
106
+ };
107
+
108
+ declare type BackendAlertResult = {
109
+ kind: 'alertStatus';
110
+ alert: BackendAlertInfo | null;
111
+ } | {
112
+ kind: 'alertHandled';
113
+ handled: boolean;
114
+ alert?: BackendAlertInfo;
115
+ button?: string;
116
+ } | {
117
+ kind: 'alertWait';
118
+ alert: BackendAlertInfo | null;
119
+ waitedMs?: number;
120
+ timedOut?: boolean;
121
+ };
122
+
123
+ declare type BackendAppEvent = {
124
+ name: string;
125
+ payload?: Record<string, unknown>;
126
+ };
127
+
128
+ declare type BackendAppInfo = {
129
+ id: string;
130
+ name?: string;
131
+ bundleId?: string;
132
+ packageName?: string;
133
+ activity?: string;
134
+ };
135
+
136
+ declare type BackendAppListFilter = 'all' | 'user-installed';
137
+
138
+ declare type BackendAppState = {
139
+ appId?: string;
140
+ bundleId?: string;
141
+ packageName?: string;
142
+ activity?: string;
143
+ state?: 'unknown' | 'notRunning' | 'running' | 'foreground' | 'background';
144
+ details?: Record<string, unknown>;
145
+ };
146
+
147
+ declare type BackendBackOptions = {
148
+ mode?: 'in-app' | 'system';
149
+ };
150
+
64
151
  declare type BackendCapabilityName = (typeof BACKEND_CAPABILITY_NAMES)[number];
65
152
 
66
153
  declare type BackendCapabilitySet = readonly BackendCapabilityName[];
67
154
 
155
+ declare type BackendClipboardTextResult = {
156
+ text: string;
157
+ };
158
+
68
159
  declare type BackendCommandContext = {
69
160
  session?: string;
70
161
  requestId?: string;
@@ -74,6 +165,71 @@ declare type BackendCommandContext = {
74
165
  metadata?: Record<string, unknown>;
75
166
  };
76
167
 
168
+ declare type BackendDeviceFilter = {
169
+ platform?: AgentDeviceBackendPlatform | 'apple';
170
+ target?: 'mobile' | 'tv' | 'desktop';
171
+ kind?: 'simulator' | 'emulator' | 'device' | 'desktop';
172
+ };
173
+
174
+ declare type BackendDeviceInfo = {
175
+ id: string;
176
+ name: string;
177
+ platform: AgentDeviceBackendPlatform;
178
+ target?: 'mobile' | 'tv' | 'desktop';
179
+ kind?: 'simulator' | 'emulator' | 'device' | 'desktop';
180
+ booted?: boolean;
181
+ details?: Record<string, unknown>;
182
+ };
183
+
184
+ declare type BackendDeviceOrientation = 'portrait' | 'portrait-upside-down' | 'landscape-left' | 'landscape-right';
185
+
186
+ declare type BackendDeviceTarget = {
187
+ id?: string;
188
+ name?: string;
189
+ platform?: AgentDeviceBackendPlatform;
190
+ target?: 'mobile' | 'tv' | 'desktop';
191
+ headless?: boolean;
192
+ };
193
+
194
+ declare type BackendDiagnosticsPageOptions = BackendDiagnosticsTimeWindow & {
195
+ cursor?: string;
196
+ limit?: number;
197
+ };
198
+
199
+ declare type BackendDiagnosticsTimeWindow = {
200
+ since?: string;
201
+ until?: string;
202
+ };
203
+
204
+ declare type BackendDumpNetworkOptions = BackendDiagnosticsPageOptions & {
205
+ include?: BackendNetworkIncludeMode;
206
+ };
207
+
208
+ declare type BackendDumpNetworkResult = {
209
+ entries: readonly BackendNetworkEntry[];
210
+ nextCursor?: string;
211
+ timeWindow?: BackendDiagnosticsTimeWindow;
212
+ backend?: string;
213
+ redacted?: boolean;
214
+ notes?: readonly string[];
215
+ };
216
+
217
+ declare type BackendEnsureSimulatorOptions = {
218
+ device: string;
219
+ runtime?: string;
220
+ boot?: boolean;
221
+ reuseExisting?: boolean;
222
+ };
223
+
224
+ declare type BackendEnsureSimulatorResult = {
225
+ udid: string;
226
+ device: string;
227
+ runtime: string;
228
+ created: boolean;
229
+ booted: boolean;
230
+ simulatorSetPath?: string | null;
231
+ };
232
+
77
233
  declare type BackendEscapeHatches = {
78
234
  androidShell?(context: BackendCommandContext, args: readonly string[]): Promise<BackendShellResult>;
79
235
  iosRunnerCommand?(context: BackendCommandContext, command: BackendRunnerCommand): Promise<BackendActionResult>;
@@ -88,21 +244,166 @@ declare type BackendFindTextResult = {
88
244
  found: boolean;
89
245
  };
90
246
 
247
+ declare type BackendInstallResult = Record<string, unknown> & {
248
+ appId?: string;
249
+ appName?: string;
250
+ bundleId?: string;
251
+ packageName?: string;
252
+ launchTarget?: string;
253
+ installablePath?: string;
254
+ archivePath?: string;
255
+ };
256
+
257
+ declare type BackendInstallSource = {
258
+ kind: 'path';
259
+ path: string;
260
+ } | {
261
+ kind: 'uploadedArtifact';
262
+ id: string;
263
+ } | {
264
+ kind: 'url';
265
+ url: string;
266
+ };
267
+
91
268
  declare type BackendInstallTarget = {
92
- app: string;
93
- artifactPath: string;
269
+ app?: string;
270
+ source: BackendInstallSource;
271
+ };
272
+
273
+ declare type BackendKeyboardOptions = {
274
+ action: 'status' | 'get' | 'dismiss';
275
+ };
276
+
277
+ declare type BackendKeyboardResult = {
278
+ platform?: 'android' | 'ios' | 'macos' | 'linux';
279
+ action?: BackendKeyboardOptions['action'];
280
+ visible?: boolean;
281
+ inputType?: string | null;
282
+ type?: string | null;
283
+ wasVisible?: boolean;
284
+ dismissed?: boolean;
285
+ attempts?: number;
286
+ };
287
+
288
+ declare type BackendLogEntry = {
289
+ timestamp?: string;
290
+ level?: 'debug' | 'info' | 'warn' | 'error' | string;
291
+ message: string;
292
+ source?: string;
293
+ metadata?: Record<string, unknown>;
294
+ };
295
+
296
+ declare type BackendLongPressOptions = {
297
+ durationMs?: number;
298
+ };
299
+
300
+ declare type BackendMeasurePerfOptions = BackendDiagnosticsTimeWindow & {
301
+ sampleMs?: number;
302
+ metrics?: readonly string[];
303
+ };
304
+
305
+ declare type BackendMeasurePerfResult = {
306
+ metrics: readonly BackendPerfMetric[];
307
+ startedAt?: string;
308
+ endedAt?: string;
309
+ backend?: string;
310
+ redacted?: boolean;
311
+ notes?: readonly string[];
312
+ };
313
+
314
+ declare type BackendNetworkEntry = {
315
+ timestamp?: string;
316
+ method?: string;
317
+ url?: string;
318
+ status?: number;
319
+ durationMs?: number;
320
+ requestHeaders?: Record<string, string>;
321
+ responseHeaders?: Record<string, string>;
322
+ requestBody?: string;
323
+ responseBody?: string;
324
+ metadata?: Record<string, unknown>;
325
+ };
326
+
327
+ declare type BackendNetworkIncludeMode = 'summary' | 'headers' | 'body' | 'all';
328
+
329
+ declare type BackendOpenOptions = {
330
+ relaunch?: boolean;
94
331
  };
95
332
 
96
333
  declare type BackendOpenTarget = {
334
+ /**
335
+ * Generic app identifier accepted by the backend. Hosted adapters should
336
+ * prefer structured appId, bundleId, or packageName when available.
337
+ */
97
338
  app?: string;
339
+ appId?: string;
340
+ bundleId?: string;
341
+ packageName?: string;
342
+ /**
343
+ * URL may be used by itself for a deep link or with an app identifier when
344
+ * the backend supports opening a URL in a specific app context.
345
+ */
98
346
  url?: string;
347
+ /**
348
+ * Platform-specific activity override, primarily for Android app launches.
349
+ */
99
350
  activity?: string;
100
351
  };
101
352
 
353
+ declare type BackendPerfMetric = {
354
+ name: string;
355
+ value?: number;
356
+ unit?: string;
357
+ status?: 'ok' | 'unavailable' | 'error';
358
+ message?: string;
359
+ metadata?: Record<string, unknown>;
360
+ };
361
+
362
+ declare type BackendPinchOptions = {
363
+ scale: number;
364
+ center?: Point;
365
+ };
366
+
367
+ declare type BackendPushInput = {
368
+ kind: 'json';
369
+ payload: Record<string, unknown>;
370
+ } | {
371
+ kind: 'file';
372
+ path: string;
373
+ };
374
+
375
+ declare type BackendReadLogsOptions = BackendDiagnosticsPageOptions & {
376
+ levels?: readonly string[];
377
+ search?: string;
378
+ source?: string;
379
+ };
380
+
381
+ declare type BackendReadLogsResult = {
382
+ entries: readonly BackendLogEntry[];
383
+ nextCursor?: string;
384
+ timeWindow?: BackendDiagnosticsTimeWindow;
385
+ backend?: string;
386
+ redacted?: boolean;
387
+ notes?: readonly string[];
388
+ };
389
+
102
390
  declare type BackendReadTextResult = {
103
391
  text: string;
104
392
  };
105
393
 
394
+ declare type BackendRecordingOptions = {
395
+ outPath?: string;
396
+ fps?: number;
397
+ quality?: number;
398
+ showTouches?: boolean;
399
+ };
400
+
401
+ declare type BackendRecordingResult = Record<string, unknown> & {
402
+ path?: string;
403
+ telemetryPath?: string;
404
+ warning?: string;
405
+ };
406
+
106
407
  declare type BackendRunnerCommand = {
107
408
  command: string;
108
409
  args?: readonly string[];
@@ -120,6 +421,19 @@ declare type BackendScreenshotResult = {
120
421
  overlayRefs?: ScreenshotOverlayRef[];
121
422
  };
122
423
 
424
+ declare type BackendScrollOptions = {
425
+ direction: 'up' | 'down' | 'left' | 'right';
426
+ amount?: number;
427
+ pixels?: number;
428
+ };
429
+
430
+ declare type BackendScrollTarget = {
431
+ kind: 'viewport';
432
+ } | {
433
+ kind: 'point';
434
+ point: Point;
435
+ };
436
+
123
437
  declare type BackendShellResult = {
124
438
  exitCode: number;
125
439
  stdout: string;
@@ -154,6 +468,10 @@ declare type BackendSnapshotResult = {
154
468
  appBundleId?: string;
155
469
  };
156
470
 
471
+ declare type BackendSwipeOptions = {
472
+ durationMs?: number;
473
+ };
474
+
157
475
  declare type BackendTapOptions = {
158
476
  button?: 'primary' | 'secondary' | 'middle';
159
477
  count?: number;
@@ -163,6 +481,14 @@ declare type BackendTapOptions = {
163
481
  doubleTap?: boolean;
164
482
  };
165
483
 
484
+ declare type BackendTraceOptions = {
485
+ outPath?: string;
486
+ };
487
+
488
+ declare type BackendTraceResult = Record<string, unknown> & {
489
+ outPath?: string;
490
+ };
491
+
166
492
  export declare const captureConformanceSuite: CommandConformanceSuite;
167
493
 
168
494
  declare type CommandClock = {
@@ -191,11 +517,16 @@ export declare type CommandConformanceFailure = {
191
517
 
192
518
  export declare type CommandConformanceFixtures = {
193
519
  session: string;
520
+ app: string;
521
+ installSourcePath: string;
522
+ appEventName: string;
523
+ appPushPayload: Record<string, unknown>;
194
524
  visibleSelector: string;
195
525
  visibleText: string;
196
526
  editableTarget: InteractionTarget;
197
527
  fillText: string;
198
528
  point: Point;
529
+ swipeTo: Point;
199
530
  };
200
531
 
201
532
  export declare type CommandConformanceReport = {
@@ -262,6 +593,8 @@ declare type CreateTempFileOptions = {
262
593
 
263
594
  export declare const defaultCommandConformanceFixtures: CommandConformanceFixtures;
264
595
 
596
+ export declare const diagnosticsConformanceSuite: CommandConformanceSuite;
597
+
265
598
  declare type DiagnosticsSink = {
266
599
  emit(event: {
267
600
  level: 'debug' | 'info' | 'warn' | 'error';
@@ -329,6 +662,8 @@ declare type RawSnapshotNode = {
329
662
  hiddenContentBelow?: boolean;
330
663
  };
331
664
 
665
+ export declare const recordingConformanceSuite: CommandConformanceSuite;
666
+
332
667
  declare type Rect = {
333
668
  x: number;
334
669
  y: number;
@@ -407,6 +742,8 @@ declare type SnapshotState = {
407
742
  comparisonSafe?: boolean;
408
743
  };
409
744
 
745
+ export declare const systemConformanceSuite: CommandConformanceSuite;
746
+
410
747
  declare type TemporaryFile = {
411
748
  path: string;
412
749
  visibility: 'internal';
@@ -1 +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};
1
+ import e from"node:assert/strict";import{commands as a,selector as s}from"../commands/index.js";let t={session:"default",app:"com.example.app",installSourcePath:"/tmp/example.app",appEventName:"example.ready",appPushPayload:{aps:{alert:"hello"}},visibleSelector:"label=Continue",visibleText:"Continue",editableTarget:s("label=Email"),fillText:"hello@example.com",point:{x:4,y:8},swipeTo:{x:24,y:28}},n=w({name:"capture",cases:[{name:"captures screenshots through the backend primitive",command:"capture.screenshot",run:async(s,t)=>{let n=await a.capture.screenshot(s,{session:t.session});e.equal(typeof n.path,"string"),e.ok(n.path.length>0)}},{name:"captures snapshots with nodes",command:"capture.snapshot",run:async(s,t)=>{let n=await a.capture.snapshot(s,{session:t.session});e.ok(Array.isArray(n.nodes))}}]}),i=w({name:"selectors",cases:[{name:"finds visible text",command:"selectors.find",run:async(s,t)=>{let n=await a.selectors.find(s,{session:t.session,query:t.visibleText,action:"exists"});e.equal(n.kind,"found"),e.equal(n.found,!0)}},{name:"reads text from a selector",command:"selectors.getText",run:async(t,n)=>{let i=await a.selectors.getText(t,{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(t,n)=>{let i=await a.selectors.isVisible(t,{session:n.session,target:s(n.visibleSelector)});e.equal(i.pass,!0)}},{name:"waits for visible text",command:"selectors.waitForText",run:async(s,t)=>{let n=await a.selectors.waitForText(s,{session:t.session,text:t.visibleText,timeoutMs:1});e.equal(n.kind,"text"),e.equal(n.text,t.visibleText)}}]}),o=w({name:"interactions",cases:[{name:"clicks selector targets",command:"interactions.click",run:async(t,n)=>{let i=await a.interactions.click(t,{session:n.session,target:s(n.visibleSelector)});e.equal(i.kind,"selector")}},{name:"presses explicit points",command:"interactions.press",run:async(s,t)=>{let n=await a.interactions.press(s,{session:t.session,target:{kind:"point",...t.point}});e.deepEqual(n.point,t.point)}},{name:"fills editable targets",command:"interactions.fill",run:async(s,t)=>{let n=await a.interactions.fill(s,{session:t.session,target:t.editableTarget,text:t.fillText});e.equal(n.text,t.fillText)}},{name:"types text without a target",command:"interactions.typeText",run:async(s,t)=>{let n=await a.interactions.typeText(s,{session:t.session,text:t.fillText});e.equal(n.text,t.fillText)}},{name:"focuses selector targets",command:"interactions.focus",run:async(t,n)=>{let i=await a.interactions.focus(t,{session:n.session,target:s(n.visibleSelector)});e.equal(i.kind,"selector")}},{name:"long presses selector targets",command:"interactions.longPress",run:async(t,n)=>{let i=await a.interactions.longPress(t,{session:n.session,target:s(n.visibleSelector),durationMs:500});e.equal(i.kind,"selector")}},{name:"swipes explicit points",command:"interactions.swipe",run:async(s,t)=>{let n=await a.interactions.swipe(s,{session:t.session,from:t.point,to:t.swipeTo});e.deepEqual(n.from,t.point)}},{name:"scrolls viewport targets",command:"interactions.scroll",run:async(s,t)=>{let n=await a.interactions.scroll(s,{session:t.session,target:{kind:"viewport"},direction:"down"});e.equal(n.kind,"viewport")}},{name:"pinches through the backend primitive",command:"interactions.pinch",run:async s=>{let t=await a.interactions.pinch(s,{scale:1.1});e.equal(t.kind,"pinch")}}]}),r=w({name:"system",cases:[{name:"presses back",command:"system.back",run:async(s,t)=>{let n=await a.system.back(s,{session:t.session,mode:"in-app"});e.equal(n.kind,"systemBack")}},{name:"presses home",command:"system.home",run:async(s,t)=>{let n=await a.system.home(s,{session:t.session});e.equal(n.kind,"systemHome")}},{name:"rotates devices",command:"system.rotate",run:async(s,t)=>{let n=await a.system.rotate(s,{session:t.session,orientation:"portrait"});e.equal(n.orientation,"portrait")}},{name:"reads keyboard state",command:"system.keyboard",run:async(s,t)=>{let n=await a.system.keyboard(s,{session:t.session,action:"status"});e.equal(n.kind,"keyboardState")}},{name:"reads clipboard text",command:"system.clipboard",run:async(s,t)=>{let n=await a.system.clipboard(s,{session:t.session,action:"read"});e.equal(n.kind,"clipboardText")}},{name:"opens settings",command:"system.settings",run:async(s,t)=>{let n=await a.system.settings(s,{session:t.session});e.equal(n.kind,"settingsOpened")}},{name:"reads alert state",command:"system.alert",run:async(s,t)=>{let n=await a.system.alert(s,{session:t.session,action:"get"});e.equal(n.kind,"alertStatus")}},{name:"opens app switcher",command:"system.appSwitcher",run:async(s,t)=>{let n=await a.system.appSwitcher(s,{session:t.session});e.equal(n.kind,"appSwitcherOpened")}}]}),c=w({name:"apps",cases:[{name:"opens apps by id",command:"apps.open",run:async(s,t)=>{let n=await a.apps.open(s,{session:t.session,app:t.app});e.equal(n.kind,"appOpened"),e.equal(n.target.app,t.app)}},{name:"closes apps by id",command:"apps.close",run:async(s,t)=>{let n=await a.apps.close(s,{session:t.session,app:t.app});e.equal(n.kind,"appClosed"),e.equal(n.app,t.app)}},{name:"lists apps",command:"apps.list",run:async s=>{let t=await a.apps.list(s,{filter:"all"});e.equal(t.kind,"appsList"),e.ok(Array.isArray(t.apps))}},{name:"reads app state",command:"apps.state",run:async(s,t)=>{let n=await a.apps.state(s,{session:t.session,app:t.app});e.equal(n.kind,"appState"),e.equal(n.app,t.app)}},{name:"pushes app payloads",command:"apps.push",run:async(s,t)=>{let n=await a.apps.push(s,{session:t.session,app:t.app,input:{kind:"json",payload:t.appPushPayload}});e.equal(n.kind,"appPushed"),e.equal(n.inputKind,"json")}},{name:"triggers app events",command:"apps.triggerEvent",run:async(s,t)=>{let n=await a.apps.triggerEvent(s,{session:t.session,name:t.appEventName});e.equal(n.kind,"appEventTriggered"),e.equal(n.name,t.appEventName)}}]}),l=w({name:"admin",cases:[{name:"lists devices",command:"admin.devices",run:async s=>{let t=await a.admin.devices(s,{});e.equal(t.kind,"adminDevices"),e.ok(Array.isArray(t.devices))}},{name:"boots devices",command:"admin.boot",run:async s=>{let t=await a.admin.boot(s,{});e.equal(t.kind,"deviceBooted")}},{name:"ensures simulators",command:"admin.ensureSimulator",run:async s=>{let t=await a.admin.ensureSimulator(s,{device:"iPhone 16",runtime:"iOS 18"});e.equal(t.kind,"simulatorEnsured")}},{name:"installs apps from structured sources",command:"admin.install",run:async(s,t)=>{let n=await a.admin.install(s,{app:t.app,source:{kind:"path",path:t.installSourcePath}});e.equal(n.kind,"appInstalled")}},{name:"reinstalls apps from structured sources",command:"admin.reinstall",run:async(s,t)=>{let n=await a.admin.reinstall(s,{app:t.app,source:{kind:"path",path:t.installSourcePath}});e.equal(n.kind,"appReinstalled")}},{name:"installs apps from source resolver",command:"admin.installFromSource",run:async(s,t)=>{let n=await a.admin.installFromSource(s,{source:{kind:"path",path:t.installSourcePath}});e.equal(n.kind,"appInstalledFromSource")}}]}),m=w({name:"recording",cases:[{name:"starts recording",command:"record",run:async s=>{let t=await a.recording.record(s,{action:"start"});e.equal(t.kind,"recordingStarted")}},{name:"stops traces",command:"trace",run:async s=>{let t=await a.recording.trace(s,{action:"stop"});e.equal(t.kind,"traceStopped")}}]}),p=w({name:"diagnostics",cases:[{name:"reads paginated logs",command:"diagnostics.logs",run:async s=>{let t=await a.diagnostics.logs(s,{limit:10});e.equal(t.kind,"diagnosticsLogs"),e.ok(Array.isArray(t.entries))}},{name:"dumps structured network entries",command:"diagnostics.network",run:async s=>{let t=await a.diagnostics.network(s,{limit:10,include:"summary"});e.equal(t.kind,"diagnosticsNetwork"),e.ok(Array.isArray(t.entries))}},{name:"measures perf metrics",command:"diagnostics.perf",run:async s=>{let t=await a.diagnostics.perf(s,{sampleMs:100});e.equal(t.kind,"diagnosticsPerf"),e.ok(Array.isArray(t.metrics))}}]}),d=[n,i,o,r,c,l,m,p];async function u(e,a={}){let s=a.suites??d,t=[];for(let a of s)t.push(await a.run(e));let n=t.flatMap(e=>e.failures);return{target:e.name,passed:t.reduce((e,a)=>e+a.passed,0),failed:t.reduce((e,a)=>e+a.failed,0),failures:n,suites:t}}async function y(e,a={}){let s=await u(e,a);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 w(e){return{name:e.name,cases:e.cases,run:async a=>{let s={...t,...a.fixtures},n=[],i=0;for(let t of e.cases){let o={suite:e.name,caseName:t.name,fixtures:s};try{await a.beforeEach?.(o);let e=await a.createRuntime();await t.run(e,s),i+=1}catch(a){n.push({suite:e.name,caseName:t.name,command:t.command,error:a})}finally{await a.afterEach?.(o)}}return{suite:e.name,passed:i,failed:n.length,failures:n}}}}export{l as adminConformanceSuite,c as appsConformanceSuite,y as assertCommandConformance,n as captureConformanceSuite,d as commandConformanceSuites,t as defaultCommandConformanceFixtures,p as diagnosticsConformanceSuite,o as interactionConformanceSuite,m as recordingConformanceSuite,u as runCommandConformance,i as selectorConformanceSuite,r as systemConformanceSuite};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-device",
3
- "version": "0.12.5",
3
+ "version": "0.12.6",
4
4
  "description": "Unified control plane for physical and virtual devices via an agent-driven CLI.",
5
5
  "license": "MIT",
6
6
  "author": "Callstack",
@@ -75,7 +75,7 @@ Use `snapshot --raw --platform macos` only when debugging AX structure or collec
75
75
  Things not to rely on:
76
76
 
77
77
  - Mobile-only helpers such as `install`, `reinstall`, or `push`.
78
- - Desktop-global click or fill parity from `desktop` or `menubar` sessions.
78
+ - Desktop-global click, fill, or gesture parity from `desktop` or `menubar` sessions.
79
79
  - Raw coordinate assumptions across runs.
80
80
 
81
81
  Troubleshooting:
@@ -13,7 +13,7 @@ Open this file for remote daemon HTTP flows that let an agent running in a Linux
13
13
 
14
14
  ## Most common mistake to avoid
15
15
 
16
- Do not run remote tenant work by repeating `--remote-config` on every command. `--remote-config` is a `connect` input. After connecting, use normal `agent-device` commands; the active connection supplies daemon URL, tenant, run, lease, and prepared Metro runtime context.
16
+ Do not run remote tenant work by repeating `--remote-config` on every command. `--remote-config` is a `connect` input. After connecting, use normal `agent-device` commands; the active connection supplies daemon URL, tenant, run, and session context, then resolves lease and Metro details only when a later command actually needs them.
17
17
 
18
18
  ## Preferred remote flow
19
19
 
@@ -34,7 +34,7 @@ agent-device fill @e3 "test@example.com"
34
34
  agent-device disconnect
35
35
  ```
36
36
 
37
- `connect` resolves the remote profile, verifies daemon reachability through the normal client path, allocates or refreshes the tenant lease, prepares local Metro when the profile has Metro fields, starts the local Metro companion when the bridge needs it, and writes local non-secret connection state for later commands. `disconnect` closes the session when possible, stops the Metro companion owned by that connection, releases the lease, and removes local connection state.
37
+ `connect` resolves the remote profile, generates a local session name when the profile omits one, stores local non-secret connection state, and defers tenant lease allocation plus Metro preparation until a later command needs them. When a command such as `open`, `install`, `apps`, or `snapshot` needs a lease, the client allocates or refreshes it from the connected scope. When a command needs Metro runtime hints, the client prepares Metro locally at that point and starts the local Metro companion when the bridge needs it, including `batch` runs whose steps open an app. `disconnect` closes the session when possible, stops the Metro companion owned by that connection, releases the lease when one was allocated, and removes local connection state.
38
38
 
39
39
  After `connect`, normal `agent-device` commands use the active remote connection. Do not repeat `--remote-config` on every command.
40
40
 
@@ -63,18 +63,26 @@ Example `remote-config.json` shape:
63
63
  "tenant": "acme",
64
64
  "runId": "run-123",
65
65
  "sessionIsolation": "tenant",
66
- "session": "adc-android",
67
66
  "platform": "android",
67
+ "metroPublicBaseUrl": "http://127.0.0.1:8081"
68
+ }
69
+ ```
70
+
71
+ Optional overrides stay available for advanced cases:
72
+
73
+ ```json
74
+ {
75
+ "session": "adc-android",
68
76
  "leaseBackend": "android-instance",
69
77
  "metroProjectRoot": ".",
70
- "metroPublicBaseUrl": "http://127.0.0.1:8081",
78
+ "metroKind": "expo",
71
79
  "metroProxyBaseUrl": "https://bridge.example.com/metro/acme/run-123"
72
80
  }
73
81
  ```
74
82
 
75
83
  - Keep secrets in env/config managed by the operator boundary. Do not persist auth tokens in connection state.
76
84
  - Omit Metro fields for non-React Native flows.
77
- - Put `tenant`, `runId`, `session`, `sessionIsolation`, `platform`, and `leaseBackend` in the remote profile when possible so agents can run `agent-device connect --remote-config ./remote-config.json` without extra scope flags.
85
+ - Put `tenant`, `runId`, and `sessionIsolation` in the remote profile so agents can run `agent-device connect --remote-config ./remote-config.json` without extra scope flags. Add `platform`, `leaseBackend`, `session`, or Metro overrides only when the default inference is not enough for that flow.
78
86
  - Explicit command-line flags override connected defaults. Use them intentionally when switching session, platform, target, tenant, run, or lease scope.
79
87
  - For React Native Metro runs with `metroProxyBaseUrl`, `agent-device >= 0.11.12` can manage the local companion tunnel, but Metro itself still needs to be running locally.
80
88
  - Use a lease backend that matches the bridge target platform, for example `android-instance`, `ios-instance`, or an explicit `--lease-backend` override.