| name | playwright-test-architecture |
| description | Use when setting up Playwright test projects and organizing test suites with proper configuration and project structure. |
| allowed-tools | ["Bash","Read","Write","Edit"] |
Playwright Test Architecture
Master test organization, configuration, and project structure for scalable
and maintainable Playwright test suites. This skill covers best practices
for organizing tests, configuring projects, and optimizing test execution.
Installation and Setup
npm init playwright@latest
npx playwright install chromium firefox webkit
npm install -D @playwright/test
npm install -D @playwright/test@latest
npx playwright install
npx playwright --version
Project Configuration
Basic Configuration
playwright.config.ts:
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
timeout: 30000,
expect: {
timeout: 5000,
},
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
reporter: 'html',
use: {
baseURL: 'http://localhost:3000',
trace: 'on-first-retry',
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'] },
},
],
webServer: {
command: 'npm run start',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
timeout: 120000,
},
});
Advanced Configuration
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
testMatch: '**/*.spec.ts',
testIgnore: '**/fixtures/**',
timeout: 30000,
globalSetup: require.resolve('./global-setup'),
globalTeardown: require.resolve('./global-teardown'),
expect: {
timeout: 5000,
toHaveScreenshot: {
maxDiffPixels: 100,
},
toMatchSnapshot: {
threshold: 0.2,
},
},
fullyParallel: true,
workers: process.env.CI ? 2 : undefined,
retries: process.env.CI ? 2 : 0,
forbidOnly: !!process.env.CI,
maxFailures: process.env. ? : ,
: ,
: ,
: [
[, { : }],
[, { : }],
[, { : }],
[],
],
: {
: process.. || ,
: ,
: ,
: ,
: !!process..,
: { : , : },
: ,
: ,
: ,
: ,
: ,
: ,
: [],
: { : , : - },
: ,
: {
: {
: ,
: { : , : },
},
},
},
: [
{
: ,
: ,
},
{
: ,
: { ...devices[] },
: [],
},
{
: ,
: { ...devices[] },
: [],
},
{
: ,
: { ...devices[] },
: [],
},
{
: ,
: { ...devices[] },
: [],
},
{
: ,
: { ...devices[] },
: [],
},
{
: ,
: {
...devices[],
: ,
},
: [],
},
{
: ,
: {
...devices[],
: ,
},
: [],
},
{
: ,
: {
: { : , : },
},
: [],
},
],
: {
: ,
: ,
: !process..,
: ,
: ,
: ,
},
});
Test Organization Patterns
By Feature
tests/
├── auth/
│ ├── login.spec.ts
│ ├── logout.spec.ts
│ ├── registration.spec.ts
│ └── password-reset.spec.ts
├── checkout/
│ ├── cart.spec.ts
│ ├── payment.spec.ts
│ └── confirmation.spec.ts
└── profile/
├── settings.spec.ts
└── preferences.spec.ts
By Page
tests/
├── pages/
│ ├── home.spec.ts
│ ├── product-list.spec.ts
│ ├── product-detail.spec.ts
│ └── checkout.spec.ts
└── workflows/
├── purchase-flow.spec.ts
└── user-journey.spec.ts
By User Journey
tests/
├── critical-paths/
│ ├── new-user-signup.spec.ts
│ ├── existing-user-login.spec.ts
│ └── purchase-completion.spec.ts
├── secondary-flows/
│ ├── profile-management.spec.ts
│ └── search-and-filter.spec.ts
└── edge-cases/
├── error-handling.spec.ts
└── boundary-conditions.spec.ts
Hybrid Approach (Recommended)
tests/
├── e2e/ # End-to-end user journeys
│ ├── checkout-flow.spec.ts
│ └── user-onboarding.spec.ts
├── features/ # Feature-specific tests
│ ├── auth/
│ │ ├── login.spec.ts
│ │ └── registration.spec.ts
│ ├── products/
│ │ ├── search.spec.ts
│ │ └── filters.spec.ts
│ └── profile/
│ └── settings.spec.ts
├── integration/ # API and integration tests
│ ├── api-auth.spec.ts
│ └── api-products.spec.ts
└── visual/ # Visual regression tests
├── homepage.spec.ts
└── product-page.spec.ts
Test File Structure
Basic Test Structure
import { test, expect } from '@playwright/test';
test.describe('Login Feature', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/login');
});
test('should login with valid credentials', async ({ page }) => {
await page.getByLabel('Email').fill('user@example.com');
await page.getByLabel('Password').fill('password123');
await page.getByRole('button', { name: 'Login' }).click();
await expect(page).toHaveURL('/dashboard');
await expect(page.getByText('Welcome back')).toBeVisible();
});
test('should show error with invalid credentials', async ({ page }) => {
await page.getByLabel('Email').fill('user@example.com');
await page.().();
page.(, { : }).();
(
page.()
).();
(page).();
});
});
Advanced Test Structure
import { test, expect } from '@playwright/test';
test.describe('Shopping Cart', () => {
test.beforeAll(async () => {
});
test.beforeEach(async ({ page }) => {
await page.goto('/products');
});
test.afterEach(async ({ page }, testInfo) => {
if (testInfo.status !== testInfo.expectedStatus) {
const screenshot = await page.screenshot();
await testInfo.attach('screenshot', {
body: screenshot,
contentType: 'image/png',
});
}
});
test.afterAll(async () => {
});
test.describe('Adding Items', () => {
test('should add single item', async ({ page }) => {
});
test('should add multiple items', async ({ page }) => {
});
});
test.describe('Removing Items', {
test.( ({ page }) => {
});
(, ({ page }) => {
});
(, ({ page }) => {
});
});
});
Parallel Execution and Sharding
Parallel Execution
test.describe.configure({ mode: 'parallel' });
test.describe.configure({ mode: 'serial' });
test.describe('Database Tests', () => {
test.describe.configure({ mode: 'serial' });
test('should create record', async ({ page }) => {
});
test('should update record', async ({ page }) => {
});
test('should delete record', async ({ page }) => {
});
});
Test Sharding
npx playwright test --shard=1/4
npx playwright test --shard=2/4
npx playwright test --shard=3/4
npx playwright test --shard=4/4
strategy:
matrix:
shardIndex: [1, 2, 3, 4]
shardTotal: [4]
steps:
- run: npx playwright test --shard=${{ matrix.shardIndex }}/
${{ matrix.shardTotal }}
Worker Configuration
export default defineConfig({
workers: undefined,
workers: 4,
workers: '50%',
workers: process.env.CI ? 2 : undefined,
});
Retry Strategies
Configuration-Based Retries
export default defineConfig({
retries: 2,
retries: process.env.CI ? 2 : 0,
projects: [
{
name: 'chromium',
retries: 1,
},
{
name: 'webkit',
retries: 3,
},
],
});
Test-Level Retries
test('flaky test', async ({ page }) => {
test.fixme();
});
test('critical test', async ({ page }) => {
test.slow();
});
test.describe(() => {
test.describe.configure({ retries: 3 });
test('needs extra retries', async ({ page }) => {
});
});
Reporter Configuration
Built-in Reporters
export default defineConfig({
reporter: [
['list'],
['line'],
['dot'],
['html', {
outputFolder: 'playwright-report',
open: 'never',
}],
['json', {
outputFile: 'test-results/results.json',
}],
['junit', {
outputFile: 'test-results/junit.xml',
}],
['github'],
],
});
Custom Reporter
import { Reporter, TestCase, TestResult } from '@playwright/test/reporter';
class CustomReporter implements Reporter {
onBegin(config, suite) {
console.log(`Starting test run with ${suite.allTests().length} tests`);
}
onTestBegin(test: TestCase) {
console.log(`Starting test: ${test.title}`);
}
onTestEnd(test: TestCase, result: TestResult) {
console.log(`Finished test: ${test.title} - ${result.status}`);
}
onEnd(result) {
console.log(`Finished test run: ${result.status}`);
}
}
export default CustomReporter;
export default defineConfig({
reporter: [
['./custom-reporter.ts'],
['html'],
],
});
Environment-Specific Configuration
Multi-Environment Setup
import { defineConfig } from '@playwright/test';
const env = process.env.ENV || 'local';
const baseURLs = {
local: 'http://localhost:3000',
staging: 'https://staging.example.com',
production: 'https://example.com',
};
export default defineConfig({
use: {
baseURL: baseURLs[env],
},
});
Environment Files
export const environments = {
local: {
baseURL: 'http://localhost:3000',
apiURL: 'http://localhost:8000',
timeout: 30000,
},
staging: {
baseURL: 'https://staging.example.com',
apiURL: 'https://api-staging.example.com',
timeout: 60000,
},
production: {
baseURL: 'https://example.com',
apiURL: 'https://api.example.com',
timeout: 90000,
},
};
import { environments } from './config/environments';
const env = process.env.ENV || 'local';
const config = environments[env];
export default defineConfig({
use: {
baseURL: config.baseURL,
actionTimeout: config.timeout,
},
});
Trace and Video Recording
Trace Configuration
export default defineConfig({
use: {
trace: 'on-first-retry',
trace: 'on',
trace: 'retain-on-failure',
trace: 'off',
trace: {
mode: 'on',
screenshots: true,
snapshots: true,
},
},
});
Video Recording
export default defineConfig({
use: {
video: 'on-first-retry',
video: 'retain-on-failure',
video: 'on',
video: 'off',
video: {
mode: 'on',
size: { width: 1280, height: 720 },
},
},
});
Screenshot Configuration
export default defineConfig({
use: {
screenshot: 'only-on-failure',
screenshot: 'on',
screenshot: 'off',
},
});
Global Setup and Teardown
Global Setup
import { chromium, FullConfig } from '@playwright/test';
async function globalSetup(config: FullConfig) {
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto('https://example.com/login');
await page.getByLabel('Email').fill('admin@example.com');
await page.getByLabel('Password').fill('admin123');
await page.getByRole('button', { name: 'Login' }).click();
await page.context().storageState({
path: 'auth.json',
});
await browser.close();
}
export default globalSetup;
Global Teardown
import { FullConfig } from '@playwright/test';
import fs from 'fs';
async function globalTeardown(config: FullConfig) {
if (fs.existsSync('auth.json')) {
fs.unlinkSync('auth.json');
}
console.log('Cleaning up test data...');
}
export default globalTeardown;
Test Grouping and Tagging
Using Tags
test('@smoke @critical should login', async ({ page }) => {
});
test.describe('@regression', () => {
test('test 1', async ({ page }) => {
});
test('test 2', async ({ page }) => {
});
});
npx playwright test --grep @smoke
npx playwright test --grep-invert @slow
npx playwright test --grep "@smoke|@critical"
When to Use This Skill
- Setting up new Playwright test projects from scratch
- Configuring CI/CD pipelines for test execution
- Organizing large test suites for maintainability
- Optimizing test execution time with parallelization
- Implementing retry strategies for flaky tests
- Configuring multi-environment test execution
- Setting up test reporting for different audiences
- Establishing test architecture patterns for teams
- Debugging test failures with traces and videos
- Scaling test suites across multiple projects
Resources