@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.
- package/CHANGELOG.md +21 -0
- package/LICENSE +1 -0
- package/cli.js +10 -3
- package/config-DfwUvfKT.js +3 -0
- package/daemon.js +2 -2
- package/package.json +5 -4
- package/skills/appetize/SKILL.md +78 -207
- package/skills/appetize/references/commands.md +52 -336
- package/viewer.html +1 -1
- package/config-CmsJj7_W.js +0 -2
package/skills/appetize/SKILL.md
CHANGED
|
@@ -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.
|
|
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
|
|
19
|
-
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
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
|
|
38
|
-
`appetize build list
|
|
39
|
-
|
|
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
|
-
|
|
51
|
-
|
|
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
|
|
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
|
-
- `
|
|
83
|
-
- `
|
|
84
|
-
- `logs.
|
|
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
|
|
153
|
-
a
|
|
154
|
-
|
|
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
|
|
161
|
-
scrolled away or covered
|
|
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-
|
|
92
|
+
appetize swipe --from-text 'Drag me' --to-test-id drop-zone --json
|
|
175
93
|
```
|
|
176
94
|
|
|
177
|
-
`swipe
|
|
178
|
-
|
|
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
|
|
189
|
-
|
|
190
|
-
|
|
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
|
|
205
|
-
navigated somewhere unexpected.
|
|
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>`
|
|
215
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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
|
|
250
|
-
|
|
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,
|
|
255
|
-
|
|
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
|
|
268
|
-
screenshots or
|
|
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
|
|
274
|
-
|
|
|
275
|
-
| `No active sessions`
|
|
276
|
-
| `
|
|
277
|
-
| `No session found for id X`
|
|
278
|
-
|
|
|
279
|
-
|
|
|
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
|
|
289
|
-
|
|
290
|
-
|
|
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
|
-
|
|
306
|
-
`
|
|
307
|
-
`
|
|
308
|
-
|
|
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
|
-
|
|
313
|
-
|
|
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
|
|
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`
|
|
326
|
-
|
|
327
|
-
|
|
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`
|
|
332
|
-
behaviour
|
|
333
|
-
`appetize <command> --help`.
|
|
203
|
+
Run `appetize <command> --help` for flags. `references/commands.md` covers
|
|
204
|
+
behaviour and edge cases `--help` does not.
|