Next.js cookbook · Intermediate

Test a Next.js app with Vitest and Playwright

Set up fast component tests and a few end-to-end tests for critical flows in a Next.js project.

Approach

Use Vitest + Testing Library (jsdom) for Client Components and pure functions, test Server Actions/data functions as plain async functions with mocked I/O, and use Playwright against next build && next start for end-to-end flows (async Server Components are best covered by E2E tests).

Solution

// vitest.config.mts
import { defineConfig } from "vitest/config";
import react from "@vitejs/plugin-react";
import tsconfigPaths from "vite-tsconfig-paths";
export default defineConfig({ plugins: [tsconfigPaths(), react()], test: { environment: "jsdom", setupFiles: ["./vitest.setup.ts"] } });

// components/ThemeToggle.test.tsx
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
it("lets the user pick dark mode", async () => {
  render(<ThemeToggle />);
  await userEvent.selectOptions(await screen.findByLabelText("Theme"), "dark");
  expect(screen.getByLabelText("Theme")).toHaveValue("dark");
});

// e2e/checkout.spec.ts
import { test, expect } from "@playwright/test";
test("guest can check out", async ({ page }) => {
  await page.goto("/products/blue-mug");
  await page.getByRole("button", { name: "Add to cart" }).click();
  await page.getByRole("link", { name: /cart/i }).click();
  await page.getByRole("button", { name: "Checkout" }).click();
  await expect(page.getByRole("heading", { name: "Order confirmed" })).toBeVisible();
});
// playwright.config.ts (excerpt)
export default defineConfig({
  webServer: { command: "npm run build && npm run start", url: "http://localhost:3000", reuseExistingServer: !process.env.CI },
  use: { baseURL: "http://localhost:3000", trace: "on-first-retry" },
});

Pitfalls

  • Trying to unit test async Server Components with jsdom (limited support) — use E2E.
  • E2E tests against next dev (slower, different behavior).
  • Shared test accounts causing flaky E2E runs.

Variations

  • MSW to mock external APIs in both Vitest and Playwright.

Further reading: Next.js: Testing

Reference: https://nextjs.org/docs/app/guides/testing

More Next.js recipes