@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qawolf/cli",
3
- "version": "1.12.0",
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.26.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
- "webdriverio": "9.27.1",
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
- "typescript": "6.0.3"
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 stop` is what ends it, so stop a runner you launched
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 current directory's files with it |
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 an interactive runner |
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 `stop`
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
- `outcome` for `launched` or `already-running`. Reusing one id is therefore the
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 `stop`,
42
- since starting a pod in order to stop it would be absurd.
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 call on a new runner has to be a run. That means a flow file and a
61
- `package.json` on disk, even if all you want is to drive the browser by hand;
62
- there is no "just give me a screen" call. Once one run has happened, the
63
- screenshot-and-act loop below works for the rest of the runner's life.
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 node20WithPlaywright` instead. The message says which.
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 <file>` ships the current directory's runnable files with the
157
- request. The runner holds no copy of your project, so what runs is exactly what
158
- is on disk at that moment, uncommitted edits included. A `package.json` has to
159
- be there, since the run reads its npm dependencies from it, and the files may
160
- carry at most 4 MiB in total: run from a directory holding the flow and what it
161
- imports rather than from the root of a large monorepo. A missing file, a missing
162
- `package.json` and files over the cap are all refused before any runner is
163
- resolved or launched, so a typo costs nothing.
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 stop` when you are done rather
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 .outcome
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 stop
411
+ qawolf runner terminate
290
412
  ```