-
Notifications
You must be signed in to change notification settings - Fork 194
Expand file tree
/
Copy pathcli-help-topics.test.ts
More file actions
641 lines (623 loc) · 35.8 KB
/
Copy pathcli-help-topics.test.ts
File metadata and controls
641 lines (623 loc) · 35.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
import { test } from 'vitest';
import assert from 'node:assert/strict';
import { usage, usageForCommand } from '../args.ts';
test('usage includes concise top-level commands', async () => {
const usageText = await usage();
assert.match(
usageText,
/install-from-source\s{2,}Install app builds from URLs, remote source specs, or CI artifacts/,
);
assert.match(usageText, /prepare\s{2,}Pre-warm platform helpers/);
assert.match(
usageText,
/metro\s{2,}Prepare Metro\/Re\.Pack reachability for React Native\/Expo apps or trigger app reloads/,
);
assert.match(usageText, /batch --steps <json> \| --steps-file <path>/);
assert.match(usageText, /network\s{2,}Inspect HTTP\(S\) traffic parsed from session app logs/);
assert.match(usageText, /clipboard read \| clipboard write <text>/);
assert.match(usageText, /keyboard \[action\]/);
assert.match(usageText, /trigger-app-event\s{2,}Invoke app-defined automation\/test events/);
assert.match(usageText, /gesture <pan\|fling\|swipe\|pinch\|rotate\|transform> \.\.\./);
assert.doesNotMatch(
usageText,
/install-from-source <url> \| install-from-source --github-actions-artifact/,
);
assert.doesNotMatch(usageText, /prepare ios-runner --platform ios\|macos/);
assert.doesNotMatch(usageText, /metro prepare --public-base-url <url>/);
assert.doesNotMatch(usageText, /^ network dump/m);
assert.doesNotMatch(usageText, /trigger-app-event <event> \[payloadJson\]/);
assert.doesNotMatch(usageText, /^ pan <x> <y> <dx> <dy> \[durationMs\]/m);
assert.doesNotMatch(usageText, /^ fling <up\|down\|left\|right>/m);
assert.doesNotMatch(usageText, /^ pinch <scale> \[x\] \[y\]/m);
assert.doesNotMatch(usageText, /^ rotate-gesture <degrees>/m);
assert.match(usageText, /rotate <orientation>/);
assert.match(usageText, /record start \[path\] \| record stop/);
assert.match(usageText, /trace start <path> \| trace stop <path>/);
});
test('usage includes only global flags in the top-level global flags section', async () => {
const usageText = await usage();
const flagsSection = usageText.slice(
usageText.indexOf('Global Flags:'),
usageText.indexOf('Agent Quickstart:'),
);
assert.match(flagsSection, /^Global Flags:/);
assert.match(flagsSection, /--config <path>/);
assert.match(flagsSection, /--json/);
assert.match(flagsSection, /--help, -h/);
assert.match(flagsSection, /--version, -V/);
assert.match(flagsSection, /test --verbose prints per-test step timings without debug logs/);
assert.doesNotMatch(flagsSection, /--target mobile\|tv/);
assert.doesNotMatch(flagsSection, /--ios-simulator-device-set <path>/);
assert.doesNotMatch(flagsSection, /--android-device-allowlist <serials>/);
assert.doesNotMatch(flagsSection, /--state-dir <path>/);
assert.doesNotMatch(flagsSection, /--daemon-transport auto\|socket\|http/);
assert.doesNotMatch(flagsSection, /--daemon-server-mode socket\|http\|dual/);
assert.doesNotMatch(flagsSection, /--tenant <id>/);
assert.doesNotMatch(flagsSection, /--session-isolation none\|tenant/);
assert.doesNotMatch(flagsSection, /--run-id <id>/);
assert.doesNotMatch(flagsSection, /--lease-id <id>/);
assert.doesNotMatch(
flagsSection,
/--lease-backend ios-simulator\|ios-instance\|android-instance\|web-instance/,
);
assert.doesNotMatch(flagsSection, /--relaunch/);
assert.doesNotMatch(flagsSection, /--header <name:value>/);
assert.doesNotMatch(flagsSection, /--restart/);
assert.doesNotMatch(flagsSection, /--fps <n>/);
assert.doesNotMatch(flagsSection, /--quality <medium\|high>/);
assert.doesNotMatch(flagsSection, /--save-script \[path\]/);
assert.doesNotMatch(flagsSection, /--metadata/);
});
test('usage includes agent workflows, config, environment, and examples footers', async () => {
const usageText = await usage();
assert.match(
usageText,
/CLI to automate supported app, device, desktop, and web targets for AI agents/,
);
assert.ok(
usageText.indexOf('Agent Workflows:') < usageText.indexOf('Commands:'),
'Agent workflows should appear before the command list for agents that only read the top of help.',
);
assert.ok(
usageText.indexOf('Agent Starting Point:') < usageText.indexOf('Agent Workflows:'),
'The agent starting point should appear before topic selection.',
);
assert.match(usageText, /Agent Starting Point:/);
assert.match(usageText, /Write full command lines starting with agent-device/);
assert.match(usageText, /Default app loop: agent-device open <app>/);
assert.match(usageText, /Use --settle on every mutating app action that supports it/);
assert.match(usageText, /The settled diff is the next snapshot/);
assert.match(usageText, /If --settle prints not settled, follow its hint/);
assert.match(usageText, /Use refs or selectors as targets/);
assert.match(usageText, /Pick the help mode below/);
assert.match(usageText, /Agent Quickstart:/);
assert.match(usageText, /Planning output contract/);
assert.match(
usageText,
/Plain snapshot reads state; snapshot -i refreshes current interactive refs only/,
);
assert.match(usageText, /agent-facing, token-efficient view for planning and targeting actions/);
assert.match(usageText, /Truncated text\/input preview: expand first with snapshot -s @e12/);
assert.match(usageText, /React Native apps: read help react-native/);
assert.match(usageText, /localhost URL opens with a port auto-configure host reachability/);
assert.match(usageText, /Expo Go\/dev clients: use the provided URL when given/);
assert.match(usageText, /open "Expo Go" <url> --platform ios/);
assert.match(usageText, /Do not use plain snapshot or snapshot --diff for this recovery check/);
assert.match(usageText, /Install flows: install\/install-from-source first/);
assert.match(usageText, /fill 'id="field-email"' "qa@example\.com" replaces/);
assert.match(usageText, /do not use fill <target> ""/);
assert.match(usageText, /Android IME capture: if fill says input was captured/);
assert.match(usageText, /Implicit default sessions are scoped to the current worktree/);
assert.match(usageText, /if a prompt names a Session, include --session <name>/);
assert.match(usageText, /Run mutating commands serially within one session/);
assert.match(usageText, /After mutation: refs are stale/);
assert.match(usageText, /use its selector directly; otherwise refresh with snapshot -i/);
assert.match(usageText, /verify the action with diff snapshot -i or snapshot --diff/);
assert.match(usageText, /Sparse or AX-unavailable snapshot/);
assert.match(usageText, /macOS context menus use click <ref> --button secondary/);
assert.match(
usageText,
/Remote lifecycle: use connect, then open, commands, close, and disconnect/,
);
assert.match(usageText, /connect proxy --daemon-base-url <proxy-agent-device-url>/);
assert.match(usageText, /Device leases are automatic on open/);
assert.match(usageText, /expire after five minutes of inactivity/);
assert.match(usageText, /app-owned back uses back/);
assert.match(usageText, /Web browser sessions: read help web/);
assert.match(
usageText,
/open <url> --platform web -> snapshot -i -> click\/fill\/get\/is\/find\/wait\/screenshot -> close/,
);
assert.match(usageText, /Session state contains request diagnostics and runner\.log/);
assert.match(usageText, /logs clear --restart\/mark\/path/);
assert.match(usageText, /network dump --include headers/);
assert.match(usageText, /Full operating guide: agent-device help workflow/);
assert.match(usageText, /Exploratory QA: agent-device help dogfood/);
assert.match(usageText, /Agent Workflows:/);
assert.match(
usageText,
/agent-device help manual-qa\s+Follow a manual test script with exact interactions and verification/,
);
assert.match(
usageText,
/agent-device help dogfood\s+Explore an app and report issues with evidence/,
);
assert.match(
usageText,
/agent-device help validate\s+Validate code changes, perf, visuals, logs, and cleanup/,
);
assert.match(
usageText,
/agent-device help workflow\s+Full app automation reference for commands, refs, selectors, and waits/,
);
assert.match(
usageText,
/agent-device help debugging\s+Use when logs, network, audio, perf memory, traces, alerts, or diagnostics matter/,
);
assert.match(
usageText,
/agent-device help tv\s+Use when navigating Android TV or tvOS focus-first surfaces/,
);
assert.match(
usageText,
/agent-device help react-devtools\s+Use when inspecting components, props\/state\/hooks, renders, or profiles/,
);
assert.match(
usageText,
/agent-device help physical-device\s+Use when using a connected phone\/tablet or iOS signing setup/,
);
assert.match(
usageText,
/agent-device help react-native\s+Use when the target app is React Native, Expo, or a dev client/,
);
assert.match(
usageText,
/agent-device help web\s+Use when automating a browser through agent-device sessions/,
);
assert.match(usageText, /Configuration:/);
assert.match(
usageText,
/Default config files: ~\/\.agent-device\/config\.json, \.\/agent-device\.json/,
);
assert.match(
usageText,
/Use --config <path> or AGENT_DEVICE_CONFIG to load one explicit config file\./,
);
assert.match(usageText, /Environment:/);
assert.match(usageText, /AGENT_DEVICE_SESSION\s+Explicit session name/);
assert.match(usageText, /AGENT_DEVICE_PLATFORM\s+Default platform binding/);
assert.match(usageText, /AGENT_DEVICE_SESSION_LOCK\s+Bound-session conflict mode/);
assert.match(usageText, /AGENT_DEVICE_DAEMON_BASE_URL\s+Connect to remote daemon/);
assert.match(usageText, /Examples:/);
assert.match(usageText, /agent-device open Settings --platform ios/);
assert.match(usageText, /agent-device open https:\/\/example\.com --platform web/);
assert.match(usageText, /agent-device snapshot -i/);
assert.match(usageText, /agent-device fill @e3 "test@example\.com"/);
assert.match(usageText, /agent-device replay \.\/session\.ad/);
assert.match(usageText, /agent-device test \.\/suite --platform android/);
});
test('usageForCommand resolves workflow help topic', async () => {
const help = await usageForCommand('workflow');
if (help === null) throw new Error('Expected workflow help text');
assert.match(help, /agent-device help workflow/);
assert.match(help, /Use selectors as positional targets/);
assert.match(help, /Do not use CSS selectors/);
assert.match(help, /Snapshot legend:/);
assert.match(help, /@e12 \[button\] label="Add to cart"/);
assert.match(help, /Truncated text\/input previews: do not use get text first/);
assert.match(help, /snapshot -s @e7/);
assert.match(help, /Use plain fill\/type first for ordinary login and form fields/);
assert.match(help, /--delay-ms intentionally paces character entry/);
assert.match(help, /Read-only visible\/state question: use snapshot\/get\/is\/find/);
assert.match(help, /Use snapshot -i only when refs are needed/);
assert.match(help, /install-from-source --github-actions-artifact org\/repo:app-debug/);
assert.match(help, /Discovery is not enough when the task asks to open\/start/);
assert.match(help, /If the task says install, use install/);
assert.match(help, /Do not open artifact paths or invent package ids/);
assert.match(help, /agent-device get attrs @e4/);
assert.match(help, /Ambiguous find: add --first or --last/);
assert.match(help, /report that gap instead of typing\/searching\/navigating/);
assert.match(help, /App-owned action sheets, menus, and camera\/scan screens are normal UI/);
assert.match(help, /wait for a concrete result before returning to chat\/form state/);
assert.match(help, /choose a point near the center of the intended app-owned target/);
assert.match(help, /Avoid screen edges, tab bars, navigation bars, and home indicators/);
assert.match(help, /Android transform injects a geometric two-finger path/);
assert.match(help, /verify semantic app state or coarse per-component effects/);
assert.match(help, /instead of exact numeric deltas/);
assert.match(help, /prefer isolated gesture pan, gesture pinch, or gesture rotate/);
assert.match(help, /longpress accepts coordinates, @refs, or selectors/);
assert.match(help, /use help react-native for Metro\/Re\.Pack Fast Refresh/);
assert.match(help, /iOS Allow Paste prompt cannot be exercised under XCUITest/);
assert.match(help, /Empty replacement is not a supported clear-field command/);
assert.match(help, /do not plan fill <target> ""/);
assert.match(help, /To hide the keyboard, use keyboard dismiss/);
assert.match(
help,
/press a visible app control such as Done only when that is the intended fallback/,
);
assert.match(help, /UNSUPPORTED_OPERATION/);
assert.match(help, /Stateful commands within one session must run serially/);
assert.match(
help,
/Do not run open\/press\/fill\/type\/scroll\/back\/alert\/replay\/batch\/close commands in parallel/,
);
assert.match(help, /agent-device clipboard write "some text"/);
assert.match(help, /For gesture-heavy iOS simulator proof videos, prefer --hide-touches/);
assert.match(help, /only a means to reveal or reach an expected target/);
assert.match(help, /using the id, selector, or text named by the task/);
assert.match(
help,
/iOS simulator transform uses private XCTest synthesis for a continuous two-finger pan\/scale\/rotation path/,
);
assert.match(help, /Android Gboard handwriting\/stylus UI can capture text/);
assert.match(help, /targetInput\/actualInput details/);
assert.match(help, /Do not keep retrying fill\/type against the same field/);
assert.match(help, /provider-native text injection when available/);
assert.match(help, /Do not switch to raw adb, clipboard, or paste as an agent fallback/);
assert.match(help, /if no URL is provided but a target\/app name is provided, open that target/);
assert.match(help, /localhost\/127\.0\.0\.1\/\[::1\] with a port auto-configure/);
assert.match(help, /Manual adb reverse tcp:<port> tcp:<port> is only needed/);
assert.match(help, /do not stop at the action itself/);
assert.match(help, /do not split clear\/restart/);
assert.match(help, /do not write network log headers/);
assert.match(help, /Web: agent-device uses a managed, pinned agent-browser backend/);
assert.match(
help,
/Use --platform web when a browser step belongs inside an agent-device session/,
);
assert.match(help, /use agent-browser directly for standalone web automation/);
assert.match(help, /agent-device web setup/);
assert.match(help, /agent-device web doctor/);
assert.match(help, /agent-device open https:\/\/example\.com --platform web/);
assert.match(help, /agent-device get text @e2 --platform web/);
assert.match(help, /agent-device is visible 'label="Welcome"' --platform web/);
assert.match(help, /agent-device find text "Welcome" exists --platform web/);
assert.match(help, /agent-device close --platform web/);
assert.match(help, /Use agent-browser directly for browser-specific features/);
assert.match(help, /agent-device open exp:\/\/127\.0\.0\.1:8081 --platform ios/);
assert.match(help, /agent-device open "Expo Go" exp:\/\/127\.0\.0\.1:8081 --platform ios/);
assert.match(help, /There is no open-url command/);
assert.match(help, /direct URL open can report success while leaving the runner\/shell focused/);
assert.match(help, /verify with snapshot -i after opening/);
assert.match(help, /snapshot returns a sparse\/AX-unavailable state/);
assert.match(help, /Use plain screenshot, not screenshot --overlay-refs/);
assert.match(help, /retry snapshot -i after reaching another screen/);
assert.match(help, /test \.\/e2e\/maestro --maestro --device udid1,emulator-5554 --shard-all 2/);
assert.match(help, /agent-device open exp:\/\/127\.0\.0\.1:8081 --platform android/);
assert.match(help, /apps lookup misses the project but shows Expo Go\/dev-client/);
assert.match(help, /metro prepare --kind expo/);
assert.match(help, /agent-device prepare ios-runner --platform ios --timeout 240000/);
assert.match(help, /prepare ios-runner builds\/reuses the XCTest runner/);
assert.match(
help,
/not a recovery step for "runner already owned by another agent-device daemon"/,
);
assert.match(help, /prepared runner does not keep a live lease/);
assert.match(help, /help react-devtools/);
assert.match(help, /help react-native/);
assert.doesNotMatch(help, /agent-device react-devtools profile/);
});
test('usageForCommand resolves tv help topic', async () => {
const help = await usageForCommand('tv');
if (help === null) throw new Error('Expected tv help text');
assert.match(help, /agent-device help tv/);
assert.match(help, /agent-device tv-remote press down/);
assert.match(help, /agent-device screenshot \.\/tv-focus\.png --overlay-refs/);
assert.match(help, /tv-remote longpress select/);
assert.match(help, /tv-remote press select --duration-ms 500/);
assert.match(help, /longpress is CLI sugar for --duration-ms 500/);
assert.match(help, /ok, center, and enter are input aliases for select/);
assert.match(help, /do not switch to raw adb keyevent/);
assert.match(help, /Use --platform ios --target tv/);
});
test('usageForCommand resolves web help topic', async () => {
const help = await usageForCommand('web');
if (help === null) throw new Error('Expected web help text');
assert.match(help, /agent-device help web/);
assert.match(help, /agent-device uses a managed, pinned agent-browser backend/);
assert.match(help, /agent-device owns command\/session\/replay integration/);
assert.match(help, /agent-browser owns browser launch, page control, screenshots/);
assert.match(
help,
/Use --platform web when a browser step belongs inside an agent-device session/,
);
assert.match(help, /Use agent-browser directly for standalone web automation/);
assert.match(help, /agent-device web setup/);
assert.match(help, /agent-device web doctor/);
assert.match(help, /agent-device open https:\/\/example\.com --platform web/);
assert.match(help, /agent-device snapshot -i --platform web/);
assert.match(help, /agent-device get text @e2 --platform web/);
assert.match(help, /agent-device is visible 'label="Welcome"' --platform web/);
assert.match(help, /agent-device find text "Welcome" exists --platform web/);
assert.match(help, /agent-device click @e12 --platform web/);
assert.match(help, /agent-device fill @e13 "qa@example\.com" --platform web/);
assert.match(help, /agent-device wait text "Welcome" 3000 --platform web/);
assert.match(help, /agent-device network dump 25 --include headers --platform web/);
assert.match(help, /agent-device audio probe start 10 1000 --platform web/);
assert.match(help, /Audio probe start uses duration seconds first, then bucket milliseconds/);
assert.match(help, /agent-device screenshot \.\/artifacts\/web-home\.png --platform web/);
assert.match(help, /agent-device close --platform web/);
assert.match(help, /open <url>, snapshot -i, get text\/attrs/);
assert.match(help, /is visible\/exists\/text, find text\/selector/);
assert.match(help, /click\/press @ref or selector/);
assert.match(help, /network dump/);
assert.match(help, /audio probe/);
assert.match(help, /network routing\/interception\/HAR/);
assert.match(help, /Use agent-browser directly for those browser-specific workflows/);
assert.match(help, /Do not claim web e2e CI exists/);
assert.match(help, /Do not use native mobile or desktop setup commands/);
});
test('usageForCommand resolves debugging help topic', async () => {
const help = await usageForCommand('debugging');
if (help === null) throw new Error('Expected debugging help text');
assert.match(help, /agent-device help debugging/);
assert.match(help, /Use logs when you need the lead-up timeline/);
assert.match(help, /relaunches the session app through devicectl process launch --console/);
assert.match(help, /Use debug symbols when you have crash\.ips\/crash\.log/);
assert.match(help, /Use Xcode\/LLDB when you need live state/);
assert.match(help, /debug symbols --artifact crash\.ips --search-path \.\/build/);
assert.match(help, /Android Java\/R8 mapping\.txt and native ndk-stack\/addr2line/);
assert.match(help, /network\/audio evidence/);
assert.match(help, /agent-device alert wait 3000/);
assert.match(help, /iOS support is runner-derived/);
assert.match(help, /resolved app executable/);
assert.match(help, /--launch-console is only for direct iOS simulator app launches/);
assert.match(help, /runnerLogPath and requestLogPath/);
assert.match(
help,
/AGENT_DEVICE_EXEC_TRACE=1 when you need host-tool spawn timing without full debug streaming/,
);
assert.match(help, /open --debug --json/);
assert.match(help, /open_timing event/);
assert.match(help, /requests\/<request-id>\.ndjson holds daemon request diagnostics/);
assert.match(help, /daemon\.log is global daemon lifecycle evidence/);
assert.match(help, /agent-device perf memory sample --json/);
assert.match(help, /agent-device audio probe start 10 1000 --platform web/);
assert.match(help, /agent-device audio probe start 10 1000 --platform macos/);
assert.match(help, /agent-device audio probe start 10 1000 --platform ios/);
assert.match(help, /agent-device audio probe start 10 1000 --platform android/);
assert.match(help, /compact rmsDbfs and peakDbfs arrays/);
assert.match(help, /requires Screen Recording permission/);
assert.match(help, /Physical iOS and Android devices are not supported/);
assert.match(help, /Memory artifact \(android-hprof\): \/tmp\/app\.hprof \(42MB\)/);
assert.match(help, /Prefer perf memory sample over raw dumpsys\/leaks output/);
assert.match(help, /Unsupported platforms return artifact\.available=false with reason\/hint/);
assert.match(help, /Do not use settings permission to answer a dialog already on screen/);
assert.match(help, /Treat native perf output as the agent evidence/);
assert.match(help, /sizeBytes=5392410/);
assert.match(help, /5\.3 MB raw trace stays in the artifact/);
});
test('usageForCommand resolves remote help topic', async () => {
const help = await usageForCommand('remote');
if (help === null) throw new Error('Expected remote help text');
assert.match(help, /agent-device connect/);
assert.match(help, /Remote connection providers use the same lifecycle/);
assert.match(help, /connect -> open -> commands -> close -> disconnect/);
assert.match(help, /agent-device connect cloud discovers the agent-device cloud profile/);
assert.match(help, /Direct proxy: agent-device connect proxy/);
assert.match(help, /stores the shared proxy profile and client identity/);
assert.match(help, /BrowserStack: agent-device connect browserstack/);
assert.match(help, /AWS Device Farm: agent-device connect aws-device-farm/);
assert.match(help, /Roku: agent-device connect roku/);
assert.match(help, /agent-device open com\.example\.app --remote-config \.\/remote-config\.json/);
assert.match(help, /disconnect --remote-config \.\/remote-config\.json/);
assert.match(help, /connect browserstack --platform android/);
assert.match(help, /connect aws-device-farm --platform android/);
assert.match(help, /connect browserstack\/aws-device-farm\/roku/);
assert.match(
help,
/ROKU_WEBDRIVER_URL=http:\/\/127\.0\.0\.1:9000 ROKU_DEVICE_IP=192\.168\.1\.50/,
);
assert.match(help, /agent-device tv-remote press down/);
assert.match(help, /Roku does not expose provider artifacts/);
assert.match(help, /AWS_REGION=us-west-2 AWS_ACCESS_KEY_ID/);
assert.match(help, /AWS Device Farm uses the AWS CLI credential chain/);
assert.match(help, /Prefer short-lived AWS role credentials in CI/);
assert.match(help, /agent-device artifacts --json/);
assert.match(help, /Script flow, per-command config/);
assert.match(help, /Direct proxy flow for a remote Mac/);
assert.match(help, /agent-device proxy --port 4310/);
assert.match(
help,
/connect proxy --daemon-base-url https:\/\/example\.trycloudflare\.com\/agent-device --daemon-auth-token <token>/,
);
assert.match(help, /agent-device open Maps --platform ios/);
assert.match(help, /agent-device snapshot -i --platform ios/);
assert.match(help, /agent-device close/);
assert.match(help, /Device leases are acquired on open/);
assert.match(help, /expire after five minutes without commands/);
assert.match(help, /Multiple agents can share one proxy/);
assert.match(help, /disconnect releases local connection state/);
assert.match(help, /A busy direct-proxy device error means another agent owns the device/);
assert.match(help, /BrowserStack, AWS Device Farm, and Roku through local provider profiles/);
assert.match(help, /BrowserStack uses BROWSERSTACK_USERNAME and BROWSERSTACK_ACCESS_KEY/);
assert.match(
help,
/Generated connection profiles store app\/device selectors, ARNs, and Roku connection hints/,
);
assert.match(help, /local\/proxy iOS reports that the runner is already owned/);
assert.match(help, /same --remote-config to every operational command/);
assert.match(help, /Do not use --config as a remote profile flag/);
assert.match(help, /install-from-source --github-actions-artifact org\/repo:artifact/);
});
test('usageForCommand resolves physical-device help topic', async () => {
const help = await usageForCommand('physical-device');
if (help === null) throw new Error('Expected physical-device help text');
assert.match(help, /agent-device help physical-device/);
assert.match(help, /Start with Automatic Signing and only these env vars/);
assert.match(help, /AGENT_DEVICE_IOS_TEAM_ID=ABCDE12345/);
assert.match(help, /AGENT_DEVICE_IOS_BUNDLE_ID=com\.yourname\.agentdevice\.runner/);
assert.match(help, /profile name\/specifier, not a file path/);
});
test('usageForCommand resolves manual QA help topic', async () => {
const help = await usageForCommand('manual-qa');
if (help === null) throw new Error('Expected manual QA help text');
assert.match(help, /agent-device help manual-qa/);
assert.match(help, /Execute the script/);
assert.match(help, /Run snapshot -i to get current refs/);
assert.match(help, /press\/fill\/click\/longpress <ref-or-selector> --settle/);
assert.match(help, /A bare screenshot\/snapshot is not verification/);
assert.match(help, /Do not use placeholders such as @ref/);
});
test('usageForCommand resolves validate help topic', async () => {
const help = await usageForCommand('validate');
if (help === null) throw new Error('Expected validate help text');
assert.match(help, /agent-device help validate/);
assert.match(help, /validating a code change/);
assert.match(help, /pnpm build first, then pnpm clean:daemon/);
assert.match(help, /pnpm build:xcuitest/);
assert.match(help, /Use the settled diff as evidence/);
assert.match(help, /Close sessions and release leases/);
});
test('usageForCommand resolves macos help topic', async () => {
const help = await usageForCommand('macos');
if (help === null) throw new Error('Expected macos help text');
assert.match(help, /agent-device click @e66 --button secondary --platform macos/);
assert.match(help, /Context menus are not ambient UI/);
assert.match(help, /menu-item refs/);
});
test('usageForCommand resolves dogfood help topic', async () => {
const help = await usageForCommand('dogfood');
if (help === null) throw new Error('Expected dogfood help text');
assert.match(help, /agent-device help dogfood/);
assert.match(help, /Find user-visible issues from runtime behavior/);
assert.match(help, /Severity: critical blocks a core flow\/data\/crashes/);
assert.match(help, /Interactive\/behavioral issues need step screenshots/);
assert.match(help, /Static\/on-load issues can use one screenshot/);
assert.match(help, /React Native warning\/error overlays can be real findings/);
assert.match(help, /Expo Go\/dev-client shells/);
assert.match(help, /direct Android localhost URL opens with a port auto-configure/);
assert.match(help, /Keep stateful commands serial within the same session/);
assert.match(help, /prefer agent-device open "Expo Go" <url>/);
assert.match(help, /dogfood-output\/report\.md/);
assert.match(help, /ID, severity, category, title, affected flow\/screen/);
assert.match(help, /Never delete screenshots, videos, traces, or report artifacts/);
assert.match(help, /screenshot \.\/dogfood-output\/screenshots\/issue-001\.png --overlay-refs/);
});
test('usageForCommand resolves react-devtools help topic', async () => {
const help = await usageForCommand('react-devtools');
if (help === null) throw new Error('Expected react-devtools help text');
assert.match(help, /agent-device react-devtools start/);
assert.match(help, /agent-device react-devtools wait --component <ComponentName>/);
assert.match(help, /agent-device react-devtools find <ComponentName> --exact/);
assert.match(help, /agent-device react-devtools errors/);
assert.match(help, /agent-device react-devtools profile report @c5/);
assert.match(help, /agent-device react-devtools profile timeline --limit 20/);
assert.match(help, /agent-device react-devtools profile export profile\.json/);
assert.match(
help,
/agent-device react-devtools profile diff before\.json after\.json --limit 10/,
);
assert.match(help, /render causes and changed props\/state\/hooks/);
assert.match(help, /Run agent-device react-devtools status first/);
assert.match(help, /start is not a connection check/);
assert.match(help, /Always run agent-device react-devtools wait --connected after status/);
assert.match(help, /logs clear --restart before the first logs mark/);
assert.match(help, /one bounded first-pass survey/);
assert.match(help, /profile slow --limit 5 once/);
assert.match(help, /profile rerenders --limit 5 once/);
assert.match(help, /profile timeline --limit 20 only when commit timing matters/);
assert.match(help, /Do not repeatedly raise broad profile slow limits/);
assert.match(help, /profile report unless you have a specific target/);
assert.match(help, /agent-device logs mark "before catalog search"/);
assert.match(help, /agent-device react-devtools profile timeline --limit 20/);
assert.match(help, /Do not write agent-devtools/);
assert.match(help, /Every profiling and survey line must begin with agent-device react-devtools/);
assert.match(help, /agent-device network dump --include headers/);
assert.match(help, /@c refs reset after reload\/remount/);
assert.match(help, /use separate sessions\/devices/);
assert.match(help, /local service tunnel/);
assert.match(help, /Remote iOS apps attempt the legacy React DevTools websocket/);
});
test('usageForCommand resolves cdp help topic', async () => {
const help = await usageForCommand('cdp');
if (help === null) throw new Error('Expected cdp help text');
assert.match(help, /agent-device cdp target list --url http:\/\/127\.0\.0\.1:8081/);
assert.match(help, /memory usage sample --label baseline --gc/);
assert.match(help, /memory snapshot leak-triplet --baseline ms_1 --action ms_2 --cleanup ms_3/);
assert.match(help, /memory snapshot retainers --snapshot ms_3 --id <node-id>/);
assert.match(help, /Until cdp has a compact leak report command/);
assert.match(help, /Avoid cdp profile cpu, trace, network, and console by default/);
assert.match(help, /React Native\/Hermes implements a subset of browser CDP/);
});
test('usageForCommand resolves react-native help topic', async () => {
const help = await usageForCommand('react-native');
if (help === null) throw new Error('Expected react-native help text');
assert.match(help, /agent-device help react-native/);
assert.match(help, /React Native-specific automation hazards/);
assert.match(help, /Choose the next help topic/);
assert.match(help, /help workflow/);
assert.match(help, /help debugging/);
assert.match(help, /help react-devtools/);
assert.match(help, /Help workflow owns the full Expo URL command shapes/);
assert.match(help, /For app\/package launches, run metro prepare/);
assert.match(help, /agent-device doctor --platform android/);
assert.match(help, /agent-device doctor --platform android --app com\.example\.app/);
assert.match(help, /agent-device doctor --platform ios/);
assert.match(help, /agent-device doctor --remote --remote-config \.\/remote\.json/);
assert.match(help, /same host context that owns the dev server/);
assert.match(help, /sandbox probe is not authoritative/);
assert.match(help, /adb reverse only affects Android device-to-host traffic/);
assert.match(help, /Multiple local worktrees can reuse one native iOS simulator build/);
assert.match(help, /--metro-host 127\.0\.0\.1 --metro-port 8081/);
assert.match(help, /One simulator cannot run two copies of the same bundle id/);
assert.match(help, /Keep the agent-device react-devtools prefix/);
assert.match(help, /Use help react-devtools for status\/wait/);
assert.match(help, /Keep the agent-device cdp prefix/);
assert.match(help, /Use help cdp for JS heap usage samples/);
assert.match(help, /logs clear --restart/);
assert.match(help, /network dump --include headers/);
assert.match(help, /agent-device open "Agent Device Tester" --platform android/);
assert.match(help, /Start React Native slow-flow plans with this ordered scaffold/);
assert.match(help, /include the open command even when it also describes the current screen/);
assert.match(help, /agent-device react-devtools status/);
assert.match(help, /Profiling plans need both status and wait --connected before profile start/);
assert.match(help, /Do not substitute react-devtools start for status/);
assert.match(help, /If snapshot reports a React Native warning\/error overlay/);
assert.match(help, /agent-device react-native dismiss-overlay/);
assert.match(help, /verifies the overlay is gone with a fresh post-dismiss snapshot -i/);
assert.match(help, /Do not use a plain snapshot after dismiss-overlay/);
assert.match(help, /When overlay evidence and React diagnostics are required/);
assert.match(help, /agent-device react-devtools errors/);
assert.match(help, /overlay is still visible/);
assert.match(help, /Do not manually press warning\/error text bodies/);
assert.match(help, /dismiss-overlay command owns the narrow LogBox\/RedBox targeting policy/);
assert.match(help, /Android runtime permission dialogs and native alerts are handled by alert/);
assert.match(help, /snapshot times out because the UI never becomes idle/);
assert.match(help, /Report React render offenders separately/);
});
test('usage includes swipe and press series options', async () => {
const help = await usage();
assert.match(help, /diff <kind>/);
assert.match(help, /swipe <x1> <y1> <x2> <y2>/);
assert.match(help, /settings \[area\] \[options\]/);
assert.doesNotMatch(help, /--pattern one-way\|ping-pong/);
assert.doesNotMatch(help, /--interval-ms/);
});
test('usage renders concise commands inline with descriptions', async () => {
const help = await usage();
assert.match(help, /Commands:[\s\S]*\n boot\s{2,}Boot target device\/simulator/);
assert.match(help, /Commands:[\s\S]*\n shutdown\s{2,}Shutdown target simulator\/emulator/);
assert.match(help, / prepare\s{2,}Pre-warm platform helpers/);
assert.match(
help,
/ metro\s{2,}Prepare Metro\/Re\.Pack reachability for React Native\/Expo apps/,
);
assert.match(help, / perf\s{2,}Check runtime metrics, frames, memory, CPU profiles/);
assert.match(help, / cdp\s{2,}Inspect React Native CDP targets, JS heap growth/);
assert.match(help, / react-devtools\s{2,}Inspect React Native components, props, hooks/);
assert.match(help, / proxy\s{2,}Expose a local daemon through cloudflared, ngrok/);
assert.match(help, / batch --steps <json> \| --steps-file <path>\s{2,}Run multiple commands/);
assert.match(help, / test <path-or-glob>\.\.\.\s{2,}Run replay test suites/);
assert.match(
help,
/ screenshot \[path\]\s{2,}Capture screenshot with optional web full-page, desktop/,
);
assert.match(
help,
/ session\s{2,}List active sessions or print the effective daemon state directory/,
);
assert.doesNotMatch(help, / metro prepare[^\n]*--project-root/);
assert.doesNotMatch(help, /\n batch\s{2,}Run multiple commands/);
assert.doesNotMatch(help, /agent-device-proxy/);
});