# Playwright Context Documentation ## Introduction Playwright is a comprehensive framework for web testing and automation that enables cross-browser testing across Chromium, Firefox, and WebKit with a unified API. Built by Microsoft, it provides robust tools for end-to-end testing, browser automation, and web scraping with features like auto-waiting, network interception, mobile emulation, and visual regression testing. The framework is designed to eliminate flaky tests through intelligent auto-waiting mechanisms and provides powerful debugging tools including trace viewer, codegen, and inspector. Playwright Test bundles a complete test runner with built-in parallelization, fixtures, assertions, and isolation capabilities. It supports multiple programming languages (JavaScript/TypeScript, Python, .NET, Java) and runs tests in isolated browser contexts that simulate independent browser profiles. The framework excels at testing modern web applications with its ability to handle multiple tabs, frames, shadow DOM, and cross-origin scenarios while providing trusted user events indistinguishable from real user interactions. ## Browser Launching and Page Creation Launch a browser and create pages ```javascript const { chromium, firefox, webkit } = require('playwright'); (async () => { // Launch browser with specific options const browser = await chromium.launch({ headless: false, slowMo: 100, args: ['--start-maximized'] }); // Create a new browser context (isolated session) const context = await browser.newContext({ viewport: { width: 1920, height: 1080 }, userAgent: 'Custom User Agent', locale: 'en-US', permissions: ['geolocation', 'notifications'] }); // Create a new page const page = await context.newPage(); await page.goto('https://example.com'); // Perform actions await page.screenshot({ path: 'screenshot.png', fullPage: true }); // Cleanup await context.close(); await browser.close(); })(); ``` ## Test Runner Installation and Basic Tests Initialize Playwright Test and write your first test ```bash # Install Playwright Test npm init playwright@latest # Run tests npx playwright test # Run tests in headed mode npx playwright test --headed # Run specific test file npx playwright test tests/example.spec.ts # Run tests in UI mode npx playwright test --ui ``` ```javascript // tests/example.spec.ts import { test, expect } from '@playwright/test'; test('basic navigation and assertion', async ({ page }) => { // Navigate to URL await page.goto('https://playwright.dev/'); // Assert page title await expect(page).toHaveTitle(/Playwright/); // Click link and wait for navigation await page.getByRole('link', { name: 'Get started' }).click(); await expect(page).toHaveURL(/.*intro/); // Take screenshot await page.screenshot({ path: 'test-result.png' }); }); test('form interaction', async ({ page }) => { await page.goto('https://example.com/form'); // Fill input fields await page.getByLabel('Username').fill('testuser'); await page.getByLabel('Password').fill('password123'); // Check checkbox await page.getByRole('checkbox', { name: 'Remember me' }).check(); // Select dropdown option await page.selectOption('#country', 'USA'); // Submit form await page.getByRole('button', { name: 'Sign in' }).click(); // Assert success message appears await expect(page.getByText('Welcome back!')).toBeVisible(); }); ``` ## Locators and Element Interaction Find and interact with elements using robust locators ```javascript import { test, expect } from '@playwright/test'; test('locator strategies', async ({ page }) => { await page.goto('https://example.com'); // Role-based locator (recommended) await page.getByRole('button', { name: 'Submit' }).click(); // Text locator await page.getByText('Welcome').click(); await page.getByText(/welcome/i).click(); // Case insensitive // Test ID locator (best for testing) await page.getByTestId('submit-button').click(); // Label locator for form fields await page.getByLabel('Email address').fill('test@example.com'); // Placeholder locator await page.getByPlaceholder('Enter your name').fill('John Doe'); // CSS selector await page.locator('.btn-primary').click(); // XPath selector await page.locator('//button[contains(text(), "Submit")]').click(); // Chaining and filtering const product = page .getByRole('listitem') .filter({ hasText: 'Product 1' }) .getByRole('button', { name: 'Add to cart' }); await product.click(); // Working with multiple elements const items = page.getByRole('listitem'); await expect(items).toHaveCount(5); // Iterate through elements for (const item of await items.all()) { console.log(await item.textContent()); } }); ``` ## Assertions and Auto-Waiting Use web-first assertions that automatically wait for conditions ```javascript import { test, expect } from '@playwright/test'; test('assertions with auto-waiting', async ({ page }) => { await page.goto('https://example.com'); // Element visibility assertions await expect(page.getByText('Welcome')).toBeVisible(); await expect(page.getByText('Hidden')).toBeHidden(); // Text content assertions await expect(page.getByTestId('status')).toHaveText('Success'); await expect(page.getByTestId('message')).toContainText('completed'); // Element state assertions await expect(page.getByRole('button')).toBeEnabled(); await expect(page.getByRole('checkbox')).toBeChecked(); await expect(page.getByRole('textbox')).toBeEditable(); await expect(page.getByRole('textbox')).toBeFocused(); // Attribute assertions await expect(page.locator('#link')).toHaveAttribute('href', '/about'); await expect(page.locator('.btn')).toHaveClass(/btn-primary/); await expect(page.locator('input')).toHaveValue('test value'); // Count assertions await expect(page.getByRole('listitem')).toHaveCount(10); // URL and title assertions await expect(page).toHaveURL(/.*dashboard/); await expect(page).toHaveTitle('Dashboard'); // Soft assertions (don't stop test execution) await expect.soft(page.getByTestId('status')).toHaveText('Success'); await expect.soft(page.getByTestId('eta')).toHaveText('1 day'); // Custom expect message await expect(page.getByText('Login'), 'User should be logged in').toBeVisible(); // Non-retrying assertions for synchronous checks expect(2 + 2).toBe(4); expect('hello').toContain('ell'); expect([1, 2, 3]).toHaveLength(3); }); ``` ## Configuration File Configure test execution, browsers, and options ```javascript // playwright.config.ts import { defineConfig, devices } from '@playwright/test'; export default defineConfig({ // Test directory testDir: './tests', // Run tests in parallel across files fullyParallel: true, // Fail build on CI if test.only is present forbidOnly: !!process.env.CI, // Retry failed tests retries: process.env.CI ? 2 : 0, // Number of parallel workers workers: process.env.CI ? 2 : undefined, // Reporter configuration reporter: [ ['html'], ['json', { outputFile: 'test-results.json' }], ['junit', { outputFile: 'junit.xml' }] ], // Global timeout for each test timeout: 30000, // Expect timeout for assertions expect: { timeout: 5000 }, // Shared settings for all tests use: { // Base URL for navigation baseURL: 'http://localhost:3000', // Browser options headless: true, viewport: { width: 1280, height: 720 }, // Collect trace on first retry trace: 'on-first-retry', screenshot: 'only-on-failure', video: 'retain-on-failure', // Context options locale: 'en-US', timezoneId: 'America/New_York', permissions: ['geolocation'], geolocation: { longitude: -122.4194, latitude: 37.7749 }, }, // Configure projects for different browsers projects: [ { name: 'chromium', use: { ...devices['Desktop Chrome'] }, }, { name: 'firefox', use: { ...devices['Desktop Firefox'] }, }, { name: 'webkit', use: { ...devices['Desktop Safari'] }, }, { name: 'Mobile Chrome', use: { ...devices['Pixel 5'] }, }, { name: 'Mobile Safari', use: { ...devices['iPhone 13'] }, }, // Setup project for authentication { name: 'setup', testMatch: /.*\.setup\.ts/, }, { name: 'chromium-logged-in', use: { ...devices['Desktop Chrome'], storageState: 'playwright/.auth/user.json', }, dependencies: ['setup'], }, ], // Start local dev server before tests webServer: { command: 'npm run start', url: 'http://localhost:3000', reuseExistingServer: !process.env.CI, timeout: 120000, }, }); ``` ## Authentication and State Management Save and reuse authentication state across tests ```javascript // tests/auth.setup.ts import { test as setup, expect } from '@playwright/test'; import path from 'path'; const authFile = path.join(__dirname, '../playwright/.auth/user.json'); setup('authenticate', async ({ page }) => { // Navigate to login page await page.goto('https://github.com/login'); // Perform login await page.getByLabel('Username or email address').fill('username'); await page.getByLabel('Password').fill('password'); await page.getByRole('button', { name: 'Sign in' }).click(); // Wait for authentication to complete await page.waitForURL('https://github.com/'); await expect(page.getByRole('button', { name: 'View profile' })).toBeVisible(); // Save authentication state await page.context().storageState({ path: authFile }); }); // tests/authenticated.spec.ts import { test, expect } from '@playwright/test'; // This test will start with saved authentication state test('access protected page', async ({ page }) => { await page.goto('https://github.com/settings'); // User is already authenticated await expect(page.getByRole('heading', { name: 'Settings' })).toBeVisible(); }); ``` ## Network Interception and API Mocking Intercept, modify, and mock network requests ```javascript import { test, expect } from '@playwright/test'; test('intercept and modify requests', async ({ page }) => { // Block CSS files await page.route('**/*.css', route => route.abort()); // Block images await page.route(/\.(png|jpg|jpeg)$/, route => route.abort()); // Mock API response await page.route('**/api/users', route => { route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify([ { id: 1, name: 'John Doe' }, { id: 2, name: 'Jane Smith' } ]) }); }); // Modify API response await page.route('**/api/products', async route => { const response = await route.fetch(); const json = await response.json(); // Modify the response json.products.push({ id: 999, name: 'New Product' }); await route.fulfill({ response, json }); }); await page.goto('https://example.com'); // Listen to requests page.on('request', request => { console.log('Request:', request.url()); }); // Listen to responses page.on('response', response => { console.log('Response:', response.url(), response.status()); }); // Wait for specific request const responsePromise = page.waitForResponse('**/api/data'); await page.getByRole('button', { name: 'Load' }).click(); const response = await responsePromise; expect(response.status()).toBe(200); }); ``` ## API Testing Test REST APIs without browser interaction ```javascript import { test, expect } from '@playwright/test'; test('API request context', async ({ request }) => { // GET request const getResponse = await request.get('https://api.github.com/users/microsoft'); expect(getResponse.ok()).toBeTruthy(); expect(getResponse.status()).toBe(200); const userData = await getResponse.json(); expect(userData.name).toBe('Microsoft'); // POST request const postResponse = await request.post('https://api.example.com/users', { headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer token123' }, data: { name: 'John Doe', email: 'john@example.com' } }); expect(postResponse.ok()).toBeTruthy(); const newUser = await postResponse.json(); // PUT request await request.put(`https://api.example.com/users/${newUser.id}`, { data: { name: 'John Updated' } }); // DELETE request const deleteResponse = await request.delete(`https://api.example.com/users/${newUser.id}`); expect(deleteResponse.status()).toBe(204); // API assertions await expect(getResponse).toBeOK(); }); test('API context with browser context', async ({ page }) => { // Shares cookies with browser context await page.goto('https://example.com/login'); await page.getByLabel('Username').fill('user'); await page.getByLabel('Password').fill('pass'); await page.getByRole('button', { name: 'Login' }).click(); // API request will use the same cookies const response = await page.request.get('https://example.com/api/profile'); const profile = await response.json(); expect(profile.username).toBe('user'); }); ``` ## Mobile Emulation and Device Testing Test on mobile devices and custom viewport configurations ```javascript import { test, expect, devices } from '@playwright/test'; test('mobile emulation', async ({ browser }) => { // Emulate iPhone 13 const iphone13 = devices['iPhone 13']; const context = await browser.newContext({ ...iphone13, locale: 'en-US', geolocation: { longitude: -122.4194, latitude: 37.7749 }, permissions: ['geolocation'], }); const page = await context.newPage(); await page.goto('https://maps.google.com'); // Click location button await page.getByText('Your location').click(); // Wait for map to load await page.waitForLoadState('networkidle'); // Take mobile screenshot await page.screenshot({ path: 'mobile-view.png' }); await context.close(); }); test('custom viewport', async ({ browser }) => { const context = await browser.newContext({ viewport: { width: 375, height: 667 }, deviceScaleFactor: 2, isMobile: true, hasTouch: true, userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X)' }); const page = await context.newPage(); await page.goto('https://example.com'); // Touch interactions await page.locator('.menu-button').tap(); await context.close(); }); ``` ## Fixtures and Test Isolation Create reusable test fixtures for shared setup ```javascript // fixtures.ts import { test as base, expect } from '@playwright/test'; class TodoPage { constructor(private page) {} async goto() { await this.page.goto('https://demo.playwright.dev/todomvc/'); } async addTodo(text: string) { await this.page.locator('.new-todo').fill(text); await this.page.locator('.new-todo').press('Enter'); } async removeTodo(text: string) { const todo = this.page.getByText(text); await todo.hover(); await this.page.getByLabel('Delete', { exact: true }).click(); } } // Extend base test with custom fixture export const test = base.extend<{ todoPage: TodoPage }>({ todoPage: async ({ page }, use) => { const todoPage = new TodoPage(page); await todoPage.goto(); await use(todoPage); } }); export { expect }; // test.spec.ts import { test, expect } from './fixtures'; test('use todo page fixture', async ({ todoPage }) => { // todoPage is already initialized await todoPage.addTodo('Buy groceries'); await todoPage.addTodo('Walk the dog'); await expect(page.getByTestId('todo-item')).toHaveCount(2); }); // Worker-scoped fixture (shared across tests) export const testWithDB = base.extend<{}, { db: Database }>({ db: [async ({}, use) => { const db = await connectDatabase(); await use(db); await db.close(); }, { scope: 'worker' }] }); ``` ## Test Annotations and Parallelization Control test execution with annotations and parallel modes ```javascript import { test, expect } from '@playwright/test'; // Skip test test.skip('skip this test', async ({ page }) => { // This test will not run }); // Conditional skip test('conditional skip', async ({ page, browserName }) => { test.skip(browserName === 'firefox', 'Not supported in Firefox'); await page.goto('https://example.com'); }); // Only run this test test.only('focus on this test', async ({ page }) => { await page.goto('https://example.com'); }); // Fail test test.fail('expected to fail', async ({ page }) => { // Test is expected to fail await expect(page.getByText('Not Exists')).toBeVisible(); }); // Mark as slow (3x timeout) test('slow test', async ({ page }) => { test.slow(); await page.goto('https://example.com/slow-page'); }); // Set custom timeout test('custom timeout', async ({ page }) => { test.setTimeout(60000); // 60 seconds await page.goto('https://example.com'); }); // Parallel execution in a single file test.describe.configure({ mode: 'parallel' }); test.describe('parallel tests', () => { test('test 1', async ({ page }) => { await page.goto('https://example.com/1'); }); test('test 2', async ({ page }) => { await page.goto('https://example.com/2'); }); }); // Serial execution (run tests in order) test.describe.configure({ mode: 'serial' }); test.describe('serial tests', () => { test('step 1: login', async ({ page }) => { await page.goto('https://example.com/login'); }); test('step 2: perform action', async ({ page }) => { await page.goto('https://example.com/dashboard'); }); }); ``` ## Visual Regression Testing Compare screenshots and detect visual changes ```javascript import { test, expect } from '@playwright/test'; test('visual regression with screenshots', async ({ page }) => { await page.goto('https://example.com'); // Compare full page screenshot await expect(page).toHaveScreenshot('homepage.png', { fullPage: true, maxDiffPixels: 100 }); // Compare element screenshot const header = page.locator('header'); await expect(header).toHaveScreenshot('header.png', { maxDiffPixelRatio: 0.1 }); // Mask dynamic content before comparison await expect(page).toHaveScreenshot('page-with-mask.png', { mask: [ page.locator('.timestamp'), page.locator('.ad-banner') ], animations: 'disabled' }); }); test('custom screenshot comparison', async ({ page }) => { await page.goto('https://example.com'); // Take screenshot await page.screenshot({ path: 'screenshots/landing.png', fullPage: true, clip: { x: 0, y: 0, width: 800, height: 600 } }); // Screenshot specific element const card = page.locator('.product-card').first(); await card.screenshot({ path: 'screenshots/product-card.png' }); }); ``` ## Trace Viewer and Debugging Record and analyze test execution traces ```javascript // playwright.config.ts - Enable tracing export default defineConfig({ use: { trace: 'on-first-retry', // or 'on', 'off', 'retain-on-failure' video: 'retain-on-failure', screenshot: 'only-on-failure' } }); ``` ```bash # Run tests with tracing enabled npx playwright test --trace on # Open HTML report with traces npx playwright show-report # Open specific trace file npx playwright show-trace trace.zip ``` ```javascript import { test } from '@playwright/test'; test('debugging with trace', async ({ page }) => { // Pause execution for debugging await page.pause(); // Add custom trace metadata await test.info().attach('test-data', { body: JSON.stringify({ userId: 123 }), contentType: 'application/json' }); await page.goto('https://example.com'); await page.getByRole('button', { name: 'Submit' }).click(); // Step through with UI Mode: npx playwright test --ui }); ``` ## Code Generation Generate test code automatically using Codegen ```bash # Launch codegen with URL npx playwright codegen https://example.com # Codegen with specific browser npx playwright codegen --browser=webkit https://example.com # Codegen with device emulation npx playwright codegen --device="iPhone 13" https://example.com # Codegen with custom viewport npx playwright codegen --viewport-size=1280,720 https://example.com # Save generated test to file npx playwright codegen --target=javascript -o tests/generated.spec.js https://example.com # Codegen with authentication npx playwright codegen --save-storage=auth.json https://example.com npx playwright codegen --load-storage=auth.json https://example.com/dashboard ``` ## Summary Playwright provides a comprehensive solution for modern web testing with its unified API across multiple browsers and programming languages. The framework's core strengths include intelligent auto-waiting that eliminates flaky tests, built-in parallelization for fast test execution, and powerful debugging tools like trace viewer and codegen. Playwright Test integrates seamlessly with CI/CD pipelines and offers flexible configuration options for timeouts, retries, reporters, and multiple browser projects. The fixture system enables clean test organization with proper isolation, while the authentication state management allows efficient testing of protected routes without repeated login operations. The framework excels at handling complex scenarios including mobile device emulation, network interception, API testing without browser overhead, and visual regression testing with screenshot comparison. Playwright's locator strategies prioritize accessibility and maintainability through role-based and test-id selectors, supported by web-first assertions that automatically retry until conditions are met. Whether automating end-to-end tests, performing API validation, scraping dynamic content, or validating cross-browser compatibility, Playwright provides the tools and patterns needed for reliable, maintainable test automation at scale.