Default to journey-style
Users don’t care if your button says “Continue” or “Next.” They care whether they can do what they came to do — and your tests should reflect that. By default, treat a flow as a contract with the user-visible behavior rather than the UI implementation. Behavior-level tests survive UI refactors; tests bound to specific buttons, colors, and positions don’t. Describe what the user is doing, not what the UI looks like. For tests where the UI itself is the contract, see When the UI is the test.Structure
- End with verification — close the flow by asserting on the behavior you actually care about.
- Bullets and multi-line steps are fine whenever they read more naturally, such as a bulleted list of checks at the end of a flow.
- Avoid numbered lists — numbering locks in a rigid ordering that’s painful to update later. Use plain lines or bullets instead.
Good vs Bad Examples
BAD — vague, no assertion:Quoted UI labels are treated as exact ground truth. Writing
Tap the "Sign In" button couples the test to that exact label — rename the button to “Continue” and the test fails, even if the test isn’t about that button. Only quote UI text when you’re explicitly testing that the text appears, or when you need it to disambiguate between similar elements on the screen.When the UI is the test
When the UI itself is the contract — such as form validation, error messages, disabled states, or exact copy — spell out the specifics.Use environment variables
Don’t hardcode anything sensitive, such as passwords, API keys, or tokens, or environment-specific values, such as URLs, test IDs, or feature flags. Use${env:VAR_NAME} so the same flow runs across environments. Mark sensitive values as secrets to encrypt them at rest and hide them from logs.
${env:VAR_NAME} references work in code-managed flow files — values stay defined in the dashboard, never in your repo.