@qawolf/cli 1.8.1 → 1.9.2
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 +1154 -28
- package/package.json +3 -3
- package/skills/qawolf-cli/SKILL.md +89 -41
- package/skills/qawolf-cli/references/runner.md +280 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qawolf/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.2",
|
|
4
4
|
"description": "Run and manage QA Wolf flows from the terminal, CI, or an AI agent",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"automation",
|
|
@@ -60,13 +60,12 @@
|
|
|
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.25.0",
|
|
64
64
|
"@qawolf/emails": "1.1.1",
|
|
65
65
|
"@qawolf/flow-targets": "1.0.0",
|
|
66
66
|
"@qawolf/flows": "0.1.4",
|
|
67
67
|
"@qawolf/testkit": "1.1.1",
|
|
68
68
|
"appium": "2.11.3",
|
|
69
|
-
"appium-uiautomator2-driver": "3.7.0",
|
|
70
69
|
"commander": "14.0.3",
|
|
71
70
|
"env-paths": "4.0.0",
|
|
72
71
|
"expect-webdriverio": "5.6.5",
|
|
@@ -85,6 +84,7 @@
|
|
|
85
84
|
"@tsconfig/strictest": "2.0.8",
|
|
86
85
|
"@types/bun": "1.3.14",
|
|
87
86
|
"@types/picomatch": "4.0.3",
|
|
87
|
+
"appium-uiautomator2-driver": "4.2.9",
|
|
88
88
|
"knip": "6.16.1",
|
|
89
89
|
"oxfmt": "0.54.0",
|
|
90
90
|
"oxlint": "1.69.0",
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: qawolf-cli
|
|
3
|
-
description: Manage QA Wolf through the qawolf CLI. Use when asked which QA Wolf environment variables are available or to list, set, or delete them; manage environments, flows, runs, tags, or issues; authenticate; install; or
|
|
3
|
+
description: Manage QA Wolf through the qawolf CLI. Use when asked which QA Wolf environment variables are available or to list, set, or delete them; manage environments, flows, runs, tags, or issues; authenticate; install; run or list flows; or drive a live cloud browser (launch a runner, screenshot it, click and type on it, read its recorder) from a shell.
|
|
4
4
|
license: Apache-2.0
|
|
5
5
|
compatibility: Requires the qawolf CLI on PATH. Install it from @qawolf/cli or use a standalone binary from GitHub Releases.
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# QA Wolf CLI
|
|
9
9
|
|
|
10
|
-
`qawolf` runs QA Wolf flows locally
|
|
10
|
+
`qawolf` runs QA Wolf flows locally, calls the QA Wolf public API, and drives
|
|
11
|
+
interactive runners: live cloud pods holding a browser you can see and act on.
|
|
11
12
|
|
|
12
13
|
This file is an overview, not a reference. Before first using a command whose
|
|
13
14
|
flags are not shown here, run `qawolf <command> --help` once. The installed CLI
|
|
@@ -22,6 +23,11 @@ environment variable (or stored credentials from `qawolf auth login`).
|
|
|
22
23
|
their table entry notes a flag that switches them to `read`.
|
|
23
24
|
Verify with `qawolf auth whoami`. Never print or log the key.
|
|
24
25
|
|
|
26
|
+
A team API key in the environment is the whole credential, including for the
|
|
27
|
+
`runner` group. Nothing needs a browser login, a session token or a held
|
|
28
|
+
connection, so a sandbox that can set one environment variable and make
|
|
29
|
+
requests to one host can do everything below.
|
|
30
|
+
|
|
25
31
|
Commands use `https://app.qawolf.com` by default. Set `QAWOLF_HOST_URL` to
|
|
26
32
|
target another deployment host, for example
|
|
27
33
|
`https://app.staging.example.com`. `QAWOLF_API_URL` is a separate API endpoint
|
|
@@ -41,6 +47,19 @@ the target, ask instead of guessing. Do not default to the newest environment.
|
|
|
41
47
|
listed name is enough to reference `process.env.NAME` in flow code; do not ask
|
|
42
48
|
for its value merely because the CLI does not return it.
|
|
43
49
|
|
|
50
|
+
## Interactive runners cost money
|
|
51
|
+
|
|
52
|
+
An interactive runner is a live pod holding a browser, and it is billed while it
|
|
53
|
+
runs. `qawolf runner launch` starts one; `qawolf runner run` starts one too when
|
|
54
|
+
no runner is already available, and says so when it does. Reading a runner
|
|
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
|
|
57
|
+
rather than leaving it to time out.
|
|
58
|
+
|
|
59
|
+
`qawolf runner launch` remembers its runner as this directory's default, so the
|
|
60
|
+
commands that follow need no `--runner`. Override that default for one command
|
|
61
|
+
with `--runner <id>`, or for a whole session with `QAWOLF_RUNNER_ID`.
|
|
62
|
+
|
|
44
63
|
## Output
|
|
45
64
|
|
|
46
65
|
When consuming output programmatically, always pass `--json` (or `--agent`).
|
|
@@ -48,6 +67,11 @@ Human-formatted output is not stable across versions. Errors go to stderr;
|
|
|
48
67
|
a non-zero exit code means the command failed. Reuse successful read results
|
|
49
68
|
within a task unless a relevant write or target change could make them stale.
|
|
50
69
|
|
|
70
|
+
One exception to know about: on `qawolf runner events`, `--json` also switches
|
|
71
|
+
each printed line from the payload alone to the whole envelope (`sequence`,
|
|
72
|
+
`recordedAt`, `payload`). Both are JSON. Pass it when you want to page by
|
|
73
|
+
sequence, omit it when you want the payloads themselves.
|
|
74
|
+
|
|
51
75
|
## Safety: reads vs writes
|
|
52
76
|
|
|
53
77
|
Read commands do not change team data, but some have operational effects noted
|
|
@@ -58,6 +82,12 @@ successful write response is confirmation, so do not read immediately only to
|
|
|
58
82
|
verify it. Never blind-retry a write on timeout: it may have reached the server
|
|
59
83
|
the first time.
|
|
60
84
|
|
|
85
|
+
Two runner-specific costs to keep in mind. Launching a runner starts a billed
|
|
86
|
+
pod, so reuse one id rather than minting new ones per step, and stop a runner
|
|
87
|
+
when you are done. And `run`, `act` and `exec` may all have taken effect even
|
|
88
|
+
when their answer never arrives, so none of them is safe to blind-retry; `run`
|
|
89
|
+
is the expensive one, because a second submission bills a second run.
|
|
90
|
+
|
|
61
91
|
## Git-backed workflows
|
|
62
92
|
|
|
63
93
|
Inspect `git status` before publishing. Stage and commit only files changed for
|
|
@@ -68,45 +98,48 @@ current branch.
|
|
|
68
98
|
|
|
69
99
|
<!-- commands-table:start — generated by `bun run generate`, do not edit -->
|
|
70
100
|
|
|
71
|
-
|
|
72
|
-
|
|
|
73
|
-
|
|
|
74
|
-
| `qawolf auth
|
|
75
|
-
| `qawolf auth
|
|
76
|
-
| `qawolf
|
|
77
|
-
| `qawolf
|
|
78
|
-
| `qawolf
|
|
79
|
-
| `qawolf environment
|
|
80
|
-
| `qawolf environment
|
|
81
|
-
| `qawolf environment
|
|
82
|
-
| `qawolf environment
|
|
83
|
-
| `qawolf environment
|
|
84
|
-
| `qawolf environment
|
|
85
|
-
| `qawolf environment
|
|
86
|
-
| `qawolf
|
|
87
|
-
| `qawolf flow
|
|
88
|
-
| `qawolf
|
|
89
|
-
| `qawolf flows
|
|
90
|
-
| `qawolf flows
|
|
91
|
-
| `qawolf
|
|
92
|
-
| `qawolf
|
|
93
|
-
| `qawolf install
|
|
94
|
-
| `qawolf install
|
|
95
|
-
| `qawolf install
|
|
96
|
-
| `qawolf
|
|
97
|
-
| `qawolf issue
|
|
98
|
-
| `qawolf issue
|
|
99
|
-
| `qawolf
|
|
100
|
-
| `qawolf run
|
|
101
|
-
| `qawolf run
|
|
102
|
-
| `qawolf
|
|
103
|
-
| `qawolf runner
|
|
104
|
-
| `qawolf runner
|
|
105
|
-
| `qawolf runner
|
|
106
|
-
| `qawolf runner
|
|
107
|
-
| `qawolf runner
|
|
108
|
-
| `qawolf
|
|
109
|
-
| `qawolf
|
|
101
|
+
<!-- prettier-ignore -->
|
|
102
|
+
| Command | Kind | What it does |
|
|
103
|
+
| --- | --- | --- |
|
|
104
|
+
| `qawolf auth login` | local | Authenticate with your QA Wolf API key |
|
|
105
|
+
| `qawolf auth logout` | local | Remove stored credentials |
|
|
106
|
+
| `qawolf auth whoami` | read | Show authentication status |
|
|
107
|
+
| `qawolf automate` | write | Request automation for draft flows. First create a named local .flow.ts draft for every requested journey that does not already have a matching draft; never reuse a generic starter or placeholder. Each new draft must start with a JSDoc Goal: description, import flow from @qawolf/flows/web, and use export default flow(...); a comment-only file or direct test(...) call is not a valid draft. Commit and push all changes with Git to publish them, then list remote drafts to resolve every selected ID. Do not use patch to create or rename a selected flow. Finally make one automation request containing all requested flow IDs. |
|
|
108
|
+
| `qawolf doctor` | local | Diagnose problems running flows locally |
|
|
109
|
+
| `qawolf environment create` | write | Create an environment on the caller's team and return it in the environment.get shape. |
|
|
110
|
+
| `qawolf environment deleteVariable` | write | Remove one environment variable by name. Succeeds whether or not the variable existed. |
|
|
111
|
+
| `qawolf environment find` | read | List the team's environments, newest first. |
|
|
112
|
+
| `qawolf environment get` | read | Read a single environment's name, kind, health status, run concurrency limit, and termination state. |
|
|
113
|
+
| `qawolf environment getVariable` | read | Read the values of named environment variables in one call. Values are secrets. Names that do not exist go to missingNames and do not fail the call. |
|
|
114
|
+
| `qawolf environment listVariableNames` | read | Use this to answer which QA Wolf environment variables are available to test code. Returns names only; values never leave the server. |
|
|
115
|
+
| `qawolf environment setVariable` | write | Create or replace an environment variable. If the user asks to create one for "my email" without naming it, use DEFAULT_EMAIL. The value is never returned. |
|
|
116
|
+
| `qawolf environment update` | write | Update an environment owned by the caller's team and return it in the environment.get shape. Omitted fields remain unchanged. |
|
|
117
|
+
| `qawolf flow addTag` | write | Assign an existing tag to the selected flows. Create tags with tag.create. |
|
|
118
|
+
| `qawolf flow update` | write | Move a flow between draft and active readiness. The other statuses shown in the app are derived and cannot be set. |
|
|
119
|
+
| `qawolf flows list` | local (read with --remote) | List flows matching [pattern] from the local project, or from a QA Wolf environment with --remote |
|
|
120
|
+
| `qawolf flows pull` | read | Download an environment's flows into the local .qawolf/<env>/ cache |
|
|
121
|
+
| `qawolf flows run` | local (read with --env) | Run flows matching [pattern], or every flow when omitted; with --env, pull missing flows from that QA Wolf environment |
|
|
122
|
+
| `qawolf init` | local | Scaffold a QA Wolf project in the current directory |
|
|
123
|
+
| `qawolf install` | local | Install every runtime dependency the project's flows need |
|
|
124
|
+
| `qawolf install android` | local | Install Android system images, AVDs, and the Appium driver used by the project's Android flows |
|
|
125
|
+
| `qawolf install browsers` | local | Install Playwright browsers used by the project's web flows |
|
|
126
|
+
| `qawolf install clear` | local | Remove the managed runtime cache (all installed runtime versions) |
|
|
127
|
+
| `qawolf issue create` | write | Create a bug or coverage request issue for the caller's team. Maintenance issues cannot be created through the public API. |
|
|
128
|
+
| `qawolf issue find` | read | List the team's bug reports, maintenance reports, or coverage requests, newest first. |
|
|
129
|
+
| `qawolf issue get` | read | Get an issue by id. |
|
|
130
|
+
| `qawolf run create` | write | Create a run for the selected flows and/or tags in an environment. |
|
|
131
|
+
| `qawolf run find` | read | List an environment's recent runs, newest first. |
|
|
132
|
+
| `qawolf run get` | read | Get a run's status, per-flow results, and links. |
|
|
133
|
+
| `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 |
|
|
134
|
+
| `qawolf runner events` | read | Print a runner's journal, one entry per line. QA Wolf writes console, recorder, run-events, run-logs, run-status |
|
|
135
|
+
| `qawolf runner exec` | write | Evaluate a snippet against a runner's live page. Use - to read the snippet from stdin |
|
|
136
|
+
| `qawolf runner keepalive` | read | Reset a runner's inactivity clock, for a caller that pauses between actions |
|
|
137
|
+
| `qawolf runner launch` | write | Launch an interactive runner and make it this directory's default |
|
|
138
|
+
| `qawolf runner run` | write | Run a flow on an interactive runner, shipping the current directory's files with it |
|
|
139
|
+
| `qawolf runner screenshot` | read | Save a JPEG of an interactive runner's screen to a file |
|
|
140
|
+
| `qawolf runner stop` | write | Stop an interactive runner |
|
|
141
|
+
| `qawolf tag create` | write | Create a tag on the caller's team. Tags select flows in run.create. |
|
|
142
|
+
| `qawolf tag list` | read | List the team's tags, alphabetical by name. Tag names select flows in run.create. |
|
|
110
143
|
|
|
111
144
|
<!-- commands-table:end -->
|
|
112
145
|
|
|
@@ -114,3 +147,18 @@ Kinds: `read` calls the QA Wolf API without changing anything; `write`
|
|
|
114
147
|
changes team state; `local` only affects this machine. A parenthesized
|
|
115
148
|
note like `local (read with --remote)` means that flag makes the command
|
|
116
149
|
call the QA Wolf API and require auth.
|
|
150
|
+
|
|
151
|
+
## Driving a browser: the `runner` group
|
|
152
|
+
|
|
153
|
+
The `runner` commands drive a live cloud browser: `launch` one, `screenshot` to
|
|
154
|
+
see it, `act` to click and type, `run` a flow on it, `exec` a snippet against its
|
|
155
|
+
page, `events` to read its journal (including the `recorder` stream, which turns
|
|
156
|
+
your actions into Playwright locators), `keepalive` to hold it open, and `stop`
|
|
157
|
+
when done. Everything is a plain request to one host, so a shell with an API key
|
|
158
|
+
and its own vision model can close the see-and-act loop with no other tooling.
|
|
159
|
+
|
|
160
|
+
The full workflow is its own guide: how a runner is billed, why the first call
|
|
161
|
+
must be a run, the order the commands go in, the see-and-act loop, `exec`, the
|
|
162
|
+
recorder, reading history, staying alive, and an end-to-end example. **Read
|
|
163
|
+
[`references/runner.md`](references/runner.md) before driving a runner for the
|
|
164
|
+
first time.**
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
# Driving a runner from the terminal
|
|
2
|
+
|
|
3
|
+
An interactive runner is a live pod with a browser in it. You launch one, look
|
|
4
|
+
at it, act on it, run flows on it, and read what it recorded. Everything is a
|
|
5
|
+
plain request to one host, so there is no connection to hold open.
|
|
6
|
+
|
|
7
|
+
## Getting one
|
|
8
|
+
|
|
9
|
+
Runner ids are yours to choose and are scoped to your team, so `agent-1` is a
|
|
10
|
+
fine id. Launching an id that is already running attaches to that runner instead
|
|
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
|
|
13
|
+
cheap and safe pattern, and the same id with a different `--name` is refused
|
|
14
|
+
rather than silently ignored.
|
|
15
|
+
|
|
16
|
+
Commands that target a runner find one in this order: `--runner`, then
|
|
17
|
+
`QAWOLF_RUNNER_ID`, then the runner stored for the current directory (which
|
|
18
|
+
`qawolf runner launch` sets). Setting the environment variable once is the most
|
|
19
|
+
robust for a harness whose working directory may not be stable, but it comes
|
|
20
|
+
with two catches worth knowing before you rely on it.
|
|
21
|
+
|
|
22
|
+
`qawolf runner launch` is not in that order: it takes its id from `--id` and
|
|
23
|
+
never reads `QAWOLF_RUNNER_ID`. Bare `qawolf runner launch` invents a random id,
|
|
24
|
+
bills a pod under it and stores it, so a harness that exported the variable and
|
|
25
|
+
then launched without `--id` ends up with a pod it is not addressing. Pass
|
|
26
|
+
`--id` whenever you have an id in mind.
|
|
27
|
+
|
|
28
|
+
And a runner id that is set is treated as found, whether or not anything is
|
|
29
|
+
running under it. So exporting `QAWOLF_RUNNER_ID=agent-1` turns off the
|
|
30
|
+
auto-launch described next: instead of starting `agent-1`, commands try to reach
|
|
31
|
+
it and fail with exit code `4`, which reads as "retry" and never succeeds.
|
|
32
|
+
Launch that id once yourself and the rest follows.
|
|
33
|
+
|
|
34
|
+
If nothing names a runner, the commands that change something will launch one
|
|
35
|
+
and say so on stderr, naming it: `run`, `act` and `exec`. **Read that
|
|
36
|
+
announcement.** The browser it just started is fresh: nothing has been run on it,
|
|
37
|
+
nothing is signed in, and no page is open. Acting as though your earlier setup
|
|
38
|
+
survived is the single most likely way to drive the wrong page.
|
|
39
|
+
|
|
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.
|
|
43
|
+
|
|
44
|
+
## The order that matters
|
|
45
|
+
|
|
46
|
+
A freshly launched runner has no screen. The virtual desktop starts with the
|
|
47
|
+
runner's **first run** and nothing else starts it, so until you have run
|
|
48
|
+
something:
|
|
49
|
+
|
|
50
|
+
- `screenshot` and `act` fail with exit code `2`, except `navigate`, which
|
|
51
|
+
fails with exit code `1` (`action-failed`): it skips the screen but still
|
|
52
|
+
needs the runner to have run something
|
|
53
|
+
- `exec` fails with exit code `4`
|
|
54
|
+
- `events recorder` reads as empty
|
|
55
|
+
|
|
56
|
+
None of that is a fault, and none of it clears on its own. **Only
|
|
57
|
+
`qawolf runner run <flow>` starts the screen.** A bare navigate does not: it
|
|
58
|
+
fails until the first run, however long you wait.
|
|
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.
|
|
64
|
+
|
|
65
|
+
Retry on the exit code, not on the message text:
|
|
66
|
+
|
|
67
|
+
- `4` is usually transient. The screen is up but cannot serve this instant:
|
|
68
|
+
restarting after a display-size change, or busy with another request. Retry in
|
|
69
|
+
a second or two — but bound the retries, because `4` also covers a runner that
|
|
70
|
+
was reaped after inactivity, which no amount of retrying brings back. If `4`
|
|
71
|
+
persists past a few tries, relaunch the id.
|
|
72
|
+
- `2` will not clear on its own. Either nothing has run on this runner yet, so
|
|
73
|
+
run a flow, or the runner has no browser at all, so launch with
|
|
74
|
+
`--name node20WithPlaywright` instead. The message says which.
|
|
75
|
+
|
|
76
|
+
The one exception is `exec`, which reports both as `4`; read its message to tell
|
|
77
|
+
them apart.
|
|
78
|
+
|
|
79
|
+
## Seeing and acting: the loop is yours
|
|
80
|
+
|
|
81
|
+
Two primitives, and you close the loop with your own model. There is no hosted
|
|
82
|
+
vision loop on this surface.
|
|
83
|
+
|
|
84
|
+
`qawolf runner screenshot --out page.jpg` writes a real JPEG to disk, decoded,
|
|
85
|
+
because every coding harness can open an image file. Read it with whatever
|
|
86
|
+
vision you have.
|
|
87
|
+
|
|
88
|
+
`qawolf runner act <action>` performs exactly one action per call, in the
|
|
89
|
+
computer-use tool vocabulary a vision model already emits: `click`,
|
|
90
|
+
`double_click`, `scroll`, `move`, `drag`, `keypress`, `navigate`, `type`. The
|
|
91
|
+
names and the field names are unchanged from that vocabulary on purpose, so you
|
|
92
|
+
can forward a tool call rather than translate it:
|
|
93
|
+
|
|
94
|
+
```sh
|
|
95
|
+
echo '{"type":"click","button":"left","x":480,"y":260}' | qawolf runner act -
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Coordinates are pixels on the same screenshot you just read. The runner serves
|
|
99
|
+
one see-or-act request at a time, so decide what to do next from each answer
|
|
100
|
+
rather than firing several. Bounds are checked before anything is sent, so an
|
|
101
|
+
over-long `--text` or an out-of-range coordinate comes back immediately naming
|
|
102
|
+
the limit instead of occupying the runner and then failing.
|
|
103
|
+
|
|
104
|
+
`act`, `run` and `exec` are the three commands whose lost answer may still have
|
|
105
|
+
taken effect. On a `4` from `act`, take a screenshot before repeating a click.
|
|
106
|
+
`exec`'s message says the snippet could not be evaluated, but a lost answer
|
|
107
|
+
looks the same from outside, so treat a `4` from a snippet that changes something
|
|
108
|
+
as "may have run" rather than "did not run".
|
|
109
|
+
|
|
110
|
+
## The recorder: what you cannot get from pixels
|
|
111
|
+
|
|
112
|
+
`qawolf runner events recorder` is the capability that has no equivalent in a
|
|
113
|
+
screenshot. As you drive the browser, the runner records each interaction and
|
|
114
|
+
publishes `locator` (the real Playwright locator it resolved), `alternates` (the
|
|
115
|
+
others that matched the same element) and `code` (the generated Playwright call),
|
|
116
|
+
alongside `type`, `sourceUrl` and `timestamp`.
|
|
117
|
+
|
|
118
|
+
```sh
|
|
119
|
+
qawolf runner events recorder --tail 5 | jq -r '.code // .type' # what happened
|
|
120
|
+
qawolf runner events recorder --tail 5 | jq -r '[.locator] + (.alternates // []) | @tsv'
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
`code` is absent on events with no call of their own, such as a navigation, which
|
|
124
|
+
is why the first line falls back to `type`. Use these to turn a session you drove
|
|
125
|
+
by pixel coordinates into durable selectors, and to check that a click landed on
|
|
126
|
+
the element you meant rather than near it. The stream is empty until the session
|
|
127
|
+
has a browser context, so an early empty answer means "not yet", not "broken".
|
|
128
|
+
Do not add `--json` here: it wraps each line in an envelope and these field paths
|
|
129
|
+
stop matching.
|
|
130
|
+
|
|
131
|
+
## Reading the page: `exec`
|
|
132
|
+
|
|
133
|
+
`qawolf runner exec <file>` evaluates a snippet against whatever the runner's
|
|
134
|
+
browser is showing, which is how you read a value out of the page rather than
|
|
135
|
+
looking at it. Two things to know, because neither is guessable:
|
|
136
|
+
|
|
137
|
+
It does not return what the snippet evaluated to, only whether it ran. To get a
|
|
138
|
+
value back, print it and read the `console` stream. Print it behind a marker you
|
|
139
|
+
chose, and match on that rather than taking the newest line: the page logs to the
|
|
140
|
+
same stream, so anything it prints after your snippet would be what `--tail 1`
|
|
141
|
+
hands back. Entries carry `source`, which is `serverConsole` for your snippet and
|
|
142
|
+
`browserConsole` for the page, so filtering on both is what pins the value down.
|
|
143
|
+
|
|
144
|
+
```sh
|
|
145
|
+
echo 'console.log("qw-title:", await page.title())' | qawolf runner exec -
|
|
146
|
+
qawolf runner events console --tail 20 \
|
|
147
|
+
| jq -r 'select(.source == "serverConsole" and (.message | contains("qw-title:"))) | .message'
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
And the snippet imports nothing of yours by default. Pass `--file <path>` to
|
|
151
|
+
evaluate it in that file's scope, which also ships the directory's other files,
|
|
152
|
+
so the snippet can use your own page objects and helpers.
|
|
153
|
+
|
|
154
|
+
## Running a flow
|
|
155
|
+
|
|
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.
|
|
164
|
+
|
|
165
|
+
The call answers with a run id as soon as the run is accepted. **The outcome is
|
|
166
|
+
not in that answer**, it is in the `run-status` stream, whose entries carry
|
|
167
|
+
`runId`, `status` and an `errorMessage` when there is one.
|
|
168
|
+
|
|
169
|
+
**Pass `--follow` to `run` and let it wait for you.** It streams the run's logs
|
|
170
|
+
and ends on the settled status, never on the logs, so a run that prints nothing
|
|
171
|
+
still terminates the follow and a run that dies mid-sentence still reports how.
|
|
172
|
+
Exit code `1` means the run did not pass.
|
|
173
|
+
|
|
174
|
+
```sh
|
|
175
|
+
qawolf runner run flows/checkout.flow.ts --follow
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
If you would rather submit and come back later, note that `--follow` on `events`
|
|
179
|
+
does not end when the run settles — it runs until its own `--timeout`, an hour
|
|
180
|
+
by default — so it cannot be used to wait for a run. Poll instead, and decide
|
|
181
|
+
with the same rule the CLI uses: `status` is `in-progress` while the run is
|
|
182
|
+
going, and any other value means it has settled.
|
|
183
|
+
|
|
184
|
+
```sh
|
|
185
|
+
qawolf runner run flows/checkout.flow.ts --json # -> {"runId":"...","runnerId":"..."}
|
|
186
|
+
qawolf runner events run-status --run <runId> --tail 1 | jq -r '.status'
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
The one expensive mistake on this surface: **if `run` reports that the runner
|
|
190
|
+
could not be reached, that does not mean the run did not start.** The runner may
|
|
191
|
+
have accepted it and been too slow to answer, and resubmitting bills and journals
|
|
192
|
+
a second run.
|
|
193
|
+
|
|
194
|
+
There is no clean recovery here, so it is worth being plain about it. The journal
|
|
195
|
+
lives on the same pod, so while the runner stays unreachable a `run-status` read
|
|
196
|
+
fails the same way and cannot tell you whether a run is going. Wait for the
|
|
197
|
+
runner to answer again, then read `run-status` without `--run` and look at the
|
|
198
|
+
newest `runId`. Nothing ties that id back to your submission: `run` never
|
|
199
|
+
answered, so you have no id to match it against, and a runner takes work from
|
|
200
|
+
anyone addressing it. Treat the newest id as your run only if you know nothing
|
|
201
|
+
else submits to this runner; otherwise follow it to see what it is before acting
|
|
202
|
+
on it. An empty read is not proof the run did not start, though:
|
|
203
|
+
`run` returns the moment the run is accepted, and its first `run-status` entry
|
|
204
|
+
may not be written yet, so a run accepted just before the runner went quiet can
|
|
205
|
+
still be in flight with nothing to show. `runFlow` has no idempotency key, so a
|
|
206
|
+
resubmit always risks a second billed run. Prefer polling `run-status` a while
|
|
207
|
+
longer over resubmitting; only submit again once you are willing to accept that
|
|
208
|
+
risk.
|
|
209
|
+
|
|
210
|
+
## Reading history
|
|
211
|
+
|
|
212
|
+
Everything observable is an append-only stream on the pod, read by cursor or
|
|
213
|
+
tail rather than subscribed to, so attaching late still gets you the history that
|
|
214
|
+
is still there. It is not unbounded: a size cap drops the oldest entries on a
|
|
215
|
+
long-lived runner, and a `--tail N` read can stop early and hand back fewer than
|
|
216
|
+
N even when more matched. Both are warned about on stderr — dropped entries only
|
|
217
|
+
once a read holds a cursor, a stopped-early read with a pointer at `--since` —
|
|
218
|
+
so watch stderr, treat a short answer as "at least this" rather than "all there
|
|
219
|
+
was", and read what you care about as you go rather than at the end. QA Wolf writes `recorder`, `console`, `run-events`,
|
|
220
|
+
`run-logs` and `run-status`; a stream nobody has written reads as empty rather
|
|
221
|
+
than as an error, and a stream this CLI version does not know about is still
|
|
222
|
+
readable by name.
|
|
223
|
+
|
|
224
|
+
One payload per line, so shell tools compose:
|
|
225
|
+
|
|
226
|
+
```sh
|
|
227
|
+
qawolf runner events console --tail 20 | jq -r '.message'
|
|
228
|
+
qawolf runner events run-logs --run <runId> --follow > run.log
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
`--tail N` takes the newest N, `--since <sequence>` reads everything after a
|
|
232
|
+
cursor, and `--run <id>` narrows the run-scoped streams.
|
|
233
|
+
|
|
234
|
+
`--follow` polls and prints as entries arrive. It is `tail -f` with a bound: it
|
|
235
|
+
ends only at its `--timeout` (an hour by default, exit `6`), because reading
|
|
236
|
+
keeps the runner alive and billing. Redirect it to a file and stop it yourself,
|
|
237
|
+
or use repeated `--since` reads when you need the command to end sooner.
|
|
238
|
+
|
|
239
|
+
Where it does win is the cursor. The pod reports how far a read scanned rather
|
|
240
|
+
than how far it matched, and `--follow` carries that number, so a filtered read
|
|
241
|
+
that matched nothing still moves forward. A caller paging by hand cannot see it,
|
|
242
|
+
because the CLI does not print it, and the best available substitute is the
|
|
243
|
+
highest `sequence` you actually saw. So a narrow `--run` filter over a busy
|
|
244
|
+
stream stalls: with nothing matching, there is no new `sequence` to move on to,
|
|
245
|
+
and you re-read the same window until something matches (NOVA-1397).
|
|
246
|
+
|
|
247
|
+
## Staying alive
|
|
248
|
+
|
|
249
|
+
A runner is reaped after a period of inactivity, and every command that talks to
|
|
250
|
+
the runner counts as activity, including a journal read.
|
|
251
|
+
`qawolf runner keepalive` exists for the gap that creates: a harness that thinks,
|
|
252
|
+
or waits on a human, for minutes between actions would otherwise come back to a
|
|
253
|
+
pod that is gone. It resets the clock and tells you the runner is still there.
|
|
254
|
+
|
|
255
|
+
It is listed as a `read`, but it is the one read with a cost: keeping the clock
|
|
256
|
+
reset keeps a billed pod alive. Call it while you are genuinely still working, not
|
|
257
|
+
on a timer you forget, and call `qawolf runner stop` when you are done rather
|
|
258
|
+
than leaving a pod to time out. A loop that keeps a runner alive and never stops
|
|
259
|
+
it bills until someone notices.
|
|
260
|
+
|
|
261
|
+
## End to end
|
|
262
|
+
|
|
263
|
+
Run from a directory holding a flow and a `package.json`. The run is what starts
|
|
264
|
+
the screen, so it is not optional even though the goal here is to drive by hand.
|
|
265
|
+
|
|
266
|
+
```sh
|
|
267
|
+
export QAWOLF_API_KEY=... # the only credential
|
|
268
|
+
export QAWOLF_RUNNER_ID=agent-1 # so no command below needs --runner
|
|
269
|
+
|
|
270
|
+
qawolf runner launch --id agent-1 --json # --id, not the variable; read .outcome
|
|
271
|
+
qawolf runner run flows/smoke.flow.ts --follow # starts the screen; exit 1 if it failed
|
|
272
|
+
|
|
273
|
+
qawolf runner act navigate --url https://example.com/login
|
|
274
|
+
qawolf runner screenshot --out page.jpg # then read page.jpg yourself
|
|
275
|
+
qawolf runner act click --button left --x 480 --y 260
|
|
276
|
+
qawolf runner act type --text "someone@example.com"
|
|
277
|
+
|
|
278
|
+
qawolf runner events recorder --tail 5 | jq -r '[.locator] + (.alternates // []) | @tsv'
|
|
279
|
+
qawolf runner stop
|
|
280
|
+
```
|