semantic e2e · ios / android / web

Tests that describe intent,
not selectors.

You write the test flow. Convoy captures the screen, matches the control, gates the score, and executes the step.

Try an intent
phasedump
intent
score
gate
9:41LTE
Sign in
field · Email / Username
Email / Username
field · Password
Password
button · Continue
Continue
button · Log in
Log in
dropped Back
live inspect 0 0

01 — system

Intent in. Control out.

Convoy does not look up a test ID. It snapshots the screen, keeps the labelled controls, and scores the phrase against what is actually there.

01 dump

Snapshot

idb, uiautomator, or the DOM. The live accessibility tree, every step.

02 normalize

Keep labels

Role, name, bounds. Unlabelled chrome is dropped before matching.

03 match

Score

Presence first, then target. Which control is this phrase talking about?

04 gate

Refuse silence

Pass, ambiguous, or not found. A wrong control is never tapped quietly.

05 act

Tap / type / see

Retry until the next screen settles. Default window: 20 seconds.

02 — authoring

Two dialects. One runtime.

YAML handles flows and branches with which. TypeScript is for loops, platform splits, and extra setup. If two controls could fit, tighten the phrase from convoy inspect.

name: driver signs in
platforms: [ios]
tags: [smoke]
steps:
  - type: ${USERNAME}
    into: Email / Username
  - tap: Continue
  - type: ${PASSWORD}
    into: Password
  - tap: Log in
  - which:
      logged in on another device:
        - tap: Continue Anyway
      the home screen: []
  - see: the home screen
  1. Reach the screen

    Drive there, or run until the suite fails on that step.

  2. Inspect if needed

    npx convoy inspect when two controls could fit. Tighten the phrase.

  3. Capture

    npx convoy capture --name login-screen writes a fixture and a PNG.

  4. Re-run

    Ambiguous → rephrase. Not found → the control is gone. Traces: .convoy/runs/.

03 — gates

Failure is a diagnosis.

Scores, on-screen labels, next action. Not a stack trace about a missing CSS selector.

pass

The control matched. Proceed.

ambiguous

Two or more controls fit. Tighten the phrase.

not found

Missing, unlabelled, or still loading. Often a product bug.

timeout

The window elapsed. The screen never arrived.

04 — surfaces

One runner. Four ways in.

npx convoy doctor prints whatever this machine is missing.

iOS

Simulator, .app, idb. Convoy boots, installs, launches.

Android

adb and a device that is already running. It will not start an AVD.

Web

Playwright Chromium and a base URL. Optionally spawn the app server.

Fixture

Recorded JSON. No device, no key. Offline authoring and unit tests.

05 — start

In the app repo. Not this one.

terminal
# your application repository
npm install -D convoy-e2e
npx convoy init --platform ios --yes
npx convoy doctor
npx convoy run

TYPESAFE_API_KEY and app secrets live in .env. Commit convoy.config.json. Never a UDID or a password in JSON.

Matching stays host-side via Jev. The API key never enters an iOS, Android, or web binary. No key → local heuristic, fine for fixtures, not for a real app.

Reference: README. Agents: agent/ skills.

init
Config, .env, sample test. Never overwrites an existing .env.
doctor
Node, tools, resolved platform. Missing idb/adb is a warning, not a hard fail.
inspect
Labelled element table for the current screen.
capture
Fixture JSON + PNG under .convoy/captures/.
run
Every *.e2e.yaml / *.e2e.ts, or a file, or a folder.
flags
--platform --tag --headed --headless --shard 1/4 --junit