Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734
  1. # Playwright Configuration Guardrails
  2. ## Principle
  3. Load environment configs via a central map (`envConfigMap`), standardize timeouts (action 15s, navigation 30s, expect 10s, test 60s), emit HTML + JUnit reporters, and store artifacts under `test-results/` for CI upload. Keep `.env.example`, `.nvmrc`, and browser dependencies versioned so local and CI runs stay aligned.
  4. ## Rationale
  5. Environment-specific configuration prevents hardcoded URLs, timeouts, and credentials from leaking into tests. A central config map with fail-fast validation catches missing environments early. Standardized timeouts reduce flakiness while remaining long enough for real-world network conditions. Consistent artifact storage (`test-results/`, `playwright-report/`) enables CI pipelines to upload failure evidence automatically. Versioned dependencies (`.nvmrc`, `package.json` browser versions) eliminate "works on my machine" issues between local and CI environments.
  6. ## Pattern Examples
  7. ### Example 1: Environment-Based Configuration
  8. **Context**: When testing against multiple environments (local, staging, production), use a central config map that loads environment-specific settings and fails fast if `TEST_ENV` is invalid.
  9. **Implementation**:
  10. ```typescript
  11. // playwright.config.ts - Central config loader
  12. import { config as dotenvConfig } from 'dotenv';
  13. import path from 'path';
  14. // Load .env from project root
  15. dotenvConfig({
  16. path: path.resolve(__dirname, '../../.env'),
  17. });
  18. // Central environment config map
  19. const envConfigMap = {
  20. local: require('./playwright/config/local.config').default,
  21. staging: require('./playwright/config/staging.config').default,
  22. production: require('./playwright/config/production.config').default,
  23. };
  24. const environment = process.env.TEST_ENV || 'local';
  25. // Fail fast if environment not supported
  26. if (!Object.keys(envConfigMap).includes(environment)) {
  27. console.error(`❌ No configuration found for environment: ${environment}`);
  28. console.error(` Available environments: ${Object.keys(envConfigMap).join(', ')}`);
  29. process.exit(1);
  30. }
  31. console.log(`✅ Running tests against: ${environment.toUpperCase()}`);
  32. export default envConfigMap[environment as keyof typeof envConfigMap];
  33. ```
  34. ```typescript
  35. // playwright/config/base.config.ts - Shared base configuration
  36. import { defineConfig } from '@playwright/test';
  37. import path from 'path';
  38. export const baseConfig = defineConfig({
  39. testDir: path.resolve(__dirname, '../tests'),
  40. outputDir: path.resolve(__dirname, '../../test-results'),
  41. fullyParallel: true,
  42. forbidOnly: !!process.env.CI,
  43. retries: process.env.CI ? 2 : 0,
  44. workers: process.env.CI ? 1 : undefined,
  45. reporter: [
  46. ['html', { outputFolder: 'playwright-report', open: 'never' }],
  47. ['junit', { outputFile: 'test-results/results.xml' }],
  48. ['list'],
  49. ],
  50. use: {
  51. actionTimeout: 15000,
  52. navigationTimeout: 30000,
  53. trace: 'retain-on-failure-and-retries',
  54. screenshot: 'only-on-failure',
  55. video: 'retain-on-failure',
  56. },
  57. globalSetup: path.resolve(__dirname, '../support/global-setup.ts'),
  58. timeout: 60000,
  59. expect: { timeout: 10000 },
  60. });
  61. ```
  62. ```typescript
  63. // playwright/config/local.config.ts - Local environment
  64. import { defineConfig } from '@playwright/test';
  65. import { baseConfig } from './base.config';
  66. export default defineConfig({
  67. ...baseConfig,
  68. use: {
  69. ...baseConfig.use,
  70. baseURL: 'http://localhost:3000',
  71. video: 'off', // No video locally for speed
  72. },
  73. webServer: {
  74. command: 'npm run dev',
  75. url: 'http://localhost:3000',
  76. wait: {
  77. stdout: /ready|listening|localhost:/i,
  78. },
  79. reuseExistingServer: !process.env.CI,
  80. timeout: 120000,
  81. },
  82. });
  83. ```
  84. ```typescript
  85. // playwright/config/staging.config.ts - Staging environment
  86. import { defineConfig } from '@playwright/test';
  87. import { baseConfig } from './base.config';
  88. export default defineConfig({
  89. ...baseConfig,
  90. use: {
  91. ...baseConfig.use,
  92. baseURL: 'https://staging.example.com',
  93. ignoreHTTPSErrors: true, // Allow self-signed certs in staging
  94. },
  95. });
  96. ```
  97. ```typescript
  98. // playwright/config/production.config.ts - Production environment
  99. import { defineConfig } from '@playwright/test';
  100. import { baseConfig } from './base.config';
  101. export default defineConfig({
  102. ...baseConfig,
  103. retries: 3, // More retries in production
  104. use: {
  105. ...baseConfig.use,
  106. baseURL: 'https://example.com',
  107. video: 'on', // Always record production failures
  108. },
  109. });
  110. ```
  111. ```bash
  112. # .env.example - Template for developers
  113. TEST_ENV=local
  114. API_KEY=your_api_key_here
  115. DATABASE_URL=postgresql://localhost:5432/test_db
  116. ```
  117. **Key Points**:
  118. - Central `envConfigMap` prevents environment misconfiguration
  119. - Fail-fast validation with clear error message (available envs listed)
  120. - Base config defines shared settings, environment configs override
  121. - `.env.example` provides template for required secrets
  122. - `TEST_ENV=local` as default for local development
  123. - Production config increases retries and enables video recording
  124. ### Example 2: Timeout Standards
  125. **Context**: When tests fail due to inconsistent timeout settings, standardize timeouts across all tests: action 15s, navigation 30s, expect 10s, test 60s. Expose overrides through fixtures rather than inline literals.
  126. **Implementation**:
  127. ```typescript
  128. // playwright/config/base.config.ts - Standardized timeouts
  129. import { defineConfig } from '@playwright/test';
  130. export default defineConfig({
  131. // Global test timeout: 60 seconds
  132. timeout: 60000,
  133. use: {
  134. // Action timeout: 15 seconds (click, fill, etc.)
  135. actionTimeout: 15000,
  136. // Navigation timeout: 30 seconds (page.goto, page.reload)
  137. navigationTimeout: 30000,
  138. },
  139. // Expect timeout: 10 seconds (all assertions)
  140. expect: {
  141. timeout: 10000,
  142. },
  143. });
  144. ```
  145. ```typescript
  146. // playwright/support/fixtures/timeout-fixture.ts - Timeout override fixture
  147. import { test as base } from '@playwright/test';
  148. type TimeoutOptions = {
  149. extendedTimeout: (timeoutMs: number) => Promise<void>;
  150. };
  151. export const test = base.extend<TimeoutOptions>({
  152. extendedTimeout: async ({}, use, testInfo) => {
  153. const originalTimeout = testInfo.timeout;
  154. await use(async (timeoutMs: number) => {
  155. testInfo.setTimeout(timeoutMs);
  156. });
  157. // Restore original timeout after test
  158. testInfo.setTimeout(originalTimeout);
  159. },
  160. });
  161. export { expect } from '@playwright/test';
  162. ```
  163. ```typescript
  164. // Usage in tests - Standard timeouts (implicit)
  165. import { test, expect } from '@playwright/test';
  166. test('user can log in', async ({ page }) => {
  167. await page.goto('/login'); // Uses 30s navigation timeout
  168. await page.fill('[data-testid="email"]', 'test@example.com'); // Uses 15s action timeout
  169. await page.click('[data-testid="login-button"]'); // Uses 15s action timeout
  170. await expect(page.getByText('Welcome')).toBeVisible(); // Uses 10s expect timeout
  171. });
  172. ```
  173. ```typescript
  174. // Usage in tests - Per-test timeout override
  175. import { test, expect } from '../support/fixtures/timeout-fixture';
  176. test('slow data processing operation', async ({ page, extendedTimeout }) => {
  177. // Override default 60s timeout for this slow test
  178. await extendedTimeout(180000); // 3 minutes
  179. await page.goto('/data-processing');
  180. await page.click('[data-testid="process-large-file"]');
  181. // Wait for long-running operation
  182. await expect(page.getByText('Processing complete')).toBeVisible({
  183. timeout: 120000, // 2 minutes for assertion
  184. });
  185. });
  186. ```
  187. ```typescript
  188. // Per-assertion timeout override (inline)
  189. test('API returns quickly', async ({ page }) => {
  190. await page.goto('/dashboard');
  191. // Override expect timeout for fast API (reduce flakiness detection)
  192. await expect(page.getByTestId('user-name')).toBeVisible({ timeout: 5000 }); // 5s instead of 10s
  193. // Override expect timeout for slow external API
  194. await expect(page.getByTestId('weather-widget')).toBeVisible({ timeout: 20000 }); // 20s instead of 10s
  195. });
  196. ```
  197. **Key Points**:
  198. - **Standardized timeouts**: action 15s, navigation 30s, expect 10s, test 60s (global defaults)
  199. - Fixture-based override (`extendedTimeout`) for slow tests (preferred over inline)
  200. - Per-assertion timeout override via `{ timeout: X }` option (use sparingly)
  201. - Avoid hard waits (`page.waitForTimeout(3000)`) - use event-based waits instead
  202. - CI environments may need longer timeouts (handle in environment-specific config)
  203. ### Example 3: Artifact Output Configuration
  204. **Context**: When debugging failures in CI, configure artifacts (screenshots, videos, traces, HTML reports) to be captured on failure and stored in consistent locations for upload.
  205. **Implementation**:
  206. ```typescript
  207. // playwright.config.ts - Artifact configuration
  208. import { defineConfig } from '@playwright/test';
  209. import path from 'path';
  210. export default defineConfig({
  211. // Output directory for test artifacts
  212. outputDir: path.resolve(__dirname, './test-results'),
  213. use: {
  214. // Screenshot on failure only (saves space)
  215. screenshot: 'only-on-failure',
  216. // Video recording on failure + retry
  217. video: 'retain-on-failure',
  218. // Keep failed attempts and retries for flake analysis
  219. trace: 'retain-on-failure-and-retries',
  220. },
  221. reporter: [
  222. // HTML report (visual, interactive)
  223. [
  224. 'html',
  225. {
  226. outputFolder: 'playwright-report',
  227. open: 'never', // Don't auto-open in CI
  228. },
  229. ],
  230. // JUnit XML (CI integration)
  231. [
  232. 'junit',
  233. {
  234. outputFile: 'test-results/results.xml',
  235. },
  236. ],
  237. // List reporter (console output)
  238. ['list'],
  239. ],
  240. });
  241. ```
  242. ```typescript
  243. // playwright/support/fixtures/artifact-fixture.ts - Custom artifact capture
  244. import { test as base } from '@playwright/test';
  245. import fs from 'fs';
  246. import path from 'path';
  247. export const test = base.extend({
  248. // Auto-capture console logs on failure
  249. page: async ({ page }, use, testInfo) => {
  250. const logs: string[] = [];
  251. page.on('console', (msg) => {
  252. logs.push(`[${msg.type()}] ${msg.text()}`);
  253. });
  254. await use(page);
  255. // Save logs on failure
  256. if (testInfo.status !== testInfo.expectedStatus) {
  257. const logsPath = path.join(testInfo.outputDir, 'console-logs.txt');
  258. fs.writeFileSync(logsPath, logs.join('\n'));
  259. testInfo.attachments.push({
  260. name: 'console-logs',
  261. contentType: 'text/plain',
  262. path: logsPath,
  263. });
  264. }
  265. },
  266. });
  267. ```
  268. ```yaml
  269. # .github/workflows/e2e.yml - CI artifact upload
  270. name: E2E Tests
  271. on: [push, pull_request]
  272. jobs:
  273. test:
  274. runs-on: ubuntu-latest
  275. steps:
  276. - uses: actions/checkout@v4
  277. - uses: actions/setup-node@v4
  278. with:
  279. node-version-file: '.nvmrc'
  280. - name: Install dependencies
  281. run: npm ci
  282. - name: Install Playwright browsers
  283. run: npx playwright install --with-deps
  284. - name: Run tests
  285. run: npm run test
  286. env:
  287. TEST_ENV: staging
  288. # Upload test artifacts on failure
  289. - name: Upload test results
  290. if: failure()
  291. uses: actions/upload-artifact@v4
  292. with:
  293. name: test-results
  294. path: test-results/
  295. retention-days: 30
  296. - name: Upload Playwright report
  297. if: failure()
  298. uses: actions/upload-artifact@v4
  299. with:
  300. name: playwright-report
  301. path: playwright-report/
  302. retention-days: 30
  303. ```
  304. ```typescript
  305. // Example: Custom screenshot on specific condition
  306. test('capture screenshot on specific error', async ({ page }) => {
  307. await page.goto('/checkout');
  308. try {
  309. await page.click('[data-testid="submit-payment"]');
  310. await expect(page.getByText('Order Confirmed')).toBeVisible();
  311. } catch (error) {
  312. // Capture custom screenshot with timestamp
  313. await page.screenshot({
  314. path: `test-results/payment-error-${Date.now()}.png`,
  315. fullPage: true,
  316. });
  317. throw error;
  318. }
  319. });
  320. ```
  321. **Key Points**:
  322. - `screenshot: 'only-on-failure'` saves space (not every test)
  323. - `video: 'retain-on-failure'` captures full flow on failures
  324. - `trace: 'retain-on-failure-and-retries'` keeps enough history to compare failing retries against passing runs
  325. - `webServer.wait` is better than startup sleeps when local servers print readiness to stdout/stderr
  326. - HTML report at `playwright-report/` (visual debugging)
  327. - JUnit XML at `test-results/results.xml` (CI integration)
  328. - CI uploads artifacts on failure with 30-day retention
  329. - Custom fixture can capture console logs, network logs, etc.
  330. ### Example 4: Parallelization Configuration
  331. **Context**: When tests run slowly in CI, configure parallelization with worker count, sharding, and fully parallel execution to maximize speed while maintaining stability.
  332. **Implementation**:
  333. ```typescript
  334. // playwright.config.ts - Parallelization settings
  335. import { defineConfig } from '@playwright/test';
  336. import os from 'os';
  337. export default defineConfig({
  338. // Run tests in parallel within single file
  339. fullyParallel: true,
  340. // Worker configuration
  341. workers: process.env.CI
  342. ? 1 // Serial in CI for stability (or 2 for faster CI)
  343. : os.cpus().length - 1, // Parallel locally (leave 1 CPU for OS)
  344. // Prevent accidentally committed .only() from blocking CI
  345. forbidOnly: !!process.env.CI,
  346. // Retry failed tests in CI
  347. retries: process.env.CI ? 2 : 0,
  348. // Shard configuration (split tests across multiple machines)
  349. shard:
  350. process.env.SHARD_INDEX && process.env.SHARD_TOTAL
  351. ? {
  352. current: parseInt(process.env.SHARD_INDEX, 10),
  353. total: parseInt(process.env.SHARD_TOTAL, 10),
  354. }
  355. : undefined,
  356. });
  357. ```
  358. ```yaml
  359. # .github/workflows/e2e-parallel.yml - Sharded CI execution
  360. name: E2E Tests (Parallel)
  361. on: [push, pull_request]
  362. jobs:
  363. test:
  364. runs-on: ubuntu-latest
  365. strategy:
  366. fail-fast: false
  367. matrix:
  368. shard: [1, 2, 3, 4] # Split tests across 4 machines
  369. steps:
  370. - uses: actions/checkout@v4
  371. - uses: actions/setup-node@v4
  372. with:
  373. node-version-file: '.nvmrc'
  374. - name: Install dependencies
  375. run: npm ci
  376. - name: Install Playwright browsers
  377. run: npx playwright install --with-deps
  378. - name: Run tests (shard ${{ matrix.shard }})
  379. run: npm run test
  380. env:
  381. SHARD_INDEX: ${{ matrix.shard }}
  382. SHARD_TOTAL: 4
  383. TEST_ENV: staging
  384. - name: Upload test results
  385. if: failure()
  386. uses: actions/upload-artifact@v4
  387. with:
  388. name: test-results-shard-${{ matrix.shard }}
  389. path: test-results/
  390. ```
  391. ```typescript
  392. // playwright/config/serial.config.ts - Serial execution for flaky tests
  393. import { defineConfig } from '@playwright/test';
  394. import { baseConfig } from './base.config';
  395. export default defineConfig({
  396. ...baseConfig,
  397. // Disable parallel execution
  398. fullyParallel: false,
  399. workers: 1,
  400. // Used for: authentication flows, database-dependent tests, feature flag tests
  401. });
  402. ```
  403. ```typescript
  404. // Usage: Force serial execution for specific tests
  405. import { test } from '@playwright/test';
  406. // Serial execution for auth tests (shared session state)
  407. test.describe.configure({ mode: 'serial' });
  408. test.describe('Authentication Flow', () => {
  409. test('user can log in', async ({ page }) => {
  410. // First test in serial block
  411. });
  412. test('user can access dashboard', async ({ page }) => {
  413. // Depends on previous test (serial)
  414. });
  415. });
  416. ```
  417. ```typescript
  418. // Usage: Parallel execution for independent tests (default)
  419. import { test } from '@playwright/test';
  420. test.describe('Product Catalog', () => {
  421. test('can view product 1', async ({ page }) => {
  422. // Runs in parallel with other tests
  423. });
  424. test('can view product 2', async ({ page }) => {
  425. // Runs in parallel with other tests
  426. });
  427. });
  428. ```
  429. **Key Points**:
  430. - `fullyParallel: true` enables parallel execution within single test file
  431. - Workers: 1 in CI (stability), N-1 CPUs locally (speed)
  432. - Sharding splits tests across multiple CI machines (4x faster with 4 shards)
  433. - `test.describe.configure({ mode: 'serial' })` for dependent tests
  434. - `forbidOnly: true` in CI prevents `.only()` from blocking pipeline
  435. - Matrix strategy in CI runs shards concurrently
  436. ### Example 5: Project Configuration
  437. **Context**: When testing across multiple browsers, devices, or configurations, use Playwright projects to run the same tests against different environments (chromium, firefox, webkit, mobile).
  438. **Implementation**:
  439. ```typescript
  440. // playwright.config.ts - Multiple browser projects
  441. import { defineConfig, devices } from '@playwright/test';
  442. export default defineConfig({
  443. projects: [
  444. // Desktop browsers
  445. {
  446. name: 'chromium',
  447. use: { ...devices['Desktop Chrome'] },
  448. },
  449. {
  450. name: 'firefox',
  451. use: { ...devices['Desktop Firefox'] },
  452. },
  453. {
  454. name: 'webkit',
  455. use: { ...devices['Desktop Safari'] },
  456. },
  457. // Mobile browsers
  458. {
  459. name: 'mobile-chrome',
  460. use: { ...devices['Pixel 5'] },
  461. },
  462. {
  463. name: 'mobile-safari',
  464. use: { ...devices['iPhone 13'] },
  465. },
  466. // Tablet
  467. {
  468. name: 'tablet',
  469. use: { ...devices['iPad Pro'] },
  470. },
  471. ],
  472. });
  473. ```
  474. ```typescript
  475. // playwright.config.ts - Authenticated vs. unauthenticated projects
  476. import { defineConfig } from '@playwright/test';
  477. import path from 'path';
  478. export default defineConfig({
  479. projects: [
  480. // Setup project (runs first, creates auth state)
  481. {
  482. name: 'setup',
  483. testMatch: /global-setup\.ts/,
  484. },
  485. // Authenticated tests (reuse auth state)
  486. {
  487. name: 'authenticated',
  488. dependencies: ['setup'],
  489. use: {
  490. storageState: path.resolve(__dirname, './playwright/.auth/user.json'),
  491. },
  492. testMatch: /.*authenticated\.spec\.ts/,
  493. },
  494. // Unauthenticated tests (public pages)
  495. {
  496. name: 'unauthenticated',
  497. testMatch: /.*unauthenticated\.spec\.ts/,
  498. },
  499. ],
  500. });
  501. ```
  502. ```typescript
  503. // playwright/support/global-setup.ts - Setup project for auth
  504. import { chromium, FullConfig } from '@playwright/test';
  505. import path from 'path';
  506. async function globalSetup(config: FullConfig) {
  507. const browser = await chromium.launch();
  508. const page = await browser.newPage();
  509. // Perform authentication
  510. await page.goto('http://localhost:3000/login');
  511. await page.fill('[data-testid="email"]', 'test@example.com');
  512. await page.fill('[data-testid="password"]', 'password123');
  513. await page.click('[data-testid="login-button"]');
  514. // Wait for authentication to complete
  515. await page.waitForURL('**/dashboard');
  516. // Save authentication state
  517. await page.context().storageState({
  518. path: path.resolve(__dirname, '../.auth/user.json'),
  519. });
  520. await browser.close();
  521. }
  522. export default globalSetup;
  523. ```
  524. ```bash
  525. # Run specific project
  526. npx playwright test --project=chromium
  527. npx playwright test --project=mobile-chrome
  528. npx playwright test --project=authenticated
  529. # Run multiple projects
  530. npx playwright test --project=chromium --project=firefox
  531. # Run all projects (default)
  532. npx playwright test
  533. ```
  534. ```typescript
  535. // Usage: Project-specific test
  536. import { test, expect } from '@playwright/test';
  537. test('mobile navigation works', async ({ page, isMobile }) => {
  538. await page.goto('/');
  539. if (isMobile) {
  540. // Open mobile menu
  541. await page.click('[data-testid="hamburger-menu"]');
  542. }
  543. await page.click('[data-testid="products-link"]');
  544. await expect(page).toHaveURL(/.*products/);
  545. });
  546. ```
  547. ```yaml
  548. # .github/workflows/e2e-cross-browser.yml - CI cross-browser testing
  549. name: E2E Tests (Cross-Browser)
  550. on: [push, pull_request]
  551. jobs:
  552. test:
  553. runs-on: ubuntu-latest
  554. strategy:
  555. fail-fast: false
  556. matrix:
  557. project: [chromium, firefox, webkit, mobile-chrome]
  558. steps:
  559. - uses: actions/checkout@v4
  560. - uses: actions/setup-node@v4
  561. - run: npm ci
  562. - run: npx playwright install --with-deps
  563. - name: Run tests (${{ matrix.project }})
  564. run: npx playwright test --project=${{ matrix.project }}
  565. ```
  566. **Key Points**:
  567. - Projects enable testing across browsers, devices, and configurations
  568. - `devices` from `@playwright/test` provide preset configurations (Pixel 5, iPhone 13, etc.)
  569. - `dependencies` ensures setup project runs first (auth, data seeding)
  570. - `storageState` shares authentication across tests (0 seconds auth per test)
  571. - `testMatch` filters which tests run in which project
  572. - CI matrix strategy runs projects in parallel (4x faster with 4 projects)
  573. - `isMobile` context property for conditional logic in tests
  574. ## Integration Points
  575. - **Used in workflows**: `*framework` (config setup), `*ci` (parallelization, artifact upload)
  576. - **Related fragments**:
  577. - `fixture-architecture.md` - Fixture-based timeout overrides
  578. - `ci-burn-in.md` - CI pipeline artifact upload
  579. - `test-quality.md` - Timeout standards (no hard waits)
  580. - `data-factories.md` - Per-test isolation (no shared global state)
  581. ## Configuration Checklist
  582. **Before deploying tests, verify**:
  583. - [ ] Environment config map with fail-fast validation
  584. - [ ] Standardized timeouts (action 15s, navigation 30s, expect 10s, test 60s)
  585. - [ ] Artifact storage at `test-results/` and `playwright-report/`
  586. - [ ] HTML + JUnit reporters configured
  587. - [ ] `.env.example`, `.nvmrc`, browser versions committed
  588. - [ ] Parallelization configured (workers, sharding)
  589. - [ ] Projects defined for cross-browser/device testing (if needed)
  590. - [ ] CI uploads artifacts on failure with 30-day retention
  591. _Source: Playwright book repo, enterprise configuration example, Murat testing philosophy (lines 216-271)._