Playwright Automation Guide with TypeScript
Playwright Automation Guide with TypeScript
PLAYWRIGHT HANDBOOK
1
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
Table of Contents
2
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
PART I: FOUNDATIONS
3
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
With modern tools like Playwright, the Testing Trophy represents a better approach:
4
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
await [Link]('/login');
await [Link]('Account Number').fill('123456789');
await [Link]('PIN').fill('1234');
await [Link]('button', { name: 'Login' }).click();
await expect(page).toHaveURL('/dashboard');
await expect([Link]('Account Balance')).toBeVisible();
});
Cross-Browser Support
Playwright natively supports ALL major browser engines:
Auto-Wait Mechanism
Playwright automatically waits for elements to be ready before acting:
• Attached to DOM
• Visible on screen
• Stable (not animating)
• Receives events (not covered)
5
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
vs Selenium
• Speed: Playwright 20-50% faster
• Setup: Playwright auto-downloads browsers
• API: Playwright modern async/await
• Reliability: Playwright built-in auto-wait
vs Cypress
• Browsers: Playwright supports Safari/WebKit
• Multi-tab: Playwright full support
• Mobile: Playwright native mobile browsers
• Languages: Playwright supports Python, .NET, Java
vs Puppeteer
• Browsers: Playwright adds Firefox + WebKit
• Testing: Playwright built for testing
• Selectors: Playwright user-facing locators
6
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
2.1 Prerequisites
• [Link] 16+ (LTS version recommended)
• Visual Studio Code (recommended)
• Basic TypeScript knowledge
• Command line familiarity
• TypeScript? → Yes
• Test directory? → tests
• GitHub Actions? → Yes
• Install browsers? → Yes
7
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
2.3 Configuration
8
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
Role-Based (Recommended)
await [Link]('button', { name: 'Submit' })
await [Link]('link', { name: 'Contact' })
await [Link]('textbox', { name: 'Email' })
Text-Based
await [Link]('Sign up')
await [Link](/sign up/i) // regex
Label-Based
await [Link]('Email address')
await [Link]('Password')
Test ID
await [Link]('submit-btn')
9
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
// Type text
await [Link]('Username').fill('john');
// Check/uncheck
await [Link]('checkbox').check();
// Select dropdown
await [Link]('Country').selectOption('USA');
3.4 Assertions
// Page
await expect(page).toHaveTitle('Dashboard');
await expect(page).toHaveURL(/dashboard/);
// Element
await expect([Link]('Success')).toBeVisible();
await expect([Link]('heading')).toHaveText('Welcome');
await
expect([Link]('Email')).toHaveValue('test@[Link]');
10
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
await expect(page).toHaveURL(/dashboard/);
await expect([Link]('Welcome')).toBeVisible();
});
});
11
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
Browser
const browser = await [Link]({ headless: false });
12
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
13
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
14
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
3. Text-based
[Link]('Welcome')
4. Test ID (Stable)
[Link]('submit-btn')
5.3 Filtering
await page
.getByRole('listitem')
.filter({ hasText: 'Product 1' })
.click();
15
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
16
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
6.1 Clicking
// Simple click
await [Link]('button').click();
// Double click
await [Link]('File').dblclick();
// Right click
await [Link]('Item').click({ button: 'right' });
6.2 Typing
// Fill (clears first)
await [Link]('Username').fill('[Link]');
// Press keys
await [Link]('Enter');
await [Link]('Control+A');
// Radio button
await [Link]('radio', { name: 'Option 1' }).check();
// Dropdown
17
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
await [Link]('Country').selectOption('USA');
await [Link]('Country').selectOption({ label: 'United
States' });
18
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
// Contains
await
expect([Link]('paragraph')).toContainText('Playwright');
19
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
// Array match
await expect([Link]('listitem')).toHaveText(['Item 1', 'Item
2']);
7.7 Attributes
await expect([Link]('button')).toHaveAttribute('type',
'submit');
await expect([Link]('button')).toHaveClass('btn-primary');
await expect([Link]('button')).toHaveClass(/btn-/);
7.8 Count
await expect([Link]('listitem')).toHaveCount(5);
await expect([Link]('listitem')).toHaveCount(0);
7.9 Screenshots
await expect(page).toHaveScreenshot('[Link]');
await
expect([Link]('button')).toHaveScreenshot('[Link]');
20
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
constructor(page: Page) {
[Link] = page;
[Link] = [Link]('Email');
[Link] = [Link]('Password');
[Link] = [Link]('button', { name: 'Log in' });
}
async goto() {
await [Link]('/login');
}
21
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
await [Link]();
await [Link]('user@[Link]', 'pass123');
await expect(page).toHaveURL('/dashboard');
});
constructor(page: Page) {
[Link] = page;
[Link] = [Link]('logo');
[Link] = [Link]('search');
[Link] = [Link]('user-menu');
[Link] = [Link]('logout');
}
async clickLogo() {
await [Link]();
}
async logout() {
22
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
await [Link]();
await [Link]();
}
}
async navigateToTransactions() {
await [Link]('transactions');
}
}
constructor(page: Page) {
[Link] = page;
[Link] = new HeaderComponent(page);
[Link] = new FooterComponent(page);
}
}
23
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
constructor(page: Page) {
super(page);
[Link] = [Link]('button', { name: 'Add to
Cart' });
}
async addToCart() {
await [Link]();
}
}
24
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
SUMMARY
PART I - Foundations:
• Testing Trophy Strategy for modern applications
• Why Playwright for enterprise applications
• Environment setup and configuration
• Writing your first tests
• Playwright architecture and auto-waiting
Next Steps:
• Practice writing tests with role-based locators
• Implement Component Objects in your project
• Set up CI/CD with GitHub Actions
• Move to Part III for advanced patterns
25
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
26
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
Table of Contents
27
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
28
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
29
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
},
dependencies: ['setup']
}
]
});
🏢 ENTERPRISE: This pattern saves 5-10 seconds per test. For 1000
tests, that's 1.5-3 hours saved!
// Single file
await [Link]('Upload
document').setInputFiles('[Link]');
30
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
// Multiple files
await [Link]('Upload photos').setInputFiles([
'[Link]',
'[Link]',
'[Link]'
]);
// Remove files
await [Link]('Upload').setInputFiles([]);
await [Link]('/stores');
31
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
body: [Link]([
{ id: 1, name: 'Alice', role: 'Admin' },
{ id: 2, name: 'Bob', role: 'User' }
])
});
});
await [Link]('/users');
// Cleanup
await [Link]();
32
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
await [Link]();
});
33
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
34
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
expect([Link]()).toBe(200);
POST Request
test('POST create user', async ({ request }) => {
const response = await
[Link]('[Link] {
data: {
name: 'John Doe',
email: 'john@[Link]',
role: 'user'
}
});
expect([Link]()).toBe(201);
Authentication Headers
35
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
expect([Link]()).toBe(200);
});
36
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
});
🏢 ENTERPRISE: The 80/20 Rule: Use APIs for 80% of test setup
(creating users, adding data, setting state). Use UI for the critical 20% you
actually want to test.
37
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
name: 'auth_token',
value: token,
domain: 'localhost',
path: '/'
}]);
// Verify
await expect([Link]('Purchase Successful')).toBeVisible();
38
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
39
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
Element Screenshot
test('header visual test', async ({ page }) => {
await [Link]('/');
const header = [Link]('header');
await expect(header).toHaveScreenshot('[Link]');
});
Advanced Configuration
test('dashboard with masking', async ({ page }) => {
await [Link]('/dashboard');
await expect(page).toHaveScreenshot('[Link]', {
maxDiffPixels: 100, // Allow 100 pixels difference
threshold: 0.2, // 20% difference threshold
mask: [ // Hide dynamic content
[Link]('.timestamp'),
[Link]('.balance')
40
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
],
animations: 'disabled' // Disable animations
});
});
Updating Baselines
# Update all screenshots
npx playwright test --update-snapshots
Installation
npm install -D @axe-core/playwright
41
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
expect([Link]).toEqual([]);
});
if ([Link] > 0) {
[Link]('Accessibility Violations:');
[Link](violation => {
[Link](`- ${[Link]}`);
[Link](` Impact: ${[Link]}`);
[Link](` Elements: ${[Link]}`);
});
}
expect([Link]).toEqual([]);
});
42
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
expect([Link]).toEqual([]);
});
43
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
return {
FCP: [Link](e => [Link] === 'first-contentful-
paint')?.startTime,
LCP: [Link]('largest-contentful-
paint')[0]?.startTime,
domContentLoaded: navigation?.domContentLoadedEventEnd -
navigation?.domContentLoadedEventStart,
loadComplete: navigation?.loadEventEnd -
navigation?.loadEventStart
};
});
// Assert thresholds
expect([Link]).toBeLessThan(1800); // < 1.8s
expect([Link]).toBeLessThan(2500); // < 2.5s
});
44
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
});
expect([Link]).toBe(0);
});
45
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
return {
pageLoadTime: nav?.loadEventEnd - nav?.fetchStart,
domContentLoaded: nav?.domContentLoadedEventEnd -
nav?.fetchStart,
totalPageSize: [Link]((sum, r) => sum +
[Link], 0),
numberOfRequests: [Link]
};
});
// Performance budget
const budget = {
pageLoadTime: 3000, // 3 seconds
domContentLoaded: 2000, // 2 seconds
totalPageSize: 2 * 1024 * 1024, // 2 MB
numberOfRequests: 50 // 50 requests
};
expect([Link]).toBeLessThan([Link]);
expect([Link]).toBeLessThan([Link]
);
expect([Link]).toBeLessThan([Link]);
expect([Link]).toBeLessThan([Link]
);
});
46
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
47
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
48
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
playwright-project/
├── tests/
│ ├── e2e/ # End-to-end tests
│ │ ├── auth/
│ │ ├── transactions/
│ │ └── admin/
│ ├── api/ # API tests
│ ├── visual/ # Visual regression
│ └── performance/ # Performance tests
├── pages/ # Page Objects
│ ├── [Link]
│ └── [Link]
├── components/ # Component Objects
│ ├── [Link]
│ └── [Link]
├── fixtures/ # Custom fixtures
│ └── [Link]
├── helpers/ # Utility functions
│ ├── [Link]
│ └── [Link]
├── data/ # Test data
│ └── [Link]
├── config/ # Environment configs
│ ├── [Link]
│ └── [Link]
└── [Link]
// fixtures/[Link]
import { test as base } from '@playwright/test';
import { LoginPage } from '../pages/LoginPage';
49
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
type CustomFixtures = {
loginPage: LoginPage;
authenticatedPage: Page;
};
await use(page);
}
});
50
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
const users = [
{ role: 'admin', email: 'admin@[Link]', expectedUrl: '/admin'
},
{ role: 'user', email: 'user@[Link]', expectedUrl:
'/dashboard' },
{ role: 'guest', email: 'guest@[Link]', expectedUrl:
'/welcome' }
];
51
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
// config/[Link]
export const environments = {
local: {
baseUrl: '[Link]
apiUrl: '[Link]
},
staging: {
baseUrl: '[Link]
apiUrl: '[Link]
},
production: {
baseUrl: '[Link]
apiUrl: '[Link]
}
};
52
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
Basic Workflow
# .github/workflows/[Link]
name: Playwright Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- uses: actions/upload-artifact@v3
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 30
53
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
The Math
• 1000 tests × 5 seconds each = 5000 seconds (83 minutes)
• With 10 shards: 5000 ÷ 10 = 500 seconds (8.3 minutes)
• With 20 shards: 5000 ÷ 20 = 250 seconds (4.2 minutes)
jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
shardIndex: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
shardTotal: [10]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- uses: actions/upload-artifact@v3
if: always()
with:
name: playwright-report-${{ [Link] }}
54
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
path: playwright-report/
Configuration
// [Link]
export default defineConfig({
workers: [Link] ? 2 : undefined, // 2 in CI, max locally
fullyParallel: true, // Parallel within files
retries: [Link] ? 2 : 0, // Retry failures in CI
});
Docker Integration
# Dockerfile
FROM [Link]/playwright:v1.40.0-focal
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
CMD ["npx", "playwright", "test"]
# [Link]
version: '3'
services:
playwright:
build: .
environment:
- CI=true
volumes:
- ./playwright-report:/app/playwright-report
✅ BEST PRACTICE: Use 10 shards for most projects. Use 20+ shards for
massive test suites (2000+ tests). GitHub Actions allows up to 256 parallel
jobs.
55
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
# Pause on test
await [Link](); // Add this line in your test
Configuration
// [Link]
use: {
trace: 'on-first-retry', // Capture on failure
screenshot: 'only-on-failure',
video: 'retain-on-failure'
}
View Trace
56
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
Network Debugging
[Link]('request', request => {
[Link]('→', [Link](), [Link]());
});
Console Debugging
[Link]('console', msg => {
[Link]('Browser console:', [Link]());
});
Element Debugging
// Check if element exists
const count = await [Link]();
[Link](`Found ${count} elements`);
57
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
// Highlight element
await [Link]();
Timeout Issues
// Increase timeout for specific test
[Link](120000); // 2 minutes
Flaky Tests
// Element screenshot
await [Link]('.error').screenshot({ path: '[Link]' });
58
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
59
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
await [Link]('/transactions');
60
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
});
await [Link]('/dashboard');
// Clean up database
const response = await fetch('[Link] {
method: 'DELETE',
headers: { 'X-Test-Cleanup': 'true' }
});
if ([Link]) {
[Link]('✓ Test data cleaned up successfully');
}
}
Configuration
// [Link]
export default defineConfig({
globalTeardown: [Link]('./global-teardown'),
});
61
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
🏢 ENTERPRISE: After 1000 tests, ensure all test data in the database is
cleaned up via API. This prevents data pollution and ensures tests remain
independent.
Usage
const loginButton = await smartLocator(
page,
'[Link]-btn', // Primary
[ // Fallbacks
62
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
'button#login',
'[data-testid="login-button"]',
'button:has-text("Log in")'
]
);
await [Link]();
63
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
64
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
Example Prompt
Prompt: "Generate a Playwright test for a banking login flow
with multi-factor authentication. The test should:
1. Navigate to /login
2. Enter account number and PIN
3. Handle OTP verification
4. Verify successful login to dashboard"
AI-Generated Output
test('banking login with MFA', async ({ page }) => {
await [Link]('/login');
65
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
await expect(page).toHaveURL('/dashboard');
await expect([Link]('Welcome')).toBeVisible();
});
Self-Healing Implementation
// utils/[Link]
import { Page, Locator } from '@playwright/test';
import OpenAI from 'openai';
66
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
Usage
const loginButton = await aiSmartLocator(
67
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
page,
'[Link]-btn',
['button#login', '[data-testid="login"]'],
'The main login button on the login page'
);
await [Link]();
The Challenge
Testing edge cases (special characters, unicode, SQL injection, XSS) is tedious and
often incomplete.
68
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
expect(hasError || hasSuccess).toBeTruthy();
}
});
69
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
71
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
CONCLUSION
Next Steps:
• Implement test sharding in your CI/CD pipeline
• Add accessibility tests with axe-core
72
Author-Vaibhav Sahu
PLAYWRIGHT HANDBOOK
73