End-to-End Testing with Playwright
End-to-End Testing with Playwright
Section titled “End-to-End Testing with Playwright”Playwright is Microsoft’s browser automation library. It drives real browsers (Chromium, Firefox, WebKit) to test your application as a user would.
Installation
Section titled “Installation”npm init playwright@latest
# Or manuallynpm install -D @playwright/testnpx playwright install # download browser binariesBasic Test
Section titled “Basic Test”import { test, expect } from '@playwright/test';
test('user can log in', async ({ page }) => { await page.goto('http://localhost:3000/login');
await page.fill('[name="email"]', 'alice@example.com'); await page.fill('[name="password"]', 'secret123'); await page.click('button[type="submit"]');
await expect(page).toHaveURL('/dashboard'); await expect(page.getByRole('heading')).toContainText('Welcome, Alice');});Configuration
Section titled “Configuration”import { defineConfig, devices } from '@playwright/test';
export default defineConfig({ testDir: './tests', fullyParallel: true, forbidOnly: !!process.env.CI, retries: process.env.CI ? 2 : 0, reporter: 'html', use: { baseURL: 'http://localhost:3000', trace: 'on-first-retry', }, projects: [ { name: 'chromium', use: { ...devices['Desktop Chrome'] } }, { name: 'firefox', use: { ...devices['Desktop Firefox'] } }, { name: 'Mobile Safari', use: { ...devices['iPhone 14'] } }, ], webServer: { command: 'npm run dev', url: 'http://localhost:3000', reuseExistingServer: !process.env.CI, },});Locators
Section titled “Locators”Playwright uses locators that are resilient to DOM changes:
// By role (best — matches ARIA semantics)page.getByRole('button', { name: 'Submit' })page.getByRole('heading', { level: 1 })page.getByRole('textbox', { name: 'Email' })
// By labelpage.getByLabel('Password')
// By placeholderpage.getByPlaceholder('Search...')
// By textpage.getByText('Welcome back')
// By test ID (data-testid attribute)page.getByTestId('submit-button')
// CSS / XPath (fallback — prefer role-based locators)page.locator('.submit-btn')page.locator('input[name="email"]')Actions
Section titled “Actions”await page.goto('/login');await page.fill('[name="email"]', 'alice@example.com');await page.click('button[type="submit"]');await page.check('[name="remember-me"]');await page.selectOption('select[name="country"]', 'GB');await page.keyboard.press('Enter');await page.hover('.tooltip-trigger');
// Wait for navigationawait page.waitForURL('/dashboard');
// Upload a fileawait page.setInputFiles('input[type="file"]', './test-file.pdf');Assertions
Section titled “Assertions”await expect(page).toHaveURL('/dashboard');await expect(page).toHaveTitle('Dashboard | MyApp');await expect(page.getByRole('heading')).toContainText('Welcome');await expect(page.getByRole('button', { name: 'Submit' })).toBeVisible();await expect(page.getByRole('button', { name: 'Save' })).toBeDisabled();await expect(page.getByTestId('error-msg')).toHaveText('Email is required');await expect(page.locator('.spinner')).toBeHidden();Fixtures and Page Objects
Section titled “Fixtures and Page Objects”Page Object Model encapsulates page interaction logic:
import { Page, Locator } from '@playwright/test';
export class LoginPage { readonly email: Locator; readonly password: Locator; readonly submitButton: Locator;
constructor(private page: Page) { this.email = page.getByLabel('Email'); this.password = page.getByLabel('Password'); this.submitButton = page.getByRole('button', { name: 'Log in' }); }
async login(email: string, password: string) { await this.page.goto('/login'); await this.email.fill(email); await this.password.fill(password); await this.submitButton.click(); }}
// tests/login.spec.tstest('logs in successfully', async ({ page }) => { const loginPage = new LoginPage(page); await loginPage.login('alice@example.com', 'secret123'); await expect(page).toHaveURL('/dashboard');});API Mocking
Section titled “API Mocking”test('shows error when API fails', async ({ page }) => { await page.route('/api/users', route => route.fulfill({ status: 500, body: 'Server error' }) );
await page.goto('/users'); await expect(page.getByText('Something went wrong')).toBeVisible();});Running Tests
Section titled “Running Tests”npx playwright test # all tests, headlessnpx playwright test --headed # with browser visiblenpx playwright test login.spec.ts # specific filenpx playwright test --project=chromium # specific browsernpx playwright test --debug # step through with DevToolsnpx playwright test --ui # interactive UI modenpx playwright show-report # view HTML reportGitHub Actions
Section titled “GitHub Actions”- name: Install dependencies run: npm ci
- name: Install Playwright browsers run: npx playwright install --with-deps
- name: Run Playwright tests run: npx playwright test
- name: Upload report uses: actions/upload-artifact@v4 if: always() with: name: playwright-report path: playwright-report/ retention-days: 30