Reports & test runs
Actions are recorded into a test run. One auto-starts on the first action — set
VERIKUN_NO_RUN=1 to disable.
Every command becomes a step with its timing, the selector and the identifier it resolved through, and pass/fail. A failing step also captures a screenshot and the UI hierarchy of the page.
Where it lives
Section titled “Where it lives”./.verikun/run/ active run (run.json + artifacts/) — auto-created on first action./.verikun/runs/<id>/ archived runs (report.xml, report.html, run.json, artifacts/)./.verikun/suites/<id>/ suite overviews (index.json, index.html)./.verikun/run-<lane>/ active run of one lane of a PARALLEL suite (VERIKUN_LANE)Run state is on disk, not in memory, so a later vk process can pick it up — that is what
lets vk device reset undo a change after the flow that made it has died.
What is recorded
Section titled “What is recorded”Actions plus wait and assert. Not inspection — ui, find, devices — because
reading the screen is not a test step. The one exception is log, which is recorded so its
device-log capture lands in the report.
Archiving
Section titled “Archiving”vk run archive smokeFinalizes the run into ./.verikun/runs/<id>/:
| File | Contents |
|---|---|
report.xml |
JUnit: one <testcase> per step with timings, <failure> for failed assertions, <error> for environment errors, and the resolved identifier in <system-out>. Drops straight into CI. |
report.html |
Self-contained report — see below |
artifacts/logcat.txt |
Full device log for the run window (default) |
artifacts/logcat-app.txt |
App-scoped log, when a package or bundle was launched |
run.json |
The raw recording |
vk run archive exits non-zero when the run contained failures, so the same command both
produces the report and gates CI.
report.html
Section titled “report.html”Every step, the identifiers used, any screenshots taken, the screenshot and hierarchy of any
failed page, a link to the full device log, an app-scoped log accordion when the run launched
an app, and any per-step logs from vk log. A run that did not pass says so in a banner at
the top.
Expanding the accordion shows the captured device log inline:
Device logs
Section titled “Device logs”Logs are pulled at archive time into artifacts/logcat.txt, scoped to the run’s window
where possible — no explicit vk log is required.
- Opt out on green runs with
vk run archive --no-logsorVERIKUN_NO_LOGS. Failed runs still capture. - Capture is best-effort and never blocks sealing.
- An on-demand
vk logstill attaches into its own step, kept tail-first so a crash trace survives the size cap. - Over
--serverthe same data is served by the server, so a remote CI archive matches a local one.
Secrets
Section titled “Secrets”Step names never include typed text. vk text redacts the value into the step message
when the field carries the password flag.
Redaction is detection-based, so a field the platform does not flag as a password is stored
verbatim. Two known gaps: on iOS a Flutter obscured field is never flagged
(#44; see
Platform support), and a value
typed from {{env.NAME}} is not masked by name
(#57). Failure evidence — screenshots and
hierarchies — is raw as well.
Pass/fail mapping
Section titled “Pass/fail mapping”The returned exit code, or a thrown CliError’s code:
| Exit code | Report outcome |
|---|---|
0 |
passed |
1 |
<failure> — an assertion |
≥ 2 |
<error> — environment or usage |
A failed run can never archive green
Section titled “A failed run can never archive green”A failure the vk ai engine produces with no command to attach it to — a guard giving up, a
budget or timeout abort — is recorded as one synthetic failed step (only when no step is
already red), so the archive and the [ai] … verdict on the console always agree. Its
evidence is a screenshot plus hierarchy locally; over --server the hierarchy only
(#48). The rule:
Contracts.
Automatic rollover
Section titled “Automatic rollover”So an implicit run never silently merges unrelated activity, the active run auto-closes (archives) and a fresh one starts when the context changes:
| Trigger | Applies to | Tune with |
|---|---|---|
| Idle too long (default 30 min) | implicit runs only | VERIKUN_RUN_IDLE_MIN (minutes; 0 disables) |
| Different device serial | any run | — |
| Different session | any run | VERIKUN_SESSION (falls back to TERM_SESSION_ID) |
A run you named with vk run start is sticky to idle — only a hard context change
(device or session) rolls it over.
Rollover always archives the old run, never discards it, and prints the reason and destination to stderr.
Rollover and device overrides
Section titled “Rollover and device overrides”Device-state snapshots live in the run file, so a rollover has to carry them:
- On a same-device rollover, unrestored overrides are carried forward into the new run.
- On a device-change rollover, verikun warns with the exact
vk device set … --device <serial>needed to undo them — it cannot drive the old device from a process pointed at the new one.
The suite manifest: index.json
Section titled “The suite manifest: index.json”A stable, schemaVersioned manifest written to ./.verikun/suites/<id>/. Publish steps in
CI compose over it.
| Field | Meaning |
|---|---|
schemaVersion |
Currently 1. Additive changes do not bump it. |
verikun |
Version of the process that wrote the manifest — the client’s. For a --server run that is not what drove the device; see server. |
server |
--server runs only: {url, verikun, reads} — which server drove the device, on which version, and whether it read the hierarchy via the companion or the stock dump |
tests[] |
Per-test: pass/fail, step count, cost, duration, run id |
tests[].modelRepairs |
How many steps the model repaired. 0 on a clean replay — gate on it to fail a build that needed healing |
tests[].planSteps |
Top-level steps in the plan that ran — the compile’s size, unlike steps, which counts what executed. Comparable across a pass and a failure, so an unusually short compile is visible when diffing runs of the same test |
tests[].flaky / .attempts |
Present when --retries recovered the test |
tests[].device |
Which device ran this test. Set on a parallel suite, where the assignment is dynamic |
concurrency |
How many devices ran the suite at once. Absent (or 1) for a serial suite |
totals |
Suite-level counts |
totals.durationMs |
Device time — the sum of every test’s duration. Equal to elapsed time serially; across N devices it is what the gate cost in device-seconds |
totals.wallClockMs |
Elapsed time, startedAt → finishedAt — how long you actually waited |
warnings[] |
Recovered flakes, environment retries, and devices retired mid-suite |
aborted |
{reason, notRun, kind} when the suite stopped early. kind is environment (exit 3) or budget (exit 1, from --max-suite-cost-usd) |
On an abort, the not-run tests get no rows and no place in totals, so
passed + failed === tests still holds and nothing downstream mistakes a skipped test for a
regression.
Fields added since the first schema are all additive. A manifest written before they existed
simply omits them — treat a missing concurrency as 1.
Disabling recording
Section titled “Disabling recording”VERIKUN_NO_RUN=1 vk tap @xNothing is recorded, captured or attached. Useful when driving a device interactively and you do not want a report.