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