E2E Testing
PlaywrightTest complete user flows in a real browser environment.
Overview
End-to-end tests verify that your application works correctly from a user's perspective, testing complete flows across multiple pages. Oorty uses Playwright for reliable, cross-browser E2E testing.
Test on Chromium, Firefox, and WebKit with a single test suite.
Playwright automatically waits for elements, reducing flaky tests.
Debug failed tests with screenshots, videos, and DOM snapshots.
Writing E2E Tests
Place E2E tests in the e2e/ directory at your project root.
Testing Authentication Flow
// e2e/auth.spec.tsimport { test, expect } from '@playwright/test'test.describe('Authentication', () => { test('user can sign up for an account', async ({ page }) => { await page.goto('/signup') // Fill out sign up form await page.getByLabel('Name').fill('John Doe') await page.getByLabel('Email').fill('john@example.com') await page.getByLabel('Password').fill('securepassword123') await page.getByLabel('Confirm Password').fill('securepassword123') // Submit form await page.getByRole('button', { name: /create account/i }).click() // Verify redirect to dashboard await expect(page).toHaveURL('/dashboard') await expect(page.getByText('Welcome, John')).toBeVisible() }) test('user can log in with valid credentials', async ({ page }) => { await page.goto('/login') await page.getByLabel('Email').fill('john@example.com') await page.getByLabel('Password').fill('securepassword123') await page.getByRole('button', { name: /sign in/i }).click() await expect(page).toHaveURL('/dashboard') }) test('shows error for invalid credentials', async ({ page }) => { await page.goto('/login') await page.getByLabel('Email').fill('john@example.com') await page.getByLabel('Password').fill('wrongpassword') await page.getByRole('button', { name: /sign in/i }).click() await expect( page.getByText('Invalid email or password') ).toBeVisible() await expect(page).toHaveURL('/login') }) test('user can log out', async ({ page }) => { // First log in await page.goto('/login') await page.getByLabel('Email').fill('john@example.com') await page.getByLabel('Password').fill('securepassword123') await page.getByRole('button', { name: /sign in/i }).click() // Now log out await page.getByRole('button', { name: /account/i }).click() await page.getByRole('menuitem', { name: /log out/i }).click() await expect(page).toHaveURL('/') await expect( page.getByRole('link', { name: /sign in/i }) ).toBeVisible() })})Testing a Shopping Cart Flow
// e2e/checkout.spec.tsimport { test, expect } from '@playwright/test'test.describe('Checkout Flow', () => { test.beforeEach(async ({ page }) => { // Log in before each test await page.goto('/login') await page.getByLabel('Email').fill('test@example.com') await page.getByLabel('Password').fill('password123') await page.getByRole('button', { name: /sign in/i }).click() await expect(page).toHaveURL('/dashboard') }) test('complete purchase flow', async ({ page }) => { // Browse products await page.goto('/products') // Add item to cart const product = page.getByTestId('product-card').first() await product.getByRole('button', { name: /add to cart/i }).click() // Verify cart badge updates await expect(page.getByTestId('cart-count')).toHaveText('1') // Go to cart await page.getByRole('link', { name: /cart/i }).click() await expect(page).toHaveURL('/cart') // Verify item in cart await expect(page.getByTestId('cart-item')).toHaveCount(1) // Proceed to checkout await page.getByRole('button', { name: /checkout/i }).click() await expect(page).toHaveURL('/checkout') // Fill shipping details await page.getByLabel('Address').fill('123 Main St') await page.getByLabel('City').fill('San Francisco') await page.getByLabel('ZIP Code').fill('94102') // Fill payment details (Stripe test card) const stripeFrame = page.frameLocator('iframe[name^="__privateStripeFrame"]') await stripeFrame.getByPlaceholder('Card number').fill('4242424242424242') await stripeFrame.getByPlaceholder('MM / YY').fill('12/30') await stripeFrame.getByPlaceholder('CVC').fill('123') // Complete order await page.getByRole('button', { name: /place order/i }).click() // Verify success await expect(page).toHaveURL(/\/orders\/[a-z0-9]+/) await expect( page.getByRole('heading', { name: /order confirmed/i }) ).toBeVisible() }) test('can update item quantity in cart', async ({ page }) => { // Add item to cart await page.goto('/products') await page.getByTestId('product-card').first() .getByRole('button', { name: /add to cart/i }).click() // Go to cart await page.goto('/cart') // Increase quantity await page.getByRole('button', { name: /increase/i }).click() await expect(page.getByTestId('item-quantity')).toHaveValue('2') // Verify total updates const initialTotal = await page.getByTestId('cart-total').textContent() await page.getByRole('button', { name: /increase/i }).click() await expect(page.getByTestId('cart-total')).not.toHaveText(initialTotal!) }) test('can remove item from cart', async ({ page }) => { // Add item to cart await page.goto('/products') await page.getByTestId('product-card').first() .getByRole('button', { name: /add to cart/i }).click() // Go to cart and remove await page.goto('/cart') await page.getByRole('button', { name: /remove/i }).click() // Verify empty cart await expect(page.getByText(/your cart is empty/i)).toBeVisible() })})Page Object Pattern
For complex applications, use the Page Object pattern to organize your tests:
// e2e/pages/login-page.tsimport { Page, Locator } from '@playwright/test'export class LoginPage { readonly page: Page readonly emailInput: Locator readonly passwordInput: Locator readonly submitButton: Locator readonly errorMessage: Locator constructor(page: Page) { this.page = page this.emailInput = page.getByLabel('Email') this.passwordInput = page.getByLabel('Password') this.submitButton = page.getByRole('button', { name: /sign in/i }) this.errorMessage = page.getByRole('alert') } 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.submitButton.click() }}Running E2E Tests
# Run all E2E testsnpm run test:e2e# Run in headed mode (see the browser)npm run test:e2e -- --headed# Run specific test filenpm run test:e2e -- auth.spec.ts# Run with UI mode for debuggingnpm run test:e2e -- --ui# Generate test code by recordingnpx playwright codegen localhost:3000Debugging Failed Tests
Playwright provides powerful debugging tools:
Trace Viewer
View a timeline of test execution with screenshots, network requests, and DOM snapshots.
npx playwright show-trace test-results/trace.zipDebug Mode
Step through tests with Playwright Inspector.
PWDEBUG=1 npm run test:e2eVideo Recording
Record videos of test runs (enabled by default in CI).
npm run test:e2e -- --video=on