Blog
frontend
4 min read

Test What Users Actually Do: Frontend E2E Testing in Practice

E2E tests replay critical user journeys in a real browser, catching breaks between pieces that unit and integration tests don’t cover alone.

Test What Users Actually Do: Frontend E2E Testing in Practice

One-line takeaway: **E2E (End-to-End) tests replay critical user journeys in a real browser, catching breaks between pieces that unit and integration tests don’t cover alone.**

User experience is a connected flow—click → navigate → input → API → UI update. Testing only isolated pieces often misses failures in those connections. The key is validating the journey **as users experience it**. ([Playwright Docs](https://playwright.dev/), [Cypress Docs](https://docs.cypress.io/))


Background / Problem

Frontend defects often happen at boundaries:

  • unexpected redirects after routing changes
  • broken loading/error UX around API calls
  • session/cookie issues leading to blank screens

Unit tests verify parts, but they don’t guarantee the full browser journey.


Core Concepts

E2E tests validate the flow

E2E tests run your app in a real browser and simulate what users do: click, type, navigate, and observe UI updates after network requests.

```mermaid

flowchart TB

U["Unit<br/>Function / Component"] --> I["Integration<br/>Module boundaries"]

I --> E["E2E<br/>Full user journey"]

U -->|"Fast feedback"| Goal1["Logic correctness"]

I -->|"Contract checks"| Goal2["Integration stability"]

E -->|"Real browser"| Goal3["User experience validation"]

```

Expected result: You can reason about failures by scope, making test design goal-driven.

Why unit tests alone miss real-world breakages

Unit tests can prove “a login function returns success.” Users experience “click login → navigate → session persists → page renders correctly.”

E2E tests target these connections and help catch failures users would feel immediately after release.


Approach

Approach 1) Keep E2E focused on critical journeys

Because E2E has runtime and environment cost, it’s usually most effective to cover the flows that hurt most when broken:

  • auth and protected routes
  • conversion flows (purchase/checkout/booking)
  • search/filter → detail → key action

Expected result: You prevent high-impact regressions without inflating the suite.

Approach 2) Choose Cypress vs Playwright by how you operate

  • Cypress: strong interactive debugging workflow. ([Cypress E2E Docs](https://docs.cypress.io/app/end-to-end-testing/writing-your-first-end-to-end-test))
  • Playwright: test-runner oriented automation and browser coverage; Next.js has an official guide as well. ([Next.js + Playwright](https://nextjs.org/docs/pages/guides/testing/playwright))

Expected result: Tool choice becomes an operating decision, not a trend.


Implementation (Code)

The examples below keep the setup reproducible in a Next.js project, aligned with the official guide. ([Next.js + Playwright](https://nextjs.org/docs/pages/guides/testing/playwright))

1) Configure Playwright (start Next.js and run tests)

```ts

// playwright.config.ts

import { defineConfig } from "@playwright/test";

export default defineConfig({

use: { baseURL: "http://localhost:3000" },

webServer: {

command: "npm run dev",

url: "http://localhost:3000",

reuseExistingServer: !process.env.CI,

},

});

```

Expected result: Server startup and readiness are standardized across local and CI runs.

2) Write a scenario around user actions

```ts

// tests/navigation.spec.ts

import { test, expect } from "@playwright/test";

test("navigation works", async ({ page }) => {

await page.goto("/");

await page.getByRole("link", { name: "About" }).click();

await expect(page).toHaveURL(/\/about$/);

await expect(page.locator("h1")).toContainText("About");

});

```

Expected result: You validate the journey exactly as a user performs it.


Verification Checklist

At least one critical journey is covered (auth, conversion, key action).
Tests are independent and don’t rely on previous test state. ([Cypress Best Practices](https://docs.cypress.io/app/core-concepts/best-practices))
Prefer condition-based assertions over time-based sleeps.
Keep artifacts (report/screenshot/trace) to debug CI failures. ([Playwright Docs](https://playwright.dev/))

Common Mistakes / FAQ

Q1. Isn’t unit testing enough for stability?

Unit tests give fast confidence for individual parts. User experience is the result of many parts working together.

E2E tests focus on those connections, reducing failures that users would notice immediately after release.

Q2. Why do E2E suites get flaky?

Shared state, time-based waits, and environment dependencies (data/network) are common sources.

Independent tests and condition-based checks improve reliability. ([Cypress Best Practices](https://docs.cypress.io/app/core-concepts/best-practices))

Q3. Should we apply E2E only to important pages?

Yes. E2E is most effective when it protects the journeys that matter.

Cover the rest with unit/integration tests to keep the overall suite balanced.


Summary (3–5 lines)

E2E tests replay user actions in a real browser and validate the end-to-end flow.

They’re especially good at catching issues around routing, sessions, and integrations that unit tests can miss.

Choose Cypress or Playwright based on how your team debugs, runs CI, and supports browsers.


Conclusion

E2E is not about maximizing test count. It’s about protecting journeys that matter end-to-end.

Start small with critical flows and keep the suite diagnosable so maintenance stays predictable.


References

  • [Next.js + Playwright](https://nextjs.org/docs/pages/guides/testing/playwright)
  • [Playwright Docs](https://playwright.dev/)
  • [Cypress E2E Docs](https://docs.cypress.io/app/end-to-end-testing/writing-your-first-end-to-end-test)
  • [Cypress Best Practices](https://docs.cypress.io/app/core-concepts/best-practices)

Related Posts