Writing a Bruno E2E test
Read .claude/rules/testing.md first for the full fixture and helper reference. See
docs/playwright-testing-guide.md for the extended walkthrough (codegen, running,
debugging, troubleshooting) — but where it disagrees with this skill or the actual repo,
trust the repo: specs live in tests/ (not e2e-tests/), the suite is fullyParallel,
and projects are default, system-pac, auth, and ssl
(verify in playwright.config.ts).
Start with codegen or by hand
- Codegen (fastest for a new flow):
npm run test:codegen <name>launches the app, records your clicks, and writestests/<name>.spec.ts. Treat the output as a rough draft — move it into the right area folder, swap generated selectors fordata-testidor helpers, wrap steps intest.step, and delete dead recorded actions. - By hand:
import { test, expect } from '../../playwright'and follow the steps below.
Steps
- Place the spec at
tests/<area>/<name>.spec.ts— reuse an existing area dir undertests/(list it for the current set; e.g.request/,collection/,environments/,runner/,scripting/) or add one. Pick the project:defaultfor most specs;auth/ssl/system-paconly when the test needs those servers or external dependencies. - Use fixtures from
playwright/index.ts(read it for the full set) instead of launching the app yourself:page/electronApp— default app with onboarding skipped.pageWithUserData— seeds data fromtests/<area>/init-user-data/(useinit-user-data-fresh/for new-user flows).createTmpDir(tag?)— throwaway dir for collections/files the test creates; cleaned up automatically, so prefer it over hardcoded paths.newPage,launchElectronApp,reuseOrLaunchElectronApp,restartApp— for multi-window or app-restart scenarios.collectionFixturePath/workspaceFixturePath— copy a committed fixture to a tmp dir and get its path.
- Use helpers from
tests/utils/page/instead of hand-rolling clicks — read the module for the current set (e.g.createCollection,sendRequest,runCollection). - Structure each test arrange → act → assert, and wrap the phases in
test.stepso the HTML report reads clearly. Prefer several focused assertions over one broad check. - Wait for readiness:
await page.locator('[data-app-state="loaded"]').waitFor()before asserting on app state. - Locate by
data-testid(Bruno's dominant convention) or documented classes (.collection-item-name); stablegetByRole/getByTextlocators are fine too. Avoid brittle CSS,#id, or index-based selectors. If no stable selector exists, add adata-testidto the component rather than locating by text. - If the test mutates a committed fixture, restore it in
afterAll. Existing specs do this withgit checkout <fixturePath>(e.g.tests/response-examples/*.spec.ts) — follow the pattern already used by neighbouring specs in that area. - Use auto-retrying assertions (
expect(locator).toBeVisible()); never assert immediately after a file-watcher-triggered change (created files take a tick to appear). Reservepage.waitForTimeout()for when no locator assertion can wait instead. - Run it:
npx playwright test tests/<area>/<name>.spec.ts --project=default(the web + test servers start automatically). Debug with--ui,--headed,--debug, or--trace on(thennpx playwright show-trace test-results/**/trace.zip).
Checklist before done
- Test is isolated — unique tmp paths (
createTmpDir), no state shared with other tests - Passes on a clean second run (retry-safe; worker fixtures persist across retries)
- Folders expanded before asserting on their children (collapsed = not in DOM)
- Any mutated committed fixture is restored in
afterAll - Any codegen-generated draft refactored to fixtures/helpers + stable selectors
- Steps wrapped in
test.step; notest.only, nopage.pause()