End-to-end tests with Playwright BDD
Our end-to-end tests are written with the help of the Playwright BDD framework.
The e2e folder (this folder) contains the global setup for the tests:
- Global setup file
e2e/global-setup.tsthat runs once before all tests and resets the test user quotas - Fixtures file
e2e/fixtures.tsthat automatically logs in the test user before each test - Shared step files
e2e/shared.steps.tsthat implement shared steps for all features
Each feature or component in our application may have a tests sub-folder which contains:
- Gherkin feature files
tests/<bdd-feature-name>.feature - Step files
tests/<bdd-feature-name>.steps.tsthat implement the steps for each corresponding feature file - Shared step files
tests/shared.steps.tsthat implement shared steps for the feature or component
See also playwright.config.ts for the configuration of the tests, which is located in the parent folder.
Playwright needs to be installed:
pnpm exec playwright installWe use test users that are already signed up and ready to used for the tests.
The user credentials are stored in environment variables PLAYWRIGHT_EMAIL_1 and PLAYWRIGHT_PASSWORD_1.
Load them from .env.e2e.
Note: If tests run sequentially (workers: 1), only one test user (_1) is needed. If that number of workers in playwright.config.ts is higher than 1, add additional test users with corresponding suffixes (_2, _3, etc.).
Run all end-to-end tests:
pnpm run e2eRun a specific test:
pnpm run e2e:grep "Name of the scenario"View test report:
pnpm exec playwright show-reportDebug tests with Playwright UI (see video, console logs, etc.):
pnpm run e2e:ui
# Can also filter which scenarios will be listed (it won't run them directly, only list them), e.g.,
pnpm run e2e:ui -- viewing-and-editing
pnpm run e2e:ui -- viewing-and-editing-documents.feature:10- The user should provide a rough outline of the scenarios that they intend to test.
- Create a
testsfolder in your feature/component directory (if it doesn't exist) - Create a
*.featurefile with Gherkin syntax to describe the scenarios that they intend to test:- The file name should be the name of the feature or component, or of the scenario if there are already feature files, e.g.,
viewing-and-editing.featurein the documents feature. - Reuse the existing steps whenever possible: run
grep "\(Given\|When\|Then\)(" **/shared.steps.tsto list all shared steps that are implemented. Those will be automatically discovered by Playwright BDD, so no need to import them in the step file.
- The file name should be the name of the feature or component, or of the scenario if there are already feature files, e.g.,
- IMPORTANT: Ask the user to review the feature file and provide feedback on the scenarios and steps.
- Once the user is happy with the feature file, do NOT write any code yet. Instead, use Playwright MCP to manually perform the scenarios and take notes on the selectors and actions that you need to implement in the step file (write those down in a markdown file). It is very likely that the steps will need to be adjusted to how the UI actually works, so update the feature file accordingly.
- Based on your notes, add all necessary ARIA attributes in order to make selection more straightforward, while making the application more accessible at the same time.
- Check with the user that the changes made to the feature file are OK.
- Run
pnpm run e2e:bddgento generate snippets for the new steps. - Fill in the new steps in the generated .steps.ts file, using your notes from the previous step.
- Run the test with
pnpm run e2e -- <feature-file-name>.featureorpnpm run e2e:grep "Name of the scenario"for a specific scenario (if there are more than one). - Fix any issues until the test passes. Keep the Playwright MCP open to help you debug any issues quickly, as this will be faster than re-running the tests multiple times!
- When the test passes, ask the user for review.
- Check any refactoring that could be done to improve the test, e.g. a step that is duplicated in multiple places, a step that is not DRY, etc.
- Ask the user for final review, and if they are happy, you may close the Playwright MCP.
Follow a similar process to writing a new end-to-end test:
- Understand the changes that need to be made
- Adapt the feature file to reflect the changes
- Manually perform the updated scenarios
- Update the feature file and step file to reflect the changes
Refer to the Playwright Best Practices documentation for more details.
- Never use page.waitForTimeout() - Only for debugging, never in production tests
- Trust auto-waiting - Most Playwright actions (click, fill, etc.) already wait for elements to be actionable
- Wait for specific conditions, not page states - Wait for observable changes (URL, element, response), not generic "page loaded"
await page.click('#button') await page.waitForURL('**/expected-path')
await page.click('#load') await expect(page.locator('text=Success')).toBeVisible()
await Promise.all([ page.waitForResponse('**/api/endpoint'), page.click('#fetch') ])
await expect(page.locator('#element')).toBeVisible() await expect(page.locator('#element')).toBeHidden()
- page.goto(url) already waits for 'load' event - usually sufficient
- Only specify waitUntil: 'domcontentloaded' if you need faster tests and don't need images
- Avoid networkidle unless dealing with apps that have continuous network activity