What are the best practices for using Playwright?

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.

Related Questions

Get Started Now

WebScraping.AI provides rotating proxies, Chromium rendering and built-in HTML parser for web scraping
Icon