Answer
Scenario: Enterprise Playwright Framework Design for a Team
Project Structure
CODE
playwright-framework/
├── pages/ # Page Object Model
│ ├── BasePage.ts
│ ├── auth/
│ │ ├── LoginPage.ts
│ │ └── RegisterPage.ts
│ ├── products/
│ │ ├── ProductListPage.ts
│ │ └── ProductDetailPage.ts
│ └── checkout/
│ ├── CartPage.ts
│ ├── ShippingPage.ts
│ └── PaymentPage.ts
│
├── fixtures/ # Custom test fixtures
│ ├── index.ts # Re-exports all fixtures
│ ├── page-fixtures.ts # Page objects as fixtures
│ └── auth-fixtures.ts # Auth state fixtures
│
├── helpers/ # Shared utilities
│ ├── api.helper.ts # API setup/teardown
│ ├── database.helper.ts # Direct DB operations
│ ├── date.helper.ts
│ └── random.helper.ts # Test data generators
│
├── test-data/ # Test data management
│ ├── users.ts # User constants/factories
│ ├── products.ts # Product test data
│ └── factories/ # Data factories
│ └── user.factory.ts
│
├── tests/ # Test files by feature
│ ├── auth/
│ │ ├── login.spec.ts
│ │ └── register.spec.ts
│ ├── products/
│ │ └── product-listing.spec.ts
│ ├── checkout/
│ │ └── checkout.spec.ts
│ └── smoke/
│ └── smoke.spec.ts
│
├── .auth/ # Auth state files (gitignored)
│ ├── user.json
│ └── admin.json
│
├── global-setup.ts
├── global-teardown.ts
├── playwright.config.ts
└── .env.example
playwright.config.ts (Team-Standard Config)
TypeScript
import { defineConfig, devices } from '@playwright/test';
import dotenv from 'dotenv';
dotenv.config({ path: `.env.${process.env.TEST_ENV || 'staging'}` });
export default defineConfig({
testDir: './tests',
fullyParallel: true,
workers: process.env.CI ? 4 : 2,
retries: process.env.CI ? 2 : 0,
globalSetup: './global-setup.ts',
globalTeardown: './global-teardown.ts',
reporter: [
['html', { outputFolder: 'playwright-report' }],
['json', { outputFile: 'results/results.json' }],
process.env.CI ? ['github'] : ['list'],
],
use: {
baseURL: process.env.BASE_URL || 'http://localhost:3000',
headless: true,
trace: 'retain-on-failure',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
actionTimeout: 15_000,
navigationTimeout: 30_000,
},
projects: [
{ name: 'setup', testMatch: '**/*.setup.ts' },
{
name: 'chromium',
use: { ...devices['Desktop Chrome'], storageState: '.auth/user.json' },
dependencies: ['setup'],
},
],
});
Data Factory Pattern
TypeScript
// helpers/factories/user.factory.ts
export function createUser(overrides: Partial<User> = {}): User {
return {
email: `user-${Date.now()}-${Math.random().toString(36).slice(2)}@test.com`,
password: 'TestPass123!',
name: 'Test User',
role: 'viewer',
...overrides,
};
}
// Usage in test
const adminUser = createUser({ role: 'admin', email: 'admin@test.com' });
Team Guidelines
- ✓One POM class per page — pages should have no test logic
- ✓Use fixtures for all shared setup — no copy-paste across tests
- ✓API setup for test data — never create data via UI in beforeEach
- ✓Tag all tests —
@smoke,@regression,@api,@auth - ✓No hard-coded waits — use
waitFor()or assertions - ✓Descriptive test names —
'user with expired session is redirected to login' - ✓Teardown via API — clean up test data after tests that create records
- ✓Store secrets in CI — never in code or
.envfiles committed to git
