End-to-End Testing
What it is
Section titled “What it is”An end-to-end (E2E) test exercises the entire system the way a user does: real browser, real frontend, real backend, real database. Nothing is mocked. It is the highest confidence and the highest cost test you can write — slow, and prone to flakiness.
Because it is expensive, keep the E2E layer thin: cover only the journeys that must never break (signup, login, checkout, payment), not every variation.
When to use it / when to avoid it
Section titled “When to use it / when to avoid it”Use it for
- Critical user journeys end to end
- Wiring across the full stack (browser → API → DB → back)
- Smoke checks after deployment
Avoid it when
- The same behaviour can be covered more cheaply by a component or integration test
- You are tempted to use E2E to test validation rules — that belongs lower
Frameworks & tools
Section titled “Frameworks & tools”| Tool | Ecosystem | Notes |
|---|---|---|
| Playwright | Multi | Cross-browser, auto-wait, tracing, codegen |
| Cypress | Multi | Great DX, time-travel debugging |
| Selenium | Multi | The original; WebDriver standard |
| WebdriverIO | Node | Flexible WebDriver/DevTools runner |
| Puppeteer | Node | Chromium/Firefox control |
| TestCafe | Node | No WebDriver, easy setup |
| Robot Framework + SeleniumLibrary | Python | Keyword-driven E2E, also RPA |
| Nightwatch | Node | WebDriver-based |
Quick setup
Section titled “Quick setup”npm init playwright@latestnpx playwright testnpx playwright test --uinpm install -D cypressnpx cypress openpip install seleniumpip install robotframework robotframework-seleniumlibraryrobot tests/npm init wdio@latest .Example
Section titled “Example”import { test, expect } from '@playwright/test';
test('user can sign up and reach the dashboard', async ({ page }) => { await page.goto('/signup'); await page.getByLabel('Email').fill('ada@example.com'); await page.getByLabel('Password').fill('s3cret'); await page.getByRole('button', { name: 'Create account' }).click();
await expect(page.getByRole('heading', { name: 'Dashboard' })) .toBeVisible();});it('signs up and reaches the dashboard', () => { cy.visit('/signup'); cy.findByLabelText('Email').type('ada@example.com'); cy.findByLabelText('Password').type('s3cret'); cy.findByRole('button', { name: /create account/i }).click(); cy.findByRole('heading', { name: /dashboard/i }).should('be.visible');});*** Settings ***Library SeleniumLibrary
*** Test Cases ***Dang Ky Va Vao Dashboard Open Browser ${BASE_URL}/signup chrome Input Text id=email ada@example.com Input Text id=password s3cret Click Button Create account Wait Until Page Contains Dashboard [Teardown] Close Browserdef test_signup(driver, base_url): driver.get(f"{base_url}/signup") driver.find_element("id", "email").send_keys("ada@example.com") driver.find_element("id", "password").send_keys("s3cret") driver.find_element("css selector", "button[type=submit]").click() assert "Dashboard" in driver.page_sourceAnti-patterns & best practices
Section titled “Anti-patterns & best practices”- Keep the suite small. Cover journeys, not permutations. Push detail down the pyramid.
- Use resilient locators. Prefer roles and labels over brittle CSS/XPath.
- Never use fixed sleeps. Use auto-waiting (
expect(...).toBeVisible()), notsleep(3). - Isolate state. Each test creates its own data so order does not matter.
- Capture artifacts on failure. Screenshots, video and traces turn a red run into a diagnosable one.
- Run in parallel in CI and quarantine genuinely flaky tests rather than ignoring red.
Self-assessment quiz
Section titled “Self-assessment quiz”Self-assessment quiz
Pick one answer per question, then press Check answers.