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 (--rolewith--name,--label,--text,--placeholder,--testid,--selector,--near <text>for the one in the card or row that shows the text,--nth). Refs fromax(click 12) are an error: they change between runs.check --label Termsticks a box;check "<question>"with no locator asks navigera a yes/no question about the page (a model call). -
setsteps are navigera’s:set viewport 390 844,set device "iPhone 16 Pro"(55 devices),set geo 52.52 13.405,set locale de-DE, andset permissions grant|deny|reset geolocation notifications camera microphone clipboard(alsomidi,sensors,background-sync), which a page then sees as granted, denied or still to be asked. (Aset NAME = valueat 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.btfile, asuseandfor eachfiles 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.harreplays a recorded HAR (navigera’snetwork --harwrites one; requests it has no entry for fail, or reach the network with--not-found continue), androute "*/api/me" --modify --replace free prochanges the real response (--set-header 'Name: value'too).tests/fixtures/har.bthas both. -
Words split like a shell’s, with no expansion; inside double quotes
\"and\\stand for themselves and\n,\rfor a line break (a textarea’s value).${NAME}and${NAME:-default}come fromsave, 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}afterexpect download. -
${secret:NAME}: from the environment, else--secrets <file>(NAME=valuelines; 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 theotpauth://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
setvalue can hold${…}too (a default, a--var, anotherset, 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 httpshows the lasthttpanswer (its status, headers and body),print body,print json <$.path>andprint header <name>a part of it (JSON indented), andprint "order ${id}"a line with its variables filled in. -
exec [--timeout-ms <ms>] <program> <args…>runs a program (no shell:sh -c "…"orpwsh -c "…"for one) in the.btfile’s directory, withBITTI_BASE_URLset, 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}, andexpect 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 checksexit. 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).--formsends an urlencoded form,--multipartamultipart/form-dataone (@fileuploads the file beside the.bt,;type=image/pngafter it sets its type),--queryadds an encodedname=valueto the URL, and--user ada:${secret:PW}or--bearer ${secret:TOKEN}sets theAuthorizationheader (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 < 300mscheck it, andsave id = json $.id(orheader <name>) keeps a value. A 4xx or 5xx fails the step, with the start of the body, unless a step indented under it checksstatus. 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-followgives the 3xx itself.--insecureaccepts a certificate nothing vouches for (a local server’s own). The client is built in (rustls, the operating system’s certificates, the proxy fromhttps_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 witherrors(GraphQL sends them with a 200) fails the step, unless a step under it checksjson $.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. Eachexpectin 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 -
pausestops 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 waitsBITTI_PAUSE_Sseconds, and under CI not at all unless that is set. A run with apausein it runs one worker. -
clock install [--time 2024-02-02T08:00:00] [--paused]puts the page’s time under the scenario’s control (navigera’sclock):Date,performance.nowand timers start there, in every page the scenario opens;clock run-for 15m(ormm:ss) advances it, firing the timers due on the way in order;clock fast-forward,clock set <time>,clock pause-at <time>andclock resumedo 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
examplesline with a table under it, orscenario "…" 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.