15 Days of Playwright - Day 9: Test Configuration & Parallel Execution

Learn how to configure Playwright for optimal performance and run tests in parallel to significantly reduce execution time.

15 Days of Playwright - Day 9: Test Configuration & Parallel Execution
Jhonatas Matos

Jhonatas Matos

After learning how to organize tests with Page Object Model, the next step is optimizing test execution. According to Playwright's documentation, proper configuration can improve test performance by 300% or more through parallel execution.

Today, I'll explore Playwright's configuration system and how to leverage parallel testing to dramatically reduce test suite execution time.

Understanding Playwright Configuration

The playwright.config.js file is the heart of Playwright's test setup. The official documentation recommends this structure for managing all aspects of test execution, from timeouts to reporters.

Basic Configuration

This example shows the essential settings every Playwright configuration should include:

import { defineConfig } from '@playwright/test';

export default defineConfig({
  // Global timeout for each test
  timeout: 30000,
  
  // Number of retries for failed tests
  retries: process.env.CI ? 2 : 0,
  
  // Maximum time for assertions to complete
  expect: {
    timeout: 5000
  },
  
  // Reporters for test results
  reporter: [
    ['html', { outputFolder: 'test-results/report' }],
    ['list'] // Simple console output
  ],
  
  // Global setup and teardown
  globalSetup: require.resolve('./global-setup'),
  globalTeardown: require.resolve('./global-teardown')
});

Key configuration options explained:

  • timeout: Maximum time for a single test to complete (prevents hanging tests)
  • retries: Automatic retry attempts for flaky tests (essential for CI)
  • expect.timeout: Specific timeout for assertions (shorter than test timeout)
  • reporter: How test results are displayed and stored
  • globalSetup/teardown: Code that runs once before/after all tests

This basic configuration provides a solid foundation that can be extended with more advanced options as your test suite grows.

Parallel Test Execution

Playwright's parallel execution is one of its most powerful features. By default, tests run in parallel, but you can control the intensity and behavior through configuration.

Configuring Parallel Workers

Parallel workers are the key to faster test execution. Each worker runs tests in its own process, allowing multiple tests to run simultaneously.

export default defineConfig({
  // Number of parallel workers
  workers: process.env.CI ? 4 : 2,
  
  // Or use percentage of CPU cores
  // workers: '50%',
  
  // Force tests to run in sequence
  // workers: 1,
  
  use: {
    // Base URL for all tests
    baseURL: process.env.BASE_URL || 'http://localhost:3000',
    
    // Trace generation for debugging
    trace: process.env.CI ? 'on-first-retry' : 'on',
    
    // Video recording
    video: process.env.CI ? 'on' : 'off',
    
    // Screenshot on failure
    screenshot: 'only-on-failure'
  }
});

Parallelism strategies:

  • workers: process.env.CI ? 4 : 2: Use more workers in CI (where you have more resources), fewer locally
  • workers: '50%': Use half of available CPU cores (automatic scaling)
  • workers: 1: Force sequential execution (useful for debugging)

Environment-aware settings:

  • trace: In CI, only trace on first retry to save resources. Locally, always trace for debugging
  • video: Record videos in CI for failure analysis, disable locally for performance
  • screenshot: Capture screenshots only when tests fail to reduce artifact size

Best practices:

  • Start with workers: '50%' and adjust based on test performance
  • Use more workers for CPU-heavy CI environments
  • Use fewer workers for memory-intensive tests
  • Monitor test stability - too many workers can cause resource contention

This configuration can reduce test execution time by 70-80% compared to sequential runs.

Multi-Browser Configuration

Playwright supports testing across multiple browsers simultaneously through the projects configuration. This allows you to run the same test suite against different environments in parallel.

Multiple Browser Projects

The projects array lets you define different testing environments that run concurrently. Each project can have its own browser, device, and settings.

export default defineConfig({
  projects: [
    {
      name: 'chromium',
      use: { 
        browserName: 'chromium',
        viewport: { width: 1280, height: 720 }
      },
    },
    {
      name: 'firefox',
      use: { 
        browserName: 'firefox',
        viewport: { width: 1280, height: 720 }
      },
    },
    {
      name: 'webkit',
      use: { 
        browserName: 'webkit',
        viewport: { width: 1280, height: 720 }
      },
    },
    // Mobile emulation
    {
      name: 'Mobile Chrome',
      use: { 
        browserName: 'chromium',
        ...devices['iPhone 12']
      },
    }
  ]
});

Key concepts:

  • projects: Array of test environments that run in parallel
    • name: Unique identifier for each project (shown in reports)
    • use: Configuration specific to each project
    • browserName: Which browser to use (chromium, firefox, webkit)
    • viewport: Consistent viewport size across browsers
    • devices: Pre-defined mobile device configurations

Execution behavior:

  • Each project runs in parallel with others
  • Tests execute against all specified browsers simultaneously
  • Results are aggregated in the final report
  • You can run specific projects using --project=project-name

Mobile testing made easy:

  • Use ...devices['device-name'] to emulate mobile devices
  • Includes pre-configured viewport, user agent, and device metrics
  • No need to manually configure mobile settings

Benefits of multi-project setup:

  • Cross-browser testing without additional test code
  • Parallel execution across different environments
  • Consistent configuration for each browser/device
  • Flexible execution - run all projects or specific ones

This approach ensures your application works correctly across all target browsers and devices with minimal configuration effort.

Environment-Specific Configuration

Playwright's configuration system allows you to create environment-aware settings that adapt to different execution contexts. This is essential for maintaining optimal performance across local development, staging, and production environments.

Different Settings per Environment

Using environment variables and conditional logic, you can tailor your configuration to each environment's specific needs.

export default defineConfig({
  // Common settings
  timeout: 30000,
  
  // Environment-specific overrides
  use: {
    baseURL: process.env.BASE_URL || 'http://localhost:3000',
    trace: process.env.CI ? 'on-first-retry' : 'retain-on-failure',
    
    // Different viewports per environment
    viewport: process.env.CI 
      ? { width: 1920, height: 1080 } 
      : { width: 1280, height: 720 }
  },
  
  // Different retry strategies
  retries: process.env.CI ? 2 : 0,
  
  // Different reporters
  reporter: process.env.CI ? 'html' : 'list'
});

Environment detection patterns:

  • process.env.CI: Detects CI/CD environments (GitHub Actions, Jenkins, etc.)
  • process.env.BASE_URL: Custom environment variables for different deployments
  • process.env.NODE_ENV: Common Node.js environment indicator

Common environment-specific settings:

CI/CD Environment (process.env.CI = true):

retries: 2: Automatic retries for flaky tests in unstable environments trace: 'on-first-retry': Balance between debugging and performance viewport: { width: 1920, height: 1080 }: Desktop resolution for consistency reporter: 'html': Comprehensive reports for CI systems

Local Development (process.env.CI = false):

retries: 0: Immediate failure for quick feedback during development trace: 'retain-on-failure': Detailed debugging info when needed viewport: { width: 1280, height: 720 }: Common development resolution reporter: 'list': Simple console output for quick feedback

Benefits of environment-aware configuration:

  • Optimized performance for each environment
  • Better resource management in resource-constrained CI systems
  • Improved debugging with appropriate detail levels
  • Consistent behavior across different execution contexts

Setting environment variables:

# Local development
BASE_URL=http://localhost:3000 npx playwright test

# CI environment  
CI=true BASE_URL=https://staging.example.com npx playwright test

# Production testing
CI=true BASE_URL=https://app.example.com npx playwright test

This approach ensures your tests run optimally in every environment while maintaining consistency and reliability.

Advanced Configuration Patterns

Playwright's project system enables sophisticated test organization through tagging and filtering. This allows you to create different test suites with customized configurations for specific purposes.

Tagging and Filtering Tests

You can use test tags to categorize tests and then create dedicated projects that only run tests with specific tags. This enables tailored execution environments for different test types.

export default defineConfig({
  projects: [
    {
      name: 'smoke',
      grep: /@smoke/,
      use: { 
        browserName: 'chromium',
        // Fast settings for smoke tests
        timeout: 15000
      }
    },
    {
      name: 'regression',
      grep: /@regression/,
      use: { 
        browserName: 'chromium',
        // Comprehensive settings for regression
        trace: 'on',
        video: 'on'
      }
    },
    {
      name: 'api',
      grep: /@api/,
      use: { 
        // API tests don't need browser
        browserName: 'chromium',
        // But can run headless for speed
        headless: true
      }
    }
  ]
});

How tagging works:

  • grep: Regular expression to match test names or tags
  • @smoke, @regression, @api: Convention for test categorization
  • Test-level tagging: Add tags to individual tests using comments or metadata

Example test tagging:

// In your test files:
test('@smoke Login with valid credentials', async ({ page }) => {
  // Smoke test implementation
});

test('@regression @api User profile API tests', async ({ request }) => {
  // API regression test
});

Project-specific configurations:

Smoke Tests (@smoke):

timeout: 15000: Shorter timeout for quick feedback Fast execution: Minimal tracing and video Critical path only: Focus on essential functionality Run frequency: Before deployments, after major changes

Regression Tests (@regression):

trace: 'on': Detailed tracing for debugging video: 'on': Video recording for failure analysis Comprehensive coverage: All functionality tested Run frequency: Nightly, weekly schedules

API Tests (@api):

headless: true: No UI overhead for faster execution Browser context: Still available for API calls that need cookies Focus: Backend functionality without UI dependencies

Execution commands:

# Run only smoke tests
npx playwright test --project=smoke

# Run regression tests
npx playwright test --project=regression

# Run API tests
npx playwright test --project=api

# Run tests with specific tag across all projects
npx playwright test --grep="@smoke"

# Exclude tests with specific tag
npx playwright test --grep-invert="@slow"

Benefits of test tagging:

  • Targeted execution: Run specific test suites when needed
  • Customized configuration: Different settings for different test types
  • Faster feedback: Quick smoke tests for rapid validation
  • Resource optimization: Appropriate settings for each test category
  • Better organization: Logical grouping of related tests

This pattern transforms your test suite from a monolithic entity into a modular, targeted testing system that can adapt to different needs and contexts.

Custom Test Directories

export default defineConfig({
  // Test directory structure
  testDir: './tests',                  // Directory containing test files
  
  // File inclusion patterns
  testMatch: '**/*.spec.js',           // Only include .spec.js files
  
  // File exclusion patterns  
  testIgnore: '**/*.test.js',          // Exclude .test.js files
  
  // Output directories
  outputDir: 'test-results',           // Test artifacts directory
  snapshotDir: '__snapshots__'         // Visual comparison snapshots
});

Best Practices for Parallel Execution

Parallel test execution requires careful configuration to avoid resource contention and ensure test isolation. Playwright's parallel execution model needs proper tuning for optimal performance.

Optimizing for Parallelism

This configuration demonstrates advanced patterns for maximizing parallel execution efficiency while maintaining test reliability.

export default defineConfig({
  // Optimal worker count
  workers: process.env.CI 
    ? Math.min(8, require('os').cpus().length - 1) 
    : 3,
  
  // Test isolation for parallel execution
  use: {
    // Each test gets its own storage state
    storageState: undefined,
    
    // Fresh context for each test
    bypassCSP: true
  }
});

Advanced Worker Configuration:

  • Math.min(8, require('os').cpus().length - 1): Dynamic CPU-based calculation

  • require('os').cpus().length: Gets actual CPU core count

  • - 1: Leaves one core free for system operations

  • Math.min(8, ...): Caps maximum at 8 workers to avoid overloading

  • process.env.CI ? ... : 3: 3 workers locally for development comfort

Why dynamic worker calculation matters:

  • Avoids overloading: Prevents using all CPU cores
  • Adapts to hardware: Works on different CI machines
  • Balances performance: Optimal worker count for each environment

Test Isolation Techniques:

  • storageState: undefined: Ensures each test starts with clean authentication state
  • bypassCSP: true: Avoids content security policy issues in parallel execution

Additional Parallelism Optimizations:

// For even better parallel performance:
export default defineConfig({
  // Prevent resource sharing between tests
  fullyParallel: true,
  
  // Fail fast on CI - don't run all tests if many are failing
  forbidOnly: !!process.env.CI,
  
  // Disable parallelization when debugging
  workers: process.env.DEBUG ? 1 : undefined,
  
  use: {
    // Reduce memory usage per context
    reducedMotion: 'reduce',
    
    // Faster teardown for parallel contexts
    serviceWorkers: 'block',
  }
});

Monitoring Parallel Performance:

# See worker utilization
npx playwright test --workers=4 --reporter=line

# Test with different worker counts
npx playwright test --workers=2
npx playwright test --workers=4
npx playwright test --workers=6

# Measure execution time
time npx playwright test --workers=4

Signs of Optimal Parallelization:

✅ Tests complete 3-4x faster with parallel execution
✅ CPU utilization around 70-80% (not 100%)
✅ No test failures due to resource contention
✅ Consistent execution times across runs

Common Parallelism Issues to Avoid:

❌ Too many workers causing memory exhaustion
❌ Shared state between tests causing flakiness
❌ Resource contention (database, API rate limiting)
❌ Non-atomic tests that depend on execution order

This configuration ensures you get the maximum benefit from parallel execution while maintaining test stability and reliability.

CLI Commands for Configuration

Useful Execution Commands

# Run with specific number of workers
npx playwright test --workers=4

# Run specific project
npx playwright test --project=firefox

# Run tests with specific tag
npx playwright test --grep="@smoke"

# Run tests from specific file
npx playwright test tests/login.spec.js

# Run with custom timeout
npx playwright test --timeout=60000

# Run with trace enabled
npx playwright test --trace=on

Practical Implementation

Want to see all these configuration patterns working together in a real test suite? Check out the complete example on GitHub:

Link to GitHub projectGitHub Octocat

Official Resources

Conclusion

Proper test configuration and parallel execution are essential for modern test automation. Playwright's flexible configuration system allows you to:

  • Reduce test execution time by 80-90% through parallelization
  • Run tests across multiple browsers simultaneously
  • Customize settings for different environments and needs
  • Scale test suites to thousands of tests without slowing down

By leveraging these features, you can transform your test suite from a slow, sequential process into a fast, efficient testing pipeline.


Thank you for reading and see you in the next lesson! ☕💜