brainstorming
Structured design dialogue that validates ideas before implementation begins.
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.
Full skill instructions
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.
// 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');
});
});
// 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);
});
});
# 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
// 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');
});
// 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,
},
});
See also: integration-testing, visual-regression-testing, accessibility-testing, test-automation-framework skills.
Structured design dialogue that validates ideas before implementation begins.
Comprehensive design intelligence for web and mobile UI/UX across 10 technology stacks.
Comprehensive implementation plans for multi-step tasks, breaking down specs into bite-sized, testable steps.
Introduction to the obra skills system with mandatory skill invocation rules and best practices.
Execute a written implementation plan with critical review and task checkpoints.
Delegate independent tasks to specialized agents working concurrently with isolated context.
Isolated git worktrees with smart directory selection and safety verification.
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.
Plan searchable and shareable content that drives traffic, builds authority, and generates leads.
README-first repository scanner that extracts commands and classifies reproduction candidates without executing them.
Brainstorm and prioritize marketing strategies tailored to your SaaS stage, budget, and goals.
Plan and optimize your website's page hierarchy, navigation, URL structure, and internal linking.
The world's fastest calendar for remote work
Capture, organize, and utilize your knowledge effortlessly.
Instant Summaries of Audio & Video Interviews with AnySummary
Transform PDFs into engaging mind maps.
Chat with any PDF instantly
Create an optimal daily plan using your voice
A productivity platform to centralize organizational knowledge and workflows with contextual AI assistance.
AskYourPDF Pricing Plans: Tailored to Your Needs