Getting Started.md
Copy to clipboard
## System Requirement
Node.js: latest 20.x, 22.x or 24.x.
Windows 11+, Windows Server 2019+
macOS 14 (Ventura) or later.
Debian 12 / 13, Ubuntu 22.04 / 24.04 (x86-64 or arm64)
# Install Playwright with TypeScript
npm init playwright@latest
# Or install manually
npm install -D @playwright/test
npm install -D typescript
npx playwright install
Project Structure.md
Copy to clipboard
## Project Structure
```
├── tests/
│ ├── example.spec.ts
│ └── auth.spec.ts
├── playwright.config.ts
├── package.json
└── tsconfig.json
```
Core Concepts: Browser, Context & Page.md
Copy to clipboard
### Hierarchy Overview
```
Playwright
└── Browser (Chrome, Firefox, Safari)
└── Browser Context (Isolated session)
└── Page (Individual tab/window)
└── Frame (iframes within page)
```
**Think of it like:**
- **Browser** = The application itself (Chrome.exe)
- **Browser Context** = An incognito window (isolated cookies/storage)
- **Page** = A single tab in that window
Browser.md
Copy to clipboard
# The browser instance represents the actual browser process.
```
import { chromium, firefox, webkit } from '@playwright/test';
// Launch browser manually
const browser = await chromium.launch({
headless: false, // Show browser UI
slowMo: 100 // Slow down by 100ms
});
// Multiple browsers can run simultaneously
const chromeBrowser = await chromium.launch();
const firefoxBrowser = await firefox.launch();
const safariaBrowser = await webkit.launch();
// Close browser when done
await browser.close();
```
**Browser Properties:**
```typescript
const browser = await chromium.launch();
// Get browser type
console.log(browser.browserType()); // 'chromium'
// Check if connected
console.log(browser.isConnected()); // true
// Get all contexts
const contexts = browser.contexts();
console.log(contexts.length);
```
Browser Context.md
Copy to clipboard
An isolated incognito-like session.
**Each context has its own cookies, localStorage, and cache.**
```
import { test } from '@playwright/test';
test('browser context example', async ({ browser }) => {
// Create isolated context
const context = await browser.newContext({
viewport: { width: 1920, height: 1080 },
locale: 'en-US',
timezoneId: 'America/New_York',
permissions: ['geolocation'],
geolocation: { latitude: 40.7128, longitude: -74.0060 },
userAgent: 'Custom User Agent'
});
// Create pages in this context
const page = await context.newPage();
await page.goto('https://example.com');
// All pages in this context share cookies
await context.close();
});
```
**Why Use Contexts?**
```
// Test with different users simultaneously
test('multiple users', async ({ browser }) => {
// User 1 - Admin
const adminContext = await browser.newContext();
const adminPage = await adminContext.newPage();
await adminPage.goto('/login');
await adminPage.fill('#username', 'admin');
await adminPage.fill('#password', 'admin123');
await adminPage.click('button[type="submit"]');
// User 2 - Regular user (separate session)
const userContext = await browser.newContext();
const userPage = await userContext.newPage();
await userPage.goto('/login');
await userPage.fill('#username', 'user');
await userPage.fill('#password', 'user123');
await userPage.click('button[type="submit"]');
// Both users logged in simultaneously, isolated from each other
await expect(adminPage.locator('.role')).toHaveText('Admin');
await expect(userPage.locator('.role')).toHaveText('User');
await adminContext.close();
await userContext.close();
});
```
**Context with Authentication:**
```typescript
// Save authentication state
test('save auth state', async ({ browser }) => {
const context = await browser.newContext();
const page = await context.newPage();
await page.goto('/login');
await page.fill('#username', 'testuser');
await page.fill('#password', 'password');
await page.click('button[type="submit"]');
// Save cookies and localStorage
await context.storageState({ path: 'auth.json' });
await context.close();
});
// Reuse authentication
test('reuse auth', async ({ browser }) => {
// Load saved state
const context = await browser.newContext({
storageState: 'auth.json'
});
const page = await context.newPage();
await page.goto('/dashboard'); // Already logged in!
await context.close();
});
```
Page.md
Copy to clipboard
A single tab/window within a browser context.
```
import { test, expect } from '@playwright/test';
test('page basics', async ({ page }) => {
// Navigate
await page.goto('https://example.com');
// Get page info
console.log(await page.title());
console.log(page.url());
// Viewport
const viewport = page.viewportSize();
console.log(viewport); // { width: 1280, height: 720 }
// Set viewport
await page.setViewportSize({ width: 1920, height: 1080 });
});
```
**Multiple Pages (Tabs):**
```
test('multiple tabs', async ({ context }) => {
// Tab 1
const page1 = await context.newPage();
await page1.goto('https://example.com');
// Tab 2
const page2 = await context.newPage();
await page2.goto('https://playwright.dev');
// Tab 3
const page3 = await context.newPage();
await page3.goto('https://github.com');
// Work with specific tabs
await page1.fill('#search', 'test');
await page2.click('a.docs-link');
// Get all pages
const allPages = context.pages();
console.log(`Open tabs: ${allPages.length}`); // 3
// Close specific page
await page2.close();
});
```
**Page Events:**
```
test('page events', async ({ page }) => {
// Listen to console messages
page.on('console', msg => {
console.log(`Browser console: ${msg.text()}`);
});
// Listen to page errors
page.on('pageerror', error => {
console.error(`Page error: ${error.message}`);
});
// Listen to requests
page.on('request', request => {
console.log(`→ ${request.method()} ${request.url()}`);
});
// Listen to responses
page.on('response', response => {
console.log(`← ${response.status()} ${response.url()}`);
});
// Listen to dialog (alert/confirm/prompt)
page.on('dialog', dialog => {
console.log(`Dialog: ${dialog.message()}`);
dialog.accept();
});
await page.goto('https://example.com');
});
```
Writing Tests.md
Copy to clipboard
### First Test
```
import { test, expect } from '@playwright/test';
test('basic test', async ({ page }) => {
await page.goto('https://playwright.dev/');
const title = await page.title();
expect(title).toBe('Fast and reliable end-to-end testing');
});
```
### Run Tests
# Run all tests
npx playwright test
# Run in headed mode
npx playwright test --headed
# Run specific file
npx playwright test tests/login.spec.ts
# Run in debug mode
npx playwright test --debug
# Run with UI mode
npx playwright test --ui
Test Structure.md
Copy to clipboard
### Basic Test
```
import { test, expect } from '@playwright/test';
test('has title', async ({ page }) => {
await page.goto('https://example.com/');
await expect(page).toHaveTitle(/Example/);
});
```
### Test with Describe Block
```
import { test, expect } from '@playwright/test';
test.describe('Login Flow', () => {
test('successful login', async ({ page }) => {
await page.goto('/login');
await page.fill('#username', 'testuser');
await page.fill('#password', 'password123');
await page.click('button[type="submit"]');
await expect(page).toHaveURL('/dashboard');
});
test('failed login', async ({ page }) => {
await page.goto('/login');
await page.fill('#username', 'wronguser');
await page.fill('#password', 'wrongpass');
await page.click('button[type="submit"]');
await expect(page.locator('.error')).toBeVisible();
});
});
```
Hooks (Before/After).md
Copy to clipboard
#### beforeEach & afterEach
```
import { test, expect } from '@playwright/test';
test.describe('User Profile', () => {
test.beforeEach(async ({ page }) => {
// Runs before each test in this describe block
await page.goto('/login');
await page.fill('#username', 'testuser');
await page.fill('#password', 'pass123');
await page.click('button[type="submit"]');
});
test.afterEach(async ({ page }) => {
// Runs after each test in this describe block
await page.click('#logout');
});
test('update profile', async ({ page }) => {
await page.goto('/profile');
await page.fill('#name', 'New Name');
await page.click('button:has-text("Save")');
});
test('view profile', async ({ page }) => {
await page.goto('/profile');
await expect(page.locator('.username')).toBeVisible();
});
});
```
#### beforeAll & afterAll
```
import { test, expect } from '@playwright/test';
test.describe('Database Tests', () => {
test.beforeAll(async () => {
// Runs once before all tests in this describe block
console.log('Setting up test database...');
// Initialize database, create test data, etc.
});
test.afterAll(async () => {
// Runs once after all tests in this describe block
console.log('Cleaning up test database...');
// Clean up database, remove test data, etc.
});
test('test 1', async ({ page }) => {
// Test implementation
});
test('test 2', async ({ page }) => {
// Test implementation
});
});
```
#### All Hooks Together
```
import { test, expect } from '@playwright/test';
test.describe('Complete Hooks Example', () => {
test.beforeAll(async ({ browser }) => {
// Runs ONCE before all tests
console.log('1. beforeAll - Setup test suite');
});
test.afterAll(async () => {
// Runs ONCE after all tests
console.log('6. afterAll - Teardown test suite');
});
test.beforeEach(async ({ page }) => {
// Runs BEFORE each test
console.log('2. beforeEach - Setup for individual test');
await page.goto('/');
});
test.afterEach(async ({ page }) => {
// Runs AFTER each test
console.log('4. afterEach - Cleanup for individual test');
});
test('first test', async ({ page }) => {
console.log('3. Running first test');
await expect(page).toHaveTitle(/Example/);
});
test('second test', async ({ page }) => {
console.log('5. Running second test');
await expect(page).toHaveURL(/example/);
});
});
// Execution order:
// 1. beforeAll
// 2. beforeEach (test 1)
// 3. first test
// 4. afterEach (test 1)
// 2. beforeEach (test 2)
// 5. second test
// 4. afterEach (test 2)
// 6. afterAll
```
Test Annotations.md
Copy to clipboard
#### Basic Annotations
```
import { test } from '@playwright/test';
// Skip test
test.skip('not ready yet', async ({ page }) => {
// This test will be skipped
});
// Only run this test
test.only('focus on this', async ({ page }) => {
await page.goto('/');
});
// Mark as failing
test.fail('known issue', async ({ page }) => {
// Test is expected to fail
});
// Conditional skip
test('mobile only', async ({ page, isMobile }) => {
test.skip(!isMobile, 'Desktop only');
await page.goto('/mobile-view');
});
// Slow test (3x timeout)
test.slow('slow test', async ({ page }) => {
// Triples the test timeout
await page.goto('/slow-page');
});
// Fixme annotation
test.fixme('needs investigation', async ({ page }) => {
// Similar to skip, but indicates it needs fixing
});
```
Test Tagging.md
Copy to clipboard
#### Test Tagging
```typescript
import { test, expect } from '@playwright/test';
// Single tag
test('login test @smoke', async ({ page }) => {
await page.goto('/login');
await page.fill('#username', 'testuser');
await page.click('button[type="submit"]');
});
// Multiple tags
test('checkout flow @smoke @critical @e2e', async ({ page }) => {
await page.goto('/cart');
await page.click('button:has-text("Checkout")');
await expect(page).toHaveURL(/checkout/);
});
// Tags in describe blocks
test.describe('User Management @admin', () => {
test('create user @smoke', async ({ page }) => {
// Test implementation
});
test('delete user @critical', async ({ page }) => {
// Test implementation
});
test('update permissions @regression', async ({ page }) => {
// Test implementation
});
});
// Nested tags
test.describe('API Tests @api', () => {
test.describe('User Endpoints @users', () => {
test('GET /users @smoke', async ({ request }) => {
const response = await request.get('/api/users');
expect(response.ok()).toBeTruthy();
});
test('POST /users @critical', async ({ request }) => {
const response = await request.post('/api/users', {
data: { name: 'Test User' }
});
expect(response.status()).toBe(201);
});
});
});
```
#### Running Tagged Tests
```bash
# Run tests with specific tag
npx playwright test --grep @smoke
# Run tests with multiple tags (OR)
npx playwright test --grep "@smoke|@critical"
# Run tests with all tags (AND) - use positive lookahead
npx playwright test --grep "(?=.*@smoke)(?=.*@critical)"
# Exclude tests with specific tag
npx playwright test --grep-invert @slow
# Run smoke tests but exclude WIP
npx playwright test --grep @smoke --grep-invert @wip
# Run in specific describe block
npx playwright test --grep "User Management"
```
Fixtures.md
Copy to clipboard
## Fixtures
Fixtures are isolated contexts that provide setup and teardown for tests. Playwright comes with built-in fixtures and allows you to create custom ones.
### Built-in Fixtures
```
import { test, expect } from '@playwright/test';
test('built-in fixtures', async ({ page, context, browser, browserName, request }) => {
// page - Isolated page for the test
await page.goto('https://example.com');
// context - Isolated browser context
const newPage = await context.newPage();
// browser - Browser instance
console.log(browser.version());
// browserName - Current browser name
console.log(browserName); // 'chromium', 'firefox', or 'webkit'
// request - API testing context
const response = await request.get('https://api.example.com/data');
expect(response.ok()).toBeTruthy();
});
```
### Common Built-in Fixtures
```
test('all built-in fixtures', async ({
page, // Page instance
context, // Browser context
browser, // Browser instance
browserName, // Browser name string
request, // APIRequestContext for API testing
playwright, // Playwright instance
}) => {
// Use fixtures in your test
});
// Additional fixtures from test info
test('test info fixtures', async ({ }, testInfo) => {
console.log(testInfo.title); // Test title
console.log(testInfo.file); // Test file path
console.log(testInfo.project.name); // Project name
console.log(testInfo.retry); // Retry count
console.log(testInfo.timeout); // Test timeout
});
```
### Custom Test-Scoped Fixtures
Test-scoped fixtures are set up before each test and torn down after.
```
import { test as base, expect } from '@playwright/test';
// Define fixture types
type MyFixtures = {
todoPage: string;
settingsPage: string;
};
// Extend base test with custom fixtures
const test = base.extend<MyFixtures>({
// Simple value fixture
todoPage: '/todo',
// Fixture with setup/teardown
settingsPage: async ({ page }, use) => {
// Setup: runs before the test
await page.goto('/settings');
console.log('Settings page loaded');
// Provide the fixture value to the test
await use('/settings');
// Teardown: runs after the test
console.log('Settings page test completed');
},
});
export { test, expect };
// Use custom fixtures
test('use custom fixtures', async ({ todoPage, settingsPage, page }) => {
console.log(`Todo page URL: ${todoPage}`);
console.log(`Settings page URL: ${settingsPage}`);
await expect(page).toHaveURL(settingsPage);
});
```
Locators & Selectors.md
Copy to clipboard
## Recommended Locators
```
import { test, expect } from '@playwright/test';
test('recommended locators', async ({ page }) => {
await page.goto('/');
// By role (BEST - accessibility-friendly)
await page.getByRole('button', { name: 'Submit' }).click();
await page.getByRole('link', { name: 'Home' }).click();
await page.getByRole('textbox', { name: 'Email' }).fill('test@test.com');
// By label
await page.getByLabel('Password').fill('secret');
await page.getByLabel('Subscribe to newsletter').check();
// By placeholder
await page.getByPlaceholder('Search...').fill('Playwright');
// By text
await page.getByText('Welcome back').click();
await page.getByText(/welcome/i).click(); // Case insensitive
// By test ID (recommended for testing)
await page.getByTestId('submit-button').click();
});
```
### CSS Selectors
```
test('css selectors', async ({ page }) => {
// ID
await page.locator('#username').fill('john');
// Class
await page.locator('.btn-primary').click();
// Attribute
await page.locator('[data-test="login-btn"]').click();
// Combined
await page.locator('button.submit[type="submit"]').click();
// nth child
await page.locator('li').nth(2).click();
await page.locator('li').first().click();
await page.locator('li').last().click();
});
```
### XPath Selectors
```
test('xpath selectors', async ({ page }) => {
// XPath
await page.locator('//button[@type="submit"]').click();
await page.locator('//input[@id="username"]').fill('test');
// Contains text
await page.locator('//div[contains(text(), "Welcome")]').click();
});
```
### Chaining Locators
```
test('chaining locators', async ({ page }) => {
// Find button inside a form
await page.locator('form').locator('button[type="submit"]').click();
// Filter by text
await page.getByRole('button').filter({ hasText: 'Submit' }).click();
// Filter by another locator
await page
.getByRole('listitem')
.filter({ has: page.getByRole('button', { name: 'Delete' }) })
.click();
});
```
### Locator Count
```
test('count elements', async ({ page }) => {
await page.goto('/products');
const productCount = await page.locator('.product-card').count();
expect(productCount).toBeGreaterThan(0);
// Assert count
await expect(page.locator('.product-card')).toHaveCount(10);
});
```
Actions.md
Copy to clipboard
### Click Actions
```
test('click actions', async ({ page }) => {
// Single click
await page.click('button#submit');
// Double click
await page.dblclick('.item');
// Right click
await page.click('button', { button: 'right' });
// Click with modifiers
await page.click('a', { modifiers: ['Ctrl'] });
// Force click (even if hidden)
await page.click('button', { force: true });
// Click at position
await page.click('canvas', { position: { x: 100, y: 200 } });
});
```
### Input Actions
```
test('input actions', async ({ page }) => {
// Type text
await page.fill('input[name="email"]', 'test@example.com');
// Type with delay
await page.type('input', 'Hello World', { delay: 100 });
// Clear input
await page.fill('input[name="search"]', '');
// Press single key
await page.press('input', 'Enter');
await page.press('input', 'Escape');
// Press key combination
await page.press('body', 'Control+A');
await page.press('body', 'Meta+S'); // Command on Mac
});
```
### Checkbox & Radio
```
test('checkbox and radio', async ({ page }) => {
// Check
await page.check('input[type="checkbox"]#terms');
// Uncheck
await page.uncheck('input[type="checkbox"]#newsletter');
// Set checked state
await page.setChecked('input[type="checkbox"]', true);
// Radio button
await page.check('input[type="radio"][value="option1"]');
// Assert checked
await expect(page.locator('#terms')).toBeChecked();
await expect(page.locator('#newsletter')).not.toBeChecked();
});
```
### Select Dropdown
```
test('select dropdown', async ({ page }) => {
// Select by value
await page.selectOption('select#country', 'us');
// Select by label
await page.selectOption('select#country', { label: 'United States' });
// Select by index
await page.selectOption('select#country', { index: 2 });
// Multiple selection
await page.selectOption('select[multiple]', ['value1', 'value2']);
// Get selected value
const value = await page.$eval('select#country', el => el.value);
console.log(value);
});
```
### File Upload
```
test('file upload', async ({ page }) => {
// Single file
await page.setInputFiles('input[type="file"]', 'path/to/file.pdf');
// Multiple files
await page.setInputFiles('input[type="file"]', [
'file1.jpg',
'file2.jpg'
]);
// From buffer
await page.setInputFiles('input[type="file"]', {
name: 'test.txt',
mimeType: 'text/plain',
buffer: Buffer.from('file content')
});
// Remove files
await page.setInputFiles('input[type="file"]', []);
});
```
### Hover & Focus
```
test('hover and focus', async ({ page }) => {
// Hover
await page.hover('.menu-item');
// Focus
await page.focus('input#search');
// Blur
await page.locator('input').blur();
// Scroll into view
await page.locator('#footer').scrollIntoViewIfNeeded();
});
```
Assertions.md
Copy to clipboard
### Page Assertions
```
import { test, expect } from '@playwright/test';
test('page assertions', async ({ page }) => {
await page.goto('https://example.com');
// Title
await expect(page).toHaveTitle('Example Domain');
await expect(page).toHaveTitle(/Example/);
// URL
await expect(page).toHaveURL('https://example.com/');
await expect(page).toHaveURL(/example\.com/);
// Screenshot comparison
await expect(page).toHaveScreenshot('homepage.png');
});
```
### Element Assertions
```
test('element assertions', async ({ page }) => {
await page.goto('/');
const button = page.locator('button#submit');
// Visibility
await expect(button).toBeVisible();
await expect(button).toBeHidden();
// Enabled/Disabled
await expect(button).toBeEnabled();
await expect(button).toBeDisabled();
// Text content
await expect(button).toHaveText('Submit');
await expect(button).toHaveText(/sub/i);
await expect(button).toContainText('Sub');
// Value
await expect(page.locator('input')).toHaveValue('test');
// Attribute
await expect(button).toHaveAttribute('type', 'submit');
await expect(button).toHaveAttribute('disabled', '');
// Class
await expect(button).toHaveClass('btn btn-primary');
await expect(button).toHaveClass(/btn/);
// Count
await expect(page.locator('.item')).toHaveCount(5);
// CSS
await expect(button).toHaveCSS('color', 'rgb(255, 0, 0)');
});
```
### Checkbox Assertions
```
test('checkbox assertions', async ({ page }) => {
await page.goto('/form');
// Checked state
await expect(page.locator('#terms')).toBeChecked();
await expect(page.locator('#newsletter')).not.toBeChecked();
// Editable
await expect(page.locator('input')).toBeEditable();
// Empty
await expect(page.locator('input#search')).toBeEmpty();
});
```
### Soft Assertions
```
test('soft assertions', async ({ page }) => {
await page.goto('/');
// Continue test even if assertion fails
await expect.soft(page.locator('.header')).toBeVisible();
await expect.soft(page).toHaveTitle('Expected Title');
// Test continues and reports all failures at the end
await page.click('button');
});
```
TypeScript Types & Interfaces.md
Copy to clipboard
### Page Object with Types
```
import { Page, Locator } from '@playwright/test';
class LoginPage {
readonly page: Page;
readonly usernameInput: Locator;
readonly passwordInput: Locator;
readonly submitButton: Locator;
readonly errorMessage: Locator;
constructor(page: Page) {
this.page = page;
this.usernameInput = page.locator('#username');
this.passwordInput = page.locator('#password');
this.submitButton = page.locator('button[type="submit"]');
this.errorMessage = page.locator('.error-message');
}
async goto(): Promise<void> {
await this.page.goto('/login');
}
async login(username: string, password: string): Promise<void> {
await this.usernameInput.fill(username);
await this.passwordInput.fill(password);
await this.submitButton.click();
}
async getErrorMessage(): Promise<string> {
return await this.errorMessage.textContent() || '';
}
}
export { LoginPage };
```
### Custom Test Fixtures
```
import { test as base, expect } from '@playwright/test';
import { LoginPage } from './pages/LoginPage';
type MyFixtures = {
loginPage: LoginPage;
authenticatedPage: Page;
};
const test = base.extend<MyFixtures>({
loginPage: async ({ page }, use) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await use(loginPage);
},
authenticatedPage: async ({ page }, use) => {
await page.goto('/login');
await page.fill('#username', 'testuser');
await page.fill('#password', 'password');
await page.click('button[type="submit"]');
await use(page);
}
});
export { test, expect };
```
### Test Data Types
```
interface User {
username: string;
email: string;
password: string;
role: 'admin' | 'user' | 'guest';
}
interface Product {
id: number;
name: string;
price: number;
inStock: boolean;
}
const testUsers: User[] = [
{ username: 'admin', email: 'admin@test.com', password: 'admin123', role: 'admin' },
{ username: 'user1', email: 'user@test.com', password: 'pass123', role: 'user' }
];
test('login with typed data', async ({ page }) => {
const user: User = testUsers[0];
await page.goto('/login');
await page.fill('#username', user.username);
await page.fill('#password', user.password);
await page.click('button[type="submit"]');
});
```
### API Response Types
```
interface ApiUser {
id: number;
name: string;
email: string;
created_at: string;
}
interface ApiResponse<T> {
success: boolean;
data: T;
message?: string;
}
test('typed API response', async ({ request }) => {
const response = await request.get('https://api.example.com/users/1');
const json: ApiResponse<ApiUser> = await response.json();
expect(json.success).toBe(true);
expect(json.data.id).toBe(1);
expect(json.data.email).toContain('@');
});
```
Configuration (playwright.config.ts)
Copy to clipboard
### Basic Configuration
```
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
// Timeout for each test
timeout: 30 * 1000,
// Timeout for expect() assertions
expect: {
timeout: 5000
},
// Run tests in files in parallel
fullyParallel: true,
// Fail the build on CI if you accidentally left test.only
forbidOnly: !!process.env.CI,
// Retry on CI only
retries: process.env.CI ? 2 : 0,
// Opt out of parallel tests on CI
workers: process.env.CI ? 1 : undefined,
// Reporter to use
reporter: 'html',
use: {
// Base URL
baseURL: 'http://localhost:3000',
// Collect trace when retrying failed test
trace: 'on-first-retry',
// Screenshot on failure
screenshot: 'only-on-failure',
// Video on failure
video: 'retain-on-failure',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
],
});
```
### Multiple Environments
```
import { defineConfig } from '@playwright/test';
export default defineConfig({
projects: [
{
name: 'staging',
use: {
baseURL: 'https://staging.example.com',
},
},
{
name: 'production',
use: {
baseURL: 'https://example.com',
},
},
],
});
```
```
# Run against specific environment
npx playwright test --project=staging
```
### Mobile Emulation
```
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
projects: [
{
name: 'Mobile Chrome',
use: { ...devices['Pixel 5'] },
},
{
name: 'Mobile Safari',
use: { ...devices['iPhone 13'] },
},
{
name: 'Tablet',
use: { ...devices['iPad Pro'] },
},
],
});
```
### Authentication Setup
```
import { defineConfig } from '@playwright/test';
export default defineConfig({
use: {
// Storage state for authenticated tests
storageState: 'auth.json',
},
// Setup project for authentication
projects: [
{
name: 'setup',
testMatch: /.*\.setup\.ts/,
},
{
name: 'chromium',
use: {
...devices['Desktop Chrome'],
storageState: 'playwright/.auth/user.json',
},
dependencies: ['setup'],
},
],
});
```
Page Object Model.md
Copy to clipboard
### Login Page Object
```
import { Page, Locator, expect } from '@playwright/test';
export class LoginPage {
readonly page: Page;
readonly usernameInput: Locator;
readonly passwordInput: Locator;
readonly loginButton: Locator;
readonly errorMessage: Locator;
constructor(page: Page) {
this.page = page;
this.usernameInput = page.getByLabel('Username');
this.passwordInput = page.getByLabel('Password');
this.loginButton = page.getByRole('button', { name: 'Login' });
this.errorMessage = page.locator('.error');
}
async goto() {
await this.page.goto('/login');
}
async login(username: string, password: string) {
await this.usernameInput.fill(username);
await this.passwordInput.fill(password);
await this.loginButton.click();
}
async verifyLoginSuccess() {
await expect(this.page).toHaveURL('/dashboard');
}
async verifyLoginError(message: string) {
await expect(this.errorMessage).toHaveText(message);
}
}
```
### Dashboard Page Object
```
import { Page, Locator } from '@playwright/test';
export class DashboardPage {
readonly page: Page;
readonly welcomeMessage: Locator;
readonly logoutButton: Locator;
readonly userMenu: Locator;
constructor(page: Page) {
this.page = page;
this.welcomeMessage = page.locator('.welcome');
this.logoutButton = page.getByRole('button', { name: 'Logout' });
this.userMenu = page.locator('#user-menu');
}
async goto() {
await this.page.goto('/dashboard');
}
async logout() {
await this.userMenu.click();
await this.logoutButton.click();
}
async getWelcomeMessage(): Promise<string> {
return await this.welcomeMessage.textContent() || '';
}
}
```
### Using Page Objects in Tests
```
import { test, expect } from '@playwright/test';
import { LoginPage } from './pages/LoginPage';
import { DashboardPage } from './pages/DashboardPage';
test.describe('Login Flow', () => {
test('successful login', async ({ page }) => {
const loginPage = new LoginPage(page);
const dashboardPage = new DashboardPage(page);
await loginPage.goto();
await loginPage.login('testuser', 'password123');
await loginPage.verifyLoginSuccess();
const welcomeText = await dashboardPage.getWelcomeMessage();
expect(welcomeText).toContain('Welcome back');
});
test('failed login', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login('wronguser', 'wrongpass');
await loginPage.verifyLoginError('Invalid credentials');
});
});
```
Configuration (playwright.config.ts)
Copy to clipboard
### Basic Configuration
```
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
// Timeout for each test
timeout: 30 * 1000,
// Timeout for expect() assertions
expect: {
timeout: 5000
},
// Run tests in files in parallel
fullyParallel: true,
// Fail the build on CI if you accidentally left test.only
forbidOnly: !!process.env.CI,
// Retry on CI only
retries: process.env.CI ? 2 : 0,
// Opt out of parallel tests on CI
workers: process.env.CI ? 1 : undefined,
// Reporter to use
reporter: 'html',
use: {
// Base URL
baseURL: 'http://localhost:3000',
// Collect trace when retrying failed test
trace: 'on-first-retry',
// Screenshot on failure
screenshot: 'only-on-failure',
// Video on failure
video: 'retain-on-failure',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
],
});
```
### Multiple Environments
```
import { defineConfig } from '@playwright/test';
export default defineConfig({
projects: [
{
name: 'staging',
use: {
baseURL: 'https://staging.example.com',
},
},
{
name: 'production',
use: {
baseURL: 'https://example.com',
},
},
],
});
```
```
# Run against specific environment
npx playwright test --project=staging
```
### Mobile Emulation
```
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
projects: [
{
name: 'Mobile Chrome',
use: { ...devices['Pixel 5'] },
},
{
name: 'Mobile Safari',
use: { ...devices['iPhone 13'] },
},
{
name: 'Tablet',
use: { ...devices['iPad Pro'] },
},
],
});
```
### Authentication Setup
```
import { defineConfig } from '@playwright/test';
export default defineConfig({
use: {
// Storage state for authenticated tests
storageState: 'auth.json',
},
// Setup project for authentication
projects: [
{
name: 'setup',
testMatch: /.*\.setup\.ts/,
},
{
name: 'chromium',
use: {
...devices['Desktop Chrome'],
storageState: 'playwright/.auth/user.json',
},
dependencies: ['setup'],
},
],
});
```
Page Object Model.md
Copy to clipboard
### Login Page Object
```
import { Page, Locator, expect } from '@playwright/test';
export class LoginPage {
readonly page: Page;
readonly usernameInput: Locator;
readonly passwordInput: Locator;
readonly loginButton: Locator;
readonly errorMessage: Locator;
constructor(page: Page) {
this.page = page;
this.usernameInput = page.getByLabel('Username');
this.passwordInput = page.getByLabel('Password');
this.loginButton = page.getByRole('button', { name: 'Login' });
this.errorMessage = page.locator('.error');
}
async goto() {
await this.page.goto('/login');
}
async login(username: string, password: string) {
await this.usernameInput.fill(username);
await this.passwordInput.fill(password);
await this.loginButton.click();
}
async verifyLoginSuccess() {
await expect(this.page).toHaveURL('/dashboard');
}
async verifyLoginError(message: string) {
await expect(this.errorMessage).toHaveText(message);
}
}
```
### Dashboard Page Object
```
import { Page, Locator } from '@playwright/test';
export class DashboardPage {
readonly page: Page;
readonly welcomeMessage: Locator;
readonly logoutButton: Locator;
readonly userMenu: Locator;
constructor(page: Page) {
this.page = page;
this.welcomeMessage = page.locator('.welcome');
this.logoutButton = page.getByRole('button', { name: 'Logout' });
this.userMenu = page.locator('#user-menu');
}
async goto() {
await this.page.goto('/dashboard');
}
async logout() {
await this.userMenu.click();
await this.logoutButton.click();
}
async getWelcomeMessage(): Promise<string> {
return await this.welcomeMessage.textContent() || '';
}
}
```
### Using Page Objects in Tests
```
import { test, expect } from '@playwright/test';
import { LoginPage } from './pages/LoginPage';
import { DashboardPage } from './pages/DashboardPage';
test.describe('Login Flow', () => {
test('successful login', async ({ page }) => {
const loginPage = new LoginPage(page);
const dashboardPage = new DashboardPage(page);
await loginPage.goto();
await loginPage.login('testuser', 'password123');
await loginPage.verifyLoginSuccess();
const welcomeText = await dashboardPage.getWelcomeMessage();
expect(welcomeText).toContain('Welcome back');
});
test('failed login', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login('wronguser', 'wrongpass');
await loginPage.verifyLoginError('Invalid credentials');
});
});
```
Best Practices.md
Copy to clipboard
### Use Data-TestId for Stable Selectors
```
// In your HTML
<button data-testid="submit-button">Submit</button>
// In your test
await page.getByTestId('submit-button').click();
```
### Avoid Hard-coded Waits
```
// Bad
await page.waitForTimeout(3000);
await page.click('button');
// Good
await page.waitForSelector('button:visible');
await page.click('button');
```
### Use Auto-waiting Assertions
```
// Bad
await page.waitForSelector('.success');
const text = await page.locator('.success').textContent();
expect(text).toBe('Success!');
// Good
await expect(page.locator('.success')).toHaveText('Success!');
```
### Isolate Tests
```
// Each test is independent
test('test 1', async ({ page }) => {
await page.goto('/');
// Complete test logic
});
test('test 2', async ({ page }) => {
await page.goto('/');
// Don't rely on test 1
});
```
### Use Page Objects for Reusability
```
// Reusable page objects
class ProductPage {
constructor(readonly page: Page) {}
async addToCart(productName: string) {
await this.page.click(`[data-product="${productName}"] .add-to-cart`);
}
}
```
### Meaningful Test Names
```
// Bad
test('test1', async ({ page }) => { /*...*/ });
// Good
test('user can add product to cart and checkout', async ({ page }) => { /*...*/ });
```
Debugging.md
Copy to clipboard
### Debug Mode
```
# Run in debug mode
npx playwright test --debug
# Debug specific test
npx playwright test tests/login.spec.ts --debug
# Debug from specific line
npx playwright test --debug-line=42
```
### Debugging in Code
```
import { test } from '@playwright/test';
test('debug example', async ({ page }) => {
await page.goto('/');
// Pause execution - opens Playwright Inspector
await page.pause();
await page.click('button');
});
```
### Console Logs
```
test('console logs', async ({ page }) => {
// Listen to console messages
page.on('console', msg => {
console.log('Browser console:', msg.text());
});
await page.goto('/');
});
```
### Trace Viewer
```
// playwright.config.ts
export default defineConfig({
use: {
trace: 'on-first-retry', // or 'on', 'off', 'retain-on-failure'
},
});
```
```
# View trace
npx playwright show-trace trace.zip
```
Quick Commands Reference.md
Copy to clipboard
# Installation
npm init playwright@latest # Install Playwright with test setup
npm install -D @playwright/test # Install Playwright Test
npx playwright install # Install required browsers
npx playwright install chromium # Install specific browser
npx playwright install --with-deps # Install browsers with system dependencies
# Run Tests
npx playwright test # Run all tests
npx playwright test --headed # Run with browser UI visible
npx playwright test --ui # Run in UI mode (interactive)
npx playwright test --project=chromium # Run on specific browser
npx playwright test --project=firefox --project=webkit # Multiple browsers
npx playwright test tests/login.spec.ts # Run specific file
npx playwright test tests/ # Run all tests in folder
npx playwright test --grep @smoke # Run tests with tag
npx playwright test --grep-invert @slow # Exclude tests with tag
npx playwright test --grep "Login" # Run tests matching pattern
npx playwright test --workers=4 # Run with 4 parallel workers
npx playwright test --workers=1 # Run tests serially
npx playwright test --retries=2 # Retry failed tests 2 times
npx playwright test --max-failures=5 # Stop after 5 failures
npx playwright test --shard=1/3 # Run first third of tests
npx playwright test --repeat-each=3 # Repeat each test 3 times
# Run Specific Tests
npx playwright test login.spec.ts:15 # Run test at line 15
npx playwright test -g "login test" # Run test with specific title
npx playwright test --last-failed # Re-run only failed tests
# Debug
npx playwright test --debug # Debug mode with Playwright Inspector
npx playwright test --debug-line=42 # Debug from specific line
npx playwright test --headed --slowmo=1000 # Slow down execution by 1000ms
npx playwright test --timeout=60000 # Set test timeout to 60s
npx playwright test --trace on # Enable tracing
npx playwright show-trace trace.zip # View trace file
npx playwright show-report # Open HTML report
npx playwright test --reporter=list # Use list reporter
npx playwright test --reporter=dot # Use dot reporter
npx playwright test --reporter=html # Generate HTML report
npx playwright test --reporter=json # Generate JSON report
npx playwright test --reporter=junit # Generate JUnit XML report
# Code Generation & Recording
npx playwright codegen # Generate code for new test
npx playwright codegen example.com # Generate code for specific URL
npx playwright codegen --target=typescript # Generate TypeScript code
npx playwright codegen --save-storage=auth.json # Save authentication state
npx playwright codegen --load-storage=auth.json # Load authentication state
npx playwright codegen --device="iPhone 13" # Emulate device
npx playwright codegen --color-scheme=dark # Use dark mode
npx playwright codegen --viewport-size=1280,720 # Set viewport size
# Browser Management
npx playwright install # Install all browsers
npx playwright install chromium # Install specific browser
npx playwright install firefox # Install Firefox
npx playwright install webkit # Install WebKit (Safari)
npx playwright install msedge # Install MS Edge
npx playwright uninstall # Uninstall all browsers
npx playwright install-deps # Install system dependencies only
# Screenshots & Visual Testing
npx playwright test --update-snapshots # Update all screenshots
npx playwright test --update-snapshots -g "login" # Update specific snapshots
npx playwright test --ignore-snapshots # Ignore screenshot assertions
# Configuration & Info
npx playwright --version # Show Playwright version
npx playwright test --list # List all tests without running
npx playwright test --config=custom.config.ts # Use custom config file
npx playwright show-report # Open last HTML report
npx playwright open # Open interactive mode
# Environment Variables
PWDEBUG=1 npx playwright test # Open Playwright Inspector
HEADED=1 npx playwright test # Run headed mode
DEBUG=pw:api npx playwright test # Debug Playwright API calls
PLAYWRIGHT_HTML_REPORT=./report npx playwright test # Set report directory
# CI/CD Optimizations
npx playwright test --workers=2 --retries=2 # CI-friendly settings
npx playwright test --reporter=dot,html # Multiple reporters
npx playwright test --forbid-only # Fail if test.only() is found
npx playwright test --pass-with-no-tests # Don't fail if no tests found
# Testing Specific Scenarios
npx playwright test --globalTimeout=3600000 # Set global timeout (1 hour)
npx playwright test --quiet # Suppress output
npx playwright test --reporter=line # Minimal output
npx playwright test -x # Stop after first failure
# Advanced
npx playwright test --fullyParallel # Run tests in parallel within files
npx playwright test --no-deps # Do not run project dependencies
npx playwright cr # Open Chromium browser
npx playwright ff # Open Firefox browser
npx playwright wk # Open WebKit browser
```