@bugzy-ai/bugzy 1.9.3 → 1.9.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +273 -273
  3. package/dist/cli/index.cjs +24 -50
  4. package/dist/cli/index.cjs.map +1 -1
  5. package/dist/cli/index.js +23 -49
  6. package/dist/cli/index.js.map +1 -1
  7. package/dist/index.cjs +21 -46
  8. package/dist/index.cjs.map +1 -1
  9. package/dist/index.js +21 -46
  10. package/dist/index.js.map +1 -1
  11. package/dist/subagents/index.cjs.map +1 -1
  12. package/dist/subagents/index.js.map +1 -1
  13. package/dist/subagents/metadata.cjs.map +1 -1
  14. package/dist/subagents/metadata.js.map +1 -1
  15. package/dist/tasks/index.cjs +20 -9
  16. package/dist/tasks/index.cjs.map +1 -1
  17. package/dist/tasks/index.js +20 -9
  18. package/dist/tasks/index.js.map +1 -1
  19. package/dist/templates/init/.bugzy/runtime/knowledge-base.md +61 -0
  20. package/dist/templates/init/.bugzy/runtime/knowledge-maintenance-guide.md +97 -0
  21. package/dist/templates/init/.bugzy/runtime/project-context.md +35 -0
  22. package/dist/templates/init/.bugzy/runtime/subagent-memory-guide.md +87 -0
  23. package/dist/templates/init/.bugzy/runtime/templates/test-plan-template.md +50 -0
  24. package/dist/templates/init/.bugzy/runtime/templates/test-result-schema.md +498 -0
  25. package/dist/templates/init/.bugzy/runtime/test-execution-strategy.md +535 -0
  26. package/dist/templates/init/.bugzy/runtime/testing-best-practices.md +632 -0
  27. package/dist/templates/init/.gitignore-template +25 -0
  28. package/package.json +95 -95
  29. package/templates/init/.bugzy/runtime/knowledge-base.md +61 -61
  30. package/templates/init/.bugzy/runtime/knowledge-maintenance-guide.md +97 -97
  31. package/templates/init/.bugzy/runtime/project-context.md +35 -35
  32. package/templates/init/.bugzy/runtime/subagent-memory-guide.md +87 -87
  33. package/templates/init/.bugzy/runtime/templates/test-plan-template.md +50 -50
  34. package/templates/init/.bugzy/runtime/templates/test-result-schema.md +498 -498
  35. package/templates/init/.bugzy/runtime/test-execution-strategy.md +535 -535
  36. package/templates/init/.bugzy/runtime/testing-best-practices.md +724 -724
  37. package/templates/init/.env.testdata +18 -18
  38. package/templates/init/.gitignore-template +24 -24
  39. package/templates/init/AGENTS.md +155 -155
  40. package/templates/init/CLAUDE.md +157 -157
  41. package/templates/init/test-runs/README.md +45 -45
  42. package/templates/playwright/BasePage.template.ts +190 -190
  43. package/templates/playwright/auth.setup.template.ts +89 -89
  44. package/templates/playwright/dataGenerators.helper.template.ts +148 -148
  45. package/templates/playwright/dateUtils.helper.template.ts +96 -96
  46. package/templates/playwright/pages.fixture.template.ts +50 -50
  47. package/templates/playwright/playwright.config.template.ts +97 -97
  48. package/templates/playwright/reporters/bugzy-reporter.ts +454 -454
@@ -1,724 +1,724 @@
1
- # Testing Best Practices Reference
2
-
3
- ## Two-Phase Test Automation Workflow
4
-
5
- **Critical Distinction**: Separate test scenario discovery from automation implementation.
6
-
7
- ### Phase 1: Test Scenario Discovery (WHAT to test)
8
-
9
- **Goal**: Understand application behavior and identify what needs testing coverage.
10
-
11
- **Activities**:
12
- - Explore features and user workflows through manual interaction
13
- - Identify critical user paths and edge cases
14
- - Document test scenarios in human-readable format
15
- - Evaluate automation ROI for each scenario
16
- - Create manual test case documentation
17
-
18
- **Output**: Test plan with prioritized scenarios and automation decisions
19
-
20
- ### Phase 2: Automation Implementation (HOW to automate)
21
-
22
- **Goal**: Build robust test automation framework validated with working tests.
23
-
24
- **Activities**:
25
- - Technical exploration to identify correct selectors
26
- - Create Page Object infrastructure
27
- - Generate ONE smoke test to validate framework
28
- - Run and debug until test passes consistently
29
- - Scale to additional tests only after validation
30
-
31
- **Output**: Working test automation with validated Page Objects
32
-
33
- ### The "Test One First" Validation Loop
34
-
35
- **CRITICAL**: Always validate your framework with ONE working test before scaling.
36
-
37
- ```
38
- 1. Explore app for selectors (use Playwright MCP or codegen)
39
- 2. Create Page Objects with verified selectors
40
- 3. Write ONE critical path test (e.g., login)
41
- 4. Run the test: npx playwright test <test-file>
42
- 5. If fails → Debug and fix → Go to step 4
43
- 6. If passes → Run 3-5 more times to ensure stability
44
- 7. Once stable → Scale to additional tests
45
- ```
46
-
47
- **Why this matters**:
48
- - Catches framework issues early (config, setup, auth)
49
- - Validates selectors work in real application
50
- - Prevents generating 50 broken tests
51
- - Builds confidence in Page Object reliability
52
-
53
- **Example validation workflow**:
54
- ```bash
55
- # Generate ONE test first
56
- npx playwright test tests/specs/auth/login.spec.ts
57
-
58
- # Run multiple times to verify stability
59
- npx playwright test tests/specs/auth/login.spec.ts --repeat-each=5
60
-
61
- # Check for flakiness
62
- npx playwright test tests/specs/auth/login.spec.ts --workers=1
63
-
64
- # Once stable, generate more tests
65
- ```
66
-
67
- ## Page Object Model (POM) Architecture
68
-
69
- **Core Principle**: Separate locators, actions, and assertions into distinct layers to isolate UI changes from test logic.
70
-
71
- ### Page Object Structure
72
-
73
- ```typescript
74
- import { type Page, type Locator } from '@playwright/test';
75
-
76
- export class LoginPage {
77
- readonly page: Page;
78
-
79
- // Centralized selectors as readonly properties
80
- readonly emailInput: Locator;
81
- readonly passwordInput: Locator;
82
- readonly loginButton: Locator;
83
-
84
- constructor(page: Page) {
85
- this.page = page;
86
- this.emailInput = page.getByLabel('Email');
87
- this.passwordInput = page.getByLabel('Password');
88
- this.loginButton = page.getByRole('button', { name: 'Sign In' });
89
- }
90
-
91
- async navigate(): Promise<void> {
92
- await this.page.goto('/login');
93
- }
94
-
95
- async login(email: string, password: string): Promise<void> {
96
- await this.emailInput.fill(email);
97
- await this.passwordInput.fill(password);
98
- await this.loginButton.click();
99
- }
100
- }
101
- ```
102
-
103
- ### Key Rules for Page Objects
104
-
105
- - ✅ Define all locators as `readonly` properties
106
- - ✅ Initialize locators in constructor
107
- - ✅ Use method names that describe actions (login, fillEmail, clickSubmit)
108
- - ✅ Return data, never assert in page objects
109
- - ❌ Never put `expect()` assertions in page objects
110
- - ❌ Never use hardcoded waits (`waitForTimeout`)
111
-
112
- ## Selector Priority (Most to Least Resilient)
113
-
114
- 1. **Role-based**: `page.getByRole('button', { name: 'Submit' })` - Best for semantic HTML
115
- 2. **Label**: `page.getByLabel('Email')` - Perfect for form inputs
116
- 3. **Text**: `page.getByText('Welcome back')` - Good for headings/static content
117
- 4. **Placeholder**: `page.getByPlaceholder('Enter email')` - Inputs without labels
118
- 5. **Test ID**: `page.getByTestId('submit-btn')` - Stable but requires data-testid attributes
119
- 6. **CSS selectors**: `page.locator('.btn-primary')` - Avoid; breaks with styling changes
120
-
121
- ### When to Use Test IDs
122
-
123
- Add `data-testid` attributes for:
124
- - Critical user flows (checkout, login, signup)
125
- - Complex components (data tables, multi-step forms)
126
- - Elements where role-based selectors are ambiguous
127
-
128
- ```html
129
- <button data-testid="checkout-submit">Complete Purchase</button>
130
- ```
131
-
132
- ```typescript
133
- await page.getByTestId('checkout-submit').click();
134
- ```
135
-
136
- ## Playwright Codegen for Selector Discovery
137
-
138
- **Playwright's built-in codegen is faster and more reliable than manual selector creation.**
139
-
140
- ### Using Codegen
141
-
142
- ```bash
143
- # Start codegen from specific URL
144
- npx playwright codegen https://your-app.com
145
-
146
- # With authentication (loads saved state)
147
- npx playwright codegen --load-storage=tests/.auth/user.json https://your-app.com
148
-
149
- # Target specific browser
150
- npx playwright codegen --browser=chromium https://your-app.com
151
- ```
152
-
153
- **Workflow**:
154
- 1. Run codegen and interact with your application
155
- 2. Playwright generates test code with verified selectors
156
- 3. Copy generated selectors to your Page Objects
157
- 4. Refactor code to follow Page Object Model pattern
158
- 5. Extract reusable logic to fixtures and helpers
159
-
160
- ### Hybrid Approach: Codegen + AI Refactoring
161
-
162
- ```
163
- 1. Use Playwright codegen → Generates working test with selectors
164
- 2. Use AI (Claude) → Refactor to Page Objects, extract fixtures, add types
165
- 3. Best of both worlds: Reliability (codegen) + Intelligence (AI)
166
- ```
167
-
168
- **Example**:
169
- ```typescript
170
- // Raw codegen output
171
- await page.goto('https://example.com/');
172
- await page.getByLabel('Email').click();
173
- await page.getByLabel('Email').fill('test@example.com');
174
-
175
- // After AI refactoring into Page Object
176
- class LoginPage {
177
- readonly emailInput = this.page.getByLabel('Email');
178
-
179
- async fillEmail(email: string) {
180
- await this.emailInput.fill(email);
181
- }
182
- }
183
- ```
184
-
185
- ## Smoke Test Strategy
186
-
187
- **Smoke tests are a minimal suite of critical path tests that validate core functionality.**
188
-
189
- ### Characteristics
190
-
191
- - **Fast**: Target < 5 minutes total execution time
192
- - **Critical**: Cover must-work features (login, core user flows)
193
- - **Stable**: High reliability, minimal flakiness
194
- - **CI/CD**: Run on every commit/pull request
195
-
196
- ### Tagging Smoke Tests
197
-
198
- ```typescript
199
- // tests/specs/auth/login.spec.ts
200
- test('should login with valid credentials @smoke', async ({ page }) => {
201
- // Critical path test
202
- });
203
-
204
- test('should show error with invalid password', async ({ page }) => {
205
- // Not tagged - functional test only
206
- });
207
- ```
208
-
209
- ### Running Smoke Tests
210
-
211
- ```bash
212
- # Run only smoke tests
213
- npx playwright test --grep @smoke
214
-
215
- # In CI/CD pipeline
216
- npx playwright test --grep @smoke --workers=2
217
-
218
- # Smoke tests as gate for full suite
219
- npx playwright test --grep @smoke && npx playwright test
220
- ```
221
-
222
- ### Smoke Test Suite Example
223
-
224
- ```
225
- @smoke test coverage:
226
- ✓ Login with valid credentials
227
- ✓ Navigate to dashboard
228
- ✓ Create new item (core feature)
229
- ✓ View item details
230
- ✓ Logout
231
-
232
- Target: < 5 minutes, 100% pass rate
233
- ```
234
-
235
- ## Test Organization
236
-
237
- ### File Structure by Feature
238
-
239
- ```
240
- tests/
241
- ├── specs/ # Tests organized by feature
242
- │ ├── auth/
243
- │ │ └── login.spec.ts
244
- │ └── checkout/
245
- │ └── purchase-flow.spec.ts
246
- ├── pages/ # Page Object Models
247
- │ ├── LoginPage.ts
248
- │ └── CheckoutPage.ts
249
- ├── components/ # Reusable UI components
250
- ├── fixtures/ # Custom test fixtures
251
- ├── helpers/ # Utility functions
252
- └── setup/ # Global setup/teardown
253
- ```
254
-
255
- ### Test Structure with test.step()
256
-
257
- **REQUIRED**: All tests must use `test.step()` to organize actions into high-level logical phases. This enables:
258
- - Video navigation by step (users can jump to specific phases in test execution videos)
259
- - Clear test structure and intent
260
- - Granular error tracking (know exactly which phase failed)
261
- - Better debugging with step-level timing
262
-
263
- ```typescript
264
- test.describe('Purchase flow', () => {
265
- test.beforeEach(async ({ page }) => {
266
- // Common setup
267
- });
268
-
269
- test('should complete purchase with credit card', async ({ page }) => {
270
- const checkoutPage = new CheckoutPage(page);
271
-
272
- await test.step('Add item to cart', async () => {
273
- await checkoutPage.addItemToCart('Product A');
274
- await expect(checkoutPage.cartCount).toHaveText('1');
275
- });
276
-
277
- await test.step('Navigate to checkout', async () => {
278
- await checkoutPage.goToCheckout();
279
- await expect(page).toHaveURL('/checkout');
280
- });
281
-
282
- await test.step('Fill payment information', async () => {
283
- await checkoutPage.fillPaymentInfo({
284
- cardNumber: '4111111111111111',
285
- expiry: '12/25',
286
- cvv: '123'
287
- });
288
- });
289
-
290
- await test.step('Submit order', async () => {
291
- await checkoutPage.submitOrder();
292
- await expect(page).toHaveURL('/confirmation');
293
- });
294
-
295
- await test.step('Verify order confirmation', async () => {
296
- await expect(checkoutPage.confirmationMessage).toBeVisible();
297
- await expect(checkoutPage.orderNumber).toContain('ORD-');
298
- });
299
- });
300
- });
301
- ```
302
-
303
- **Step Granularity Guidelines**:
304
- - Target **3-7 steps per test** for optimal video navigation
305
- - Each step should represent a logical phase (e.g., "Login", "Navigate to settings", "Update profile")
306
- - Avoid micro-steps (e.g., "Click button", "Fill field") - group related actions
307
- - Step titles should be user-friendly and descriptive
308
-
309
- ## Video-Synchronized Test Steps
310
-
311
- **REQUIRED for all tests**: Use `test.step()` API to create video-navigable test execution.
312
-
313
- ### Why test.step() is Required
314
-
315
- Every test generates a video recording with `steps.json` file containing:
316
- - Step-by-step breakdown of test actions
317
- - Video timestamps for each step (in seconds from test start)
318
- - Step status (success/failed)
319
- - Step duration
320
-
321
- This enables users to:
322
- - Click on a step to jump to that point in the video
323
- - See exactly when and where a test failed
324
- - Navigate through test execution like a timeline
325
- - Debug issues by reviewing specific test phases
326
-
327
- ### test.step() Best Practices
328
-
329
- ```typescript
330
- import { test, expect } from '@playwright/test';
331
-
332
- test('user can update profile settings', async ({ page }) => {
333
- const settingsPage = new SettingsPage(page);
334
- const profilePage = new ProfilePage(page);
335
-
336
- await test.step('Navigate to settings page', async () => {
337
- await settingsPage.navigate();
338
- await expect(settingsPage.pageHeading).toBeVisible();
339
- });
340
-
341
- await test.step('Open profile section', async () => {
342
- await settingsPage.clickProfileTab();
343
- await expect(profilePage.nameInput).toBeVisible();
344
- });
345
-
346
- await test.step('Update profile information', async () => {
347
- await profilePage.updateName('John Doe');
348
- await profilePage.updateEmail('john@example.com');
349
- });
350
-
351
- await test.step('Save changes', async () => {
352
- await profilePage.clickSaveButton();
353
- await expect(profilePage.successMessage).toBeVisible();
354
- });
355
-
356
- await test.step('Verify changes persisted', async () => {
357
- await page.reload();
358
- await expect(profilePage.nameInput).toHaveValue('John Doe');
359
- await expect(profilePage.emailInput).toHaveValue('john@example.com');
360
- });
361
- });
362
- ```
363
-
364
- ### What Gets Recorded in steps.json
365
-
366
- ```json
367
- {
368
- "steps": [
369
- {
370
- "index": 1,
371
- "timestamp": "2025-11-17T09:26:22.335Z",
372
- "videoTimeSeconds": 0,
373
- "action": "Navigate to settings page",
374
- "status": "success",
375
- "description": "Navigate to settings page - completed successfully",
376
- "technicalDetails": "test.step",
377
- "duration": 1234
378
- },
379
- {
380
- "index": 2,
381
- "timestamp": "2025-11-17T09:26:23.569Z",
382
- "videoTimeSeconds": 1,
383
- "action": "Open profile section",
384
- "status": "success",
385
- "description": "Open profile section - completed successfully",
386
- "technicalDetails": "test.step",
387
- "duration": 856
388
- }
389
- ],
390
- "summary": {
391
- "totalSteps": 5,
392
- "successfulSteps": 5,
393
- "failedSteps": 0,
394
- "skippedSteps": 0
395
- }
396
- }
397
- ```
398
-
399
- ### Step Naming Conventions
400
-
401
- ✅ **Good step names** (user-friendly, high-level):
402
- - "Navigate to login page"
403
- - "Login with valid credentials"
404
- - "Add item to cart"
405
- - "Complete checkout process"
406
- - "Verify order confirmation"
407
-
408
- ❌ **Bad step names** (too technical, too granular):
409
- - "Click the login button"
410
- - "Fill email field"
411
- - "Wait for page load"
412
- - "Assert element visible"
413
- - "page.goto('/login')"
414
-
415
- ### Smoke Test Example with test.step()
416
-
417
- ```typescript
418
- // tests/specs/auth/login.spec.ts
419
- test('should login and navigate through all main pages @smoke', async ({ page }) => {
420
- const loginPage = new LoginPage(page);
421
- const dashboardPage = new DashboardPage(page);
422
-
423
- await test.step('Navigate to login page', async () => {
424
- await loginPage.navigate();
425
- await expect(loginPage.pageHeading).toBeVisible();
426
- });
427
-
428
- await test.step('Login with valid credentials', async () => {
429
- await loginPage.login(
430
- process.env.TEST_OWNER_EMAIL!,
431
- process.env.TEST_OWNER_PASSWORD!
432
- );
433
- await page.waitForURL(/.*\/dashboard/);
434
- });
435
-
436
- await test.step('Navigate to Overview page', async () => {
437
- await dashboardPage.navigateToOverview();
438
- await expect(dashboardPage.overviewNavLink).toBeVisible();
439
- });
440
-
441
- await test.step('Navigate to Settings page', async () => {
442
- await dashboardPage.navigateToSettings();
443
- await expect(dashboardPage.settingsNavLink).toBeVisible();
444
- });
445
-
446
- await test.step('Logout and verify redirect', async () => {
447
- await dashboardPage.logout();
448
- await page.waitForURL(/.*\/login/);
449
- await expect(loginPage.pageHeading).toBeVisible();
450
- });
451
- });
452
- ```
453
-
454
- ## Authentication & Session Management
455
-
456
- **Always authenticate once and reuse session state** across tests.
457
-
458
- ```typescript
459
- // tests/setup/auth.setup.ts
460
- import { test as setup } from '@playwright/test';
461
-
462
- const authFile = 'tests/.auth/user.json';
463
-
464
- setup('authenticate', async ({ page }) => {
465
- await page.goto('/login');
466
- await page.getByLabel('Email').fill(process.env.USER_EMAIL!);
467
- await page.getByLabel('Password').fill(process.env.USER_PASSWORD!);
468
- await page.getByRole('button', { name: 'Sign in' }).click();
469
-
470
- await page.waitForURL('/dashboard');
471
- await page.context().storageState({ path: authFile });
472
- });
473
- ```
474
-
475
- Configure in `playwright.config.ts`:
476
-
477
- ```typescript
478
- projects: [
479
- { name: 'setup', testMatch: /.*\.setup\.ts/ },
480
- {
481
- name: 'chromium',
482
- use: { storageState: 'tests/.auth/user.json' },
483
- dependencies: ['setup'],
484
- },
485
- ]
486
- ```
487
-
488
- ## Async Operations & Waiting
489
-
490
- ### Use Built-in Auto-waiting
491
-
492
- Playwright automatically waits for elements to be:
493
- - Visible
494
- - Enabled
495
- - Stable (not animating)
496
- - Ready to receive events
497
-
498
- ```typescript
499
- // ✅ GOOD: Auto-waiting
500
- await page.click('#submit');
501
- await expect(page.locator('.result')).toBeVisible();
502
-
503
- // ❌ BAD: Manual arbitrary wait
504
- await page.click('#submit');
505
- await page.waitForTimeout(3000);
506
- ```
507
-
508
- ### Explicit Waiting (when needed)
509
-
510
- ```typescript
511
- // Wait for element state
512
- await page.locator('.loading').waitFor({ state: 'hidden' });
513
-
514
- // Wait for URL change
515
- await page.waitForURL('**/dashboard');
516
-
517
- // Wait for network request
518
- const response = await page.waitForResponse(
519
- resp => resp.url().includes('/api/data') && resp.status() === 200
520
- );
521
- ```
522
-
523
- ## Common Anti-Patterns to Avoid
524
-
525
- | ❌ Anti-Pattern | ✅ Correct Approach |
526
- |----------------|-------------------|
527
- | `await page.waitForTimeout(3000)` | `await expect(element).toBeVisible()` |
528
- | `const el = await page.$('.btn')` | `await page.locator('.btn').click()` |
529
- | Tests depend on execution order | Each test is fully independent |
530
- | Assertions in Page Objects | Assertions only in test files |
531
- | `#app > div:nth-child(2) > button` | `page.getByRole('button', { name: 'Submit' })` |
532
- | `retries: 5` to mask flakiness | `retries: 2` + fix root cause |
533
-
534
- ## Debugging Workflow
535
-
536
- When a test fails:
537
-
538
- 1. **Reproduce locally**: `npx playwright test failing-test.spec.ts --headed`
539
- 2. **Enable trace**: `npx playwright test --trace on`
540
- 3. **View trace**: `npx playwright show-trace test-results/.../trace.zip`
541
- 4. **Identify failure**: Scrub timeline, check DOM snapshots
542
- 5. **Review network**: Look for failed API calls
543
- 6. **Check console**: JavaScript errors/warnings
544
- 7. **Fix selector**: Use inspector's locator picker
545
- 8. **Verify fix**: Run test 10 times to ensure stability
546
-
547
- ## API Testing for Speed
548
-
549
- **Use API calls for test setup** (10-20x faster than UI):
550
-
551
- ```typescript
552
- test('should display user dashboard', async ({ request, page }) => {
553
- // FAST: Create test data via API
554
- await request.post('/api/users', {
555
- data: { name: 'Test User', email: 'test@example.com' }
556
- });
557
-
558
- // UI: Test the actual user experience
559
- await page.goto('/dashboard');
560
- await expect(page.getByText('Test User')).toBeVisible();
561
- });
562
- ```
563
-
564
- ## Configuration Essentials
565
-
566
- ```typescript
567
- // playwright.config.ts
568
- export default defineConfig({
569
- testDir: './tests/specs',
570
- fullyParallel: true,
571
- retries: process.env.CI ? 2 : 0,
572
- workers: process.env.CI ? 1 : undefined,
573
-
574
- timeout: 30000,
575
- expect: { timeout: 5000 },
576
-
577
- use: {
578
- baseURL: process.env.BASE_URL,
579
- trace: 'on-first-retry',
580
- screenshot: 'only-on-failure',
581
- video: 'retain-on-failure',
582
- actionTimeout: 10000,
583
- },
584
- });
585
- ```
586
-
587
- ## Test Cleanup Strategy
588
-
589
- **Design Principle**: Each test cleans up what IT creates. No pre-cleanup needed.
590
-
591
- ### Why Post-Test Cleanup (Not Pre-Test)
592
-
593
- Pre-test cleanup adds complexity:
594
- - Extra login/logout cycles before each test
595
- - Longer timeouts (5 min vs 3 min)
596
- - Test code cluttered with cleanup steps
597
- - Test failures can leave cleanup incomplete
598
-
599
- Post-test cleanup via fixtures is better:
600
- - Runs AFTER test completes (pass or fail)
601
- - Automatic - no manual steps in test code
602
- - Cleaner test flow focused on main scenario
603
- - Guaranteed execution
604
-
605
- ### Cleanup Fixture Pattern
606
-
607
- ```typescript
608
- // Import from cleanup fixture instead of pages fixture
609
- import { test, expect } from '../../fixtures/cleanup.fixture';
610
-
611
- test('create absence', async ({
612
- page,
613
- loginPage,
614
- navigationPage,
615
- myAbsencesPage,
616
- withAbsenceCleanup // Triggers cleanup after test
617
- }) => {
618
- // Reference fixture to satisfy TypeScript
619
- void withAbsenceCleanup;
620
-
621
- // Main test flow - no pre-cleanup needed
622
- await test.step('Login as employee', async () => {
623
- await loginPage.navigateToLogin();
624
- await loginPage.login(email, password);
625
- // ...
626
- });
627
-
628
- await test.step('Create absence with privacy="Yes"', async () => {
629
- // CRITICAL: Use privacy="Yes" so admin can see and delete during cleanup
630
- await myAbsencesPage.createAbsence({ privacy: 'Yes', ... });
631
- });
632
-
633
- // Cleanup runs automatically after test (pass or fail)
634
- });
635
- ```
636
-
637
- ### When to Use Cleanup Fixtures
638
-
639
- | Scenario | Use Cleanup Fixture? |
640
- |----------|---------------------|
641
- | Test creates data (absences, users, etc.) | Yes - use `cleanup.fixture.ts` |
642
- | Read-only test (verify UI, check dropdown options) | No - use `pages.fixture.ts` |
643
- | Test modifies then deletes own data | Optional - cleanup acts as safety net |
644
-
645
- ### Creating Custom Cleanup Fixtures
646
-
647
- ```typescript
648
- // tests/fixtures/cleanup.fixture.ts
649
- export const test = pagesTest.extend<CleanupFixtures>({
650
- withAbsenceCleanup: [
651
- async ({ page }, use) => {
652
- // Run the test first
653
- await use();
654
-
655
- // POST-TEST CLEANUP: Clean up what the test created
656
- await cleanupTestAbsences({ page, ... });
657
- },
658
- { auto: false }, // Must be explicitly requested
659
- ],
660
- });
661
- ```
662
-
663
- Key implementation details:
664
- - `await use()` runs the test
665
- - Cleanup code runs AFTER `use()` returns
666
- - `{ auto: false }` means test must include fixture in parameters
667
- - Cleanup should handle errors gracefully (non-blocking)
668
-
669
- ### Privacy Setting for Admin Cleanup
670
-
671
- **CRITICAL**: When admin cleans up absences created by employee tests:
672
- - Absences with `privacy="Yes"` are visible to admin
673
- - Absences with `privacy="No"` are hidden from admin
674
-
675
- **Rule**: Tests MUST create absences with `privacy="Yes"` to enable admin cleanup.
676
-
677
- ## Production-Ready Checklist
678
-
679
- **Configuration:**
680
- - [ ] Parallel execution enabled (`fullyParallel: true`)
681
- - [ ] Retry strategy configured (2 in CI, 0 locally)
682
- - [ ] Base URL from environment variables
683
- - [ ] Artifact capture optimized (`on-first-retry`)
684
-
685
- **Architecture:**
686
- - [ ] Page Object Model for all major pages
687
- - [ ] Component Objects for reusable UI elements
688
- - [ ] Custom fixtures for common setup
689
- - [ ] Tests organized by feature/user journey
690
-
691
- **Best Practices:**
692
- - [ ] No `waitForTimeout()` usage
693
- - [ ] Tests are independent (run in any order)
694
- - [ ] Assertions in test files, not Page Objects
695
- - [ ] Role-based selectors prioritized
696
- - [ ] No hardcoded credentials
697
- - [ ] Framework validated with ONE working test before scaling
698
- - [ ] Smoke tests tagged with @smoke for CI/CD
699
- - [ ] All tests use `test.step()` for video-navigable execution (3-7 steps per test)
700
- - [ ] Tests that create data use cleanup fixtures (post-test cleanup, no pre-cleanup)
701
-
702
- **Test Independence Validation:**
703
- - [ ] Each test can run in isolation: `npx playwright test <single-test>`
704
- - [ ] Tests pass in parallel: `npx playwright test --workers=4`
705
- - [ ] Tests pass in random order: `npx playwright test --shard=1/3` (run multiple shards)
706
- - [ ] No shared state between tests (each uses fixtures)
707
- - [ ] Tests cleanup after themselves (via fixtures or API)
708
-
709
- **CI/CD:**
710
- - [ ] Smoke tests run on every commit (`npx playwright test --grep @smoke`)
711
- - [ ] Full suite runs on pull requests
712
- - [ ] Artifacts uploaded (reports, traces)
713
- - [ ] Failure notifications configured
714
- - [ ] Test results published to PR comments
715
-
716
- ---
717
-
718
- **Remember**: The six critical pillars are:
719
- 1. **Two-Phase Approach** - Separate WHAT to test from HOW to automate
720
- 2. **Test One First** - Validate framework with ONE working test before scaling
721
- 3. **Page Object Model** - Isolate UI changes from test logic
722
- 4. **Role-based selectors** - Resist breakage with semantic HTML
723
- 5. **Authentication state reuse** - Maximize speed and reliability
724
- 6. **Post-test cleanup** - Each test cleans up what IT creates via fixtures
1
+ # Testing Best Practices Reference
2
+
3
+ ## Two-Phase Test Automation Workflow
4
+
5
+ **Critical Distinction**: Separate test scenario discovery from automation implementation.
6
+
7
+ ### Phase 1: Test Scenario Discovery (WHAT to test)
8
+
9
+ **Goal**: Understand application behavior and identify what needs testing coverage.
10
+
11
+ **Activities**:
12
+ - Explore features and user workflows through manual interaction
13
+ - Identify critical user paths and edge cases
14
+ - Document test scenarios in human-readable format
15
+ - Evaluate automation ROI for each scenario
16
+ - Create manual test case documentation
17
+
18
+ **Output**: Test plan with prioritized scenarios and automation decisions
19
+
20
+ ### Phase 2: Automation Implementation (HOW to automate)
21
+
22
+ **Goal**: Build robust test automation framework validated with working tests.
23
+
24
+ **Activities**:
25
+ - Technical exploration to identify correct selectors
26
+ - Create Page Object infrastructure
27
+ - Generate ONE smoke test to validate framework
28
+ - Run and debug until test passes consistently
29
+ - Scale to additional tests only after validation
30
+
31
+ **Output**: Working test automation with validated Page Objects
32
+
33
+ ### The "Test One First" Validation Loop
34
+
35
+ **CRITICAL**: Always validate your framework with ONE working test before scaling.
36
+
37
+ ```
38
+ 1. Explore app for selectors (use Playwright MCP or codegen)
39
+ 2. Create Page Objects with verified selectors
40
+ 3. Write ONE critical path test (e.g., login)
41
+ 4. Run the test: npx playwright test <test-file>
42
+ 5. If fails → Debug and fix → Go to step 4
43
+ 6. If passes → Run 3-5 more times to ensure stability
44
+ 7. Once stable → Scale to additional tests
45
+ ```
46
+
47
+ **Why this matters**:
48
+ - Catches framework issues early (config, setup, auth)
49
+ - Validates selectors work in real application
50
+ - Prevents generating 50 broken tests
51
+ - Builds confidence in Page Object reliability
52
+
53
+ **Example validation workflow**:
54
+ ```bash
55
+ # Generate ONE test first
56
+ npx playwright test tests/specs/auth/login.spec.ts
57
+
58
+ # Run multiple times to verify stability
59
+ npx playwright test tests/specs/auth/login.spec.ts --repeat-each=5
60
+
61
+ # Check for flakiness
62
+ npx playwright test tests/specs/auth/login.spec.ts --workers=1
63
+
64
+ # Once stable, generate more tests
65
+ ```
66
+
67
+ ## Page Object Model (POM) Architecture
68
+
69
+ **Core Principle**: Separate locators, actions, and assertions into distinct layers to isolate UI changes from test logic.
70
+
71
+ ### Page Object Structure
72
+
73
+ ```typescript
74
+ import { type Page, type Locator } from '@playwright/test';
75
+
76
+ export class LoginPage {
77
+ readonly page: Page;
78
+
79
+ // Centralized selectors as readonly properties
80
+ readonly emailInput: Locator;
81
+ readonly passwordInput: Locator;
82
+ readonly loginButton: Locator;
83
+
84
+ constructor(page: Page) {
85
+ this.page = page;
86
+ this.emailInput = page.getByLabel('Email');
87
+ this.passwordInput = page.getByLabel('Password');
88
+ this.loginButton = page.getByRole('button', { name: 'Sign In' });
89
+ }
90
+
91
+ async navigate(): Promise<void> {
92
+ await this.page.goto('/login');
93
+ }
94
+
95
+ async login(email: string, password: string): Promise<void> {
96
+ await this.emailInput.fill(email);
97
+ await this.passwordInput.fill(password);
98
+ await this.loginButton.click();
99
+ }
100
+ }
101
+ ```
102
+
103
+ ### Key Rules for Page Objects
104
+
105
+ - ✅ Define all locators as `readonly` properties
106
+ - ✅ Initialize locators in constructor
107
+ - ✅ Use method names that describe actions (login, fillEmail, clickSubmit)
108
+ - ✅ Return data, never assert in page objects
109
+ - ❌ Never put `expect()` assertions in page objects
110
+ - ❌ Never use hardcoded waits (`waitForTimeout`)
111
+
112
+ ## Selector Priority (Most to Least Resilient)
113
+
114
+ 1. **Role-based**: `page.getByRole('button', { name: 'Submit' })` - Best for semantic HTML
115
+ 2. **Label**: `page.getByLabel('Email')` - Perfect for form inputs
116
+ 3. **Text**: `page.getByText('Welcome back')` - Good for headings/static content
117
+ 4. **Placeholder**: `page.getByPlaceholder('Enter email')` - Inputs without labels
118
+ 5. **Test ID**: `page.getByTestId('submit-btn')` - Stable but requires data-testid attributes
119
+ 6. **CSS selectors**: `page.locator('.btn-primary')` - Avoid; breaks with styling changes
120
+
121
+ ### When to Use Test IDs
122
+
123
+ Add `data-testid` attributes for:
124
+ - Critical user flows (checkout, login, signup)
125
+ - Complex components (data tables, multi-step forms)
126
+ - Elements where role-based selectors are ambiguous
127
+
128
+ ```html
129
+ <button data-testid="checkout-submit">Complete Purchase</button>
130
+ ```
131
+
132
+ ```typescript
133
+ await page.getByTestId('checkout-submit').click();
134
+ ```
135
+
136
+ ## Playwright Codegen for Selector Discovery
137
+
138
+ **Playwright's built-in codegen is faster and more reliable than manual selector creation.**
139
+
140
+ ### Using Codegen
141
+
142
+ ```bash
143
+ # Start codegen from specific URL
144
+ npx playwright codegen https://your-app.com
145
+
146
+ # With authentication (loads saved state)
147
+ npx playwright codegen --load-storage=tests/.auth/user.json https://your-app.com
148
+
149
+ # Target specific browser
150
+ npx playwright codegen --browser=chromium https://your-app.com
151
+ ```
152
+
153
+ **Workflow**:
154
+ 1. Run codegen and interact with your application
155
+ 2. Playwright generates test code with verified selectors
156
+ 3. Copy generated selectors to your Page Objects
157
+ 4. Refactor code to follow Page Object Model pattern
158
+ 5. Extract reusable logic to fixtures and helpers
159
+
160
+ ### Hybrid Approach: Codegen + AI Refactoring
161
+
162
+ ```
163
+ 1. Use Playwright codegen → Generates working test with selectors
164
+ 2. Use AI (Claude) → Refactor to Page Objects, extract fixtures, add types
165
+ 3. Best of both worlds: Reliability (codegen) + Intelligence (AI)
166
+ ```
167
+
168
+ **Example**:
169
+ ```typescript
170
+ // Raw codegen output
171
+ await page.goto('https://example.com/');
172
+ await page.getByLabel('Email').click();
173
+ await page.getByLabel('Email').fill('test@example.com');
174
+
175
+ // After AI refactoring into Page Object
176
+ class LoginPage {
177
+ readonly emailInput = this.page.getByLabel('Email');
178
+
179
+ async fillEmail(email: string) {
180
+ await this.emailInput.fill(email);
181
+ }
182
+ }
183
+ ```
184
+
185
+ ## Smoke Test Strategy
186
+
187
+ **Smoke tests are a minimal suite of critical path tests that validate core functionality.**
188
+
189
+ ### Characteristics
190
+
191
+ - **Fast**: Target < 5 minutes total execution time
192
+ - **Critical**: Cover must-work features (login, core user flows)
193
+ - **Stable**: High reliability, minimal flakiness
194
+ - **CI/CD**: Run on every commit/pull request
195
+
196
+ ### Tagging Smoke Tests
197
+
198
+ ```typescript
199
+ // tests/specs/auth/login.spec.ts
200
+ test('should login with valid credentials @smoke', async ({ page }) => {
201
+ // Critical path test
202
+ });
203
+
204
+ test('should show error with invalid password', async ({ page }) => {
205
+ // Not tagged - functional test only
206
+ });
207
+ ```
208
+
209
+ ### Running Smoke Tests
210
+
211
+ ```bash
212
+ # Run only smoke tests
213
+ npx playwright test --grep @smoke
214
+
215
+ # In CI/CD pipeline
216
+ npx playwright test --grep @smoke --workers=2
217
+
218
+ # Smoke tests as gate for full suite
219
+ npx playwright test --grep @smoke && npx playwright test
220
+ ```
221
+
222
+ ### Smoke Test Suite Example
223
+
224
+ ```
225
+ @smoke test coverage:
226
+ ✓ Login with valid credentials
227
+ ✓ Navigate to dashboard
228
+ ✓ Create new item (core feature)
229
+ ✓ View item details
230
+ ✓ Logout
231
+
232
+ Target: < 5 minutes, 100% pass rate
233
+ ```
234
+
235
+ ## Test Organization
236
+
237
+ ### File Structure by Feature
238
+
239
+ ```
240
+ tests/
241
+ ├── specs/ # Tests organized by feature
242
+ │ ├── auth/
243
+ │ │ └── login.spec.ts
244
+ │ └── checkout/
245
+ │ └── purchase-flow.spec.ts
246
+ ├── pages/ # Page Object Models
247
+ │ ├── LoginPage.ts
248
+ │ └── CheckoutPage.ts
249
+ ├── components/ # Reusable UI components
250
+ ├── fixtures/ # Custom test fixtures
251
+ ├── helpers/ # Utility functions
252
+ └── setup/ # Global setup/teardown
253
+ ```
254
+
255
+ ### Test Structure with test.step()
256
+
257
+ **REQUIRED**: All tests must use `test.step()` to organize actions into high-level logical phases. This enables:
258
+ - Video navigation by step (users can jump to specific phases in test execution videos)
259
+ - Clear test structure and intent
260
+ - Granular error tracking (know exactly which phase failed)
261
+ - Better debugging with step-level timing
262
+
263
+ ```typescript
264
+ test.describe('Purchase flow', () => {
265
+ test.beforeEach(async ({ page }) => {
266
+ // Common setup
267
+ });
268
+
269
+ test('should complete purchase with credit card', async ({ page }) => {
270
+ const checkoutPage = new CheckoutPage(page);
271
+
272
+ await test.step('Add item to cart', async () => {
273
+ await checkoutPage.addItemToCart('Product A');
274
+ await expect(checkoutPage.cartCount).toHaveText('1');
275
+ });
276
+
277
+ await test.step('Navigate to checkout', async () => {
278
+ await checkoutPage.goToCheckout();
279
+ await expect(page).toHaveURL('/checkout');
280
+ });
281
+
282
+ await test.step('Fill payment information', async () => {
283
+ await checkoutPage.fillPaymentInfo({
284
+ cardNumber: '4111111111111111',
285
+ expiry: '12/25',
286
+ cvv: '123'
287
+ });
288
+ });
289
+
290
+ await test.step('Submit order', async () => {
291
+ await checkoutPage.submitOrder();
292
+ await expect(page).toHaveURL('/confirmation');
293
+ });
294
+
295
+ await test.step('Verify order confirmation', async () => {
296
+ await expect(checkoutPage.confirmationMessage).toBeVisible();
297
+ await expect(checkoutPage.orderNumber).toContain('ORD-');
298
+ });
299
+ });
300
+ });
301
+ ```
302
+
303
+ **Step Granularity Guidelines**:
304
+ - Target **3-7 steps per test** for optimal video navigation
305
+ - Each step should represent a logical phase (e.g., "Login", "Navigate to settings", "Update profile")
306
+ - Avoid micro-steps (e.g., "Click button", "Fill field") - group related actions
307
+ - Step titles should be user-friendly and descriptive
308
+
309
+ ## Video-Synchronized Test Steps
310
+
311
+ **REQUIRED for all tests**: Use `test.step()` API to create video-navigable test execution.
312
+
313
+ ### Why test.step() is Required
314
+
315
+ Every test generates a video recording with `steps.json` file containing:
316
+ - Step-by-step breakdown of test actions
317
+ - Video timestamps for each step (in seconds from test start)
318
+ - Step status (success/failed)
319
+ - Step duration
320
+
321
+ This enables users to:
322
+ - Click on a step to jump to that point in the video
323
+ - See exactly when and where a test failed
324
+ - Navigate through test execution like a timeline
325
+ - Debug issues by reviewing specific test phases
326
+
327
+ ### test.step() Best Practices
328
+
329
+ ```typescript
330
+ import { test, expect } from '@playwright/test';
331
+
332
+ test('user can update profile settings', async ({ page }) => {
333
+ const settingsPage = new SettingsPage(page);
334
+ const profilePage = new ProfilePage(page);
335
+
336
+ await test.step('Navigate to settings page', async () => {
337
+ await settingsPage.navigate();
338
+ await expect(settingsPage.pageHeading).toBeVisible();
339
+ });
340
+
341
+ await test.step('Open profile section', async () => {
342
+ await settingsPage.clickProfileTab();
343
+ await expect(profilePage.nameInput).toBeVisible();
344
+ });
345
+
346
+ await test.step('Update profile information', async () => {
347
+ await profilePage.updateName('John Doe');
348
+ await profilePage.updateEmail('john@example.com');
349
+ });
350
+
351
+ await test.step('Save changes', async () => {
352
+ await profilePage.clickSaveButton();
353
+ await expect(profilePage.successMessage).toBeVisible();
354
+ });
355
+
356
+ await test.step('Verify changes persisted', async () => {
357
+ await page.reload();
358
+ await expect(profilePage.nameInput).toHaveValue('John Doe');
359
+ await expect(profilePage.emailInput).toHaveValue('john@example.com');
360
+ });
361
+ });
362
+ ```
363
+
364
+ ### What Gets Recorded in steps.json
365
+
366
+ ```json
367
+ {
368
+ "steps": [
369
+ {
370
+ "index": 1,
371
+ "timestamp": "2025-11-17T09:26:22.335Z",
372
+ "videoTimeSeconds": 0,
373
+ "action": "Navigate to settings page",
374
+ "status": "success",
375
+ "description": "Navigate to settings page - completed successfully",
376
+ "technicalDetails": "test.step",
377
+ "duration": 1234
378
+ },
379
+ {
380
+ "index": 2,
381
+ "timestamp": "2025-11-17T09:26:23.569Z",
382
+ "videoTimeSeconds": 1,
383
+ "action": "Open profile section",
384
+ "status": "success",
385
+ "description": "Open profile section - completed successfully",
386
+ "technicalDetails": "test.step",
387
+ "duration": 856
388
+ }
389
+ ],
390
+ "summary": {
391
+ "totalSteps": 5,
392
+ "successfulSteps": 5,
393
+ "failedSteps": 0,
394
+ "skippedSteps": 0
395
+ }
396
+ }
397
+ ```
398
+
399
+ ### Step Naming Conventions
400
+
401
+ ✅ **Good step names** (user-friendly, high-level):
402
+ - "Navigate to login page"
403
+ - "Login with valid credentials"
404
+ - "Add item to cart"
405
+ - "Complete checkout process"
406
+ - "Verify order confirmation"
407
+
408
+ ❌ **Bad step names** (too technical, too granular):
409
+ - "Click the login button"
410
+ - "Fill email field"
411
+ - "Wait for page load"
412
+ - "Assert element visible"
413
+ - "page.goto('/login')"
414
+
415
+ ### Smoke Test Example with test.step()
416
+
417
+ ```typescript
418
+ // tests/specs/auth/login.spec.ts
419
+ test('should login and navigate through all main pages @smoke', async ({ page }) => {
420
+ const loginPage = new LoginPage(page);
421
+ const dashboardPage = new DashboardPage(page);
422
+
423
+ await test.step('Navigate to login page', async () => {
424
+ await loginPage.navigate();
425
+ await expect(loginPage.pageHeading).toBeVisible();
426
+ });
427
+
428
+ await test.step('Login with valid credentials', async () => {
429
+ await loginPage.login(
430
+ process.env.TEST_OWNER_EMAIL!,
431
+ process.env.TEST_OWNER_PASSWORD!
432
+ );
433
+ await page.waitForURL(/.*\/dashboard/);
434
+ });
435
+
436
+ await test.step('Navigate to Overview page', async () => {
437
+ await dashboardPage.navigateToOverview();
438
+ await expect(dashboardPage.overviewNavLink).toBeVisible();
439
+ });
440
+
441
+ await test.step('Navigate to Settings page', async () => {
442
+ await dashboardPage.navigateToSettings();
443
+ await expect(dashboardPage.settingsNavLink).toBeVisible();
444
+ });
445
+
446
+ await test.step('Logout and verify redirect', async () => {
447
+ await dashboardPage.logout();
448
+ await page.waitForURL(/.*\/login/);
449
+ await expect(loginPage.pageHeading).toBeVisible();
450
+ });
451
+ });
452
+ ```
453
+
454
+ ## Authentication & Session Management
455
+
456
+ **Always authenticate once and reuse session state** across tests.
457
+
458
+ ```typescript
459
+ // tests/setup/auth.setup.ts
460
+ import { test as setup } from '@playwright/test';
461
+
462
+ const authFile = 'tests/.auth/user.json';
463
+
464
+ setup('authenticate', async ({ page }) => {
465
+ await page.goto('/login');
466
+ await page.getByLabel('Email').fill(process.env.USER_EMAIL!);
467
+ await page.getByLabel('Password').fill(process.env.USER_PASSWORD!);
468
+ await page.getByRole('button', { name: 'Sign in' }).click();
469
+
470
+ await page.waitForURL('/dashboard');
471
+ await page.context().storageState({ path: authFile });
472
+ });
473
+ ```
474
+
475
+ Configure in `playwright.config.ts`:
476
+
477
+ ```typescript
478
+ projects: [
479
+ { name: 'setup', testMatch: /.*\.setup\.ts/ },
480
+ {
481
+ name: 'chromium',
482
+ use: { storageState: 'tests/.auth/user.json' },
483
+ dependencies: ['setup'],
484
+ },
485
+ ]
486
+ ```
487
+
488
+ ## Async Operations & Waiting
489
+
490
+ ### Use Built-in Auto-waiting
491
+
492
+ Playwright automatically waits for elements to be:
493
+ - Visible
494
+ - Enabled
495
+ - Stable (not animating)
496
+ - Ready to receive events
497
+
498
+ ```typescript
499
+ // ✅ GOOD: Auto-waiting
500
+ await page.click('#submit');
501
+ await expect(page.locator('.result')).toBeVisible();
502
+
503
+ // ❌ BAD: Manual arbitrary wait
504
+ await page.click('#submit');
505
+ await page.waitForTimeout(3000);
506
+ ```
507
+
508
+ ### Explicit Waiting (when needed)
509
+
510
+ ```typescript
511
+ // Wait for element state
512
+ await page.locator('.loading').waitFor({ state: 'hidden' });
513
+
514
+ // Wait for URL change
515
+ await page.waitForURL('**/dashboard');
516
+
517
+ // Wait for network request
518
+ const response = await page.waitForResponse(
519
+ resp => resp.url().includes('/api/data') && resp.status() === 200
520
+ );
521
+ ```
522
+
523
+ ## Common Anti-Patterns to Avoid
524
+
525
+ | ❌ Anti-Pattern | ✅ Correct Approach |
526
+ |----------------|-------------------|
527
+ | `await page.waitForTimeout(3000)` | `await expect(element).toBeVisible()` |
528
+ | `const el = await page.$('.btn')` | `await page.locator('.btn').click()` |
529
+ | Tests depend on execution order | Each test is fully independent |
530
+ | Assertions in Page Objects | Assertions only in test files |
531
+ | `#app > div:nth-child(2) > button` | `page.getByRole('button', { name: 'Submit' })` |
532
+ | `retries: 5` to mask flakiness | `retries: 2` + fix root cause |
533
+
534
+ ## Debugging Workflow
535
+
536
+ When a test fails:
537
+
538
+ 1. **Reproduce locally**: `npx playwright test failing-test.spec.ts --headed`
539
+ 2. **Enable trace**: `npx playwright test --trace on`
540
+ 3. **View trace**: `npx playwright show-trace test-results/.../trace.zip`
541
+ 4. **Identify failure**: Scrub timeline, check DOM snapshots
542
+ 5. **Review network**: Look for failed API calls
543
+ 6. **Check console**: JavaScript errors/warnings
544
+ 7. **Fix selector**: Use inspector's locator picker
545
+ 8. **Verify fix**: Run test 10 times to ensure stability
546
+
547
+ ## API Testing for Speed
548
+
549
+ **Use API calls for test setup** (10-20x faster than UI):
550
+
551
+ ```typescript
552
+ test('should display user dashboard', async ({ request, page }) => {
553
+ // FAST: Create test data via API
554
+ await request.post('/api/users', {
555
+ data: { name: 'Test User', email: 'test@example.com' }
556
+ });
557
+
558
+ // UI: Test the actual user experience
559
+ await page.goto('/dashboard');
560
+ await expect(page.getByText('Test User')).toBeVisible();
561
+ });
562
+ ```
563
+
564
+ ## Configuration Essentials
565
+
566
+ ```typescript
567
+ // playwright.config.ts
568
+ export default defineConfig({
569
+ testDir: './tests/specs',
570
+ fullyParallel: true,
571
+ retries: process.env.CI ? 2 : 0,
572
+ workers: process.env.CI ? 1 : undefined,
573
+
574
+ timeout: 30000,
575
+ expect: { timeout: 5000 },
576
+
577
+ use: {
578
+ baseURL: process.env.BASE_URL,
579
+ trace: 'on-first-retry',
580
+ screenshot: 'only-on-failure',
581
+ video: 'retain-on-failure',
582
+ actionTimeout: 10000,
583
+ },
584
+ });
585
+ ```
586
+
587
+ ## Test Cleanup Strategy
588
+
589
+ **Design Principle**: Each test cleans up what IT creates. No pre-cleanup needed.
590
+
591
+ ### Why Post-Test Cleanup (Not Pre-Test)
592
+
593
+ Pre-test cleanup adds complexity:
594
+ - Extra login/logout cycles before each test
595
+ - Longer timeouts (5 min vs 3 min)
596
+ - Test code cluttered with cleanup steps
597
+ - Test failures can leave cleanup incomplete
598
+
599
+ Post-test cleanup via fixtures is better:
600
+ - Runs AFTER test completes (pass or fail)
601
+ - Automatic - no manual steps in test code
602
+ - Cleaner test flow focused on main scenario
603
+ - Guaranteed execution
604
+
605
+ ### Cleanup Fixture Pattern
606
+
607
+ ```typescript
608
+ // Import from cleanup fixture instead of pages fixture
609
+ import { test, expect } from '../../fixtures/cleanup.fixture';
610
+
611
+ test('create absence', async ({
612
+ page,
613
+ loginPage,
614
+ navigationPage,
615
+ myAbsencesPage,
616
+ withAbsenceCleanup // Triggers cleanup after test
617
+ }) => {
618
+ // Reference fixture to satisfy TypeScript
619
+ void withAbsenceCleanup;
620
+
621
+ // Main test flow - no pre-cleanup needed
622
+ await test.step('Login as employee', async () => {
623
+ await loginPage.navigateToLogin();
624
+ await loginPage.login(email, password);
625
+ // ...
626
+ });
627
+
628
+ await test.step('Create absence with privacy="Yes"', async () => {
629
+ // CRITICAL: Use privacy="Yes" so admin can see and delete during cleanup
630
+ await myAbsencesPage.createAbsence({ privacy: 'Yes', ... });
631
+ });
632
+
633
+ // Cleanup runs automatically after test (pass or fail)
634
+ });
635
+ ```
636
+
637
+ ### When to Use Cleanup Fixtures
638
+
639
+ | Scenario | Use Cleanup Fixture? |
640
+ |----------|---------------------|
641
+ | Test creates data (absences, users, etc.) | Yes - use `cleanup.fixture.ts` |
642
+ | Read-only test (verify UI, check dropdown options) | No - use `pages.fixture.ts` |
643
+ | Test modifies then deletes own data | Optional - cleanup acts as safety net |
644
+
645
+ ### Creating Custom Cleanup Fixtures
646
+
647
+ ```typescript
648
+ // tests/fixtures/cleanup.fixture.ts
649
+ export const test = pagesTest.extend<CleanupFixtures>({
650
+ withAbsenceCleanup: [
651
+ async ({ page }, use) => {
652
+ // Run the test first
653
+ await use();
654
+
655
+ // POST-TEST CLEANUP: Clean up what the test created
656
+ await cleanupTestAbsences({ page, ... });
657
+ },
658
+ { auto: false }, // Must be explicitly requested
659
+ ],
660
+ });
661
+ ```
662
+
663
+ Key implementation details:
664
+ - `await use()` runs the test
665
+ - Cleanup code runs AFTER `use()` returns
666
+ - `{ auto: false }` means test must include fixture in parameters
667
+ - Cleanup should handle errors gracefully (non-blocking)
668
+
669
+ ### Privacy Setting for Admin Cleanup
670
+
671
+ **CRITICAL**: When admin cleans up absences created by employee tests:
672
+ - Absences with `privacy="Yes"` are visible to admin
673
+ - Absences with `privacy="No"` are hidden from admin
674
+
675
+ **Rule**: Tests MUST create absences with `privacy="Yes"` to enable admin cleanup.
676
+
677
+ ## Production-Ready Checklist
678
+
679
+ **Configuration:**
680
+ - [ ] Parallel execution enabled (`fullyParallel: true`)
681
+ - [ ] Retry strategy configured (2 in CI, 0 locally)
682
+ - [ ] Base URL from environment variables
683
+ - [ ] Artifact capture optimized (`on-first-retry`)
684
+
685
+ **Architecture:**
686
+ - [ ] Page Object Model for all major pages
687
+ - [ ] Component Objects for reusable UI elements
688
+ - [ ] Custom fixtures for common setup
689
+ - [ ] Tests organized by feature/user journey
690
+
691
+ **Best Practices:**
692
+ - [ ] No `waitForTimeout()` usage
693
+ - [ ] Tests are independent (run in any order)
694
+ - [ ] Assertions in test files, not Page Objects
695
+ - [ ] Role-based selectors prioritized
696
+ - [ ] No hardcoded credentials
697
+ - [ ] Framework validated with ONE working test before scaling
698
+ - [ ] Smoke tests tagged with @smoke for CI/CD
699
+ - [ ] All tests use `test.step()` for video-navigable execution (3-7 steps per test)
700
+ - [ ] Tests that create data use cleanup fixtures (post-test cleanup, no pre-cleanup)
701
+
702
+ **Test Independence Validation:**
703
+ - [ ] Each test can run in isolation: `npx playwright test <single-test>`
704
+ - [ ] Tests pass in parallel: `npx playwright test --workers=4`
705
+ - [ ] Tests pass in random order: `npx playwright test --shard=1/3` (run multiple shards)
706
+ - [ ] No shared state between tests (each uses fixtures)
707
+ - [ ] Tests cleanup after themselves (via fixtures or API)
708
+
709
+ **CI/CD:**
710
+ - [ ] Smoke tests run on every commit (`npx playwright test --grep @smoke`)
711
+ - [ ] Full suite runs on pull requests
712
+ - [ ] Artifacts uploaded (reports, traces)
713
+ - [ ] Failure notifications configured
714
+ - [ ] Test results published to PR comments
715
+
716
+ ---
717
+
718
+ **Remember**: The six critical pillars are:
719
+ 1. **Two-Phase Approach** - Separate WHAT to test from HOW to automate
720
+ 2. **Test One First** - Validate framework with ONE working test before scaling
721
+ 3. **Page Object Model** - Isolate UI changes from test logic
722
+ 4. **Role-based selectors** - Resist breakage with semantic HTML
723
+ 5. **Authentication state reuse** - Maximize speed and reliability
724
+ 6. **Post-test cleanup** - Each test cleans up what IT creates via fixtures