@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.
- package/CHANGELOG.md +20 -0
- package/LICENSE +1 -0
- package/cli.js +9 -9
- package/config-B56X5rgA.js +3 -0
- package/daemon.js +1 -1
- package/package.json +4 -4
- package/skills/appetize/SKILL.md +78 -204
- package/skills/appetize/references/commands.md +63 -354
- package/viewer.html +1 -1
- package/config-D0ukqfLN.js +0 -3
package/skills/appetize/SKILL.md
CHANGED
|
@@ -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.
|
|
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
|
|
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. 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
|
|
39
|
-
`appetize build list
|
|
40
|
-
|
|
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
|
|
48
|
-
|
|
49
|
-
|
|
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
|
|
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
|
-
- `
|
|
84
|
-
- `
|
|
85
|
-
- `logs.
|
|
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
|
|
154
|
-
a
|
|
155
|
-
|
|
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
|
|
162
|
-
scrolled away or covered
|
|
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-
|
|
92
|
+
appetize swipe --from-text 'Drag me' --to-test-id drop-zone --json
|
|
176
93
|
```
|
|
177
94
|
|
|
178
|
-
`swipe
|
|
179
|
-
|
|
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
|
|
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
|
-
|
|
190
|
-
|
|
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
|
-
|
|
196
|
-
|
|
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
|
|
206
|
-
navigated somewhere unexpected.
|
|
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>`
|
|
216
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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
|
-
|
|
242
|
-
|
|
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,
|
|
256
|
-
|
|
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
|
|
269
|
-
screenshots or
|
|
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
|
|
275
|
-
|
|
|
276
|
-
| `No active sessions`
|
|
277
|
-
| `
|
|
278
|
-
| `No session found for id X`
|
|
279
|
-
|
|
|
280
|
-
|
|
|
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
|
|
288
|
-
|
|
289
|
-
|
|
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
|
-
|
|
305
|
-
`
|
|
306
|
-
`
|
|
307
|
-
|
|
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
|
-
|
|
312
|
-
|
|
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
|
|
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`
|
|
325
|
-
|
|
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
|
|
331
|
-
|
|
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`
|
|
336
|
-
behaviour
|
|
337
|
-
`appetize <command> --help`.
|
|
210
|
+
Run `appetize <command> --help` for flags. `references/commands.md` covers
|
|
211
|
+
behaviour and edge cases `--help` does not.
|