Production Framework
A production test framework is more than a collection of test files. It needs configuration management for multiple environments, custom reporters for team visibility, sensible project structure that scales, and conventions that new team members can follow without a manual.
Project Structure at Scale
e2e/
├── playwright.config.ts # Main configuration
├── global-setup.ts # One-time setup (DB seed, auth)
├── global-teardown.ts # One-time cleanup
├── fixtures/
│ ├── index.ts # Re-exports all custom fixtures
│ ├── auth.fixture.ts # Authentication fixtures
│ └── data.fixture.ts # Test data fixtures
├── pages/
│ ├── base.page.ts # Base page object with shared methods
│ ├── login.page.ts
│ ├── dashboard.page.ts
│ └── components/
│ ├── header.component.ts
│ └── modal.component.ts
├── tests/
│ ├── auth/
│ │ ├── login.spec.ts
│ │ └── registration.spec.ts
│ ├── checkout/
│ │ ├── cart.spec.ts
│ │ └── payment.spec.ts
│ └── admin/
│ └── user-management.spec.ts
├── test-data/
│ ├── users.json
│ └── products.json
└── utils/
├── api-client.ts # API helper for setup/teardown
└── test-helpers.ts # Shared utility functions
Key Conventions
- One spec file per feature area, not per page — tests are organized by what they verify
- Page objects in
pages/, fixtures infixtures/— clear separation - Shared test data in
test-data/— not hardcoded in tests - Utilities in
utils/— API clients, date helpers, generators
Configuration Management
Multi-Environment Setup
// playwright.config.ts
import { defineConfig } from '@playwright/test';
const env = process.env.TEST_ENV || 'staging';
const envConfig = {
local: { baseURL: 'http://localhost:3000' },
staging: { baseURL: 'https://staging.example.com' },
production: { baseURL: 'https://example.com' },
};
export default defineConfig({
testDir: './tests',
timeout: 60_000,
retries: env === 'production' ? 0 : 2,
workers: env === 'local' ? undefined : 4,
use: {
baseURL: envConfig[env].baseURL,
screenshot: 'only-on-failure',
trace: 'on-first-retry',
video: 'on-first-retry',
},
projects: [
{ name: 'setup', testMatch: /.*\.setup\.ts/ },
{
name: 'chromium',
dependencies: ['setup'],
use: { browserName: 'chromium', storageState: '.auth/user.json' },
},
{
name: 'firefox',
dependencies: ['setup'],
use: { browserName: 'firefox', storageState: '.auth/user.json' },
},
],
});
Running Against Different Environments
# Default (staging)
npx playwright test
# Local development
TEST_ENV=local npx playwright test
# Production smoke tests
TEST_ENV=production npx playwright test --grep @smoke
Test Tagging
Use tags to categorize tests for selective execution:
test('@smoke login with valid credentials', async ({ page }) => {
// Critical path — runs on every deployment
});
test('@regression cart handles 100 items', async ({ page }) => {
// Thorough — runs nightly
});
test('@slow report generation with large dataset', async ({ page }) => {
// Resource-intensive — runs on-demand
});
npx playwright test --grep @smoke # Only smoke tests
npx playwright test --grep-invert @slow # Everything except slow tests
Custom Reporters
Playwright supports multiple reporters simultaneously:
// playwright.config.ts
export default defineConfig({
reporter: [
['list'], // Console output
['html', { open: 'never' }], // HTML report
['json', { outputFile: 'results.json' }], // Machine-readable
['junit', { outputFile: 'results.xml' }], // CI integration
],
});
Global Setup and Teardown
// global-setup.ts
import { chromium, FullConfig } from '@playwright/test';
async function globalSetup(config: FullConfig) {
// Seed the database
// Create test users via API
// Warm up the application
}
export default globalSetup;
// playwright.config.ts
export default defineConfig({
globalSetup: require.resolve('./global-setup'),
globalTeardown: require.resolve('./global-teardown'),
});
Key Takeaways
- Organize by feature (not by page) — tests, page objects, and fixtures each have their own directory
- Use environment variables for multi-environment configuration — one config file, multiple targets
- Tag tests (
@smoke,@regression,@slow) for selective execution - Stack reporters (HTML + JSON + JUnit) for different audiences
- Global setup handles one-time tasks like database seeding and authentication