Skip to main content

Playwright Test Plugin

This guide covers how to use the Playwright Test Plugin with Nx for end-to-end testing of web applications.

Introduction

Playwright is a modern end-to-end testing framework that enables automated testing across different browsers. The Nx Playwright Plugin integrates Playwright with Nx workspaces, providing:

  • Project generators for setting up Playwright tests
  • Executors for running tests
  • Seamless integration with the Nx build system

Setup and Installation

Prerequisites

  • Node.js v20.19.5
  • Pnpm v10.2.0
  • An Nx workspace

Adding Playwright to an Nx Project

  1. Install the required packages:
npm install -D @playwright/test @nx/playwright
# Or using pnpm
pnpm add -D @playwright/test @nx/playwright
  1. Generate e2e test setup for an existing project:
nx g @nx/playwright:configuration --project=your-app

This will create:

  • A playwright.config.ts file
  • An e2e directory with example tests
  • Updates to your project configuration

Configuration

The main configuration file is playwright.config.ts:

import { workspaceRoot } from '@nx/devkit';
import { nxE2EPreset } from '@nx/playwright/preset';
import { defineConfig, devices, ReporterDescription } from '@playwright/test';

export default defineConfig({
...nxE2EPreset(__filename, { testDir: './e2e' }),
/* Test directory and other options */
testDir: './e2e',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,

/* Configure options for all browsers */
use: {
baseURL: 'http://localhost:4200',
trace: 'on',
screenshot: 'only-on-failure',
video: 'on-first-retry',
},

/* Configure test reporters */
reporter: [['html', { open: 'never' }], ['list']],

/* Run your local dev server before starting the tests */
webServer: {
command: 'nx serve your-app',
url: 'http://localhost:4200',
reuseExistingServer: !process.env.CI,
cwd: workspaceRoot,
},

/* Configure projects for different browsers */
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
],
});

Key Configuration Options

OptionDescription
testDirDirectory containing test files
fullyParallelRun tests in parallel for faster execution
retriesNumber of times to retry failed tests
use.baseURLBase URL for navigating in tests
use.traceTrace collection configuration
webServerDev server configuration
projectsBrowser configurations

Writing Tests

Test Structure

Playwright tests are written using the test and expect functions:

import { test, expect } from '@playwright/test';

test('basic test', async ({ page }) => {
// Navigate to a page
await page.goto('/');

// Interact with the page
await page.locator('button').click();

// Make assertions
expect(await page.locator('.result').innerText()).toContain('Success');
});

Test Fixtures

Playwright provides built-in fixtures like page, context, and browser that are automatically initialized for each test:

test('using multiple fixtures', async ({ page, context, browser }) => {
// Use page, context, and browser objects
});

Custom Fixtures

You can create custom fixtures for reusable test setup:

// fixtures.ts
import { test as base } from '@playwright/test';
import { LoginPage } from './pages/login-page';

type CustomFixtures = {
loginPage: LoginPage;
loggedInPage: Page;
};

export const test = base.extend<CustomFixtures>({
loginPage: async ({ page }, use) => {
const loginPage = new LoginPage(page);
await use(loginPage);
},

loggedInPage: async ({ page }, use) => {
await page.goto('/login');
await page.fill('input[name="username"]', 'testuser');
await page.fill('input[name="password"]', 'password');
await page.click('button[type="submit"]');
await use(page);
},
});

Page Object Model

Organize tests using the Page Object Model pattern:

// pages/login-page.ts
import { Locator, Page } from '@playwright/test';

export class LoginPage {
readonly page: Page;
readonly usernameInput: Locator;
readonly passwordInput: Locator;
readonly submitButton: Locator;

constructor(page: Page) {
this.page = page;
this.usernameInput = page.locator('input[name="username"]');
this.passwordInput = page.locator('input[name="password"]');
this.submitButton = page.locator('button[type="submit"]');
}

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.submitButton.click();
}
}

// tests/login.spec.ts
import { test, expect } from '@playwright/test';
import { LoginPage } from '../pages/login-page';

test('successful login', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login('testuser', 'password');

// Verify successful login
expect(page.url()).toContain('/dashboard');
});

Running Tests

Running All Tests

nx e2e your-app

Running Specific Tests

# Run a specific test file
nx e2e your-app --testFile=login.spec.ts

# Run tests with a specific browser
nx e2e your-app --browser=firefox

# Run in headed mode (visible browser)
nx e2e your-app --headed

# Run in debug mode
nx e2e your-app --debug

Test Reporters

Playwright supports multiple reporters:

reporter: [
['html', { open: 'never' }], // HTML report
['list'], // Console output
['json', { outputFile: 'results.json' }], // JSON report
],

After running tests, view the HTML report:

npx playwright show-report

CI/CD Integration

Example GitHub Actions configuration:

e2e-tests:
container:
image: mcr.microsoft.com/playwright:v1.51.1-noble
options: --ipc=host
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- run: npx nx e2e your-app
- uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: |
test-results/
playwright-report/
retention-days: 7

API Testing

Playwright can also be used for API testing:

import { test, expect } from '@playwright/test';

test('API test', async ({ request }) => {
const response = await request.get('/api/users');
expect(response.ok()).toBeTruthy();

const users = await response.json();
expect(users.length).toBeGreaterThan(0);
});

Visual Testing

Playwright supports visual testing through screenshots:

import { test, expect } from '@playwright/test';

test('visual comparison', async ({ page }) => {
await page.goto('/dashboard');

// Compare screenshot with baseline
await expect(page).toHaveScreenshot('dashboard.png');
});

Parallelization and Sharding

For faster test execution:

// In playwright.config.ts
export default defineConfig({
fullyParallel: true, // Run tests in parallel
workers: process.env.CI ? 4 : undefined, // Limit workers in CI
});

// Running with sharding in CI
// job 1
nx e2e your-app --shard=1/3
// job 2
nx e2e your-app --shard=2/3
// job 3
nx e2e your-app --shard=3/3

Best Practices

  1. Structure tests logically: Group related tests in the same file
  2. Use page objects: Encapsulate page interactions in classes
  3. Test isolation: Each test should be independent
  4. Minimize wait times: Use Playwright's auto-waiting instead of explicit waits
  5. Use descriptive test names: Makes reports and debugging easier
  6. Clean up test data: Reset state between tests
  7. Leverage test hooks: Use beforeEach/afterEach for common setup/teardown

Troubleshooting Common Issues

  • Element not found: Check your selectors and make sure the element is visible
  • Test timeout: Increase the test timeout or optimize slow operations
  • Flaky tests: Add retries and ensure proper test isolation
  • CI failures: Run tests with --debug locally to reproduce issues

Additional Resources