@appetize/cli 0.19.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 +14 -0
- package/LICENSE +1 -0
- package/cli.js +9 -9
- package/config-DfwUvfKT.js +3 -0
- package/daemon.js +1 -1
- package/package.json +3 -3
- package/skills/appetize/SKILL.md +75 -208
- package/skills/appetize/references/commands.md +52 -352
- 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.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. 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.
|
|
46
|
-
|
|
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`.
|
|
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.
|
|
50
34
|
|
|
51
|
-
|
|
52
|
-
|
|
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,29 @@ 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
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
```
|
|
188
|
-
|
|
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.
|
|
194
|
-
|
|
195
|
-
In `type`, `{...}` names a key: `{Enter}`, `{Tab}`, `{Backspace}`, the arrows.
|
|
196
|
-
`{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.
|
|
197
101
|
|
|
198
102
|
### 3. Verify
|
|
199
103
|
|
|
@@ -202,9 +106,8 @@ appetize screenshot after-login --json
|
|
|
202
106
|
```
|
|
203
107
|
|
|
204
108
|
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.
|
|
109
|
+
gesture was delivered — the app may have shown an error, stayed put, or
|
|
110
|
+
navigated somewhere unexpected.
|
|
208
111
|
|
|
209
112
|
### 4. Wait by inspecting, not by sleeping
|
|
210
113
|
|
|
@@ -212,52 +115,32 @@ for you.
|
|
|
212
115
|
appetize inspect --select-test-id home-feed --timeout 5000 --json
|
|
213
116
|
```
|
|
214
117
|
|
|
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.
|
|
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.
|
|
219
120
|
|
|
220
121
|
### 5. Change the code, reinstall, repeat
|
|
221
122
|
|
|
222
|
-
|
|
223
|
-
each new build on the device rather than starting over.
|
|
123
|
+
Keep the session running and put each new build on the device.
|
|
224
124
|
|
|
225
|
-
On Android, install
|
|
125
|
+
On Android, install over adb in seconds:
|
|
226
126
|
|
|
227
127
|
```bash
|
|
228
128
|
adb connect <adbSerial>
|
|
229
129
|
adb -s <adbSerial> install -r ./app/build/outputs/apk/debug/app-debug.apk
|
|
230
130
|
```
|
|
231
131
|
|
|
232
|
-
|
|
233
|
-
|
|
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.
|
|
234
136
|
|
|
235
|
-
|
|
236
|
-
|
|
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:
|
|
240
|
-
|
|
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.
|
|
137
|
+
Either way, go back to step 1 — do not assume the new build is running until you
|
|
138
|
+
have seen it.
|
|
252
139
|
|
|
253
140
|
### 6. Show your work
|
|
254
141
|
|
|
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:
|
|
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:
|
|
261
144
|
|
|
262
145
|
```bash
|
|
263
146
|
appetize recording start demo --json
|
|
@@ -265,73 +148,57 @@ appetize recording start demo --json
|
|
|
265
148
|
appetize recording stop --json
|
|
266
149
|
```
|
|
267
150
|
|
|
268
|
-
Where
|
|
269
|
-
screenshots or
|
|
270
|
-
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.
|
|
271
153
|
|
|
272
154
|
## Troubleshooting
|
|
273
155
|
|
|
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.
|
|
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 |
|
|
284
164
|
|
|
285
165
|
## Dismissing the soft keyboard
|
|
286
166
|
|
|
287
|
-
There is no
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
**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:
|
|
292
170
|
|
|
293
171
|
```bash
|
|
294
172
|
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
173
|
appetize press back --json
|
|
302
174
|
```
|
|
303
175
|
|
|
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.
|
|
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.
|
|
310
180
|
|
|
311
|
-
|
|
312
|
-
|
|
181
|
+
Do not change the app under test to make this easier — that changes the thing
|
|
182
|
+
you are verifying.
|
|
313
183
|
|
|
314
184
|
## Deep links
|
|
315
185
|
|
|
316
|
-
Prefer
|
|
186
|
+
Prefer a deep link over navigating screen by screen:
|
|
317
187
|
|
|
318
188
|
```bash
|
|
319
189
|
appetize open myapp://orders/42 --json
|
|
320
|
-
appetize open https://myapp.com/orders/42 --json
|
|
321
190
|
appetize inspect --select-test-id order-42 --timeout 5000 --json
|
|
322
191
|
```
|
|
323
192
|
|
|
324
|
-
`open`
|
|
325
|
-
|
|
326
|
-
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.
|
|
327
195
|
|
|
328
196
|
## Logged-out state
|
|
329
197
|
|
|
330
|
-
On iOS, `appetize keychain clear` clears the
|
|
331
|
-
|
|
198
|
+
On iOS, `appetize keychain clear` clears the keychain without restarting the
|
|
199
|
+
session.
|
|
332
200
|
|
|
333
201
|
## Full command reference
|
|
334
202
|
|
|
335
|
-
`references/commands.md`
|
|
336
|
-
behaviour
|
|
337
|
-
`appetize <command> --help`.
|
|
203
|
+
Run `appetize <command> --help` for flags. `references/commands.md` covers
|
|
204
|
+
behaviour and edge cases `--help` does not.
|