ccqa 1.14.0 → 1.16.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/README.md +125 -233
- package/dist/bin/ccqa.mjs +2692 -2196
- package/dist/hub-client/index.d.mts +96 -6
- package/dist/hub-client/index.mjs +20 -0
- package/dist/package.json +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,40 +1,26 @@
|
|
|
1
1
|
# ccqa
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> [!WARNING]
|
|
4
|
+
> ccqa is under active development. Expect breaking changes.
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
test spec in YAML; Claude drives a real browser **once** to discover the
|
|
7
|
-
route; ccqa compiles that recording into ordinary test code your CI replays
|
|
8
|
-
with no model in the loop.
|
|
6
|
+
**Your Claude subscription already includes a QA engineer.**
|
|
9
7
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
replays
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
credential in CI.
|
|
8
|
+
Write a test spec in YAML. Claude drives a real browser **once** to
|
|
9
|
+
discover the route, and ccqa compiles the recording into plain test code
|
|
10
|
+
your CI replays — no model in the loop, no API key. Claude returns only
|
|
11
|
+
where it pays: auditing specs against the code, explaining failures, and
|
|
12
|
+
driving `mode: live` specs.
|
|
16
13
|
|
|
17
14
|
[日本語版 README](./docs/README.ja.md)
|
|
18
15
|
|
|
19
|
-
##
|
|
16
|
+
## Quick start
|
|
20
17
|
|
|
21
18
|
```bash
|
|
22
|
-
pnpm add -D ccqa vitest agent-browser
|
|
19
|
+
pnpm add -D ccqa vitest agent-browser # Node 20+
|
|
23
20
|
```
|
|
24
21
|
|
|
25
|
-
|
|
26
|
-
[
|
|
27
|
-
[vitest](https://vitest.dev) are peer dependencies of the **default
|
|
28
|
-
agent-browser target** — they run its recorded tests. A project that only uses
|
|
29
|
-
an external target (`playwright`, `runn`) needs just `ccqa` plus that tool
|
|
30
|
-
(e.g. `pnpm add -D ccqa @playwright/test`); ccqa executes it through the
|
|
31
|
-
target's `runCommand`.
|
|
32
|
-
|
|
33
|
-
## Quick start
|
|
34
|
-
|
|
35
|
-
**1. Write a spec** — by hand, or interactively with
|
|
36
|
-
[`ccqa draft`](./docs/draft.md). (`ccqa init` scaffolds the `.ccqa/`
|
|
37
|
-
skeleton.)
|
|
22
|
+
Write a spec — `ccqa init` scaffolds the tree,
|
|
23
|
+
[`ccqa draft`](./docs/draft.md) writes one with you:
|
|
38
24
|
|
|
39
25
|
```yaml
|
|
40
26
|
# .ccqa/features/tasks/test-cases/create-and-complete/spec.yaml
|
|
@@ -50,264 +36,170 @@ steps:
|
|
|
50
36
|
expected: Task appears in the task list with status "Open"
|
|
51
37
|
```
|
|
52
38
|
|
|
53
|
-
|
|
54
|
-
embedding an environment, so the same spec runs against local and staging. A
|
|
55
|
-
`.env` file covers you locally; in CI the values come from a hub
|
|
56
|
-
(`ccqa hub var set`) so nothing environment-specific lives in the repo. See
|
|
57
|
-
[Profiles and environment variables](./docs/running.md#profiles-and-environment-variables).
|
|
58
|
-
|
|
59
|
-
```bash
|
|
60
|
-
echo 'APP_URL=http://localhost:3000' >> .env
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
**3. Record once** — Claude drives the browser and generates the test:
|
|
64
|
-
|
|
65
|
-
```bash
|
|
66
|
-
ccqa record tasks/create-and-complete
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
**4. Run it** — vitest replays the recording; no LLM involved:
|
|
39
|
+
Record once, replay forever:
|
|
70
40
|
|
|
71
41
|
```bash
|
|
72
|
-
|
|
42
|
+
echo 'APP_URL=http://localhost:3000' >> .env # ${VAR}s stay out of specs
|
|
43
|
+
ccqa record tasks/create-and-complete # Claude drives the browser
|
|
44
|
+
ccqa run tasks/create-and-complete # vitest replays — no LLM
|
|
73
45
|
```
|
|
74
46
|
|
|
75
|
-
|
|
76
|
-
See [Running specs](./docs/running.md) for flags and the report format.
|
|
47
|
+
Every run writes `report.json` and step screenshots to `ccqa-report/`.
|
|
77
48
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
[`ccqa hub session capture`](./docs/sessions.md) and
|
|
49
|
+
Some logins cannot be replayed from a recording — an SSO redirect, a
|
|
50
|
+
device-trust prompt. Sign in by hand once with
|
|
51
|
+
[`ccqa hub session capture`](./docs/sessions.md), and specs start from
|
|
52
|
+
that saved session.
|
|
81
53
|
|
|
82
54
|
## How it works
|
|
83
55
|
|
|
84
56
|
```
|
|
85
|
-
spec.yaml ──► ccqa record
|
|
86
|
-
steps + Claude drives
|
|
87
|
-
expected the browser
|
|
88
|
-
results discovers the tool-neutral (reuse-first) / runn
|
|
89
|
-
route IR
|
|
90
|
-
|
|
91
|
-
test code ──► ccqa run ────────► report.json ─► ccqa hub push /
|
|
92
|
-
vitest replay / + evidence --report-to-hub
|
|
93
|
-
runCommand / + artifacts team dashboard,
|
|
94
|
-
live (Claude failure triage,
|
|
95
|
-
drives per step) grading & learning
|
|
57
|
+
spec.yaml ──► ccqa record ──► ir.json ──► test code ──► ccqa run
|
|
58
|
+
steps + Claude drives recorded per-target replayed in CI,
|
|
59
|
+
expected the browser actions emit no LLM
|
|
96
60
|
```
|
|
97
61
|
|
|
98
62
|
A spec runs in one of two ways:
|
|
99
63
|
|
|
100
|
-
**Deterministic (the default).**
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
stable. The `target:` field picks only **what the recording compiles
|
|
104
|
-
into**; every target is the same deterministic replay:
|
|
64
|
+
**Deterministic (the default).** The recording compiles into plain test
|
|
65
|
+
code and CI replays it with no model in the loop. `target:` picks only
|
|
66
|
+
what it compiles into:
|
|
105
67
|
|
|
106
68
|
| `target:` | Generated file | Replayed by |
|
|
107
69
|
|---|---|---|
|
|
108
|
-
| `agent-browser` (default) | `test.spec.ts` (vitest
|
|
109
|
-
| `playwright` |
|
|
110
|
-
| `runn` | `runbook.yaml` (API scenario
|
|
111
|
-
|
|
112
|
-
`runCommand` is the one-line command your repo already uses to run that
|
|
113
|
-
tool, declared once in `.ccqa/config.yaml` — e.g.
|
|
114
|
-
`pnpm exec playwright test {files}`. See
|
|
115
|
-
[Generation targets](./docs/targets.md) for the substitution contract.
|
|
70
|
+
| `agent-browser` (default) | `test.spec.ts` (vitest) | vitest |
|
|
71
|
+
| `playwright` | plain `@playwright/test` spec | your `runCommand` |
|
|
72
|
+
| `runn` | `runbook.yaml` (API scenario, no recording) | your `runCommand` |
|
|
116
73
|
|
|
117
74
|
**Live (`mode: live`).** No codegen: Claude drives every run and judges
|
|
118
|
-
each step's `expected` — for
|
|
119
|
-
recording would break.
|
|
120
|
-
|
|
121
|
-
## Failure analysis and drift
|
|
75
|
+
each step's `expected` — for UIs a fixed recording would break on.
|
|
122
76
|
|
|
123
|
-
|
|
124
|
-
|
|
77
|
+
vitest and agent-browser are peer dependencies of the default target; a
|
|
78
|
+
project on an external target alone needs just `ccqa` and that tool.
|
|
79
|
+
`runCommand` and reusing your existing page objects:
|
|
80
|
+
[Generation targets](./docs/targets.md).
|
|
125
81
|
|
|
126
|
-
|
|
127
|
-
— `TEST_DRIFT`, `SPEC_CHANGE`, `PRODUCT_BUG`, or `UNKNOWN` when the evidence
|
|
128
|
-
does not support a call. The label comes with a drift audit of the same spec,
|
|
129
|
-
because "did the test break" and "does the test still describe the product"
|
|
130
|
-
are the same investigation. `[base]` is what the diff is read against: a git
|
|
131
|
-
ref, or `last-green` to have each spec diff against the commit where it last
|
|
132
|
-
passed. With neither, the label rests on the failure alone and says so.
|
|
82
|
+
## Audit, then run
|
|
133
83
|
|
|
134
|
-
**
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
decides the repair, so the audit reports it: stale generated code is
|
|
139
|
-
re-recorded, a stale spec needs a human.
|
|
84
|
+
**A spec describes the code your verification environment is running** —
|
|
85
|
+
not your branch, not the tip of main. A deploy moves that code, and some
|
|
86
|
+
specs stop describing it. Those specs are not failing; they say nothing
|
|
87
|
+
true about what runs, so executing them proves nothing.
|
|
140
88
|
|
|
141
|
-
|
|
142
|
-
[Failure triage](./docs/running.md#failure-triage) and
|
|
143
|
-
[Drift detection](./docs/running.md#drift-detection).
|
|
144
|
-
|
|
145
|
-
## The hub
|
|
89
|
+
So ccqa asks the cheap question before the expensive one:
|
|
146
90
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
91
|
+
```
|
|
92
|
+
the code the verification environment is running
|
|
93
|
+
│
|
|
94
|
+
│ a spec describes this
|
|
95
|
+
▼
|
|
96
|
+
the deployed commit changes
|
|
97
|
+
│
|
|
98
|
+
▼
|
|
99
|
+
audit the specs that change reaches
|
|
100
|
+
│
|
|
101
|
+
still describes it ───┴─── no longer describes it
|
|
102
|
+
│ │
|
|
103
|
+
▼ ▼
|
|
104
|
+
run it a person repairs it
|
|
105
|
+
│
|
|
106
|
+
re-audited next round;
|
|
107
|
+
unverified until then
|
|
163
108
|
```
|
|
164
109
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
See [Hub](./docs/hub.md) for the full setup and
|
|
171
|
-
[Hub API](./docs/hub-api.md) to script it over HTTP.
|
|
110
|
+
`ccqa audit` reads each spec against the source — cents per spec, no
|
|
111
|
+
browser — and records every verdict on the **hub**, the small server
|
|
112
|
+
that holds what the team and CI share. Stale generated code is
|
|
113
|
+
re-recorded; a stale spec goes to a human and stays **unverified** —
|
|
114
|
+
neither passing nor failing — until repaired.
|
|
172
115
|
|
|
173
|
-
|
|
116
|
+
`ccqa run --only-hub-rerun-needed` asks the hub which specs are worth
|
|
117
|
+
running: cleared by the audit *and* invalidated by a deploy. A drifted spec —
|
|
118
|
+
or one whose last run failed — answers `needsRepair` and is never run. A run
|
|
119
|
+
repairs neither, and it costs dollars to learn that.
|
|
174
120
|
|
|
175
|
-
|
|
176
|
-
|
|
121
|
+
Selecting nothing is only an answer when every spec was answered. If any spec
|
|
122
|
+
is waiting on the audit, or the hub could not judge it, the run **exits
|
|
123
|
+
non-zero** rather than report a green run that verified nothing. And while a
|
|
124
|
+
run executes it claims its specs, so a cycle that starts before the last one
|
|
125
|
+
finishes skips what is already running instead of driving the same flow
|
|
126
|
+
twice.
|
|
177
127
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
| Post-deploy run | after a deploy | Which specs' last result is no longer trustworthy? |
|
|
182
|
-
| Drift audit | `schedule` | Do the specs still describe the code? |
|
|
128
|
+
When a clean spec still fails, `--on-fail-explain` labels whose problem
|
|
129
|
+
it is: `TEST_DRIFT`, `SPEC_CHANGE`, `PRODUCT_BUG`, or `UNKNOWN`. You
|
|
130
|
+
grade the calls on the hub, and it learns from your grades.
|
|
183
131
|
|
|
184
|
-
|
|
132
|
+
## In CI
|
|
185
133
|
|
|
186
|
-
- **A Claude credential.** Replaying a recorded spec uses no model, but the
|
|
187
|
-
change selection, the failure analysis and the audit all do.
|
|
188
|
-
- **A running [hub](#the-hub)**, reached with `CCQA_HUB_URL` and
|
|
189
|
-
`CCQA_HUB_TOKEN`. Only a pre-merge run with no `--hub-profile` and no
|
|
190
|
-
`--report-to-hub` can do without one.
|
|
191
|
-
|
|
192
|
-
See [Environment variables](./docs/commands.md#environment-variables) for the
|
|
193
|
-
full list.
|
|
194
|
-
|
|
195
|
-
A **profile** is one deployed environment. It names a bucket of variables and
|
|
196
|
-
saved sessions on the hub, and — since two environments sit at different
|
|
197
|
-
commits — its own deploy history. Register the variables your specs reference
|
|
198
|
-
once, from your machine:
|
|
199
|
-
|
|
200
|
-
```bash
|
|
201
|
-
ccqa hub var set APP_URL --value https://app.example --profile staging
|
|
202
134
|
```
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
Run the specs the change reaches, and label what broke.
|
|
210
|
-
|
|
211
|
-
```bash
|
|
212
|
-
ccqa run --only-affected-by --on-fail-explain --hub-profile staging \
|
|
213
|
-
--report-format github --report-to-hub
|
|
135
|
+
deploy lands
|
|
136
|
+
├─ ccqa hub deploy record --select what shipped, which specs it reaches
|
|
137
|
+
├─ ccqa audit --only-hub-audit-needed --report-to-hub
|
|
138
|
+
│ does each spec still describe it?
|
|
139
|
+
└─ ccqa run --only-hub-rerun-needed --on-fail-explain \
|
|
140
|
+
--hub-profile ci --report-to-hub
|
|
214
141
|
```
|
|
215
142
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
from the hub. Without it, a spec's `${…}` references go unresolved.
|
|
221
|
-
- `--report-format github` annotates the pull request.
|
|
222
|
-
- `--report-to-hub` streams results to the hub as the run executes.
|
|
223
|
-
|
|
224
|
-
**Set `fetch-depth: 0` on `actions/checkout`.** Both selection flags read
|
|
225
|
-
their baseline from `GITHUB_BASE_REF` and resolve it against `origin/<base>`,
|
|
226
|
-
which a shallow checkout does not have. Without it the run exits with a usage
|
|
227
|
-
error before the first test. Outside a `pull_request` workflow there is no
|
|
228
|
-
`GITHUB_BASE_REF`, so pass the base yourself: `--only-affected-by origin/main`.
|
|
143
|
+
The audit costs cents; a live spec costs dollars. Filtering first leaves
|
|
144
|
+
a run whose failures are worth reading. Record every deploy with
|
|
145
|
+
`--select` — a range recorded without it answers `unanswerable` forever, and
|
|
146
|
+
nothing fills the hole later.
|
|
229
147
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
148
|
+
| Job | Trigger | Question it answers |
|
|
149
|
+
|---|---|---|
|
|
150
|
+
| Deploy loop | after a deploy | Which specs did this deploy invalidate? |
|
|
151
|
+
| Pre-merge run | `pull_request` | Does this change break a spec, and whose fault is it? |
|
|
152
|
+
| Full audit | `schedule` | Do all the specs still describe the code? |
|
|
234
153
|
|
|
235
|
-
|
|
236
|
-
shipped:
|
|
154
|
+
The two jobs outside the loop:
|
|
237
155
|
|
|
238
156
|
```bash
|
|
239
|
-
|
|
240
|
-
|
|
157
|
+
# pull request — run what the diff reaches, label what broke
|
|
158
|
+
# (checkout with fetch-depth: 0, or the base ref is not there to resolve)
|
|
159
|
+
ccqa run --only-affected-by "origin/$GITHUB_BASE_REF" --on-fail-explain \
|
|
160
|
+
--hub-profile ci --report-format github --report-to-hub
|
|
241
161
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
```bash
|
|
245
|
-
ccqa run --only-hub-stale --hub-profile staging --report-to-hub
|
|
162
|
+
# schedule — audit everything; no browser, no deploy
|
|
163
|
+
ccqa audit --report-format github --report-to-hub
|
|
246
164
|
```
|
|
247
165
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
- `--only-hub-stale` asks the hub, per spec, whether any deploy has touched
|
|
251
|
-
it since that spec last ran.
|
|
252
|
-
|
|
253
|
-
The hub has no checkout and never runs `git`, so it cannot work out what a
|
|
254
|
-
deploy changed. That is why the selection is submitted with the deploy rather
|
|
255
|
-
than reconstructed later — and why a deploy recorded without `--select` leaves
|
|
256
|
-
a hole nothing can fill in afterwards.
|
|
257
|
-
|
|
258
|
-
**Expect it to select nothing at first.** A spec with no recorded run is
|
|
259
|
-
`neverRun`; one whose baseline predates the deploy log is `unknown`. Neither
|
|
260
|
-
runs by default. Record a deploy, run every spec once with `--report-to-hub`,
|
|
261
|
-
and the selection means something from the next deploy on. This job also reads
|
|
262
|
-
the spec inventory from the hub, so `ccqa perspectives` has to have run.
|
|
263
|
-
`--only-hub-stale-with-unknown` opts the undecided specs in.
|
|
166
|
+
Runnable workflows and every flag:
|
|
167
|
+
[CI integration](./docs/running.md#ci-integration).
|
|
264
168
|
|
|
265
|
-
|
|
169
|
+
## The hub
|
|
266
170
|
|
|
267
|
-
|
|
171
|
+
You have met the hub twice now: the audit writes its verdicts there,
|
|
172
|
+
and the run asks it what is worth running. The same server holds the
|
|
173
|
+
rest of what a team shares: the variables and sessions `${…}` resolves
|
|
174
|
+
to (CI keeps one secret), the deploy log behind the selection flags,
|
|
175
|
+
run reports with screenshots, and the prompts learned from your triage
|
|
176
|
+
grades.
|
|
268
177
|
|
|
269
178
|
```bash
|
|
270
|
-
|
|
179
|
+
export CCQA_HUB_TOKEN=$(openssl rand -hex 24)
|
|
180
|
+
export CCQA_HUB_ENCRYPTION_KEY=$(openssl rand -hex 32)
|
|
181
|
+
ccqa serve
|
|
271
182
|
```
|
|
272
183
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
The pre-merge job already audits the specs that failed. This one covers the
|
|
281
|
-
rest, because a spec can pass and still describe a product that no longer
|
|
282
|
-
exists.
|
|
283
|
-
|
|
284
|
-
### Workflows
|
|
285
|
-
|
|
286
|
-
[CI integration](./docs/running.md#ci-integration) has runnable workflows for
|
|
287
|
-
the pre-merge run and the scheduled audit.
|
|
288
|
-
[`ccqa hub deploy record`](./docs/hub.md#ccqa-hub-deploy-record) covers the
|
|
289
|
-
deploy job, including a `curl`-only variant for pipelines with no Node.
|
|
184
|
+
Anything that needs the hub names it — `--hub-profile`,
|
|
185
|
+
`--only-hub-rerun-needed`, `--report-to-hub` — and fails rather than
|
|
186
|
+
degrade when it cannot reach one. A **profile** is a named value set — a
|
|
187
|
+
tenant, an account, a role — not an environment: ccqa tracks one
|
|
188
|
+
verification environment
|
|
189
|
+
([ADR-0013](./docs/adr/0013-one-verification-environment.md)).
|
|
290
190
|
|
|
291
191
|
## Documentation
|
|
292
192
|
|
|
293
193
|
| I want to… | Read |
|
|
294
194
|
|---|---|
|
|
295
|
-
|
|
|
296
|
-
|
|
|
297
|
-
|
|
|
298
|
-
|
|
|
299
|
-
|
|
|
300
|
-
|
|
|
301
|
-
|
|
|
302
|
-
|
|
|
303
|
-
| Wire ccqa into GitHub Actions | [CI integration](./docs/running.md#ci-integration) |
|
|
304
|
-
| Run specs live (no codegen), with per-project guidance | [Live specs](./docs/live.md) |
|
|
305
|
-
| Start runs already signed in / skip device-trust gates | [Saved sessions](./docs/sessions.md) |
|
|
306
|
-
| See which assertions generated tests use | [Assertions](./docs/assertions.md) |
|
|
307
|
-
| Auto-fix failing recorded tests | [Auto-fix](./docs/auto-fix.md) |
|
|
308
|
-
| Aggregate results, sessions, and variables on a team server | [Hub](./docs/hub.md) |
|
|
309
|
-
| Script the hub over HTTP | [Hub API](./docs/hub-api.md) |
|
|
310
|
-
| Understand why ccqa is built this way | [ADR](./docs/adr/README.md) |
|
|
195
|
+
| Write specs — fields, blocks, file uploads | [spec.yaml](./docs/spec.md) |
|
|
196
|
+
| Run specs and read the report | [Running](./docs/running.md) |
|
|
197
|
+
| Wire it into GitHub Actions | [CI integration](./docs/running.md#ci-integration) |
|
|
198
|
+
| Emit Playwright / runn tests | [Targets](./docs/targets.md) |
|
|
199
|
+
| Drive specs live, with per-project guidance | [Live specs](./docs/live.md) |
|
|
200
|
+
| Sign in once and reuse the session | [Sessions](./docs/sessions.md) |
|
|
201
|
+
| Run the team hub / script it over HTTP | [Hub](./docs/hub.md) · [API](./docs/hub-api.md) |
|
|
202
|
+
| Understand why it is built this way | [ADR](./docs/adr/README.md) |
|
|
311
203
|
|
|
312
204
|
## License
|
|
313
205
|
|