Start

Writing flows

The flow file: goal, expectations, data, context and setup.

A flow is a JSON file. Three fields are required: name, app.bundleId and goal. Everything else sharpens what "worked" means or sets the app up first.

tests/checkout.json
{
  "name": "Shop: check out with a saved card",
  "app": { "bundleId": "com.example.shop", "platform": "ios" },
  "goal": "Add the first product to the cart, open the cart, and pay with the saved card",
  "context": "The cart is the bag icon at the top right. Paying opens a confirmation sheet.",
  "expect": "An order confirmation is shown with an order number",
  "expectText": ["Thank you"],
  "checkpoints": ["The cart shows one item"],
  "data": { "promo": "SAVE10" },
  "maxSteps": 15
}

Fields

FieldWhat it does
nameShown everywhere. A Prefix: rest name puts the flow in a project named by the prefix.
app.bundleIdThe app's bundle id (iOS) or package name (Android).
app.platformios (default) or android.
app.env, app.argsEnvironment variables and launch arguments for the app.
app.urlA deep link opened right after launch, so the flow starts on the screen it is about. See deep links.
goalWhat to accomplish, in plain language. This is what the model reads at every step.
contextStanding facts about the app, for every step: how a screen is laid out, which unlabelled control is which. The goal says what to do this time; context says what is always true. A project can set context for every flow in Settings.
expectA plain-language success condition, judged by the model on the final screen.
expectTextText that must be visible on the final screen. The most reliable check: use it when you can.
seenTextText that must have appeared at some point during the run, even if it is gone by the end.
checkpointsIntermediate conditions, checked as the run goes; a run that skips one fails.
dataNamed values the flow may type. Refer to them in the goal by name ("type the promo code").
tagsLabels such as ["smoke", "checkout"]. Suites select flows by them; testedok run --tag smoke runs one tag.
maxStepsStops a lost run. Default 20.
setupState to establish before launch. See below.
requiresFlows that must have passed first. See required flows.

Placeholders

Anywhere in a flow's strings:

  • {{run}}: a short random tag, different each run, so data does not collide.
  • {{ vars.name }}: a value from the selected environment.
  • {{ secrets.name }}: a secret, typed into the app and seen by nothing else.
  • {{ inbox.new }}, {{ inbox.code }}: a fresh email address and the code that arrives at it. See inboxes.
  • {{ account.role.user }}, {{ account.role.password }}: a leased test account.

Expectations

A run passes when its expectations hold on the final screen. Prefer expectText: it is exact and costs no model call. expect is for outcomes that are hard to pin to a string ("a confirmation is shown"). Text you typed into a field does not count as seen: the check is for what the app shows back.

Setup

setup runs before the app launches, on the device, so a flow starts from a known state:

"setup": {
  "clearData": true,
  "permissions": { "grant": ["camera"], "revoke": ["location"] },
  "appearance": "dark"
}
KeyEffect
clearDataWipes the app's data container and defaults: a fresh-install state.
reinstallUninstalls and reinstalls from a cached copy; also resets permissions.
permissionsgrant, revoke or reset lists of permission names (camera, location, notifications, photos, …).
appearancelight or dark.
shellCommands to run first ({adb} expands to the adb binary on Android).

Routes and replay

A passing run saves a route under routes/. The next run of the same flow replays it step by step while each screen still matches what was recorded, and starts deciding again at the first difference. --no-replay ignores routes. Delete a route file to forget it.

Where flows live

Anywhere; tests/ is the convention. testedok run <folder> finds every *.json in it. Commit them: they are versioned with the app they test.