Skip to content
Home
End-to-End Testing Guide: Cypress, Playwright & Selenium

End-to-End Testing Guide: Cypress, Playwright & Selenium

Testing & QA Testing & QA 7 min read 1489 words Beginner ExcellentWiki Editorial Team

End-to-end (E2E) testing validates that an application works correctly from the user’s perspective by exercising the full technology stack — frontend, backend, database, and external integrations. Unlike unit tests that verify individual functions or integration tests that examine component interactions, E2E tests simulate real user workflows exactly as a person would perform them.

The testing pyramid popularised by Mike Cohn places E2E tests at the top — fewest in number but highest in confidence. Google’s Testing Blog reinforces this principle, noting that E2E tests should cover critical user journeys while lower-level tests handle detailed logic verification. When E2E tests are written strategically, they catch integration bugs that no amount of unit or integration testing can find.

Why E2E Testing Matters

Unit and integration tests verify that components work in isolation or in pairs. They cannot validate that the entire system — frontend JavaScript, backend API, database queries, authentication middleware, and third-party payment processing — coordinates correctly. E2E tests expose failures in this orchestration: incorrect redirects after login, payment confirmation emails that never arrive, search results that do not match the database state.

The ISTQB Advanced Test Analyst syllabus describes E2E testing as “system-level testing that validates the complete workflow from start to finish.” Unlike lower-level tests that verify technical correctness, E2E tests validate user experience — the subjective measure of whether the application behaves as a real person expects. A system can pass every unit and integration test while being unusable due to poor error handling, confusing navigation, or slow page loads. E2E tests catch these experiential failures.

When E2E Tests Are Essential

Critical user journeys — registration, checkout, payment, password reset — warrant E2E coverage because their failure directly affects revenue and user trust. Cross-cutting features like authentication and search span multiple subsystems, making them hard to test at lower levels. Regulatory compliance often mandates E2E validation for workflows in healthcare, finance, and data privacy.

E2E Testing Tools

Cypress

Cypress runs inside the browser alongside the application, giving it direct access to DOM events, network requests, and application state. Its real-time reloading and time-travel debugging make it especially popular among frontend developers. Unlike Selenium, which runs as a separate process and communicates via the WebDriver protocol, Cypress operates in the same JavaScript event loop as the application. This architecture enables capabilities that are impossible in Selenium — stubbing browser APIs, modifying localStorage and sessionStorage, and controlling time with cy.clock() for deterministic testing of timed behaviours. Cypress also supports component testing, allowing developers to test individual components in isolation with the same API used for E2E tests, reducing the context-switching cost of moving between test types.

describe('Checkout Flow', () => {
    it('completes a purchase with a credit card', () => {
        cy.visit('/products');
        cy.get('[data-testid="add-to-cart"]').first().click();
        cy.get('[data-testid="cart-counter"]').should('contain', '1');
        cy.get('[data-testid="checkout-button"]').click();

        cy.get('[data-testid="card-number"]').type('4242424242424242');
        cy.get('[data-testid="expiry"]').type('12/28');
        cy.get('[data-testid="cvc"]').type('123');

        cy.intercept('POST', '/api/orders').as('createOrder');
        cy.get('[data-testid="pay-button"]').click();
        cy.wait('@createOrder').its('response.statusCode').should('eq', 201);

        cy.contains('Order confirmed').should('be.visible');
    });
---);

Playwright

Playwright, created by Microsoft, supports Chromium, Firefox, and WebKit with a single API. It handles auto-waiting, network interception, mobile emulation, and browser contexts for parallel testing without external driver dependencies. Playwright’s browser contexts are isolated browser sessions within a single browser instance — each has its own cookies, localStorage, and authentication state. This makes parallel test execution efficient without the overhead of launching separate browser processes.

const { test, expect } = require('@playwright/test');

test('user can reset password', async ({ page }) => {
    await page.goto('/login');
    await page.click('text=Forgot password?');
    await page.fill('[data-testid="email"]', 'user@example.com');
    await page.click('[data-testid="reset-submit"]');

    await expect(page.locator('.success-message')).toBeVisible();
    await expect(page.locator('.success-message'))
        .toContainText('Check your email');
---);

Selenium

Selenium WebDriver is the veteran framework with the broadest language and browser support. It requires explicit browser drivers and does not include built-in waiting or network interception, but its maturity and ecosystem make it indispensable for organisations with diverse technology stacks. Selenium’s W3C standardisation was a major milestone — the WebDriver specification became a W3C recommendation in 2018, ensuring consistent behaviour across browser implementations. However, Selenium’s architecture as an external process communicating via HTTP limits its speed and debugging capabilities compared to Cypress and Playwright.

Page Object Model

The Page Object Model (POM) is a design pattern that encapsulates page structure and interactions into reusable classes. Instead of scattering selectors throughout tests, each page or component has a dedicated class with methods representing user actions. POM reduces duplicate code, centralises selector maintenance, and makes tests more readable. When a page layout changes, only the page object class needs updating — individual tests remain unchanged. This pattern is supported across all major frameworks: Selenium has built-in PageFactory, Cypress uses custom commands, and Playwright encourages page object classes through fixtures.

Data Attributes Over CSS Selectors

CSS classes change for styling. DOM structure shifts during redesigns. Data attributes like data-testid are stable contracts between developers and tests. Modern frameworks support stripping these attributes from production builds, preventing any performance overhead. Establish a team convention for test attribute naming — data-testid is widely adopted, but data-cy (Cypress convention) and data-test offer alternatives. Consistency matters more than the specific attribute name.

Handling Asynchronous Operations

Wait for network responses rather than using arbitrary timeouts. Intercept API calls and wait for specific responses. Playwright’s page.waitForResponse and Cypress’s cy.intercept with cy.wait provide deterministic synchronisation. Arbitrary setTimeout or cy.wait(5000) calls create flaky tests that fail unpredictably when network conditions change.

Test Data Management

Create test data through API calls or database fixtures. Avoid relying on pre-seeded data that can become stale. Clean up created records in afterEach hooks. Use factory libraries like Faker or Factory Bot to generate realistic test data. Consider using cy.session or Playwright’s storage state to cache authentication tokens, reducing login steps in every test.

E2E Testing Strategy

Focus on happy-path tests for the most common workflows. Add error-path tests for invalid input, expired sessions, and network failures. Consider visual regression testing — tools like Percy, Applitools, and Chromatic compare screenshots to detect unintended visual changes that functional tests would miss. Visual regression complements functional E2E tests because CSS changes, layout shifts, and responsive breakage do not cause functional test failures but degrade user experience significantly.

CI/CD Integration

Run critical-path E2E tests on every pull request to catch regressions early. Execute the full suite before releases, ideally overnight. Use parallel execution across multiple CI runners to reduce feedback time. Auto-retry flaky tests once or twice before marking them as failures. Monitor the E2E suite duration trend — a suite that takes progressively longer indicates accumulating test bloat that needs pruning. Review E2E tests quarterly: remove tests that duplicate lower-level coverage and consolidate overlapping scenarios.

Challenges and Mitigations

Flakiness is the primary challenge in E2E testing. Mitigate it with explicit waits, stable selectors, and retry logic. Keep tests focused on user workflows rather than UI details. Use Docker Compose to create reproducible test environments. Monitor test pass rates over time and quarantine tests that exceed acceptable flake thresholds. A systematic approach to flakiness — detect, quarantine, fix, reintegrate — prevents flaky tests from eroding confidence in the suite.

Cost-Benefit Tradeoffs

E2E tests are expensive to write and maintain. Each test requires setup data, a running environment, and careful handling of asynchronous operations. The Google Testing Blog recommends limiting E2E tests to the most critical 10 percent of user workflows. Invest the saved effort in stronger unit and integration test coverage, which provides more reliable feedback at lower cost.

Choosing E2E Test Scenarios

Not all user journeys need E2E tests. Prioritize: critical business flows (login, checkout, payment), high-risk areas (data export, account deletion), and complex multi-step processes (onboarding wizard, report generation). Limit the E2E suite to the most important 10-20 journeys. Each E2E test takes seconds to minutes, and a large E2E suite becomes a pipeline bottleneck. Consider a “smoke test” subset that runs on every commit and a full E2E suite that runs nightly.

Running E2E Tests in CI

Run E2E tests in parallel to reduce pipeline time. Use test sharding to distribute tests across multiple CI runners. Docker Compose or Kubernetes spins up the full application stack with test-specific configuration. Use retry logic with 1-2 retries for known flaky operations but investigate flaky tests immediately. Record test artifacts (screenshots, videos, logs) for failure analysis.

FAQ

Q: How many E2E tests should I have?
A: Far fewer than unit tests. Aim for one E2E test per critical user journey — typically 10 to 20 tests for most applications.

Q: Should I use Cypress or Playwright?
A: Playwright offers broader browser support and mobile emulation. Cypress provides a superior debugging experience. Choose based on your cross-browser requirements.

Q: How do I handle authentication in E2E tests?
A: Set authentication tokens directly via API calls in the test setup rather than logging in through the UI in every test.

Q: What causes E2E test flakiness?
A: Timing issues, test ordering dependencies, network variability, and environmental differences between local and CI environments.

Internal Links

Section: Testing & QA 1489 words 7 min read Beginner 756 articles in section Report inaccuracy Back to top