ProgrUmar Logo
Module 9: Testing

End-to-End Testing with Playwright

Duration: 24 mins

Playwright E2E Tests

Playwright spins up a real browser and exercises your full stack. Run tests against a local Next.js dev server or a staging deployment. Use Playwright's codegen tool to record interactions and turn them into test scripts.

End-to-End Testing with Playwright

Unit and component tests validate logic in mock environments, but they cannot verify whether your API endpoints communicate correctly with database instances or redirect users across folders safely. End-to-End (E2E) testing validates your application by spinning up real browsers (Chromium, Firefox, WebKit) and executing workflows exactly as a user would. Playwright is the industry-standard E2E framework for Next.js. It supports auto-waiting asserts, parallel execution, cookie preservation, and captures screenshots and videos on failures. In this lesson, we will cover how to install Playwright, write browser navigation tests, configure authentication session sharing, and record test scripts using codegen.


1. Installing Playwright inside Next.js

To initialize Playwright, run the installation script inside your project root:

npm init playwright@latest

The installer will prompt you with configuration questions. Pick these settings:

  • Where to put your End-to-End tests: tests directory.
  • Add a GitHub Actions workflow: Yes (creates a CI testing pipeline automatically).
  • Install Playwright browsers: Yes (downloads sandboxed browser binaries).

2. Understanding the Playwright File Layout

The installation scaffolds the following configuration structure:

my-app/
├── tests/
│   └── example.spec.ts   ← Write your E2E test files here
├── playwright.config.ts  ← Global browser configurations
├── package.json
└── tsconfig.json

3. Writing a Basic Page Navigation Test

Let's write a test verifying that the main page loads and redirects visitors to the courses catalog. Create a test file named tests/navigation.spec.ts:

// tests/navigation.spec.ts
import { test, expect } from '@playwright/test';

test('has title and navigates to catalog', async ({ page }) => {
  // Visit the local development server (reads baseUrl from playwright.config)
  await page.goto('/');

  // Verify the page title
  await expect(page).toHaveTitle(/ProgrUmar/);

  // Locate the start course link and trigger click
  const catalogLink = page.getByRole('link', { name: /start course/i });
  await catalogLink.click();

  // Assert that the URL path updates correctly
  await expect(page).toHaveURL(/\/courses/);
});

4. Writing a Login Flow Test

To test forms and authentication updates, simulate input typing and click events. Here is a test validating a custom credential login flow:

// tests/auth.spec.ts
import { test, expect } from '@playwright/test';

test('user login flow', async ({ page }) => {
  await page.goto('/login');

  // Input credentials
  await page.getByLabel('Email address').fill('user@progrumar.com');
  await page.getByLabel('Password').fill('password123');

  // Trigger login action
  await page.getByRole('button', { name: /sign in/i }).click();

  // Assert redirection to the dashboard path
  await expect(page).toHaveURL(/\/dashboard/);

  // Assert dashboard header welcomes the user
  await expect(page.getByRole('heading', { level: 1 })).toContainText('Welcome back');
});

5. Session Sharing: Logging In Once Across Test Suites

Logging in at the start of every individual test file slows down execution times. Playwright allows you to execute a single setup step to authenticate a user, capture their session cookie state, and inject it into all downstream tests automatically:

Configure session sharing inside playwright.config.ts:

// playwright.config.ts
import { defineConfig } from '@playwright/test';

export default defineConfig({
  testDir: './tests',
  use: {
    baseURL: 'http://localhost:3000',
    // Save authentication state to a local JSON file
    storageState: 'playwright/.auth/user.json',
  },
});

Write a setup routine (e.g. tests/auth.setup.ts) to log in once, and subsequent tests will read the generated cookies automatically without performing form inputs.


6. Capturing screenshots and videos on test failures

Debugging headlessly run E2E test failures on CI pipelines is difficult. You can configure Playwright to record videos, screenshots, or trace logs whenever a test fails:

// playwright.config.ts
import { defineConfig } from '@playwright/test';

export default defineConfig({
  use: {
    baseURL: 'http://localhost:3000',
    screenshot: 'only-on-failure', // Captures screenshot on failure
    video: 'retain-on-failure', // Records video on failure
    trace: 'retain-on-failure', // Records execution steps trace logs
  },
});

These records will be saved inside the test-results/ folder on compile crashes.


7. Generating Code with Playwright Codegen

If you want to write tests quickly without inspecting DOM selectors manually, use Playwright's visual recorder CLI:

npx playwright codegen http://localhost:3000

This command opens a browser window alongside a code generator window. As you hover, click, or type inside the browser, Playwright records your inputs and generates a clean testing script instantly.


8. CLI Test Execution Commands

To execute all test suites headlessly:

npx playwright test

To open Playwright's interactive UI runner (which shows visual trace timelines and DOM steps):

npx playwright test --ui

9. Common Gotchas

  • Database Mutation Side-Effects: If your tests register a user (e.g. user@example.com) on every execution run without resetting the test database, subsequent runs will fail due to email duplicate database constraints. Always reset or seed your database between runs.
  • Flaky Assertions from Dynamic Timeouts: Standard tests wait up to 5000ms for elements to appear. If your API is experiencing high latency, tests might time out. Use page.waitForSelector on slow components.
  • Hardcoding Port Targets: Hardcoding target paths as http://localhost:3000/login inside tests will fail if your staging pipeline deploys to random ports. Always use relative URLs (/login) and configure baseURL globally.

Key Takeaways

  • Playwright executes E2E tests inside real, sandboxed browser environments.
  • Organize test files inside the tests/ folder at your project root.
  • Use accessibility selectors (getByRole) for durable element identification.
  • Configure storageState to log in once and share authentication cookies across test suites.
  • Leverage Playwright Codegen to record and generate test scripts quickly.
  • Review videos and screenshots in the test-results/ folder to debug pipeline failures.

Writing unit, component, and E2E tests guarantees that your application logic operates securely. But once your codebase is verified, you must release it to users. In the next module, we transition to **Deployment & DevOps** with a lesson on **Deploying to Vercel**, learning how to configure production pipelines and deployment environments.

Chat with us