Writing test cases
There are two ways to write a verikun test. They produce the same reports and the same exit codes; they differ in who writes the steps.
vk batch |
vk ai |
|
|---|---|---|
| You write | exact commands | plain English |
| Runs | deterministically, always | deterministically after the first compile |
| Cost | $0 | $0 on the happy path; you pay to compile and to repair |
| Control flow | none — a flat list | conditions, loops, branches |
| Recovers from a changed UI | no | yes, by asking the model to repair the step |
| Needs a model key | no | yes, unless you use a CLI backend |
A useful rule: batch for a flow you control and want pinned; ai for a flow that drifts
or has optional steps.
Explicit steps: vk batch
Section titled “Explicit steps: vk batch”vk batch reads newline-separated commands — from --file <path>, or piped on stdin —
and runs each exactly as if you had typed it as its own vk command: the same
selector auto-wait, the same
test-run recording (every line is its own
step), and the same stdout/stderr split and exit codes.
vk batch --file login.flow # from a file
vk batch <<'EOF' # …or piped on stdinlaunch com.example.apptext @email_input "user@example.com"text @password_input "hunter2" --enterassert text:"Welcome back" --wait 8sEOFWhat it guarantees:
- Each result streams to stdout as the command finishes — the same bytes you would get running the line on its own.
- It stops at the first command that exits non-zero, noting where it halted (on stderr) and exiting with that command’s code.
- Blank lines and
#comments are skipped, so a flow file can be annotated. - Globals on the
batchcall carry into every line unless a line overrides them —--device,--platform/--ios/--android, and--json. Sovk batch --ios --file fruns the whole flow against the simulator. --quietsilences the per-line progress notes on stderr; stdout data is untouched.
A worked login flow
Section titled “A worked login flow”Ending a batch with run archive turns the flow into a JUnit + HTML report in one shot:
# Fresh start — launch force-stops first, so this is a real resetlaunch com.example.app --clear
# The field lookups auto-wait up to 5s; no explicit `wait` neededtext @email_input "user@example.com"text @password_input "hunter2" --enter
# Verification. These are what make it a test.assert text:"Welcome back" --wait 8sassert @error_banner --gone
# Turn the recording into report.html + report.xml, and gate on itrun archive login-smokevk batch --file login.flowecho $? # 0 = green, 1 = a step failed, 2/3 = usage or environmentNatural language: vk ai
Section titled “Natural language: vk ai”The same test as prose:
Launch com.example.app fresh.If a "Allow notifications" dialog appears, dismiss it.Type user@example.com into the email field and hunter2 into the password field, then submit.Assert that "Welcome back" is visible and no error banner is shown.vk ai login.mdThe prose is compiled into a deterministic plan once, cached, and replayed with no model
calls; the model wakes only to repair a step whose selector stopped resolving. Two things this
buys you that batch cannot express: optional steps (If a permission dialog appears, allow it compiles to an if-present guard) and bounded loops (Scroll until the row appears). See Natural-language tests for the full
model, and AI plans & models for the plan grammar.
Which selectors to write
Section titled “Which selectors to write”This matters more than the format you choose. In order of preference: @id (the only
selector that survives localisation and copy changes), then text:, and desc: only on
Android. The reasoning and the per-platform matrix:
Selectors.
Make each test self-isolating
Section titled “Make each test self-isolating”A test should not depend on what the previous test left behind. Start it from a known state:
launch com.example.app --clear # wipes login/session/prefs, then starts freshIf you are running a suite, vk suite --app <id> does this
between tests for you.
Assert deliberately
Section titled “Assert deliberately”An assertion failure is terminal — vk ai never heals one. That makes assert the place
to state what you actually mean:
assert text:"Welcome back" --wait 8s # it appeared within 8sassert @loading_spinner --gone --wait 15s # it went away within 15sassert @submit --enabled # it is actually pressable, not just presentassert @email_input --text "user@example.com" # the field holds this value--enabled deserves special mention: a Submit button the app disables until a form is valid
is present long before it is usable. Asserting or tapping presence taps a dead control.
Where to go next
Section titled “Where to go next”- Suites — run a directory of tests as one gated pass/fail run
- Reports & test runs — what gets recorded and what the report contains
- Troubleshooting — when a step does not behave