Best Practices for Using Playwright
Playwright is a powerful Node.js library for automating Chromium, Firefox, and WebKit browsers with a single API. Following these best practices will help you write reliable, maintainable, and efficient browser automation scripts.
1. Use Async/Await Patterns
Playwright operations are asynchronous, so always use async/await syntax for cleaner, more readable code.
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch();
const context = await browser.newContext();
const page = await context.newPage();
try {
await page.goto('https://example.com');
// Your automation logic here
} finally {
await browser.close();
}
})();
2. Choose Robust Selectors
Use Playwright's powerful selector engine with resilient selection strategies:
// Prefer text content selectors
await page.click('text="Sign in"');
await page.click('button:has-text("Submit")');
// Use role-based selectors for accessibility
await page.click('role=button[name="Login"]');
// Combine selectors for specificity
await page.fill('form >> input[name="username"]', 'john@example.com');
// Use data-testid for test-specific elements
await page.click('[data-testid="submit-button"]');
3. Leverage Auto-Waiting
Playwright automatically waits for elements to be actionable, eliminating manual waits:
// Playwright waits for element to be visible and enabled
await page.click('#submit-button');
// Waits for navigation to complete
await page.click('a[href="/dashboard"]');
await page.waitForURL('**/dashboard');
// Wait for specific conditions
await page.waitForSelector('.loading-spinner', { state: 'hidden' });
await page.waitForFunction(() => window.dataLoaded === true);
4. Proper Setup and Cleanup
Always clean up resources to prevent memory leaks:
const { test, expect } = require('@playwright/test');
test.beforeEach(async ({ page }) => {
// Setup before each test
await page.goto('https://example.com');
});
test.afterEach(async ({ page }) => {
// Cleanup after each test
await page.close();
});
// For custom scripts
async function runAutomation() {
let browser, context, page;
try {
browser = await chromium.launch();
context = await browser.newContext();
page = await context.newPage();
// Your automation logic
} catch (error) {
console.error('Automation failed:', error);
} finally {
if (page) await page.close();
if (context) await context.close();
if (browser) await browser.close();
}
}
5. Implement Comprehensive Error Handling
Handle errors gracefully with specific error types:
const { TimeoutError } = require('playwright');
try {
await page.goto('https://example.com', { timeout: 30000 });
await page.click('#dynamic-button');
} catch (error) {
if (error instanceof TimeoutError) {
console.error('Operation timed out:', error.message);
} else {
console.error('Unexpected error:', error);
}
// Take screenshot for debugging
await page.screenshot({ path: 'error-screenshot.png' });
}
6. Use Playwright Test Runner
Leverage the built-in test runner for advanced features:
// playwright.config.js
module.exports = {
testDir: './tests',
timeout: 30000,
use: {
browserName: 'chromium',
headless: true,
viewport: { width: 1280, height: 720 },
screenshot: 'only-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'] } },
],
};
# Run tests with parallel execution
npx playwright test
# Run specific test file
npx playwright test login.spec.js
# Run in headed mode for debugging
npx playwright test --headed
7. Optimize with Browser Contexts
Use browser contexts for isolation and efficiency:
const browser = await chromium.launch();
// Create isolated contexts for different users/sessions
const adminContext = await browser.newContext({
storageState: 'admin-session.json'
});
const userContext = await browser.newContext({
viewport: { width: 375, height: 667 }, // Mobile viewport
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_7_1 like Mac OS X)'
});
const adminPage = await adminContext.newPage();
const userPage = await userContext.newPage();
// Both pages run independently
await Promise.all([
adminPage.goto('https://admin.example.com'),
userPage.goto('https://example.com')
]);
8. Configuration and Environment Setup
Configure Playwright for different environments:
// Set up environment-specific configuration
const config = {
development: {
baseURL: 'http://localhost:3000',
headless: false,
slowMo: 100
},
production: {
baseURL: 'https://production.example.com',
headless: true,
timeout: 60000
}
};
const environment = process.env.NODE_ENV || 'development';
const { baseURL, headless, slowMo, timeout } = config[environment];
const browser = await chromium.launch({ headless, slowMo });
const context = await browser.newContext({ baseURL });
9. Performance Optimization
Optimize your scripts for better performance:
// Disable images and CSS for faster loading
const context = await browser.newContext({
viewport: { width: 1280, height: 720 },
// Block unnecessary resources
extraHTTPHeaders: {
'Accept-Language': 'en-US,en;q=0.9'
}
});
await context.route('**/*.{png,jpg,jpeg,gif,svg,css}', route => route.abort());
// Use parallel execution for independent operations
await Promise.all([
page.fill('#username', 'user@example.com'),
page.fill('#password', 'password123'),
page.check('#remember-me')
]);
10. Debugging and Monitoring
Implement debugging strategies:
// Enable debug mode
process.env.DEBUG = 'pw:api';
// Use console logs strategically
await page.evaluate(() => console.log('Page loaded:', document.title));
// Take screenshots at key points
await page.screenshot({ path: 'step-1.png' });
// Use page.pause() for interactive debugging
if (process.env.NODE_ENV === 'development') {
await page.pause();
}
// Monitor network requests
page.on('request', request => {
console.log('Request:', request.url());
});
page.on('response', response => {
console.log('Response:', response.status(), response.url());
});
Key Takeaways
- Always use async/await for cleaner code
- Choose robust selectors that won't break with UI changes
- Trust auto-waiting instead of manual sleeps
- Handle errors gracefully with proper error types
- Use browser contexts for isolation and efficiency
- Configure environments appropriately
- Optimize performance by blocking unnecessary resources
- Implement proper cleanup to prevent resource leaks
Following these best practices will help you build reliable, maintainable browser automation scripts that can handle real-world scenarios effectively.