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
- Install the required packages:
npm install -D @playwright/test @nx/playwright
# Or using pnpm
pnpm add -D @playwright/test @nx/playwright
- Generate e2e test setup for an existing project:
nx g @nx/playwright:configuration --project=your-app
This will create:
- A
playwright.config.tsfile - An
e2edirectory 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
| Option | Description |
|---|---|
testDir | Directory containing test files |
fullyParallel | Run tests in parallel for faster execution |
retries | Number of times to retry failed tests |
use.baseURL | Base URL for navigating in tests |
use.trace | Trace collection configuration |
webServer | Dev server configuration |
projects | Browser 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
- Structure tests logically: Group related tests in the same file
- Use page objects: Encapsulate page interactions in classes
- Test isolation: Each test should be independent
- Minimize wait times: Use Playwright's auto-waiting instead of explicit waits
- Use descriptive test names: Makes reports and debugging easier
- Clean up test data: Reset state between tests
- Leverage test hooks: Use
beforeEach/afterEachfor 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
--debuglocally to reproduce issues