End-to-end (E2E) testing is a crucial part of ensuring a web application functions correctly. If you’re using Next.js inside a monorepo managed with Turborepo and pnpm, integrating Playwright for E2E testing can be incredibly powerful. This guide walks through the best practices for setting up Playwright in such an environment, ensuring a clean test environment each time.
To keep things modular, place the Playwright tests in a separate package within your monorepo. Your project structure might look like this:
/apps
/web (Next.js app)
/packages
/e2e-tests (Playwright tests)
/turbo.json
/pnpm-workspace.yaml
Inside /packages/e2e-tests/package.json
, define dependencies:
{
"name": "e2e-tests",
"private": true,
"devDependencies": {
"@playwright/test": "^1.x"
},
"scripts": {
"test": "playwright test"
}
}
Ensure pnpm-workspace.yaml
includes the package:
packages:
- "apps/*"
- "packages/*"
In playwright.config.ts
, define how Playwright launches the Next.js app:
import { defineConfig } from "@playwright/test";
export default defineConfig({
testDir: "./tests",
timeout: 30_000,
expect: { timeout: 5_000 },
fullyParallel: true,
use: {
baseURL: "http://localhost:3000",
headless: true,
trace: "on-first-retry",
},
webServer: {
command: "pnpm --filter web dev",
url: "http://localhost:3000",
reuseExistingServer: !process.env.CI,
},
});
For testing built Next.js apps, modify:
webServer: {
command: "pnpm --filter web start",
url: "http://localhost:3000",
reuseExistingServer: !process.env.CI,
},
If your tests depend on a database, always start with a clean state. Use a script to teardown, migrate, and seed your database before each test.
Modify package.json
inside e2e-tests
:
{
"scripts": {
"pretest": "pnpm db:reset",
"test": "playwright test"
}
}
Example reset script (scripts/db-reset.sh
):
pnpm db:teardown && pnpm db:migrate && pnpm db:seed
Then, define the script in package.json
:
{
"scripts": {
"db:reset": "sh ./scripts/db-reset.sh"
}
}
Run the reset script before each test:
import { test } from "@playwright/test";
import { execSync } from "child_process";
test.beforeEach(() => {
execSync("pnpm db:reset", { stdio: "inherit" });
});
To automate tests, add .github/workflows/e2e.yml
:
name: E2E Tests
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
e2e:
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v4
- name: Install pnpm
run: corepack enable && corepack prepare pnpm@latest --activate
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 18
cache: "pnpm"
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build the app
run: pnpm --filter web build
- name: Run E2E tests
run: pnpm --filter e2e-tests test
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
NEXTAUTH_SECRET: ${{ secrets.NEXTAUTH_SECRET }}
Adjust your environment variables accordingly.
Enable Playwright tracing to debug test failures. Update playwright.config.ts
:
use: {
trace: process.env.CI ? "retain-on-failure" : "on",
video: "retain-on-failure",
screenshot: "only-on-failure",
},
To inspect traces after a failed test:
npx playwright show-trace trace.zip
Run Playwright UI mode for interactive debugging:
pnpm --filter e2e-tests exec playwright test --ui
Run tests headless:
pnpm --filter e2e-tests test
✅ Keep Playwright tests in a separate package within your monorepo
✅ Start Next.js automatically before tests
✅ Reset the database before each test to maintain data integrity
✅ Run E2E tests in CI/CD with GitHub Actions
✅ Use Playwright’s trace debugging to investigate failures
Following these best practices ensures reliable and reproducible E2E tests in a Next.js monorepo setup with Turborepo and pnpm. 🚀
Are you using a similar setup? Let’s discuss your experience in the comments! 🔥
This page content is most likely AI generated. Use it with caution.