@qawolf/cli 1.12.0 → 1.13.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/dist/cli.js +170832 -456
- package/package.json +6 -4
- package/skills/qawolf-cli/SKILL.md +10 -4
- package/skills/qawolf-cli/references/runner.md +141 -19
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qawolf/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.13.0",
|
|
4
4
|
"description": "Run and manage QA Wolf flows from the terminal, CI, or an AI agent",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"automation",
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"@clack/prompts": "1.5.1",
|
|
61
61
|
"@napi-rs/keyring": "1.3.0",
|
|
62
62
|
"@oxc-node/core": "0.1.0",
|
|
63
|
-
"@qawolf/api-contracts": "0.
|
|
63
|
+
"@qawolf/api-contracts": "0.27.0",
|
|
64
64
|
"@qawolf/emails": "1.1.1",
|
|
65
65
|
"@qawolf/flow-targets": "1.0.0",
|
|
66
66
|
"@qawolf/flows": "0.1.4",
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
"superjson": "2.2.6",
|
|
74
74
|
"tar": "7.5.21",
|
|
75
75
|
"tinyglobby": "0.2.17",
|
|
76
|
-
"
|
|
76
|
+
"typescript": "6.0.3",
|
|
77
77
|
"zod": "4.4.3"
|
|
78
78
|
},
|
|
79
79
|
"devDependencies": {
|
|
@@ -82,6 +82,8 @@
|
|
|
82
82
|
"@tsconfig/strictest": "2.0.8",
|
|
83
83
|
"@types/bun": "1.3.14",
|
|
84
84
|
"@types/picomatch": "4.0.3",
|
|
85
|
+
"@wdio/globals": "9.29.1",
|
|
86
|
+
"@wdio/logger": "9.18.0",
|
|
85
87
|
"appium": "3.6.0",
|
|
86
88
|
"appium-uiautomator2-driver": "8.4.0",
|
|
87
89
|
"expect-webdriverio": "5.6.5",
|
|
@@ -89,7 +91,7 @@
|
|
|
89
91
|
"oxfmt": "0.54.0",
|
|
90
92
|
"oxlint": "1.69.0",
|
|
91
93
|
"oxlint-tsgolint": "0.23.0",
|
|
92
|
-
"
|
|
94
|
+
"webdriverio": "9.27.1"
|
|
93
95
|
},
|
|
94
96
|
"engines": {
|
|
95
97
|
"node": ">=20.19.0"
|
|
@@ -53,7 +53,7 @@ An interactive runner is a live pod holding a browser, and it is billed while it
|
|
|
53
53
|
runs. `qawolf runner launch` starts one; `qawolf runner run` starts one too when
|
|
54
54
|
no runner is already available, and says so when it does. Reading a runner
|
|
55
55
|
counts as activity, so `qawolf runner events --follow` left open keeps it alive
|
|
56
|
-
and billing. `qawolf runner
|
|
56
|
+
and billing. `qawolf runner terminate` is what ends it, so terminate a runner you launched
|
|
57
57
|
rather than leaving it to time out.
|
|
58
58
|
|
|
59
59
|
`qawolf runner launch` remembers its runner as this directory's default, so the
|
|
@@ -144,11 +144,16 @@ current branch.
|
|
|
144
144
|
| `qawolf runner act` | write | Perform one raw action on a runner's screen: click, double_click, scroll, move, drag, keypress, navigate or type. Use - to read a whole action as JSON from stdin |
|
|
145
145
|
| `qawolf runner events` | read | Print a runner's journal, one entry per line. QA Wolf writes console, recorder, run-events, run-logs, run-status |
|
|
146
146
|
| `qawolf runner exec` | write | Evaluate a snippet against a runner's live page. Use - to read the snippet from stdin |
|
|
147
|
+
| `qawolf runner import-package` | write | Install a package into a runner's live run, so a snippet or a selection can import it |
|
|
148
|
+
| `qawolf runner inspect element-html` | read | Print the HTML of the first element a selector matches |
|
|
149
|
+
| `qawolf runner inspect page-html` | read | Print the page's HTML, simplified for a model to read |
|
|
150
|
+
| `qawolf runner inspect variable` | read | Print a top-level variable's value from the running workflow |
|
|
147
151
|
| `qawolf runner keepalive` | read | Reset a runner's inactivity clock, for a caller that pauses between actions |
|
|
148
152
|
| `qawolf runner launch` | write | Launch an interactive runner and make it this directory's default |
|
|
149
|
-
| `qawolf runner run` | write | Run a flow on an interactive runner, shipping the
|
|
153
|
+
| `qawolf runner run` | write | Run a flow on an interactive runner, shipping the flow and what it imports |
|
|
150
154
|
| `qawolf runner screenshot` | read | Save a JPEG of an interactive runner's screen to a file |
|
|
151
|
-
| `qawolf runner stop` | write | Stop
|
|
155
|
+
| `qawolf runner stop-run` | write | Stop what a runner is currently executing, leaving the runner up |
|
|
156
|
+
| `qawolf runner terminate` | write | End an interactive runner, and the pod it runs on with it |
|
|
152
157
|
| `qawolf tag create` | write | Create a tag on the caller's team. Tags select flows in run.create. |
|
|
153
158
|
| `qawolf tag list` | read | List the team's tags, alphabetical by name. Tag names select flows in run.create. |
|
|
154
159
|
|
|
@@ -164,7 +169,8 @@ call the QA Wolf API and require auth.
|
|
|
164
169
|
The `runner` commands drive a live cloud browser: `launch` one, `screenshot` to
|
|
165
170
|
see it, `act` to click and type, `run` a flow on it, `exec` a snippet against its
|
|
166
171
|
page, `events` to read its journal (including the `recorder` stream, which turns
|
|
167
|
-
your actions into Playwright locators), `keepalive` to hold it open, and
|
|
172
|
+
your actions into Playwright locators), `keepalive` to hold it open, and
|
|
173
|
+
`terminate`
|
|
168
174
|
when done. Everything is a plain request to one host, so a shell with an API key
|
|
169
175
|
and its own vision model can close the see-and-act loop with no other tooling.
|
|
170
176
|
|
|
@@ -9,7 +9,7 @@ plain request to one host, so there is no connection to hold open.
|
|
|
9
9
|
Runner ids are yours to choose and are scoped to your team, so `agent-1` is a
|
|
10
10
|
fine id. Launching an id that is already running attaches to that runner instead
|
|
11
11
|
of starting and billing a second one, and the answer says which happened: read
|
|
12
|
-
`
|
|
12
|
+
`alreadyRunning`. Reusing one id is therefore the
|
|
13
13
|
cheap and safe pattern, and the same id with a different `--name` is refused
|
|
14
14
|
rather than silently ignored.
|
|
15
15
|
|
|
@@ -38,8 +38,8 @@ nothing is signed in, and no page is open. Acting as though your earlier setup
|
|
|
38
38
|
survived is the single most likely way to drive the wrong page.
|
|
39
39
|
|
|
40
40
|
No `read` command ever launches a runner. `screenshot`, `events` and `keepalive`
|
|
41
|
-
tell you there is no runner rather than quietly billing one, and so does
|
|
42
|
-
since starting a pod in order to
|
|
41
|
+
tell you there is no runner rather than quietly billing one, and so does
|
|
42
|
+
`terminate`, since starting a pod in order to end it would be absurd.
|
|
43
43
|
|
|
44
44
|
## The order that matters
|
|
45
45
|
|
|
@@ -57,10 +57,15 @@ None of that is a fault, and none of it clears on its own. **Only
|
|
|
57
57
|
`qawolf runner run <flow>` starts the screen.** A bare navigate does not: it
|
|
58
58
|
fails until the first run, however long you wait.
|
|
59
59
|
|
|
60
|
-
So the first
|
|
61
|
-
`package.json` on disk, even if all you want is to drive the
|
|
62
|
-
there is no "just give me a screen" call. Once one run has
|
|
63
|
-
screenshot-and-act loop below works for the rest of the runner's
|
|
60
|
+
So the first thing you do to a new runner has to put a browser on it. That means
|
|
61
|
+
a flow file and a `package.json` on disk, even if all you want is to drive the
|
|
62
|
+
browser by hand; there is no "just give me a screen" call. Once one run has
|
|
63
|
+
happened, the screenshot-and-act loop below works for the rest of the runner's
|
|
64
|
+
life.
|
|
65
|
+
|
|
66
|
+
A `--lines` selection is the one call that does not need a run first. Sent to a
|
|
67
|
+
runner with no browser, the runner starts one and runs your lines against it,
|
|
68
|
+
and says so on stderr. Everything else on this page waits for a run.
|
|
64
69
|
|
|
65
70
|
Retry on the exit code, not on the message text:
|
|
66
71
|
|
|
@@ -71,7 +76,7 @@ Retry on the exit code, not on the message text:
|
|
|
71
76
|
persists past a few tries, relaunch the id.
|
|
72
77
|
- `2` will not clear on its own. Either nothing has run on this runner yet, so
|
|
73
78
|
run a flow, or the runner has no browser at all, so launch with
|
|
74
|
-
`--name
|
|
79
|
+
`--name playwright` instead. The message says which.
|
|
75
80
|
|
|
76
81
|
The one exception is `exec`, which reports both as `4`; read its message to tell
|
|
77
82
|
them apart.
|
|
@@ -128,6 +133,50 @@ has a browser context, so an early empty answer means "not yet", not "broken".
|
|
|
128
133
|
Do not add `--json` here: it wraps each line in an envelope and these field paths
|
|
129
134
|
stop matching.
|
|
130
135
|
|
|
136
|
+
## Reading the page: `inspect`
|
|
137
|
+
|
|
138
|
+
`qawolf runner inspect` answers one question about the live page and prints the
|
|
139
|
+
answer on stdout by itself, so you can redirect or pipe it:
|
|
140
|
+
|
|
141
|
+
```sh
|
|
142
|
+
qawolf runner inspect element-html --selector "#email"
|
|
143
|
+
qawolf runner inspect page-html > page.html
|
|
144
|
+
qawolf runner inspect page-html --selector "#cart" # just that subtree
|
|
145
|
+
qawolf runner inspect variable --name cart | jq .total
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
`page-html` is simplified for a model to read rather than being the browser's
|
|
149
|
+
exact markup. `variable` reads a top-level variable of the running workflow and
|
|
150
|
+
prints it as JSON, which is how you see what your own code computed rather than
|
|
151
|
+
what the page shows.
|
|
152
|
+
|
|
153
|
+
One failure covers three causes, because a runner cannot tell them apart: no
|
|
154
|
+
live page, no element matching the selector, no variable under that name. All
|
|
155
|
+
three exit `2` and none clears by waiting, so read the message, which carries
|
|
156
|
+
whatever the runner said. An unreachable runner exits `4` and is worth retrying.
|
|
157
|
+
|
|
158
|
+
Use `inspect` before reaching for `exec`. Reading a value through a snippet
|
|
159
|
+
means printing it and then fishing it back out of the `console` stream, which is
|
|
160
|
+
two calls and a marker; `inspect variable` is one call and the value.
|
|
161
|
+
|
|
162
|
+
## Installing a package mid-session
|
|
163
|
+
|
|
164
|
+
`qawolf runner import-package <name>` installs a package into the runner's live
|
|
165
|
+
run, so a snippet or a selection can import it without a whole run to reinstall
|
|
166
|
+
dependencies:
|
|
167
|
+
|
|
168
|
+
```sh
|
|
169
|
+
qawolf runner import-package dayjs
|
|
170
|
+
qawolf runner import-package dayjs --package-version 1.11.13
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
The version defaults to `latest`, and the flag is `--package-version` because
|
|
174
|
+
`--version` belongs to the CLI itself. The install resolves against your
|
|
175
|
+
project's own dependencies, read from `package.json`, so it needs a run already
|
|
176
|
+
going: there is no live run on a runner that has not run anything. npm's own
|
|
177
|
+
refusal comes back verbatim on an exit `2`, which is a name or a version to
|
|
178
|
+
correct rather than something to retry.
|
|
179
|
+
|
|
131
180
|
## Reading the page: `exec`
|
|
132
181
|
|
|
133
182
|
`qawolf runner exec <file>` evaluates a snippet against whatever the runner's
|
|
@@ -153,19 +202,73 @@ so the snippet can use your own page objects and helpers.
|
|
|
153
202
|
|
|
154
203
|
## Running a flow
|
|
155
204
|
|
|
156
|
-
`qawolf runner run <
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
205
|
+
`qawolf runner run <flowFile>` ships the flow file, everything it imports, and
|
|
206
|
+
your `package.json` and `tsconfig.json`. Nothing else travels, so you can run
|
|
207
|
+
from the root of a large project without sending it. The runner holds no copy of
|
|
208
|
+
your project, so what runs is exactly what is on disk at that moment,
|
|
209
|
+
uncommitted edits included.
|
|
210
|
+
|
|
211
|
+
Imports are followed the same way a run from the QA Wolf app follows them:
|
|
212
|
+
relative paths and `tsconfig.json` path aliases, resolving `.ts` and `.js`. An
|
|
213
|
+
`export ... from` re-export is not followed, and neither is `require()`, so a
|
|
214
|
+
barrel file does not pull in what it re-exports. A `package.json` has to be
|
|
215
|
+
there, since the run reads its npm dependencies from it, and the files may carry
|
|
216
|
+
at most 30 MiB in total. A missing file, a missing `package.json` and files over
|
|
217
|
+
the cap are all refused before any runner is resolved or launched, so a typo
|
|
218
|
+
costs nothing.
|
|
219
|
+
|
|
220
|
+
After the first run on a runner, later runs send only the files whose content
|
|
221
|
+
changed, so iterating on one flow costs a small request rather than the whole
|
|
222
|
+
graph again. `--json` reports which happened as `fileSync`, `delta` or `full`.
|
|
223
|
+
Nothing about this needs managing: the baseline lives in `.qawolf/runner-files.json`,
|
|
224
|
+
a switch to another runner ignores it, and a runner that turns out not to hold
|
|
225
|
+
what was claimed gets the whole set resent automatically.
|
|
164
226
|
|
|
165
227
|
The call answers with a run id as soon as the run is accepted. **The outcome is
|
|
166
228
|
not in that answer**, it is in the `run-status` stream, whose entries carry
|
|
167
229
|
`runId`, `status` and an `errorMessage` when there is one.
|
|
168
230
|
|
|
231
|
+
### Running part of a flow
|
|
232
|
+
|
|
233
|
+
`--lines 12-40` runs those lines against the browser as it stands, so nothing is
|
|
234
|
+
re-navigated and nothing is signed in again. Use it to iterate on a step without
|
|
235
|
+
paying for the whole flow to reach it again.
|
|
236
|
+
|
|
237
|
+
The two file paths are the thing to get right, because getting them backwards
|
|
238
|
+
runs the wrong code and nothing reports it:
|
|
239
|
+
|
|
240
|
+
- the positional is **always the flow file**. It is the run's entry point, and it
|
|
241
|
+
is required for every run, selection or not.
|
|
242
|
+
- `--lines-file` is **where the lines live**. It defaults to the positional, so
|
|
243
|
+
pass it only when the range is in another file, typically a page object whose
|
|
244
|
+
method you want to run against the instance your last run left alive.
|
|
245
|
+
|
|
246
|
+
```sh
|
|
247
|
+
qawolf runner run flows/checkout.flow.ts --lines 12-40 # lines in the flow file
|
|
248
|
+
qawolf runner run flows/checkout.flow.ts --lines 4-9 \
|
|
249
|
+
--lines-file pages/login.ts # lines in a page object
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
The lines-file has to be one of the files that travel, so it lives under the
|
|
253
|
+
directory you run from. A range whose file is not collected is refused before a
|
|
254
|
+
runner is addressed, naming the path.
|
|
255
|
+
|
|
256
|
+
### Giving the run environment variables
|
|
257
|
+
|
|
258
|
+
`--env-file .env` gives the run the variables in a dotenv file, in the format
|
|
259
|
+
`qawolf flows pull` writes. It is `--env-file` and not `--env`, which on
|
|
260
|
+
`qawolf flows` means a QA Wolf environment by id or slug.
|
|
261
|
+
|
|
262
|
+
A run may carry at most 100 variables, each value at most 8 KiB. Names follow
|
|
263
|
+
what a shell accepts, and `QAWOLF_TEAM_ID` is reserved because QA Wolf sets it
|
|
264
|
+
from the key you authenticated with. All of that is refused before a runner is
|
|
265
|
+
addressed, naming the variable at fault.
|
|
266
|
+
|
|
267
|
+
If the runner had no browser, one is started before your lines run, and the
|
|
268
|
+
command says so on stderr. Those lines then ran against a fresh page rather than
|
|
269
|
+
the one an earlier run left, which is worth reading before you act on what you
|
|
270
|
+
see.
|
|
271
|
+
|
|
169
272
|
**Pass `--follow` to `run` and let it wait for you.** It reports the run's
|
|
170
273
|
status — in progress, then passed or failed — and ends on the settled status.
|
|
171
274
|
Exit code `1` means the run did not pass. Three flags mirror more streams into
|
|
@@ -264,10 +367,25 @@ pod that is gone. It resets the clock and tells you the runner is still there.
|
|
|
264
367
|
|
|
265
368
|
It is listed as a `read`, but it is the one read with a cost: keeping the clock
|
|
266
369
|
reset keeps a billed pod alive. Call it while you are genuinely still working, not
|
|
267
|
-
on a timer you forget, and call `qawolf runner
|
|
370
|
+
on a timer you forget, and call `qawolf runner terminate` when you are done rather
|
|
268
371
|
than leaving a pod to time out. A loop that keeps a runner alive and never stops
|
|
269
372
|
it bills until someone notices.
|
|
270
373
|
|
|
374
|
+
## Stopping a run vs ending a runner
|
|
375
|
+
|
|
376
|
+
Two different things, and the names are the only warning you get:
|
|
377
|
+
|
|
378
|
+
- `qawolf runner stop-run` stops what the runner is executing and leaves the
|
|
379
|
+
runner up, its browser on whatever page the run reached. The run settles as
|
|
380
|
+
stopped rather than passed or failed. Use it to abandon a run and keep the
|
|
381
|
+
browser you were working against.
|
|
382
|
+
- `qawolf runner terminate` ends the runner and the pod with it. Everything on
|
|
383
|
+
it is gone, and the next command under that id launches and bills a new one.
|
|
384
|
+
|
|
385
|
+
Both succeed when there was nothing to do, and say which: `wasRunning` is
|
|
386
|
+
`false` when no run was going, and when no runner was running. Neither is an
|
|
387
|
+
error, so a retry needs no special handling.
|
|
388
|
+
|
|
271
389
|
## End to end
|
|
272
390
|
|
|
273
391
|
Run from a directory holding a flow and a `package.json`. The run is what starts
|
|
@@ -277,7 +395,7 @@ the screen, so it is not optional even though the goal here is to drive by hand.
|
|
|
277
395
|
export QAWOLF_API_KEY=... # the only credential
|
|
278
396
|
export QAWOLF_RUNNER_ID=agent-1 # so no command below needs --runner
|
|
279
397
|
|
|
280
|
-
qawolf runner launch --id agent-1 --json # --id, not the variable; read .
|
|
398
|
+
qawolf runner launch --id agent-1 --json # --id, not the variable; read .alreadyRunning
|
|
281
399
|
qawolf runner run flows/smoke.flow.ts --follow # starts the screen; exit 1 if it failed
|
|
282
400
|
|
|
283
401
|
qawolf runner act navigate --url https://example.com/login
|
|
@@ -285,6 +403,10 @@ qawolf runner screenshot --out page.jpg # then read page.jpg yourself
|
|
|
285
403
|
qawolf runner act click --button left --x 480 --y 260
|
|
286
404
|
qawolf runner act type --text "someone@example.com"
|
|
287
405
|
|
|
406
|
+
qawolf runner inspect element-html --selector "#email"
|
|
407
|
+
qawolf runner inspect variable --name cart | jq .total
|
|
408
|
+
|
|
409
|
+
qawolf runner run flows/smoke.flow.ts --lines 12-40 --follow # just those lines
|
|
288
410
|
qawolf runner events recorder --tail 5 | jq -r '[.locator] + (.alternates // []) | @tsv'
|
|
289
|
-
qawolf runner
|
|
411
|
+
qawolf runner terminate
|
|
290
412
|
```
|