@qawolf/cli 1.10.0 → 1.11.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 CHANGED
@@ -175911,7 +175911,7 @@ function startUpdateCheck(deps) {
175911
175911
  // package.json
175912
175912
  var package_default = {
175913
175913
  name: "@qawolf/cli",
175914
- version: "1.10.0",
175914
+ version: "1.11.0",
175915
175915
  description: "Run and manage QA Wolf flows from the terminal, CI, or an AI agent",
175916
175916
  keywords: [
175917
175917
  "automation",
@@ -185970,6 +185970,14 @@ function flagUsage(field, kind) {
185970
185970
  return `${name} <KEY=VALUE...>`;
185971
185971
  return `${name} <value>`;
185972
185972
  }
185973
+ function describeFlag(schema) {
185974
+ const description = schema.description ?? "";
185975
+ const values = schema.enum ?? schema.items?.enum;
185976
+ if (!values?.length)
185977
+ return description;
185978
+ const choices = `One of: ${values.join(", ")}`;
185979
+ return description ? `${description} ${choices}` : choices;
185980
+ }
185973
185981
  function buildFlagSpecs(inputSchema) {
185974
185982
  const jsonSchema = exports_external.toJSONSchema(inputSchema, {
185975
185983
  io: "input"
@@ -185985,7 +185993,7 @@ function buildFlagSpecs(inputSchema) {
185985
185993
  flags.push({
185986
185994
  field,
185987
185995
  flag: flagUsage(field, kind.kind),
185988
- description: fieldSchema.description ?? "",
185996
+ description: describeFlag(fieldSchema),
185989
185997
  required: result.shape.required.has(field),
185990
185998
  kind: kind.kind
185991
185999
  });
@@ -186092,7 +186100,7 @@ async function handlePublicApiCommand(ctx, spec, options) {
186092
186100
  }
186093
186101
 
186094
186102
  // src/domains/publicApi/skippedContracts.ts
186095
- var handWrittenContractNames = new Set([
186103
+ var skippedContractNames = new Set([
186096
186104
  "flow.list",
186097
186105
  "runner.evaluateSnippet",
186098
186106
  "runner.launch",
@@ -186102,13 +186110,6 @@ var handWrittenContractNames = new Set([
186102
186110
  "runner.stop",
186103
186111
  "runner.takeScreenshot"
186104
186112
  ]);
186105
- var unexpressibleContractNames = new Set([
186106
- "issue.update"
186107
- ]);
186108
- var skippedContractNames = new Set([
186109
- ...handWrittenContractNames,
186110
- ...unexpressibleContractNames
186111
- ]);
186112
186113
 
186113
186114
  // src/commands/publicApi/index.ts
186114
186115
  var groupDescriptions = {
@@ -187284,4 +187285,4 @@ createProgram({ signals }).parseAsync().catch(() => {
187284
187285
  process.exitCode = 1;
187285
187286
  }).finally(() => flushAndExit(typeof process.exitCode === "number" ? process.exitCode : 0));
187286
187287
 
187287
- //# debugId=8CB687F9691544E564756E2164756E21
187288
+ //# debugId=229B48C8231EE6BC64756E2164756E21
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qawolf/cli",
3
- "version": "1.10.0",
3
+ "version": "1.11.0",
4
4
  "description": "Run and manage QA Wolf flows from the terminal, CI, or an AI agent",
5
5
  "keywords": [
6
6
  "automation",
@@ -72,6 +72,15 @@ each printed line from the payload alone to the whole envelope (`sequence`,
72
72
  `recordedAt`, `payload`). Both are JSON. Pass it when you want to page by
73
73
  sequence, omit it when you want the payloads themselves.
74
74
 
75
+ A `--json` response shows you most of its own shape, so read it first.
76
+
77
+ `qawolf run get` is the exception worth reading about before you use it. Its
78
+ artifact URLs expire, its failure fields are absent from a passing run, and its
79
+ `traceUrl` downloads a Playwright trace that you can read as JSON without
80
+ opening the trace viewer. **Read
81
+ [`references/run-results.md`](references/run-results.md) before reporting on a
82
+ run's outcome or opening its trace.**
83
+
75
84
  ## Safety: reads vs writes
76
85
 
77
86
  Read commands do not change team data, but some have operational effects noted
@@ -127,6 +136,7 @@ current branch.
127
136
  | `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
137
  | `qawolf issue find` | read | List the team's bug reports, maintenance reports, or coverage requests, newest first. |
129
138
  | `qawolf issue get` | read | Get an issue by id. |
139
+ | `qawolf issue update` | write | Update an issue owned by the caller's team. Omitted fields remain unchanged. |
130
140
  | `qawolf run create` | write | Create a run for the selected flows and/or tags in an environment. |
131
141
  | `qawolf run find` | read | List an environment's recent runs, newest first. |
132
142
  | `qawolf run get` | read | Get a run's status, per-flow results, and links. |
@@ -0,0 +1,169 @@
1
+ # Reading a run's results
2
+
3
+ How to use what `qawolf run get --run-id <id> --json` returns, and how to read
4
+ the Playwright trace it links to.
5
+
6
+ Call `run get` with `--json` and you see most of the response immediately. Read
7
+ this file for the parts a single response cannot show you: fields that appear
8
+ only when something fails, rules about the artifact URLs, and how to read a
9
+ trace without opening the trace viewer.
10
+
11
+ ## The shape
12
+
13
+ A run holds flows, a flow holds attempts, and artifacts hang off an attempt:
14
+
15
+ ```text
16
+ run
17
+ └── flows[]
18
+ ├── failure only when the flow failed
19
+ └── attempts[] oldest first
20
+ ├── logsUrl
21
+ ├── traceUrl
22
+ └── videoUrl
23
+ ```
24
+
25
+ `runId` in the response is canonical and can differ from the id you asked for.
26
+ Use the returned value for follow-up calls.
27
+
28
+ Poll `status` until it reaches `passed`, `failed` or `canceled`. The other
29
+ values mean the run is still going.
30
+
31
+ ## Fields a passing run does not show you
32
+
33
+ - `flows[].failure` exists only when a flow failed. Every flow passing means
34
+ there is no failure object at all, so its diagnosis and issue id are invisible
35
+ until something breaks. Do not conclude the field does not exist.
36
+ - `git` is populated only when a deploy notification started the run. A run
37
+ started manually or with `run create` has an empty object here.
38
+ - An attempt's `kind` and `status` select which other fields it has. Only
39
+ automated attempts that reached a verdict carry artifact URLs; canceled
40
+ attempts and manual Wolf Browser attempts carry none.
41
+ - A flow that passed after a retry still lists its failed attempts. Read the
42
+ last attempt for the outcome, and the earlier ones to see what went wrong.
43
+
44
+ ## Artifact URLs
45
+
46
+ Each automated attempt links `logsUrl` (execution logs), `videoUrl` (screen
47
+ recording) and `traceUrl` (a Playwright `trace.zip`).
48
+
49
+ 1. They are signed URLs with a limited life. The contract guarantees at least a
50
+ day. Call `run get` again for fresh ones instead of storing them; a stored
51
+ URL becomes a dead link.
52
+ 2. A URL can return 404 when that attempt did not produce that artifact. Handle
53
+ the 404 rather than treating the URL's presence as a guarantee of content.
54
+ 3. Download with a plain HTTP GET. The signature is in the URL, so no
55
+ authentication header is needed and no QA Wolf credentials are involved.
56
+
57
+ ```bash
58
+ qawolf run get --run-id "$RUN_ID" --json \
59
+ | jq -r '.flows[].attempts[-1].traceUrl // empty' \
60
+ | head -1 \
61
+ | xargs -r curl -sS -o trace.zip
62
+ ```
63
+
64
+ ## Reading the Playwright trace
65
+
66
+ The usual advice is `npx playwright show-trace trace.zip`, which opens a
67
+ browser window. That is useless in a shell and unnecessary: the zip holds
68
+ newline-delimited JSON files, and reading them directly is faster than
69
+ downloading a viewer.
70
+
71
+ The zip holds `trace.trace` (the events), `trace.network` (one request and
72
+ response per line) and a `resources/` directory of screencast frames. The
73
+ frames are most of the size, so extract only what you need.
74
+
75
+ ### The event types
76
+
77
+ Every line of `trace.trace` is one JSON object with a `type`:
78
+
79
+ - `before` — a call started. Carries `callId`, `startTime`, `class`, `method`
80
+ and `params`. `params.selector` or `params.url` is usually the target.
81
+ - `after` — that call finished. Matched to its `before` by `callId`. Carries
82
+ `endTime` and `result`, and an `error` when the call failed.
83
+ - `console` — a browser console message, with `messageType` and `text`.
84
+ - `log` — Playwright's own progress notes for a call.
85
+ - `screencast-frame`, `frame-snapshot` — the filmstrip and DOM snapshots the
86
+ viewer renders. Usually not worth reading directly.
87
+
88
+ Two details cost time if you miss them:
89
+
90
+ - **Times are monotonic milliseconds, not seconds.** A `goto` whose `startTime`
91
+ and `endTime` differ by `161.6` took 161 milliseconds. Subtract the smallest
92
+ `startTime` to get an offset from the start of the trace.
93
+ - **Return values use a serialized envelope.** `{"value":{"s":"passed"}}` is
94
+ the string `passed`, `{"n":640}` is the number `640`, and `{"o":[...]}` is an
95
+ object as a list of key and value pairs.
96
+
97
+ ### A worked example
98
+
99
+ Pairing `before` with `after` gives an action timeline with durations and
100
+ failures:
101
+
102
+ ```python
103
+ import json, sys, zipfile
104
+
105
+ with zipfile.ZipFile(sys.argv[1]) as z:
106
+ events = [json.loads(line) for line in z.read("trace.trace").decode().splitlines()]
107
+
108
+ starts = {e["callId"]: e for e in events if e["type"] == "before"}
109
+ ends = {e["callId"]: e for e in events if e["type"] == "after"}
110
+ t0 = min(e["startTime"] for e in starts.values())
111
+
112
+ for call_id, before in starts.items():
113
+ after = ends.get(call_id, {})
114
+ params = before.get("params", {})
115
+ target = params.get("selector") or params.get("url") or ""
116
+ error = after.get("error")
117
+ print(
118
+ f'{(before["startTime"] - t0) / 1000:7.2f}s'
119
+ f' {after.get("endTime", before["startTime"]) - before["startTime"]:7.1f}ms'
120
+ f' {before["class"]}.{before["method"]:<18} {target[:40]}'
121
+ f'{" FAILED: " + json.dumps(error)[:60] if error else ""}'
122
+ )
123
+
124
+ for e in events:
125
+ if e["type"] == "console" and e["messageType"] == "error":
126
+ print(f'console error: {e["text"][:70]}')
127
+ ```
128
+
129
+ It prints one line per call, in order:
130
+
131
+ ```text
132
+ 0.00s 161.6ms Frame.goto https://example.com/
133
+ 0.17s 28.3ms Frame.waitForSelector #screen
134
+ 0.20s 4.2ms Frame.innerText #fps_stats
135
+ console error: Failed to load resource: the server responded with a status of 404 ()
136
+ ```
137
+
138
+ To find why an attempt failed, read the last `after` that carries an `error`,
139
+ then the `console` errors near it in time. To see what the page did, read
140
+ `trace.network`.
141
+
142
+ ## Response fields
143
+
144
+ Every documented field of the `run.get` response. `[]` marks an array, so
145
+ `flows[].attempts[].traceUrl` is the trace URL of one attempt of one flow.
146
+
147
+ <!-- fields:start — generated by `bun run generate`, do not edit -->
148
+
149
+ - `completedAt` — When the run finished executing. Absent while queued or running, and also absent for a terminal run that never completed execution (e.g. every flow was canceled or skipped).
150
+ - `git` — The branch and commit under test. The fields are present when a deploy notification started the run, and absent for runs started another way, for example manually or with run.create.
151
+ - `git.commitUrl` — Link to the commit on the code host.
152
+ - `runId` — The run this response describes. Treat it as canonical: it can differ from the id you asked for. A deploy notification returns a run id before the run exists, and if a second notification for the same commit is folded into an earlier run, that id resolves to the earlier run instead.
153
+ - `status` — One of: queued, running, passed, failed, canceled
154
+ - `flows` — The run's flows, ordered alphabetically by name.
155
+ - `flows[].attempts` — The flow's finished execution attempts, oldest first, including manual Wolf Browser attempts. Present once at least one attempt has finished, so a flow that passed after retries also lists its failed attempts. Artifact URLs appear only on automated attempts that reached a verdict, stay valid for at least a day (call run.get again for fresh ones), and can return 404 when the attempt did not produce that artifact.
156
+ - `flows[].attempts[].logsUrl` — Signed URL for the attempt's execution logs.
157
+ - `flows[].attempts[].traceUrl` — Signed URL for the attempt's Playwright trace (a trace.zip; open it with `npx playwright show-trace`).
158
+ - `flows[].attempts[].videoUrl` — Signed URL for the attempt's screen recording.
159
+ - `flows[].attempts[].kind` — One of: automated, manual
160
+ - `flows[].attempts[].startedAt` — Absent when the attempt failed before it could start.
161
+ - `flows[].attempts[].status` — One of: passed, failed, canceled
162
+ - `flows[].failure.diagnosis` — QA Wolf's investigation verdict for the failure: `bug` means the application is broken, `maintenance` means the test needed an update and the failure does not indicate an application problem. Absent until the investigation reaches a verdict. Pass issueId to issue.get for details.
163
+ - `flows[].failure.diagnosis.issueId` — The id of the issue.
164
+ - `flows[].failure.diagnosis.type` — One of: bug, maintenance
165
+ - `flows[].flowId` — The id of the flow.
166
+ - `flows[].status` — One of: failed, queued, running, passed, canceled
167
+ - `url` — Absolute URL of the run page.
168
+
169
+ <!-- fields:end -->