End-to-End Testing: Cypress, Playwright
End-to-end testing verifies complete user workflows. This article covers E2E testing frameworks and patterns.
Introduction
E2E testing provides:
- User workflow verification
- Cross-browser testing
- Visual regression testing
- Real browser testing
- Complete application testing
Understanding E2E testing helps you:
- Test complete workflows
- Verify user interactions
- Test across browsers
- Catch integration issues
- Ensure user experience
Cypress Testing
Cypress Setup
# โ
Good: Install Cypress
npm install --save-dev cypress
# โ
Good: Open Cypress
npx cypress open
# โ
Good: Run Cypress tests
npx cypress run
npx cypress run --browser chrome
npx cypress run --spec "cypress/e2e/login.cy.js"
Cypress Tests
// โ
Good: Basic Cypress test
describe('Login Page', () => {
beforeEach(() => {
cy.visit('http://localhost:3000/login');
});
it('displays login form', () => {
cy.get('form').should('be.visible');
cy.get('input[type="email"]').should('exist');
cy.get('input[type="password"]').should('exist');
cy.get('button[type="submit"]').should('exist');
});
it('logs in with valid credentials', () => {
cy.get('input[type="email"]').type('[email protected]');
cy.get('input[type="password"]').type('password123');
cy.get('button[type="submit"]').click();
cy.url().should('include', '/dashboard');
cy.get('h1').should('contain', 'Dashboard');
});
it('shows error with invalid credentials', () => {
cy.get('input[type="email"]').type('[email protected]');
cy.get('input[type="password"]').type('wrongpassword');
cy.get('button[type="submit"]').click();
cy.get('.error-message').should('contain', 'Invalid credentials');
});
});
// โ
Good: Test user workflow
describe('User Registration Workflow', () => {
it('completes registration', () => {
cy.visit('http://localhost:3000/register');
cy.get('input[name="name"]').type('John Doe');
cy.get('input[name="email"]').type('[email protected]');
cy.get('input[name="password"]').type('password123');
cy.get('input[name="confirmPassword"]').type('password123');
cy.get('button[type="submit"]').click();
cy.url().should('include', '/verify-email');
cy.get('.success-message').should('be.visible');
});
});
// โ
Good: Test with fixtures
describe('User Profile', () => {
beforeEach(() => {
cy.fixture('user.json').then((user) => {
cy.visit(`http://localhost:3000/profile/${user.id}`);
});
});
it('displays user information', () => {
cy.fixture('user.json').then((user) => {
cy.get('h1').should('contain', user.name);
cy.get('.email').should('contain', user.email);
});
});
});
Playwright Testing
Playwright Setup
# โ
Good: Install Playwright
npm install --save-dev @playwright/test
# โ
Good: Run Playwright tests
npx playwright test
npx playwright test --headed
npx playwright test --project=chromium
npx playwright test --debug
Playwright Tests
// โ
Good: Basic Playwright test
import { test, expect } from '@playwright/test';
test.describe('Login Page', () => {
test.beforeEach(async ({ page }) => {
await page.goto('http://localhost:3000/login');
});
test('displays login form', async ({ page }) => {
await expect(page.locator('form')).toBeVisible();
await expect(page.locator('input[type="email"]')).toBeVisible();
await expect(page.locator('input[type="password"]')).toBeVisible();
});
test('logs in with valid credentials', async ({ page }) => {
await page.fill('input[type="email"]', '[email protected]');
await page.fill('input[type="password"]', 'password123');
await page.click('button[type="submit"]');
await expect(page).toHaveURL(/.*dashboard/);
await expect(page.locator('h1')).toContainText('Dashboard');
});
test('shows error with invalid credentials', async ({ page }) => {
await page.fill('input[type="email"]', '[email protected]');
await page.fill('input[type="password"]', 'wrongpassword');
await page.click('button[type="submit"]');
await expect(page.locator('.error-message')).toContainText('Invalid credentials');
});
});
// โ
Good: Test with multiple browsers
test.describe('Cross-browser Login', () => {
test('works on Chrome', async ({ browser }) => {
const context = await browser.newContext();
const page = await context.newPage();
await page.goto('http://localhost:3000/login');
// Test
});
test('works on Firefox', async ({ browser }) => {
const context = await browser.newContext();
const page = await context.newPage();
await page.goto('http://localhost:3000/login');
// Test
});
});
// โ
Good: Test with screenshots
test('takes screenshot on failure', async ({ page }) => {
await page.goto('http://localhost:3000');
await page.screenshot({ path: 'screenshot.png' });
});
Visual Regression Testing
Visual Testing
// โ
Good: Cypress visual testing
describe('Visual Regression', () => {
it('matches homepage snapshot', () => {
cy.visit('http://localhost:3000');
cy.matchImageSnapshot('homepage');
});
it('matches login page snapshot', () => {
cy.visit('http://localhost:3000/login');
cy.matchImageSnapshot('login-page');
});
});
// โ
Good: Playwright visual testing
test('matches homepage snapshot', async ({ page }) => {
await page.goto('http://localhost:3000');
await expect(page).toHaveScreenshot('homepage.png');
});
test('matches login page snapshot', async ({ page }) => {
await page.goto('http://localhost:3000/login');
await expect(page).toHaveScreenshot('login-page.png');
});
Best Practices
-
Use page objects:
// โ Good: Page object pattern class LoginPage { constructor(page) { this.page = page; this.emailInput = page.locator('input[type="email"]'); this.passwordInput = page.locator('input[type="password"]'); this.submitButton = page.locator('button[type="submit"]'); } async login(email, password) { await this.emailInput.fill(email); await this.passwordInput.fill(password); await this.submitButton.click(); } } // Usage test('logs in', async ({ page }) => { const loginPage = new LoginPage(page); await loginPage.login('[email protected]', 'password123'); }); -
Wait for elements:
// โ Good: Wait for element cy.get('.loading').should('not.exist'); cy.get('.content').should('be.visible'); // โ Bad: No waiting cy.get('.content').click(); -
Test user workflows:
// โ Good: Complete workflow it('completes purchase', () => { cy.visit('/products'); cy.get('[data-product="1"]').click(); cy.get('button[aria-label="Add to cart"]').click(); cy.get('a[href="/cart"]').click(); cy.get('button[aria-label="Checkout"]').click(); });
Summary
E2E testing is essential. Key takeaways:
- Test complete user workflows
- Use page objects
- Wait for elements
- Test across browsers
- Verify visual appearance
- Test error scenarios
- Keep tests maintainable
- Run tests regularly
Related Resources
- Cypress Documentation
- Playwright Documentation
- E2E Testing Best Practices
- Page Object Model
- Visual Testing
Next Steps
- Learn about Test Coverage
- Explore CI/CD
- Study Test Automation
- Practice E2E testing
- Build comprehensive test suites
Comments