@appetize/cli 0.18.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,137 +10,63 @@ description: >-
10
10
  or answering what is currently on screen — even if Appetize is not mentioned
11
11
  by name.
12
12
  metadata:
13
- cli-version: '0.18.0'
13
+ cli-version: '0.20.0'
14
14
  ---
15
15
 
16
16
  # Appetize
17
17
 
18
- Drive an iOS or Android app on a cloud device with the `appetize` CLI. You get a
19
- real simulator or emulator: you can see the view hierarchy, act on it, and
20
- screenshot the result.
18
+ Drive an iOS or Android app on a cloud simulator or emulator with the
19
+ `appetize` CLI.
21
20
 
22
- The single most important habit: **look before you act, and verify after**. You
23
- cannot see the screen directly, so `inspect` and `screenshot` are your eyes.
24
- Acting blind on remembered coordinates is the main way these sessions go wrong.
21
+ **Look before you act, and verify after.** You cannot see the screen, so
22
+ `inspect` and `screenshot` are your eyes. Acting blind on remembered coordinates
23
+ is the main way these sessions go wrong.
25
24
 
26
25
  ## Prereqs
27
26
 
28
- Confirm the CLI is installed and note the version:
29
-
30
- ```bash
31
- appetize --version
32
- ```
33
-
34
- End every command with `--json`, so its result is always JSON.
27
+ Run `appetize --version` to confirm the CLI is installed. End every command with
28
+ `--json`; if your tool runs commands in a terminal, add `--quiet` too.
35
29
 
36
30
  `APPETIZE_API_TOKEN` must be set for private APIs and session targets. A target
37
- is an uploaded build's public key, e.g. `app_a1b2c3`. Discover one with
38
- `appetize build list`, filtering with `--app <appId>` when you know the bundle
39
- or package id.
40
-
41
- Put a local `.apk`, `.zip` or `.tar.gz` on the platform with
42
- `appetize build upload ./app.apk`. The extension tells Appetize which platform
43
- the build targets, and the `id` it prints is a target you can start a session
44
- against immediately.
45
-
46
- A device id names a model, e.g. `iphone15pro`. Discover valid ids and the OS
47
- versions each supports with `appetize device list`, filtering with
48
- `--platform ios` or `--platform android`.
31
+ is a build's public key, e.g. `app_a1b2c3`. Find one with
32
+ `appetize build list --app <appId>`, or upload a local build with
33
+ `appetize build upload ./app.apk` and use the `id` it prints.
49
34
 
50
- `APPETIZE_ENDPOINT` sets the Appetize URL, so a non-production environment is
51
- configured once rather than flagged on every session.
35
+ A device id names a model, e.g. `iphone15pro`;
36
+ `appetize device list --platform android` shows each with its OS versions. `APPETIZE_ENDPOINT` points at a non-production
37
+ environment.
52
38
 
53
39
  ## Session lifecycle
54
40
 
55
- Start a session, naming a device and a target:
56
-
57
41
  ```bash
58
42
  appetize session start iphone15pro app_a1b2c3 --json
59
- appetize session start pixel7 app_a1b2c3 --device-os-version 14 --json
60
- appetize session start iphone15pro app_a1b2c3 --open myapp://orders/42 --json
43
+ appetize session start pixel7 app_a1b2c3 --device-os-version 14 --proxy --json
61
44
  ```
62
45
 
63
- This blocks until the device is ready, then prints the session:
64
-
65
- ```json
66
- {
67
- "adbSerial": "127.0.0.1:44555",
68
- "baseUrl": "https://lax4b-android-9.appetize.io",
69
- "controlSocket": "/home/you/.appetize/cli/sessions/tidy-pandas-jump/control.sock",
70
- "logs": {
71
- "daemon": "/home/you/.appetize/cli/sessions/tidy-pandas-jump/daemon.log",
72
- "device": "/home/you/.appetize/cli/sessions/tidy-pandas-jump/device.log.jsonl"
73
- },
74
- "sessionId": "tidy-pandas-jump",
75
- "viewerUrl": "http://127.0.0.1:4321/"
76
- }
77
- ```
46
+ This blocks until the device is ready; a background daemon then holds the
47
+ session open. From its result:
78
48
 
79
49
  - `sessionId` — what every later command takes
80
50
  - `adbSerial` — Android only; `adb connect` it to use adb
81
51
  - `viewerUrl` — local browser viewer for co-driving
82
- - `baseUrl` — the streaming host serving the session
83
- - `controlSocket` — where the daemon listens
84
- - `logs.device` — the device's own logs; `logs.daemon` is the CLI's
85
-
86
- A background daemon holds the session open.
87
-
88
- With one session running, the other commands find it automatically — you do not
89
- need to pass anything. If you start several, pass `--session-id <id>` to pick
90
- one, or name them yourself by passing `--session-id` to `session start`.
91
- `--no-wait` returns immediately with just `sessionId` and `logs`, useful when you
92
- want to do other work while the device boots.
93
-
94
- `--open` launches a URL after start. `ready` fires before the URL is opened, so
95
- wait for the destination screen with `inspect --timeout`.
96
-
97
- The daemon streams device logs as JSONL to `logs.device`. Read or `tail -f` it
98
- when you need to see what the app itself is reporting — a crash, a failed
99
- request, a log line your change was supposed to emit.
100
-
101
- Starting with `--proxy` also captures the app's network traffic as JSONL to
102
- `logs.network`. Each line is a request, response or error
103
- event, so it answers what the app actually sent and what the server actually
104
- returned — useful when the app's own logs do not say:
52
+ - `logs.device` — the app's logs as JSONL; read it for crashes and failed requests
53
+ - `logs.network` — with `--proxy`, the app's requests, responses and errors
54
+ - `logs.daemon` — the CLI's own output, for when the session misbehaves
105
55
 
106
56
  ```bash
107
57
  jq 'select(.type == "response") | "\(.response.status) \(.request.method) \(.request.url)"' network.jsonl
108
58
  ```
109
59
 
60
+ With one session running, other commands find it automatically; with several,
61
+ pass `--session-id <id>`, which also names a session on `session start`. The session's `state.json` sits beside its logs and
62
+ shows what it is doing without disturbing it.
63
+
110
64
  Stop the session when you are done, so the device is released:
111
65
 
112
66
  ```bash
113
67
  appetize session stop --json
114
68
  ```
115
69
 
116
- Session state lives in `~/.appetize/cli/sessions/<session-id>/state.json` (or under
117
- `$APPETIZE_CLI_HOME`). Reading it is a good way to see what a session is doing
118
- without disturbing it. `logs.daemon` holds the daemon's own output — read that
119
- when the session itself misbehaves, rather than the app.
120
-
121
- ## Recording the screen
122
-
123
- Video is not captured unless you ask for it — nothing streams from the device
124
- until you start:
125
-
126
- ```bash
127
- appetize recording start login-flow --json
128
- appetize recording stop --json
129
- ```
130
-
131
- `recording start` has the daemon open the video stream and write it to the file
132
- you name, printing the path. The name is required; `.mp4` is added if you omit
133
- it, and the directory must already exist. `recording stop` closes the stream and
134
- the file. Starting again while one is already running fails, whatever path you
135
- name, so a recording always begins where you asked it to. Ending the session
136
- while recording closes the file cleanly. Recording over an existing file fails unless
137
- you pass `--force`, so name a new file to record again in the same session.
138
-
139
- Any player opens it. A session that dies unexpectedly still leaves the file
140
- playable, losing at most the last second.
141
-
142
- Playback is a constant 30fps, so it does not track real time.
143
-
144
70
  ## The loop
145
71
 
146
72
  ### 1. Inspect first
@@ -149,50 +75,29 @@ Playback is a constant 30fps, so it does not track real time.
149
75
  appetize inspect --select-test-id login-form --json
150
76
  ```
151
77
 
152
- `inspect` writes the view hierarchy as JSON to `inspect.json` by default (name
153
- a path to choose another, `--pretty` to indent) and prints a summary — byte
154
- count, node count, output path — so you know what you got.
155
-
156
- Scope it with a selector and read the file rather than dumping it to the
157
- terminal. An unscoped hierarchy on an ordinary screen runs past 100 KB, which
158
- buries the thing you were looking for and wastes most of your context.
78
+ `inspect` writes the view hierarchy to `inspect.json` and prints its size. Scope
79
+ it with a selector and read the file: an unscoped hierarchy runs past 100 KB and
80
+ buries what you were looking for.
159
81
 
160
- The tree contains **only what is currently on screen**. An element that is
161
- scrolled away or covered is not in it — absence means "not visible right now",
162
- not "does not exist".
82
+ The tree holds **only what is currently on screen**. Absence means "not visible
83
+ right now" — scrolled away or covered — not "does not exist".
163
84
 
164
85
  ### 2. Act through selectors
165
86
 
166
- Prefer matching what an element _is_ over where it happens to sit:
167
-
168
87
  ```bash
169
88
  appetize tap --select-test-id save --json
170
- appetize tap --select-text 'Log in' --json
171
89
  appetize tap --select-text '/^item \d+/i' --select-index 2 --json
172
90
  appetize type 'user@example.com{Tab}hunter2{Enter}' --json
173
91
  appetize swipe --direction up --json
174
- appetize swipe --from-test-id card-3 --to-test-id archive-bin --json
92
+ appetize swipe --from-text 'Drag me' --to-test-id drop-zone --json
175
93
  ```
176
94
 
177
- `swipe` comes in two shapes. A `--direction` scrolls the screen (add
178
- `--distance` for a fraction of it), while a `--from-*`/`--to-*` pair drags along
179
- a path between two points — which is how you reorder a list or drop something on
180
- a target. Both ends take the same selectors as `tap`, so a drag can name its
181
- endpoints instead of guessing coordinates:
182
-
183
- ```bash
184
- appetize swipe --direction left --from-test-id card --from-index 1 --json
185
- appetize swipe --from-text 'Drag me' --from-y 0.9 --to-test-id drop-zone --json
186
- ```
95
+ A `--direction` swipe scrolls; a `--from-*`/`--to-*` pair drags along a path,
96
+ naming its ends with the same selectors as `tap`.
187
97
 
188
- `--select-test-id` is the most durable, then `--select-text` (an exact string,
189
- or `/regex/flags`). `--select-index` picks among multiple matches, 0-based.
190
- Coordinates (`--select-position 0.5,0.75`) still work and are the right tool for
191
- canvases, maps and sliders — but a selector survives layout changes and a
192
- coordinate does not.
193
-
194
- In `type`, `{...}` names a key: `{Enter}`, `{Tab}`, `{Backspace}`, the arrows.
195
- `{Backspace>16/}` repeats one 16 times, and `{{` types a literal `{`.
98
+ Prefer `--select-test-id`, then `--select-text` (exact, or `/regex/flags`). A
99
+ selector survives layout changes; a coordinate (`--select-position 0.5,0.75`)
100
+ does not, so save those for canvases, maps and sliders.
196
101
 
197
102
  ### 3. Verify
198
103
 
@@ -201,9 +106,8 @@ appetize screenshot after-login --json
201
106
  ```
202
107
 
203
108
  Screenshot after each meaningful step. A tap that "succeeded" only means the
204
- gesture was delivered — the app may still have shown an error, stayed put, or
205
- navigated somewhere unexpected. The name is optional and the extension is added
206
- for you.
109
+ gesture was delivered — the app may have shown an error, stayed put, or
110
+ navigated somewhere unexpected.
207
111
 
208
112
  ### 4. Wait by inspecting, not by sleeping
209
113
 
@@ -211,52 +115,32 @@ for you.
211
115
  appetize inspect --select-test-id home-feed --timeout 5000 --json
212
116
  ```
213
117
 
214
- `--timeout <ms>` makes the command wait for the element and fail with a non-zero
215
- exit if it never appears. That is your wait primitive: it returns the moment the
216
- element is there, and it tells you clearly when it is not. Sleeping guesses, and
217
- guesses are either slow or flaky.
118
+ `--timeout <ms>` returns the moment the element appears and fails if it never
119
+ does. Sleeping guesses, and guesses are either slow or flaky.
218
120
 
219
121
  ### 5. Change the code, reinstall, repeat
220
122
 
221
- When the app itself is what you are changing, keep the session running and put
222
- each new build on the device rather than starting over.
123
+ Keep the session running and put each new build on the device.
223
124
 
224
- On Android, install the APK directly over adb:
125
+ On Android, install over adb in seconds:
225
126
 
226
127
  ```bash
227
128
  adb connect <adbSerial>
228
129
  adb -s <adbSerial> install -r ./app/build/outputs/apk/debug/app-debug.apk
229
130
  ```
230
131
 
231
- This replaces the app in place and takes seconds. The `adbSerial` is the one
232
- `session start` printed. Keep reading the app's logs from `logs.device`.
233
-
234
- On iOS there is no direct install: a simulator build has to be produced by the
235
- project's CI (a workflow that archives the `.app` and uploads it to Appetize,
236
- often via `appetize build upload` or the Appetize upload action). Find that
237
- workflow, push your change so it runs, then restart the session against the
238
- build id it produced:
239
-
240
- ```bash
241
- appetize session stop --json
242
- appetize session start iphone15pro <new-id> --json
243
- ```
244
-
245
- If you cannot tell from the repository's CI configuration how an iOS build
246
- reaches Appetize, stop and ask how the change should be verified rather than
247
- guessing.
132
+ On iOS there is no direct install: the project's CI has to build the `.app` and
133
+ upload it to Appetize. Find that workflow, push so it runs, then restart the
134
+ session against the new build id. If you cannot tell from the CI configuration
135
+ how an iOS build reaches Appetize, stop and ask rather than guessing.
248
136
 
249
- Either way, go back to step 1 and inspect — do not assume the new build is
250
- running until you have seen it.
137
+ Either way, go back to step 1 — do not assume the new build is running until you
138
+ have seen it.
251
139
 
252
140
  ### 6. Show your work
253
141
 
254
- When a step changes what is on screen, or reaches a state worth seeing, take a
255
- screenshot and read the image into the transcript as you go. The person driving
256
- sees what you see, rather than a description of it.
257
-
258
- When a turn ends with a visual change or a final state that deserves a demo,
259
- record the flow and surface the video the same way:
142
+ When a step changes what is on screen, read its screenshot into the transcript,
143
+ so the person driving sees what you see. When a flow deserves a demo, record it:
260
144
 
261
145
  ```bash
262
146
  appetize recording start demo --json
@@ -264,70 +148,57 @@ appetize recording start demo --json
264
148
  appetize recording stop --json
265
149
  ```
266
150
 
267
- Where your environment lets you attach media to a pull request, include the
268
- screenshots or video in the description as evidence of the change. Otherwise the
269
- transcript is the record — never commit screenshots or recordings to the repo.
151
+ Where you can attach media to a pull request, include it as evidence. Never
152
+ commit screenshots or recordings to the repo.
270
153
 
271
154
  ## Troubleshooting
272
155
 
273
- | What you see | What it means |
274
- | ------------------------------------------------ | ------------------------------------------------------------- |
275
- | `No active sessions` | Nothing is running — `session start` first. |
276
- | `Multiple active sessions; specify --session-id` | Pass the `sessionId` of the one you want. |
277
- | `No session found for id X` | That session ended, or the id is wrong. |
278
- | Element not found | Re-inspect. It is off screen, covered, or the screen changed. |
279
- | App fails only on network calls | If you started with `--proxy`, retry without it. |
280
-
281
- A session moves through phases while starting: `requesting`, `queued`,
282
- `starting`, `downloadingApp`, `installingApp`, `launchingApp`, `ready`. If
283
- `session start` is taking a while, that sequence tells you whether you are
284
- queued for capacity or waiting on an install.
156
+ | What you see | What to do |
157
+ | ------------------------------------ | ----------------------------------------------------------------- |
158
+ | `No active sessions` | `session start`; if one should be live, read its `logs.daemon` |
159
+ | `There are multiple active sessions` | Pass `--session-id` for the one you want |
160
+ | `No session found for id X` | It ended — `endedReason` in its `state.json` says why |
161
+ | `Session is not ready` | `state.phase` in `state.json`: queued for capacity, or installing |
162
+ | Element not found | Re-inspect. It is off screen, covered, or the screen changed |
163
+ | App fails only on network calls | If you started with `--proxy`, retry without it |
285
164
 
286
165
  ## Dismissing the soft keyboard
287
166
 
288
- There is no `hideKeyboard` command, so the keyboard is dismissed differently on
289
- each platform. Tapping an inert element does **not** work: it only clears focus
290
- if the app wired that up, and many apps do not.
291
-
292
- **iOS** — press the return key:
167
+ There is no hide-keyboard command, and tapping an inert element only works if
168
+ the app wired that up. Instead, on iOS press return; on Android press back,
169
+ which dismisses the keyboard rather than navigating while it is up:
293
170
 
294
171
  ```bash
295
172
  appetize type '{Enter}' --json
296
- ```
297
-
298
- **Android** — press the back button; while the keyboard is up it dismisses
299
- the keyboard instead of navigating back:
300
-
301
- ```bash
302
173
  appetize press back --json
303
174
  ```
304
175
 
305
- Both leave you on the same screen with the field still focused. Confirm from
306
- `inspect`: on Android the IME contributes nodes with `inputmethod`, `ime` or
307
- `latin` in their identifiers, and on iOS its keys appear as nodes labelled `q`,
308
- `space` and `return`. When those are gone, the keyboard is down. Since the tree
309
- holds only what is visible, a field the keyboard covers simply is not there,
310
- which is the symptom that sends you here.
176
+ Confirm with `inspect`: the keyboard is down once Android nodes with
177
+ `inputmethod`, `ime` or `latin` in their identifiers, or iOS keys labelled `q`,
178
+ `space` and `return`, are gone. A field the keyboard covers is missing from the
179
+ tree, which is the usual symptom that sends you here.
311
180
 
312
- Resist the urge to make this easier by changing the app under test — adding a
313
- test id or a dismiss button changes the thing you are supposed to be verifying.
181
+ Do not change the app under test to make this easier — that changes the thing
182
+ you are verifying.
314
183
 
315
184
  ## Deep links
316
185
 
317
- Prefer `open` over navigating screen by screen when a deep link exists:
186
+ Prefer a deep link over navigating screen by screen:
318
187
 
319
188
  ```bash
320
189
  appetize open myapp://orders/42 --json
321
- appetize open https://myapp.com/orders/42 --json
322
190
  appetize inspect --select-test-id order-42 --timeout 5000 --json
323
191
  ```
324
192
 
325
- `open` returning only means the URL was delivered, so verify the destination
326
- screen with `inspect --timeout`. A custom scheme needs its app installed on the
327
- device. iOS caps URLs at 2048 characters.
193
+ `open`, like `session start --open`, only delivers the URL, so wait for the
194
+ destination with `inspect --timeout`. A custom scheme needs its app installed.
195
+
196
+ ## Logged-out state
197
+
198
+ On iOS, `appetize keychain clear` clears the keychain without restarting the
199
+ session.
328
200
 
329
201
  ## Full command reference
330
202
 
331
- `references/commands.md` has every command, flag, output shape and exit
332
- behaviour. Read it when you need a flag this page does not cover, or run
333
- `appetize <command> --help`.
203
+ Run `appetize <command> --help` for flags. `references/commands.md` covers
204
+ behaviour and edge cases `--help` does not.