Skip to content
e2e-testing-automation logo

E2E Testing Automation

e2e-testing-automation

Build end-to-end automated tests that simulate real user interactions across the full application stack. Use for E2E test, Selenium, Cypress, Playwright, browser automation, and user journey testing.

SKILL.md

Full skill instructions

E2E Testing Automation

Overview

End-to-end (E2E) testing validates complete user workflows from the UI through all backend systems, ensuring the entire application stack works together correctly from a user's perspective. E2E tests simulate real user interactions with browsers, handling authentication, navigation, form submissions, and validating results.

When to Use

  • Testing critical user journeys (signup, checkout, login)
  • Validating multi-step workflows
  • Testing across different browsers and devices
  • Regression testing for UI changes
  • Verifying frontend-backend integration
  • Testing with real user interactions (clicks, typing, scrolling)
  • Smoke testing deployments

Instructions

1. Playwright E2E Tests

// tests/​e2e/​checkout.spec.ts
import { test, expect, Page } from '@playwright/​test';

test.describe('E-commerce Checkout Flow', () => {
  let page: Page;

  test.beforeEach(async ({ page: p }) => {
    page = p;
    await page.goto('/');
  });

  test('complete checkout flow as guest user', async () => {
    // 1. Browse and add product to cart
    await page.click('text=Shop Now');
    await page.click('[data-testid="product-1"]');
    await expect(page.locator('h1')).toContainText('Product Name');

    await page.click('button:has-text("Add to Cart")');
    await expect(page.locator('.cart-count')).toHaveText('1');

    // 2. Go to cart and proceed to checkout
    await page.click('[data-testid="cart-icon"]');
    await expect(page.locator('.cart-item')).toHaveCount(1);
    await page.click('text=Proceed to Checkout');

    // 3. Fill shipping information
    await page.fill('[name="email"]', '[email protected]');
    await page.fill('[name="firstName"]', 'John');
    await page.fill('[name="lastName"]', 'Doe');
    await page.fill('[name="address"]', '123 Main St');
    await page.fill('[name="city"]', 'San Francisco');
    await page.selectOption('[name="state"]', 'CA');
    await page.fill('[name="zip"]', '94105');

    // 4. Enter payment information
    await page.click('text=Continue to Payment');

    // Wait for payment iframe to load
    const paymentFrame = page.frameLocator('iframe[name="payment-frame"]');
    await paymentFrame.locator('[name="cardNumber"]').fill('4242424242424242');
    await paymentFrame.locator('[name="expiry"]').fill('12/​25');
    await paymentFrame.locator('[name="cvc"]').fill('123');

    // 5. Complete order
    await page.click('button:has-text("Place Order")');

    // 6. Verify success
    await expect(page).toHaveURL(/\/​order\/​confirmation/);
    await expect(page.locator('.confirmation-message')).toContainText('Order placed successfully');

    const orderNumber = await page.locator('[data-testid="order-number"]').textContent();
    expect(orderNumber).toMatch(/^ORD-\d+$/);
  });

  test('checkout with existing user account', async () => {
    // Login first
    await page.click('text=Sign In');
    await page.fill('[name="email"]', '[email protected]');
    await page.fill('[name="password"]', 'Password123!');
    await page.click('button[type="submit"]');

    await expect(page.locator('.user-menu')).toContainText('[email protected]');

    // Add product and checkout with saved information
    await page.click('[data-testid="product-2"]');
    await page.click('button:has-text("Add to Cart")');
    await page.click('[data-testid="cart-icon"]');
    await page.click('text=Checkout');

    // Verify saved address is pre-filled
    await expect(page.locator('[name="address"]')).toHaveValue(/​./);

    // Complete checkout
    await page.click('button:has-text("Use Saved Payment")');
    await page.click('button:has-text("Place Order")');

    await expect(page).toHaveURL(/\/​order\/​confirmation/);
  });

  test('handle out of stock product', async () => {
    await page.click('[data-testid="product-out-of-stock"]');

    const addToCartButton = page.locator('button:has-text("Add to Cart")');
    await expect(addToCartButton).toBeDisabled();
    await expect(page.locator('.stock-status')).toHaveText('Out of Stock');
  });
});

2. Cypress E2E Tests

// cypress/​e2e/​authentication.cy.js
describe('User Authentication Flow', () => {
  beforeEach(() => {
    cy.visit('/');
  });

  it('should register a new user account', () => {
    cy.get('[data-cy="signup-button"]').click();
    cy.url().should('include', '/​signup');

    // Fill registration form
    const timestamp = Date.now();
    cy.get('[name="email"]').type(`user${timestamp}@example.com`);
    cy.get('[name="password"]').type('SecurePass123!');
    cy.get('[name="confirmPassword"]').type('SecurePass123!');
    cy.get('[name="firstName"]').type('Test');
    cy.get('[name="lastName"]').type('User');

    // Accept terms
    cy.get('[name="acceptTerms"]').check();

    // Submit form
    cy.get('button[type="submit"]').click();

    // Verify success
    cy.url().should('include', '/​dashboard');
    cy.get('.welcome-message').should('contain', 'Welcome, Test!');

    // Verify email sent (check via API)
    cy.request(`/​api/​test/​emails/​${timestamp}@example.com`)
      .its('body')
      .should('have.property', 'subject', 'Welcome to Our App');
  });

  it('should handle validation errors', () => {
    cy.get('[data-cy="signup-button"]').click();

    // Submit empty form
    cy.get('button[type="submit"]').click();

    // Check for validation errors
    cy.get('.error-message').should('have.length.greaterThan', 0);
    cy.get('[name="email"]')
      .parent()
      .should('contain', 'Email is required');

    // Fill invalid email
    cy.get('[name="email"]').type('invalid-email');
    cy.get('[name="password"]').type('weak');
    cy.get('button[type="submit"]').click();

    cy.get('[name="email"]')
      .parent()
      .should('contain', 'Invalid email format');
    cy.get('[name="password"]')
      .parent()
      .should('contain', 'Password must be at least 8 characters');
  });

  it('should login with valid credentials', () => {
    // Create test user first
    cy.request('POST', '/​api/​test/​users', {
      email: '[email protected]',
      password: 'Password123!',
      name: 'Test User'
    });

    // Login
    cy.get('[data-cy="login-button"]').click();
    cy.get('[name="email"]').type('[email protected]');
    cy.get('[name="password"]').type('Password123!');
    cy.get('button[type="submit"]').click();

    // Verify login successful
    cy.url().should('include', '/​dashboard');
    cy.getCookie('auth_token').should('exist');

    // Verify user menu
    cy.get('[data-cy="user-menu"]').click();
    cy.get('.user-email').should('contain', '[email protected]');
  });

  it('should maintain session across page reloads', () => {
    // Login
    cy.loginViaAPI('[email protected]', 'Password123!');
    cy.visit('/​dashboard');

    // Verify logged in
    cy.get('.user-menu').should('exist');

    // Reload page
    cy.reload();

    // Still logged in
    cy.get('.user-menu').should('exist');
    cy.getCookie('auth_token').should('exist');
  });

  it('should logout successfully', () => {
    cy.loginViaAPI('[email protected]', 'Password123!');
    cy.visit('/​dashboard');

    cy.get('[data-cy="user-menu"]').click();
    cy.get('[data-cy="logout-button"]').click();

    cy.url().should('equal', Cypress.config().baseUrl + '/');
    cy.getCookie('auth_token').should('not.exist');
  });
});

// Custom command for login
Cypress.Commands.add('loginViaAPI', (email, password) => {
  cy.request('POST', '/​api/​auth/​login', { email, password })
    .then((response) => {
      window.localStorage.setItem('auth_token', response.body.token);
    });
});

3. Selenium with Python (pytest)

# tests/​e2e/​test_search_functionality.py
import pytest
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.keys import Keys

class TestSearchFunctionality:
    @pytest.fixture
    def driver(self):
        """Setup and teardown browser."""
        options = webdriver.ChromeOptions()
        options.add_argument('--headless')
        driver = webdriver.Chrome(options=options)
        driver.implicitly_wait(10)
        yield driver
        driver.quit()

    def test_search_with_results(self, driver):
        """Test search functionality returns relevant results."""
        driver.get('http://localhost:3000')

        # Find search box and enter query
        search_box = driver.find_element(By.NAME, 'search')
        search_box.send_keys('laptop')
        search_box.send_keys(Keys.RETURN)

        # Wait for results
        wait = WebDriverWait(driver, 10)
        results = wait.until(
            EC.presence_of_all_elements_located((By.CLASS_NAME, 'search-result'))
        )

        # Verify results
        assert len(results) > 0
        assert 'laptop' in driver.page_source.lower()

        # Check first result has required elements
        first_result = results[0]
        assert first_result.find_element(By.CLASS_NAME, 'product-title')
        assert first_result.find_element(By.CLASS_NAME, 'product-price')
        assert first_result.find_element(By.CLASS_NAME, 'product-image')

    def test_search_filters(self, driver):
        """Test applying filters to search results."""
        driver.get('http://localhost:3000/​search?q=laptop')

        wait = WebDriverWait(driver, 10)

        # Wait for results to load
        wait.until(
            EC.presence_of_element_located((By.CLASS_NAME, 'search-result'))
        )

        initial_count = len(driver.find_elements(By.CLASS_NAME, 'search-result'))

        # Apply price filter
        price_filter = driver.find_element(By.ID, 'price-filter-500-1000')
        price_filter.click()

        # Wait for filtered results
        wait.until(
            EC.staleness_of(driver.find_element(By.CLASS_NAME, 'search-result'))
        )
        wait.until(
            EC.presence_of_element_located((By.CLASS_NAME, 'search-result'))
        )

        filtered_count = len(driver.find_elements(By.CLASS_NAME, 'search-result'))

        # Verify filter was applied
        assert filtered_count <= initial_count

        # Verify all prices are in range
        prices = driver.find_elements(By.CLASS_NAME, 'product-price')
        for price_elem in prices:
            price = float(price_elem.text.replace('$', '').replace(',', ''))
            assert 500 <= price <= 1000

    def test_pagination(self, driver):
        """Test navigating through search result pages."""
        driver.get('http://localhost:3000/​search?q=electronics')

        wait = WebDriverWait(driver, 10)

        # Get first page results
        first_page_results = driver.find_elements(By.CLASS_NAME, 'search-result')
        first_result_title = first_page_results[0].find_element(
            By.CLASS_NAME, 'product-title'
        ).text

        # Click next page
        next_button = driver.find_element(By.CSS_SELECTOR, '[aria-label="Next page"]')
        next_button.click()

        # Wait for new results
        wait.until(EC.staleness_of(first_page_results[0]))

        # Verify on page 2
        assert 'page=2' in driver.current_url

        second_page_results = driver.find_elements(By.CLASS_NAME, 'search-result')
        second_result_title = second_page_results[0].find_element(
            By.CLASS_NAME, 'product-title'
        ).text

        # Results should be different
        assert first_result_title != second_result_title

    def test_empty_search_results(self, driver):
        """Test handling of searches with no results."""
        driver.get('http://localhost:3000')

        search_box = driver.find_element(By.NAME, 'search')
        search_box.send_keys('xyznonexistentproduct123')
        search_box.send_keys(Keys.RETURN)

        wait = WebDriverWait(driver, 10)
        no_results = wait.until(
            EC.presence_of_element_located((By.CLASS_NAME, 'no-results'))
        )

        assert 'no results found' in no_results.text.lower()
        assert len(driver.find_elements(By.CLASS_NAME, 'search-result')) == 0

4. Page Object Model Pattern

// pages/​LoginPage.ts
import { Page, Locator } from '@playwright/​test';

export class LoginPage {
  readonly page: Page;
  readonly emailInput: Locator;
  readonly passwordInput: Locator;
  readonly loginButton: Locator;
  readonly errorMessage: Locator;

  constructor(page: Page) {
    this.page = page;
    this.emailInput = page.locator('[name="email"]');
    this.passwordInput = page.locator('[name="password"]');
    this.loginButton = page.locator('button[type="submit"]');
    this.errorMessage = page.locator('.error-message');
  }

  async goto() {
    await this.page.goto('/​login');
  }

  async login(email: string, password: string) {
    await this.emailInput.fill(email);
    await this.passwordInput.fill(password);
    await this.loginButton.click();
  }

  async getErrorMessage(): Promise<string> {
    return await this.errorMessage.textContent();
  }
}

// tests/​login.spec.ts
import { test, expect } from '@playwright/​test';
import { LoginPage } from '../​pages/​LoginPage';

test('login with invalid credentials', async ({ page }) => {
  const loginPage = new LoginPage(page);
  await loginPage.goto();
  await loginPage.login('[email protected]', 'wrongpassword');

  const error = await loginPage.getErrorMessage();
  expect(error).toContain('Invalid credentials');
});

Best Practices

✅ DO

  • Use data-testid attributes for stable selectors
  • Implement Page Object Model for maintainability
  • Test critical user journeys thoroughly
  • Run tests in multiple browsers (cross-browser testing)
  • Use explicit waits instead of sleep/​timeouts
  • Clean up test data after each test
  • Take screenshots on failures
  • Parallelize test execution where possible

❌ DON'T

  • Use brittle CSS selectors (like nth-child)
  • Test every possible UI combination (focus on critical paths)
  • Share state between tests
  • Use fixed delays (sleep/​timeout)
  • Ignore flaky tests
  • Run E2E tests for unit-level testing
  • Test third-party UI components in detail
  • Skip mobile/​responsive testing

Tools & Frameworks

  • Playwright: Modern, fast, reliable (Node.js, Python, Java, .NET)
  • Cypress: Developer-friendly, fast feedback loop (JavaScript)
  • Selenium: Cross-browser, mature ecosystem (multiple languages)
  • Puppeteer: Chrome DevTools Protocol automation (Node.js)
  • WebDriverIO: Next-gen browser automation (Node.js)

Configuration Examples

// playwright.config.ts
import { defineConfig } from '@playwright/​test';

export default defineConfig({
  testDir: './​tests/​e2e',
  timeout: 30000,
  retries: 2,
  workers: process.env.CI ? 2 : 4,

  use: {
    baseURL: 'http://localhost:3000',
    screenshot: 'only-on-failure',
    video: 'retain-on-failure',
    trace: 'on-first-retry',
  },

  projects: [
    { name: 'chromium', use: { browserName: 'chromium' } },
    { name: 'firefox', use: { browserName: 'firefox' } },
    { name: 'webkit', use: { browserName: 'webkit' } },
  ],

  webServer: {
    command: 'npm run start',
    port: 3000,
    reuseExistingServer: !process.env.CI,
  },
});

Examples

See also: integration-testing, visual-regression-testing, accessibility-testing, test-automation-framework skills.

More Productivity & Planning skills

brainstorming logo
Productivity & Planning

brainstorming

Structured design dialogue that validates ideas before implementation begins.

295.4K 385.7K
View
ui-ux-pro-max logo
Productivity & Planning

ui-ux-pro-max

Comprehensive design intelligence for web and mobile UI/UX across 10 technology stacks.

133.1K 383.9K
View
writing-plans logo
Productivity & Planning

writing-plans

Comprehensive implementation plans for multi-step tasks, breaking down specs into bite-sized, testable steps.

295.4K 268K
View
using-superpowers logo
Productivity & Planning

using-superpowers

Introduction to the obra skills system with mandatory skill invocation rules and best practices.

295.4K 259.8K
View
executing-plans logo
Productivity & Planning

executing-plans

Execute a written implementation plan with critical review and task checkpoints.

295.4K 229.1K
View
dispatching-parallel-agents logo
Productivity & Planning

dispatching-parallel-agents

Delegate independent tasks to specialized agents working concurrently with isolated context.

295.4K 206.3K
View
using-git-worktrees logo
Productivity & Planning

using-git-worktrees

Isolated git worktrees with smart directory selection and safety verification.

295.4K 205.2K
View
webapp-testing logo
Productivity & Planning

webapp-testing

Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs.

179.7K 170.6K
View
content-strategy logo
Productivity & Planning

content-strategy

Plan searchable and shareable content that drives traffic, builds authority, and generates leads.

53.3K 150.9K
View
repo-intake-and-plan logo
Productivity & Planning

repo-intake-and-plan

README-first repository scanner that extracts commands and classifies reproduction candidates without executing them.

497 139.6K
View
marketing-ideas logo
Productivity & Planning

marketing-ideas

Brainstorm and prioritize marketing strategies tailored to your SaaS stage, budget, and goals.

53.3K 137.2K
View
site-architecture logo
Productivity & Planning

site-architecture

Plan and optimize your website's page hierarchy, navigation, URL structure, and internal linking.

53.3K 112.8K
View

Productivity AI tools

Vimcal logo
Productivity

Vimcal

The world's fastest calendar for remote work

Free
View
SaveDay logo
Productivity

SaveDay

Capture, organize, and utilize your knowledge effortlessly.

Free
View
A
Productivity

Any Summary

Instant Summaries of Audio & Video Interviews with AnySummary

Freemium
View
M
Productivity

Map This

Transform PDFs into engaging mind maps.

Freemium
View
ChatPDF logo
Productivity

ChatPDF

Chat with any PDF instantly

Free
View
I
Productivity

intellisay

Create an optimal daily plan using your voice

Paid
View
A
Productivity

Aurora AI

A productivity platform to centralize organizational knowledge and workflows with contextual AI assistance.

Paid
View
AskYourPDF logo
Productivity

AskYourPDF

AskYourPDF Pricing Plans: Tailored to Your Needs

Free
View