Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

The .bt format

Every top-level line, step and variable a .bt file can hold.

Checks have their own pages: Checks: expect, Checks: soft, ax and screenshots.

Files, steps and variables

  • Top-level lines: base <url>, set NAME = value, use other.bt, flow name(a, b), step "<phrase>" (a Gherkin step’s steps, see below), auth name [ttl 30m], before all|each, after each|all, scenario "name" @tag…. Steps are indented under them.

  • A step is a navigera command without the navigera -s <session> prefix (goto /cart, click --text Add): goto, click, fill, select, press, check, hover, scroll, upload, wait, eval, set, route, state load, and the rest. Targets use navigera’s locators (--role with --name, --label, --text, --placeholder, --testid, --selector, --near <text> for the one in the card or row that shows the text, --nth). Refs from ax (click 12) are an error: they change between runs. check --label Terms ticks a box; check "<question>" with no locator asks navigera a yes/no question about the page (a model call).

  • set steps are navigera’s: set viewport 390 844, set device "iPhone 16 Pro" (55 devices), set geo 52.52 13.405, set locale de-DE, and set permissions grant|deny|reset geolocation notifications camera microphone clipboard (also midi, sensors, background-sync), which a page then sees as granted, denied or still to be asked. (A set NAME = value at the top of a file is a variable.)

  • A relative path a step reads (upload, state load, eval --file, expect js --file, init-script --file, route --body-file, route --har) is found next to the .bt file, as use and for each files are, so a suite runs the same from any directory. Files a step writes (screenshot, state save) land in the current directory.

  • Network: route "*/api/*" --body '{…}' answers in the server’s place, route --har api.har replays a recorded HAR (navigera’s network --har writes one; requests it has no entry for fail, or reach the network with --not-found continue), and route "*/api/me" --modify --replace free pro changes the real response (--set-header 'Name: value' too). tests/fixtures/har.bt has both.

  • Words split like a shell’s, with no expansion; inside double quotes \" and \\ stand for themselves and \n, \r for a line break (a textarea’s value). ${NAME} and ${NAME:-default} come from save, a flow’s arguments, the data row, before all, --var, set, then the environment; $$ (or \$) is a literal $, and quotes don’t stop ${…} ('${x}' is filled in too). Also ${run.id} (one per run: unique emails), ${scenario.name}, and ${download.path}/${download.name} after expect download.

  • ${secret:NAME}: from the environment, else --secrets <file> (NAME=value lines; the environment wins). Its value is masked as *** in everything the run prints and saves (messages, the page’s URL and title, saved values, JUnit and JSON lines).

  • ${totp:NAME}: the code an authenticator app shows right now, for a sign-in’s second step (fill --label "Authentication code" ${totp:SHOP_TOTP}). NAME is found as a secret’s is and holds the authenticator’s base32 key or the otpauth:// URI of the site’s QR code; the key is masked like a secret. A code with under 3 s left waits for the next one, so it can’t run out before the site checks it.

  • A default with spaces is one word: quote it whole, "${GREETING:-hello there}" (words split before ${…} is filled in).

  • A set value can hold ${…} too (a default, a --var, another set, a secret, the environment): it is filled in once, when a scenario starts.

  • save NAME = <command> keeps a command’s result (get text …).

  • print <command> shows a read command’s result in the run’s output (print get text --testid total, print eval "…", print ax --selector main): no check and no wait, for a look at the page or a value while a test is being written. print http shows the last http answer (its status, headers and body), print body, print json <$.path> and print header <name> a part of it (JSON indented), and print "order ${id}" a line with its variables filled in.

  • exec [--timeout-ms <ms>] <program> <args…> runs a program (no shell: sh -c "…" or pwsh -c "…" for one) in the .bt file’s directory, with BITTI_BASE_URL set, for what a browser can’t reach: seed a database (psql, sqlplus), call a script. Its exit code and output become ${exec.exit}, ${exec.stdout} and ${exec.stderr}, and expect exit 0, expect stdout contains SHIPPED, expect stderr … check them (the first 16 MB of each). A nonzero exit fails the step, with the last line of its stderr, unless a step indented under it checks exit. It is stopped after 60 s (--timeout-ms), and that fails the step whatever is under it.

    exec ./scripts/order-status.sh ${order}
      expect exit 0
      expect stdout contains SHIPPED
    

HTTP requests

  • http <METHOD> <url> [--json <text> | --body <text> | --json-file <f> | --body-file <f> | --form name=value… | --multipart name=value|name=@file…] [--query name=value]… [--user name:password | --bearer <token>] [--header "Name: value"]… [--no-follow] [--insecure] [--timeout-ms <ms>] makes a request outside the page (an API to set up or check what the UI did, or an API test of its own: see API tests; a relative URL takes the base URL). --form sends an urlencoded form, --multipart a multipart/form-data one (@file uploads the file beside the .bt, ;type=image/png after it sets its type), --query adds an encoded name=value to the URL, and --user ada:${secret:PW} or --bearer ${secret:TOKEN} sets the Authorization header (Basic or Bearer). The scenario’s cookies go with it and the cookies it sets come back to the browser, so an API sign-in signs the page in. Its answer is ${http.status}, ${http.body} and ${http.ms} (how long it took); expect status 201, expect json $.role is shopper, expect header Location ends-with /u/1, expect body contains …, expect time < 300ms check it, and save id = json $.id (or header <name>) keeps a value. A 4xx or 5xx fails the step, with the start of the body, unless a step indented under it checks status. A timeout fails it whatever is under it. Redirects are followed (up to 20, a cookie set on the way going to the next hop and to the browser). --no-follow gives the 3xx itself. --insecure accepts a certificate nothing vouches for (a local server’s own). The client is built in (rustls, the operating system’s certificates, the proxy from https_proxy/http_proxy/no_proxy): no process per request, and connections are kept alive between requests, so a call to a local server costs about a millisecond and a secret never reaches a command line.

    --graphql <query> (or --graphql-file <f>) with --variables <json> and --operation <name> sends a GraphQL request as its JSON body. An answer with errors (GraphQL sends them with a 200) fails the step, unless a step under it checks json $.errors.

    http POST /api/users --json '{"email": "${email}", "role": "shopper"}'
      expect status 201
      expect json $.role is shopper
    save user = json $.id
    

Retries, pauses and the clock

  • retry [--timeout-ms <ms>] with steps indented under it runs them again until all of them pass, a little later each time (0.1, 0.25, 0.5, then every 1 s), for what happens in the background: a job that finishes, an order that ships, a mail that is sent. Each expect in it looks once (unless it says --timeout-ms): the block is the wait. It gives up after its --timeout-ms (the run’s expect timeout, 5 s, by default) with the last try’s failure.

    retry --timeout-ms 20000
      http GET /api/orders/${order}
        expect json $.status is SHIPPED
    
  • pause stops the run there, the browser as the steps before left it, open to a second navigera session (the command to attach is printed), until Enter. With no terminal it waits BITTI_PAUSE_S seconds, and under CI not at all unless that is set. A run with a pause in it runs one worker.

  • clock install [--time 2024-02-02T08:00:00] [--paused] puts the page’s time under the scenario’s control (navigera’s clock): Date, performance.now and timers start there, in every page the scenario opens; clock run-for 15m (or mm:ss) advances it, firing the timers due on the way in order; clock fast-forward, clock set <time>, clock pause-at <time> and clock resume do the rest. A session timeout, a countdown or a date-dependent page is tested in milliseconds, and the next scenario starts on real time.

    clock install --time 2024-02-02T08:00:00 --paused
    goto /account
    clock run-for 14:59
    expect text "Signed in"
    clock run-for 1s
    expect text "Session expired"
    

Data rows

  • Data rows: an indented examples line with a table under it, or scenario "…" for each logins.csv, makes one scenario per row, named from the row (scenario "Ship to ${country}"); the row’s columns are variables.

Made from docs/format.md.