Skip to main content

Write a test

Use this guide to write a test that is reliable and cheap to maintain. For every field, see the test file format.

Write one action or check per step​

steps:
- when: I click "Add to cart"
- then: the cart shows 1 item
  • Use the words the app shows: "Add to cart", not a CSS selector or XPath.
  • Do not write waits or sleeps.
  • Make every then checkable on the page: a text, a count, a control's state, or the page shown. Avoid exact dynamic values such as dates, ids and prices.
  • Use and or but to continue the step before.

Start logged in with a role​

role: user
start: /transaction/new

The role's login test logs in once per environment, and every test with that role starts logged in. Do not write login steps. For an app without login, or a registration page, leave role: out.

Never put a password or token in a test. Use variables such as $USERNAME and $PASSWORD.

Set up a starting state with a background​

background:
- given: I have at least one contact

Background steps are visible and recorded like normal steps. Prefer them to hidden hooks.

Reuse steps with shared steps​

List what the project already has, then print the one you want to reuse:

teststream shared-steps list
teststream shared-steps get new-transaction

Use the entry named new-transaction for the example below, or replace it with a shared flow defined in your project.

Then use it:

steps:
- use: new-transaction
with: { amount: "5" }

Shared steps are made on the Shared steps screen in the app. A change to them reaches every test that uses them, so people make them, not scripts. If steps repeat and none fit, write them in the test and suggest a shared step to a teammate.

Run the same test for several values​

steps:
- use: new-transaction
with: { amount: "<amount>" }
- then: the transaction appears with amount "<shown>"
examples:
- { amount: 5, shown: "$5.00" }
- { amount: 12.5, shown: "$12.50" }

The test runs once per row. <column> is replaced by the row's value.

Generate test data​

Declare named values at the top of the test and use them as $NAME:

data:
EMAIL: { faker: internet.email }
TITLE: { faker: lorem.sentence }
steps:
- when: I enter "$EMAIL" in the email field
- and: I enter "$TITLE" as the title

Each value is generated once per run and stays the same across every action, check, replay and retry. New runs get new data. To reproduce a run's data, set dataSeed: "<seed>". See the generators.

Skip a step that may not apply​

- when: I dismiss the cookie banner
optional: true

Organise tests​

Set folder: to group tests. Filter by folder, tag or text when you list and run:

teststream tests list --folder Transactions
teststream tests list --q "pay contact"

Check and propose​

teststream tests validate pay-contact.yaml
teststream tests push pay-contact.yaml --branch feature/pay

validate reports problems with line numbers. push proposes the test on the branch, where it waits for review.

Do not weaken a check to make a test pass​

A failing check is information. If the app is wrong, the test stays red. Change a test only when the app changed on purpose or a step was worded badly. Investigate failures shows how to tell which.

Steps with consequences​

A step that pays, purchases, deletes, sends or publishes runs for real. Get a person's agreement first, and keep such steps to a minimum.