What is End-to-End Testing?
End-to-End (E2E) testing examines the entire application workflow from start to finish, ensuring all components work together as expected. It's like test-driving a car before purchase rather than just checking individual parts in isolation.
Unlike unit or integration tests that focus on specific components, E2E tests validate the entire system from the user's perspective. They answer questions like:
- Can a user successfully navigate through the application?
- Do all components work together as expected?
- Are business processes completed correctly?
- Does the application respond appropriately to user inputs?
Real-World Analogy
Think of testing a car:
- Unit Tests: Testing individual parts (brakes, engine, lights)
- Integration Tests: Testing how parts work together (engine + transmission)
- E2E Tests: Actually driving the car through different roads and conditions
E2E tests validate the experience as a whole, not just the components.
The Test Pyramid and E2E Testing
The Test Pyramid, introduced by Mike Cohn, illustrates the recommended proportions of different types of tests in a software testing strategy:
- Unit Tests (Base): Many fast, focused tests that validate individual functions and components
- Integration Tests (Middle): Moderate number of tests that validate interactions between components
- E2E Tests (Top): Fewer, more comprehensive tests that validate complete user workflows
Why E2E Tests Are at the Top
- More Complex: Test full workflows across multiple systems
- Slower: Require starting the entire application and performing real actions
- More Brittle: Can break due to changes in any part of the system
- More Resource-Intensive: Require more computing resources to run
- More Valuable: Provide the highest level of confidence that the system works
Practical Test Distribution
A well-balanced testing strategy might look like:
- 70% Unit Tests: Testing individual functions and components
- 20% Integration Tests: Testing interactions between components
- 10% E2E Tests: Testing complete user workflows
This balance provides good coverage while keeping test suites maintainable and reasonably fast.
E2E Testing Approaches
Graphical User Interface (GUI) Testing
Focuses on testing the application through its user interface, simulating actual user interactions.
- Uses tools that can control the browser or native application
- Interacts with elements like a real user would (clicks, typing, etc.)
- Verifies that UI elements are present and behave correctly
- Examples: Cypress, Selenium, Playwright, TestCafe
API-Based Testing
Tests the application by making direct API calls and verifying responses.
- Bypasses the UI to directly test business logic
- Typically faster and more reliable than GUI tests
- Can cover more scenarios in less time
- Examples: Postman, SuperTest, REST Assured
Headless Browser Testing
Uses browsers without a graphical interface to test web applications.
- Faster than testing with full browsers
- Runs well in CI/CD pipelines
- Still tests the full application stack
- Examples: Puppeteer, Playwright in headless mode
Visual Regression Testing
Compares screenshots of the application to detect visual changes.
- Checks for unexpected UI changes
- Complements functional testing
- Particularly valuable for design-heavy applications
- Examples: Percy, Applitools, Cypress Visual Testing
Choosing the Right Approach
Consider these factors when selecting your E2E testing approach:
- Application Type: Web, mobile, desktop, or API-based?
- Team Skills: What technologies is your team familiar with?
- Test Environment: Can you run browsers in your CI/CD pipeline?
- Test Speed: How fast do tests need to run?
- Coverage Needs: What aspects of the application are most critical?
Many teams use a combination of approaches for comprehensive coverage.
Key Components of E2E Testing
Test Scenarios
High-level descriptions of the workflows to test.
Example Scenario: E-commerce Checkout
"User adds items to cart, proceeds to checkout, enters shipping information, selects payment method, confirms order, and receives order confirmation."
Test Cases
Specific conditions to test with expected outcomes.
Example Test Cases for Checkout
- User can add multiple items to cart
- Cart updates quantity correctly
- User can remove items from cart
- Cart calculates totals correctly
- Checkout form validates required fields
- Order is processed when valid payment is provided
Test Steps
Detailed actions to execute during the test.
// Example steps for testing adding an item to cart
// 1. Navigate to product page
// 2. Select product options (size, color, etc.)
// 3. Click "Add to Cart" button
// 4. Verify cart icon updates
// 5. Navigate to cart page
// 6. Verify correct item appears in cart
// 7. Verify price and quantity are correct
Assertions
Statements that verify the expected outcomes.
// Example assertions for adding an item to cart
expect(cartItemCount).toBe(1);
expect(cartItemName).toBe('Expected Product Name');
expect(cartItemPrice).toBe('$19.99');
expect(cartItemQuantity).toBe(1);
expect(cartTotal).toBe('$19.99');
Test Data
The information used during test execution.
- Test Users: Accounts created specifically for testing
- Product Data: Items used in shopping scenarios
- Form Data: Information to enter in forms
- Payment Information: Test credit card numbers (never use real ones!)
Test Environment
The system configuration where tests run.
- Local Development: Running tests on developer machines
- Test/Staging: Dedicated environment similar to production
- CI/CD Pipeline: Automated environment for continuous testing
Popular E2E Testing Tools
Cypress
A modern JavaScript-based testing framework that runs directly in the browser.
// Example Cypress test
describe('Product Page', () => {
it('adds item to cart', () => {
cy.visit('/products/sample-product');
cy.get('.size-selector').select('Medium');
cy.get('.color-selector').select('Blue');
cy.get('.add-to-cart-button').click();
// Verify cart update
cy.get('.cart-count').should('contain', '1');
cy.get('.cart-icon').click();
cy.get('.cart-item').should('have.length', 1);
cy.get('.item-name').should('contain', 'Sample Product');
cy.get('.item-price').should('contain', '$19.99');
});
});
Playwright
A Microsoft-backed testing framework that supports multiple browsers and languages.
// Example Playwright test
const { test, expect } = require('@playwright/test');
test('adds item to cart', async ({ page }) => {
await page.goto('/products/sample-product');
await page.selectOption('.size-selector', 'Medium');
await page.selectOption('.color-selector', 'Blue');
await page.click('.add-to-cart-button');
// Verify cart update
await expect(page.locator('.cart-count')).toContainText('1');
await page.click('.cart-icon');
await expect(page.locator('.cart-item')).toHaveCount(1);
await expect(page.locator('.item-name')).toContainText('Sample Product');
await expect(page.locator('.item-price')).toContainText('$19.99');
});
Selenium
One of the oldest and most widely used browser automation tools.
// Example Selenium test with JavaScript
const { Builder, By, until } = require('selenium-webdriver');
(async function example() {
let driver = await new Builder().forBrowser('chrome').build();
try {
await driver.get('/products/sample-product');
await driver.findElement(By.css('.size-selector')).sendKeys('Medium');
await driver.findElement(By.css('.color-selector')).sendKeys('Blue');
await driver.findElement(By.css('.add-to-cart-button')).click();
// Verify cart update
let cartCount = await driver.findElement(By.css('.cart-count'));
await driver.wait(until.elementTextIs(cartCount, '1'), 5000);
await driver.findElement(By.css('.cart-icon')).click();
let cartItems = await driver.findElements(By.css('.cart-item'));
assert.strictEqual(cartItems.length, 1);
let itemName = await driver.findElement(By.css('.item-name')).getText();
assert.strictEqual(itemName, 'Sample Product');
let itemPrice = await driver.findElement(By.css('.item-price')).getText();
assert.strictEqual(itemPrice, '$19.99');
} finally {
await driver.quit();
}
})();
TestCafe
A Node.js tool for automated web testing with a focus on ease of use.
// Example TestCafe test
import { Selector } from 'testcafe';
fixture `Product Page`
.page `http://localhost:3000/products/sample-product`;
test('Adds item to cart', async t => {
await t
.click('.size-selector')
.click(Selector('.size-option').withText('Medium'))
.click('.color-selector')
.click(Selector('.color-option').withText('Blue'))
.click('.add-to-cart-button');
// Verify cart update
await t
.expect(Selector('.cart-count').innerText).eql('1')
.click('.cart-icon')
.expect(Selector('.cart-item').count).eql(1)
.expect(Selector('.item-name').innerText).eql('Sample Product')
.expect(Selector('.item-price').innerText).eql('$19.99');
});
Tool Comparison
| Tool | Strengths | Best For |
|---|---|---|
| Cypress | Developer-friendly, great debugging, strong documentation | Modern web applications, JavaScript developers |
| Playwright | Multi-browser, mobile testing, multiple languages | Teams needing cross-browser support, various platforms |
| Selenium | Mature, flexible, widely supported | Enterprise applications, multi-language teams |
| TestCafe | Easy setup, no WebDriver dependency | Quick test implementation, simpler web applications |
E2E Testing Best Practices
Test Critical Paths First
Focus on the most important user journeys through your application.
- User registration and login
- Core business operations (e.g., checkout, booking, searching)
- Payment flows
- High-traffic routes
Keep Tests Independent
Each test should be self-contained and not rely on the state from other tests.
- Set up the required test state at the beginning of each test
- Clean up after tests to avoid affecting subsequent tests
- Avoid test ordering dependencies
Use Stable Selectors
Choose element selectors that are less likely to change with UI updates.
Selector Stability Comparison
| More Stable | Less Stable |
|---|---|
data-testid="login-button" |
button.btn.btn-primary |
aria-label="Search" |
#search-box |
role="navigation" |
.navbar.navbar-dark |
Dedicated test attributes like data-testid provide the most stability.
Manage Test Data Effectively
Control the test data to create consistent and predictable tests.
- Use a test database that can be reset between test runs
- Create test data programmatically rather than relying on existing data
- Use factories or fixtures to generate consistent test data
- Consider using API calls to set up data instead of UI interactions
Handle Asynchronous Operations
Web applications often have asynchronous operations that need special handling in tests.
- Use explicit waits for elements or conditions rather than arbitrary delays
- Wait for network requests to complete before making assertions
- Check for loading indicators to disappear before proceeding
// Bad practice: arbitrary delay
await page.click('.submit-button');
await page.waitForTimeout(2000); // Hope 2 seconds is enough
await expect(page.locator('.success-message')).toBeVisible();
// Good practice: wait for specific condition
await page.click('.submit-button');
await page.waitForSelector('.success-message');
await expect(page.locator('.success-message')).toBeVisible();
Implement Retry Logic
Make tests more resilient by retrying operations that might fail due to timing issues.
// Example retry implementation
async function retryOperation(operation, maxRetries = 3, delay = 1000) {
let lastError;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await operation();
} catch (error) {
console.log(`Attempt ${attempt} failed: ${error.message}`);
lastError = error;
if (attempt < maxRetries) {
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
throw lastError;
}
Parallelize Test Execution
Run tests in parallel to reduce execution time, especially in CI/CD pipelines.
- Ensure tests are truly independent
- Use unique test data for each parallel test
- Configure your test runner for parallel execution
Monitor Test Flakiness
Track and reduce tests that produce inconsistent results.
- Identify tests that sometimes pass and sometimes fail
- Analyze causes of flakiness (timing issues, test dependencies, etc.)
- Refactor flaky tests to make them more reliable
- Consider quarantining extremely flaky tests until fixed
Handling Common E2E Testing Challenges
Authentication
Testing authenticated flows can be tricky, especially with modern authentication systems.
Approaches:
- UI Login: Navigate through the login form (slowest but most thorough)
- Programmatic Login: Use API calls to log in and set cookies/tokens
- Mock Authentication: Bypass authentication for testing protected routes
// Cypress example: Programmatic login
// In cypress/support/commands.js
Cypress.Commands.add('loginByApi', (email, password) => {
cy.request({
method: 'POST',
url: '/api/login',
body: { email, password }
}).then((response) => {
window.localStorage.setItem('authToken', response.body.token);
});
});
// In test
it('tests authenticated feature', () => {
cy.loginByApi('test@example.com', 'password123');
cy.visit('/dashboard');
cy.get('.user-greeting').should('contain', 'Welcome, Test User');
});
Third-Party Integrations
External services can complicate E2E testing by introducing dependencies outside your control.
Approaches:
- Test Environments: Use sandbox/test environments provided by the third-party
- Service Mocking: Mock third-party API responses
- Request Interception: Intercept and modify HTTP requests/responses
// Playwright example: Mocking a payment gateway
test('completes checkout with mocked payment', async ({ page }) => {
// Mock payment gateway response
await page.route('https://api.payment-provider.com/v1/payments', route => {
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
id: 'test-payment-123',
status: 'succeeded',
amount: 1999,
currency: 'usd'
})
});
});
// Proceed with checkout flow
await page.goto('/checkout');
// Fill in details and submit payment...
});
Handling Dates and Times
Time-dependent tests can be unreliable due to changing dates and times.
Approaches:
- Mock Date/Time: Override JavaScript's Date object
- Relative Assertions: Test relative to current time rather than absolute values
- Time Freezing: Use libraries to freeze time during tests
// Cypress example: Mocking date
cy.clock(new Date(2025, 0, 15).getTime()); // Set fixed date: Jan 15, 2025
cy.visit('/calendar');
cy.get('.current-date').should('contain', 'January 15, 2025');
Testing File Uploads and Downloads
File operations often require special handling in E2E tests.
Approaches:
- Test File Preparation: Create test files in the test setup
- Bypass UI: Use direct API calls for file operations when possible
- Download Verification: Check file existence and content after download
// Cypress example: File upload
cy.fixture('test-image.jpg', 'binary')
.then(Cypress.Blob.binaryStringToBlob)
.then(blob => {
const file = new File([blob], 'test-image.jpg', { type: 'image/jpeg' });
const dataTransfer = new DataTransfer();
dataTransfer.items.add(file);
cy.get('input[type="file"]').then(input => {
input[0].files = dataTransfer.files;
cy.wrap(input).trigger('change', { force: true });
});
});
cy.get('.upload-success').should('be.visible');
Handling Animations and Transitions
Modern UIs often include animations that can interfere with test timing.
Approaches:
- Disable Animations: Add a CSS rule to disable animations in test mode
- Wait for Transitions: Explicitly wait for animations to complete
- Animation-Aware Selectors: Use selectors that account for animation states
// CSS to disable animations in test mode
// Add to your application when running tests
if (process.env.NODE_ENV === 'test') {
const style = document.createElement('style');
style.textContent = `
*, *::before, *::after {
transition-duration: 0s !important;
animation-duration: 0s !important;
animation-delay: 0s !important;
}
`;
document.head.appendChild(style);
}
Real-World E2E Testing Example
Let's walk through a complete E2E test for an e-commerce checkout flow using Cypress.
Scenario: Complete Checkout Flow
Test the entire process from product selection to order confirmation.
// cypress/e2e/checkout-flow.cy.js
describe('Checkout Flow', () => {
let testUser;
before(() => {
// Create test data
cy.task('createTestUser').then(user => {
testUser = user;
});
cy.task('createTestProducts');
});
it('completes the checkout process successfully', () => {
// Login
cy.visit('/login');
cy.get('#email').type(testUser.email);
cy.get('#password').type(testUser.password);
cy.get('button[type="submit"]').click();
cy.url().should('include', '/dashboard');
// Browse products
cy.visit('/products');
cy.get('.product-card').first().click();
// Select options and add to cart
cy.get('.size-selector').select('Medium');
cy.get('.color-selector').select('Blue');
cy.get('.quantity-input').clear().type('2');
cy.get('.add-to-cart-button').click();
// Verify cart update
cy.get('.cart-notification').should('be.visible');
cy.get('.cart-count').should('contain', '2');
// Go to cart
cy.get('.cart-icon').click();
cy.url().should('include', '/cart');
// Verify cart contents
cy.get('.cart-item').should('have.length', 1);
cy.get('.item-quantity').should('contain', '2');
// Proceed to checkout
cy.get('.checkout-button').click();
cy.url().should('include', '/checkout');
// Fill shipping information
cy.get('#shipping-address').select('Add new address');
cy.get('#street').type('123 Test St');
cy.get('#city').type('Test City');
cy.get('#state').select('California');
cy.get('#zip').type('90210');
cy.get('#shipping-method').select('Standard Shipping');
cy.get('.continue-button').click();
// Fill payment information
cy.get('#card-number').type('4242424242424242');
cy.get('#card-expiry').type('1235');
cy.get('#card-cvc').type('123');
cy.get('#billing-same-as-shipping').check();
cy.get('.continue-button').click();
// Review order
cy.get('.order-summary').should('be.visible');
cy.get('.order-items').children().should('have.length', 1);
cy.get('.subtotal').should('exist');
cy.get('.shipping-cost').should('exist');
cy.get('.tax').should('exist');
cy.get('.total').should('exist');
// Place order
cy.get('.place-order-button').click();
// Verify order confirmation
cy.url().should('include', '/order-confirmation');
cy.get('.confirmation-message').should('contain', 'Thank you for your order');
cy.get('.order-number').should('exist');
// Verify order exists in user's order history
cy.visit('/account/orders');
cy.get('.order-item').should('have.length.at.least', 1);
cy.get('.order-item').first().should('contain', 'Processing');
});
it('validates required fields during checkout', () => {
// Add item to cart first
cy.visit('/products');
cy.get('.product-card').first().click();
cy.get('.add-to-cart-button').click();
cy.get('.cart-icon').click();
cy.get('.checkout-button').click();
// Try to continue without filling required fields
cy.get('.continue-button').click();
// Check for validation messages
cy.get('#street-error').should('be.visible');
cy.get('#city-error').should('be.visible');
cy.get('#state-error').should('be.visible');
cy.get('#zip-error').should('be.visible');
// Fill one field and check that its error disappears
cy.get('#street').type('123 Test St');
cy.get('#street-error').should('not.exist');
});
it('shows error message for invalid payment details', () => {
// Setup cart and get to payment page
cy.visit('/products');
cy.get('.product-card').first().click();
cy.get('.add-to-cart-button').click();
cy.get('.cart-icon').click();
cy.get('.checkout-button').click();
// Fill shipping information
cy.get('#shipping-address').select('Add new address');
cy.get('#street').type('123 Test St');
cy.get('#city').type('Test City');
cy.get('#state').select('California');
cy.get('#zip').type('90210');
cy.get('#shipping-method').select('Standard Shipping');
cy.get('.continue-button').click();
// Enter invalid card number
cy.get('#card-number').type('4242424242424241'); // Invalid last digit
cy.get('#card-expiry').type('1235');
cy.get('#card-cvc').type('123');
cy.get('#billing-same-as-shipping').check();
cy.get('.continue-button').click();
// Verify error message
cy.get('.payment-error').should('be.visible');
cy.get('.payment-error').should('contain', 'Your card number is invalid');
});
});
Setup for Test Isolation
// cypress/support/tasks.js
module.exports = {
async createTestUser() {
// Connect to test database
const db = require('../../db/testConnection');
// Create a unique test user
const testUser = {
email: `test-${Date.now()}@example.com`,
password: 'TestPassword123',
name: 'Test User'
};
const result = await db.collection('users').insertOne(testUser);
testUser.id = result.insertedId;
return testUser;
},
async createTestProducts() {
const db = require('../../db/testConnection');
// Create test products
const products = [
{
name: 'Test Product 1',
description: 'This is a test product',
price: 19.99,
inventory: 100,
options: {
sizes: ['Small', 'Medium', 'Large'],
colors: ['Red', 'Blue', 'Green']
}
},
{
name: 'Test Product 2',
description: 'Another test product',
price: 29.99,
inventory: 50,
options: {
sizes: ['Small', 'Medium', 'Large'],
colors: ['Black', 'White']
}
}
];
await db.collection('products').insertMany(products);
}
};
Cypress Configuration
// cypress.config.js
const { defineConfig } = require('cypress');
const tasks = require('./cypress/support/tasks');
module.exports = defineConfig({
e2e: {
baseUrl: 'http://localhost:3000',
setupNodeEvents(on, config) {
on('task', tasks);
},
env: {
apiUrl: 'http://localhost:3001/api'
},
viewportWidth: 1280,
viewportHeight: 720,
defaultCommandTimeout: 10000,
videoCompression: 15
}
});
Integrating E2E Tests into CI/CD Pipelines
E2E tests provide the most value when integrated into your continuous integration and deployment process.
GitHub Actions Example
// .github/workflows/e2e-tests.yml
name: E2E Tests
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
cypress-run:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
browser: [chrome, firefox]
services:
# Start MongoDB for the test database
mongodb:
image: mongo:5.0
ports:
- 27017:27017
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: 16
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build the app
run: npm run build
- name: Start the app and API server
run: |
npm run start:api &
npm run start &
env:
NODE_ENV: test
DATABASE_URL: mongodb://localhost:27017/test-db
API_PORT: 3001
PORT: 3000
- name: Wait for servers to start
run: |
npx wait-on http://localhost:3000
npx wait-on http://localhost:3001/api/health
- name: Run Cypress tests
uses: cypress-io/github-action@v5
with:
browser: ${{ matrix.browser }}
record: true
env:
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload screenshots if tests fail
uses: actions/upload-artifact@v3
if: failure()
with:
name: cypress-screenshots-${{ matrix.browser }}
path: cypress/screenshots
if-no-files-found: ignore
- name: Upload videos
uses: actions/upload-artifact@v3
if: always()
with:
name: cypress-videos-${{ matrix.browser }}
path: cypress/videos
if-no-files-found: ignore
Test Execution Strategy
Consider when and how to run E2E tests in your pipeline:
- Pull Requests: Run E2E tests on critical paths only to provide quick feedback
- Main/Develop Branches: Run full E2E test suite to ensure quality
- Nightly Builds: Run extended E2E tests including edge cases and performance tests
- Pre-deployment: Run full E2E suite against staging environment before production deployment
Managing Test Flakiness in CI
- Retry Mechanism: Configure test runners to retry failed tests
- Quarantine Flaky Tests: Move consistently flaky tests to a separate suite
- Video Recording: Capture videos of test runs for debugging
- Screenshot on Failure: Take screenshots when tests fail
- Detailed Logging: Increase log verbosity for CI runs
// cypress.config.js with CI-specific settings
const { defineConfig } = require('cypress');
module.exports = defineConfig({
e2e: {
baseUrl: 'http://localhost:3000',
retries: {
runMode: 2, // Retry failed tests in CI
openMode: 0 // Don't retry in development
},
// Additional CI settings
trashAssetsBeforeRuns: true,
video: true,
screenshotOnRunFailure: true
}
});
Analyzing and Improving E2E Test Results
Test Reports and Dashboards
Visualize test results to identify patterns and issues:
- Test Run History: Track success rates over time
- Failure Analysis: Categorize failures by type or area
- Execution Time: Monitor test duration trends
- Coverage Reports: See which parts of the application are tested
Common Metrics to Track
- Pass Rate: Percentage of passing tests
- Flakiness Rate: Percentage of tests with inconsistent results
- Average Duration: Mean time to run the entire test suite
- Failure Distribution: Where failures are occurring most frequently
Optimizing Test Performance
- Identify Slow Tests: Find tests taking the most time
- Reduce Setup Duplication: Share setup between related tests
- Use API Shortcuts: Bypass UI when appropriate for faster setup
- Parallelize Execution: Run tests concurrently when possible
- Optimize Selectors: Use more efficient selectors for finding elements
Maintaining E2E Tests Over Time
- Test Refactoring: Improve test quality alongside application code
- Page Object Pattern: Use abstractions to reduce duplication
- Shared Helpers: Create reusable functions for common operations
- Regular Audits: Review and clean up tests periodically
// Example Page Object Pattern
// pages/ProductPage.js
class ProductPage {
visit(productId) {
cy.visit(`/products/${productId}`);
return this;
}
selectSize(size) {
cy.get('.size-selector').select(size);
return this;
}
selectColor(color) {
cy.get('.color-selector').select(color);
return this;
}
setQuantity(quantity) {
cy.get('.quantity-input').clear().type(quantity);
return this;
}
addToCart() {
cy.get('.add-to-cart-button').click();
return this;
}
verifyAddedToCart() {
cy.get('.cart-notification').should('be.visible');
return this;
}
}
export default new ProductPage();
// Using the Page Object in a test
import ProductPage from '../pages/ProductPage';
it('adds product to cart', () => {
ProductPage
.visit('sample-product')
.selectSize('Medium')
.selectColor('Blue')
.setQuantity('2')
.addToCart()
.verifyAddedToCart();
});
Practice Activities
Activity 1: Write Your First E2E Test
Set up Cypress and write an E2E test for a simple to-do application:
- Create a test that navigates to the application
- Add a new to-do item
- Mark the item as completed
- Delete the item
- Verify each action has the expected result
Use this as a starting point:
// cypress/e2e/todo.cy.js
describe('Todo Application', () => {
beforeEach(() => {
cy.visit('http://localhost:3000');
});
it('adds, completes, and deletes a todo item', () => {
// Your code here
});
});
Activity 2: Test Authentication Flow
Create E2E tests for a user authentication flow:
- Test successful registration with valid data
- Test registration validation (password requirements, email format)
- Test successful login
- Test login with incorrect credentials
- Test password reset flow
Implement proper test isolation and data management.
Activity 3: Refactor Tests Using Page Objects
Take an existing E2E test and refactor it using the Page Object pattern:
- Identify pages or components used in the test
- Create Page Object classes for each one
- Move selectors and actions into the Page Objects
- Refactor the test to use the Page Objects
- Add a second test that reuses the Page Objects
Compare the readability and maintainability before and after.