Skip to content

A production-minded suite

Use the real browser, but do not make every test depend on an unstable third-party API. Route the request and fulfill a fixture when the test is about UI behavior.

test("renders an empty state when the API returns no results", async ({ page }) => {
await page.route("**/api/search**", async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({ results: [] }),
});
});
await page.goto("/search");
await expect(page.getByText("No results yet")).toBeVisible();
});

Keep a smaller number of end-to-end tests against the real backend for integration confidence. Use route control for repeatable UI states such as empty, error, slow, and paginated responses.

Role-based locators encourage accessible markup, but they are not a complete accessibility audit. Add explicit checks for keyboard behavior, names, and focus where those are part of the product requirement.

await page.keyboard.press("Tab");
await expect(page.getByRole("button", { name: "Open menu" })).toBeFocused();

Use an accessibility testing tool such as @axe-core/playwright as a complementary check, not as a replacement for keyboard and screen-reader-oriented tests.

The repository exposes one cumulative command:

Terminal window
npm run verify

It runs, in order:

  1. Prettier formatting validation.
  2. Astro content and TypeScript diagnostics.
  3. The production build and Pagefind index generation.
  4. Playwright browser and interaction tests.

Each stage prints a visible header and stops on failure. CI invokes the same command developers run locally, so “green locally” and “green in CI” mean the same thing.

When browser tests fail in CI, retain the Playwright report and retry traces. A pass/fail result tells you that something broke; a trace shows which page, request, locator, and visual state caused it.