Lesson 46 of 5026 min read

End-to-End Testing in Next.js with Playwright and Cypress

Learn how to write End-to-End tests for Next.js applications using Playwright or Cypress, and integrate them into a CI pipeline.

Author: CodersNexus

End-to-End Testing in Next.js with Playwright and Cypress

The previous lesson covered unit testing individual components in isolation. This lesson covers End-to-End (E2E) testing, which takes a fundamentally different approach: rather than testing one small piece in isolation, E2E tests drive a real, actual browser against your genuinely running application, verifying entire user flows work correctly from start to finish — a user logging in, adding an item to a cart, and completing checkout, exactly as a real visitor would experience it, with every piece (frontend, backend, database) genuinely working together.

This lesson covers Playwright and Cypress, the two dominant E2E testing tools in the current ecosystem, writing a realistic multi-step test flow, and integrating E2E tests into a CI pipeline so they run automatically before code reaches production.

Learning Objectives

  • Explain what End-to-End testing verifies that unit testing doesn't.
  • Set up Playwright or Cypress in a Next.js project.
  • Write an E2E test simulating a realistic, multi-step user flow.
  • Understand the tradeoffs between Playwright and Cypress.
  • Integrate E2E tests into a CI pipeline to run automatically.

Core Definitions

  • End-to-End (E2E) testing: Testing an entire user flow through a real, running application in an actual browser, verifying that all pieces (frontend, backend, database) work correctly together.
  • Playwright: A modern, cross-browser E2E testing framework (supporting Chromium, Firefox, and WebKit) built by Microsoft, known for reliability and speed.
  • Cypress: A popular E2E testing framework known for its interactive test runner and developer-friendly debugging experience, historically limited to Chromium-based browsers (though this has expanded over time).
  • Test flow: A sequence of realistic user actions (navigating, clicking, typing, asserting on results) that an E2E test simulates from start to finish.
  • Flaky test: A test that sometimes passes and sometimes fails without any actual code change, often caused by timing issues or improper handling of asynchronous behavior in a real browser environment.

Detailed Explanation

Where a unit test (Lesson 7.1) verifies one small, isolated piece of code, an E2E test verifies an entire, realistic user journey against your actual application running in a real browser — testing not just that a Counter component increments correctly in isolation, but that a user can genuinely sign up, log in, navigate to a specific page, submit a form, and see the correct result, with every real piece of your stack (frontend rendering, Server Actions, database) actually working together as they would in production.

Playwright, built by Microsoft, has become extremely popular for its reliability, speed, and genuine cross-browser support — the same test can run against Chromium, Firefox, and WebKit (Safari's engine), catching browser-specific bugs a single-browser testing approach might miss. Its API is directly designed to minimize the flaky-test problem common in browser automation, with built-in automatic waiting for elements to become actionable (visible, enabled, stable) before interacting with them, rather than requiring manual, error-prone timing workarounds.

Cypress, a longer-established and still widely-used alternative, is particularly known for its interactive test runner — a real browser window showing your test executing step-by-step in real time, with the ability to time-travel through each command's state, making debugging a failing test noticeably more visual and intuitive than reading through a text-based failure log alone. Cypress historically ran primarily in Chromium-based browsers, though its cross-browser support has expanded over time; teams choosing between the two often weigh Playwright's broader cross-browser support and generally faster execution against Cypress's especially polished developer experience and debugging tools.

A realistic E2E test for a Next.js application typically starts your actual application server (either the development server or, ideally for closer-to-production accuracy, a production build), then drives a real browser through a complete flow: navigating to a login page, filling in credentials, submitting the form, waiting for a redirect to a dashboard, and asserting that expected, user-visible content appears — all genuinely exercising your authentication system (Lesson 5.4), middleware (Lesson 6.1), and database (Module 5) together, exactly as they'd behave for a real user.

Because E2E tests are considerably slower than unit tests (a real browser must actually launch, navigate, and render pages) and inherently more prone to occasional flakiness (network timing, animation delays, real browser rendering quirks), they're typically reserved for a smaller number of genuinely critical user flows — signup, login, checkout — rather than attempting to E2E test every possible interaction, which would make a test suite prohibitively slow and unreliable. Integrating E2E tests into a CI pipeline (covered fully in Lesson 7.5) means these critical flows are automatically verified against a real, running instance of your application before any code change reaches production, catching integration-level regressions that unit tests, by design, cannot.

Unit Testing vs End-to-End Testing: What Each One Verifies

{"heading":"Unit Testing vs End-to-End Testing: What Each One Verifies","description":"Visualize the difference in scope between unit and E2E testing:\n\nUNIT TEST (Lesson 7.1):\n[Render ONE component in isolation] --> [Mock its dependencies] --> [Assert on ITS specific behavior]\n\nEND-TO-END TEST (this lesson):\n[Launch a REAL browser] --> [Navigate to the REAL running app]\n --> [Fill in login form] --> [Click submit]\n --> [REAL Server Action runs] --> [REAL database is queried]\n --> [REAL redirect happens] --> [Assert the dashboard shows the correct, real user's data]\n\nE2E verifies the ENTIRE stack working together; unit tests verify ONE piece in isolation."}

Next.js Practical Example

// e2e/login.spec.ts — a realistic Playwright E2E test
import { test, expect } from '@playwright/test';

test('user can log in and see their dashboard', async ({ page }) => {
  await page.goto('http://localhost:3000/login');

  await page.fill('input[name="email"]', 'test@example.com');
  await page.fill('input[name="password"]', 'correct-password');
  await page.click('button[type="submit"]');

  // Wait for the real redirect after a successful login
  await page.waitForURL('http://localhost:3000/dashboard');

  await expect(page.getByText('Welcome back')).toBeVisible();
});

// The equivalent test written in Cypress
describe('login flow', () => {
  it('logs in and shows the dashboard', () => {
    cy.visit('/login');
    cy.get('input[name="email"]').type('test@example.com');
    cy.get('input[name="password"]').type('correct-password');
    cy.get('button[type="submit"]').click();

    cy.url().should('include', '/dashboard');
    cy.contains('Welcome back').should('be.visible');
  });
});

Both the Playwright and Cypress versions drive a REAL browser against a genuinely running Next.js application: navigating to the actual login page, filling in real form fields, clicking a real submit button, and waiting for the real navigation to /dashboard that only happens if the entire authentication flow — the form submission, the Server Action processing the credentials, the actual database lookup, session creation, and redirect — genuinely works correctly together. Unlike a unit test, nothing here is mocked; this test would fail if the database were down, if the authentication logic had a genuine bug, or if the redirect behavior broke, exactly capturing the kind of integration-level correctness that isolated unit tests, by their very design, cannot verify.

How Companies Use Playwright and Cypress in Production

  • E-commerce platforms maintain a critical E2E test suite covering signup, login, add-to-cart, and checkout — the small number of flows where a genuine bug would directly and immediately cost revenue — running these before every production deployment.
  • SaaS companies use E2E tests specifically for their most business-critical, multi-step flows (like onboarding a new team or completing a subscription upgrade), while relying on unit tests for the much larger number of smaller, individual component behaviors.
  • Financial and healthcare applications, given their regulatory and correctness requirements, often maintain particularly thorough E2E suites covering compliance-critical flows, sometimes running them across multiple browsers via Playwright's native cross-browser support.
  • Teams migrating from Cypress to Playwright (or vice versa) commonly cite cross-browser coverage needs or specific debugging workflow preferences as their deciding factor between the two tools.
  • Open-source projects and component libraries increasingly adopt Playwright specifically for its genuine cross-browser testing capabilities, ensuring compatibility claims across Chromium, Firefox, and WebKit are actually verified rather than assumed.

Common Mistakes to Avoid

  • Attempting to E2E test every possible user interaction instead of focusing on a smaller set of genuinely critical, high-value flows.
  • Mocking dependencies in an E2E test, which defeats its entire purpose of verifying genuine, real integration across the full stack.
  • Not accounting for real network and rendering timing, leading to flaky tests when automatic waiting mechanisms aren't properly relied upon or configured.
  • Running E2E tests against a development server rather than a production build when closer-to-production accuracy actually matters for a specific test.
  • Choosing between Playwright and Cypress based on popularity alone rather than considering a project's actual cross-browser testing needs and team debugging preferences.

Interview Notes

  • End-to-End testing verifies entire user flows against a genuinely running application, unlike unit testing's isolated, mocked approach.
  • Playwright offers native cross-browser support (Chromium, Firefox, WebKit); Cypress is particularly known for its interactive, visual debugging experience.
  • Both tools include built-in automatic waiting mechanisms that significantly reduce flaky-test issues common in browser automation.
  • E2E tests are considerably slower and more prone to occasional flakiness than unit tests, making them best suited to a smaller number of critical flows.
  • E2E tests genuinely exercise the full stack — frontend, Server Actions, database — with nothing mocked, unlike unit tests.

Key Takeaways

  • E2E testing and unit testing serve fundamentally different, complementary purposes — one verifies isolated pieces, the other verifies genuine, whole-stack integration.
  • Playwright and Cypress each have genuine strengths, and the right choice depends on a project's cross-browser needs and team debugging preferences.
  • Reserving E2E tests for a smaller number of critical flows, rather than exhaustive coverage, is a deliberate, practical tradeoff given their slower, more flakiness-prone nature.
  • Integrating E2E tests into a CI pipeline (covered fully in Lesson 7.5) ensures critical user flows are automatically verified before code reaches production.

Summary

End-to-End (E2E) testing verifies that entire, realistic user flows work correctly against a genuinely running Next.js application in a real browser — frontend, backend, and database all working together exactly as they would for a real user — a fundamentally different and complementary approach to the isolated, mocked unit testing covered in Lesson 7.1. Playwright, built by Microsoft, offers native cross-browser support across Chromium, Firefox, and WebKit within the same test, along with built-in automatic waiting mechanisms that reduce flaky-test issues. Cypress, a longer-established alternative, is particularly known for its interactive, visual test runner with time-travel debugging, making it especially intuitive for diagnosing a failing test. Because E2E tests are considerably slower and somewhat more prone to flakiness than unit tests, given the overhead of a real browser genuinely launching, navigating, and rendering pages, they're typically reserved for a smaller number of genuinely critical user flows — signup, login, checkout — rather than exhaustive coverage of every possible interaction, and are ideally integrated into a CI pipeline so these critical flows are automatically verified before any code reaches production.