Command Palette

Search for a command to run...

E2E Testing

Playwright

Test 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.

Cross-Browser

Test on Chromium, Firefox, and WebKit with a single test suite.

Auto-Waiting

Playwright automatically waits for elements, reducing flaky tests.

Trace Viewer

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.ts
// e2e/auth.spec.ts
import { 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.ts
// e2e/checkout.spec.ts
import { 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.ts
// e2e/pages/login-page.ts
import { 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

Terminal
# Run all E2E tests
npm run test:e2e
# Run in headed mode (see the browser)
npm run test:e2e -- --headed
# Run specific test file
npm run test:e2e -- auth.spec.ts
# Run with UI mode for debugging
npm run test:e2e -- --ui
# Generate test code by recording
npx playwright codegen localhost:3000

Debugging 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.zip

Debug Mode

Step through tests with Playwright Inspector.

PWDEBUG=1 npm run test:e2e

Video Recording

Record videos of test runs (enabled by default in CI).

npm run test:e2e -- --video=on