Playwright with TypeScript

Cheatsheets and references

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
```

☕ Support TestiQA

These cheatsheets are free and maintained by the testiqa.
If you find them helpful, consider buying us a coffee!

Buy Me A Coffee

Your support helps keep this resource free for everyone!

Scroll to Top