@tekyzinc/gsd-t 2.46.11 → 2.50.10

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 (40) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +22 -2
  3. package/bin/debug-ledger.js +193 -0
  4. package/bin/gsd-t.js +259 -1
  5. package/commands/gsd-t-debug.md +26 -1
  6. package/commands/gsd-t-execute.md +31 -3
  7. package/commands/gsd-t-help.md +18 -2
  8. package/commands/gsd-t-integrate.md +16 -0
  9. package/commands/gsd-t-quick.md +18 -1
  10. package/commands/gsd-t-test-sync.md +5 -1
  11. package/commands/gsd-t-verify.md +6 -1
  12. package/commands/gsd-t-wave.md +26 -0
  13. package/docs/GSD-T-README.md +83 -1
  14. package/docs/architecture.md +9 -1
  15. package/docs/requirements.md +30 -0
  16. package/package.json +1 -1
  17. package/templates/CLAUDE-global.md +19 -2
  18. package/templates/stacks/_security.md +243 -0
  19. package/templates/stacks/desktop.ini +2 -0
  20. package/templates/stacks/docker.md +202 -0
  21. package/templates/stacks/firebase.md +166 -0
  22. package/templates/stacks/flutter.md +205 -0
  23. package/templates/stacks/github-actions.md +201 -0
  24. package/templates/stacks/graphql.md +216 -0
  25. package/templates/stacks/neo4j.md +218 -0
  26. package/templates/stacks/nextjs.md +184 -0
  27. package/templates/stacks/node-api.md +196 -0
  28. package/templates/stacks/playwright.md +528 -0
  29. package/templates/stacks/postgresql.md +225 -0
  30. package/templates/stacks/python.md +243 -0
  31. package/templates/stacks/react-native.md +216 -0
  32. package/templates/stacks/react.md +293 -0
  33. package/templates/stacks/redux.md +193 -0
  34. package/templates/stacks/rest-api.md +202 -0
  35. package/templates/stacks/supabase.md +188 -0
  36. package/templates/stacks/tailwind.md +169 -0
  37. package/templates/stacks/typescript.md +176 -0
  38. package/templates/stacks/vite.md +176 -0
  39. package/templates/stacks/vue.md +189 -0
  40. package/templates/stacks/zustand.md +203 -0
@@ -0,0 +1,528 @@
1
+ # Playwright Standards
2
+
3
+ These rules are MANDATORY. Violations fail the task. No exceptions.
4
+
5
+ ---
6
+
7
+ ## 1. Functional Tests Only — No Layout Tests
8
+
9
+ ```
10
+ MANDATORY:
11
+ ├── Every assertion must verify BEHAVIOR — not element existence
12
+ ├── A test that passes on an empty HTML page with matching IDs is NOT a test
13
+ ├── After every user action, assert the OUTCOME (data changed, content loaded, state updated)
14
+ ├── NEVER assert only isVisible, toBeAttached, toBeEnabled without a behavioral follow-up
15
+ └── If a test has no assertion after a click/submit/navigation, it is incomplete
16
+ ```
17
+
18
+ **BAD** — layout test (passes even if everything is broken):
19
+ ```typescript
20
+ test('user list page', async ({ page }) => {
21
+ await page.goto('/users');
22
+ await expect(page.locator('#user-table')).toBeVisible();
23
+ await expect(page.locator('.user-row')).toHaveCount(5);
24
+ });
25
+ ```
26
+
27
+ **GOOD** — functional test (fails if the feature is broken):
28
+ ```typescript
29
+ test('user list loads and displays real user data', async ({ page }) => {
30
+ await page.goto('/users');
31
+ await expect(page.locator('.user-row').first()).toContainText('jane@example.com');
32
+ await expect(page.locator('[data-testid="user-count"]')).toHaveText('5 users');
33
+ });
34
+ ```
35
+
36
+ ---
37
+
38
+ ## 2. Test Coverage Depth — Permutations and Combinations
39
+
40
+ ```
41
+ MANDATORY — every feature MUST be tested across these dimensions:
42
+
43
+ ├── HAPPY PATH: Standard successful flow end-to-end
44
+ ├── VALIDATION: Every form field with invalid, empty, boundary, and valid input
45
+ ├── ERROR STATES: Network failure, server error (500), not found (404), timeout
46
+ ├── EMPTY STATES: No data, empty lists, first-time user with zero records
47
+ ├── EDGE CASES: Boundary values, special characters, max-length input, Unicode
48
+ ├── PERMISSIONS: Unauthorized access, role-based visibility, disabled actions
49
+ ├── STATE TRANSITIONS: Every state a record can be in, and transitions between them
50
+ └── CONCURRENT: Actions while loading, double-submit, rapid navigation
51
+ ```
52
+
53
+ ### Coverage Matrix — Build One Per Feature
54
+
55
+ For any feature with N inputs or states, build a coverage matrix:
56
+
57
+ **Example: User creation form (3 fields, 2 roles, invite toggle)**
58
+
59
+ | Test | Name | Email | Role | Invite | Expected |
60
+ |------|------|-------|------|--------|----------|
61
+ | Happy path | "Jane" | jane@x.com | Admin | on | Created + invite sent |
62
+ | Happy path (no invite) | "Jane" | jane@x.com | Viewer | off | Created, no invite |
63
+ | Empty name | "" | jane@x.com | Admin | on | Validation error on name |
64
+ | Empty email | "Jane" | "" | Admin | on | Validation error on email |
65
+ | Invalid email | "Jane" | "notanemail" | Admin | on | Validation error on email |
66
+ | Duplicate email | "Jane" | existing@x.com | Admin | on | 409 conflict error shown |
67
+ | Name at max length | "A"×100 | jane@x.com | Admin | on | Created (boundary) |
68
+ | Name exceeds max | "A"×101 | jane@x.com | Admin | on | Validation error |
69
+ | Special chars in name | "O'Brien-José" | jane@x.com | Admin | on | Created (Unicode safe) |
70
+ | XSS in name | `<script>` | jane@x.com | Admin | on | Sanitized, no execution |
71
+ | Each role option | "Jane" | jane@x.com | {each role} | on | Correct role assigned |
72
+ | Server error | "Jane" | jane@x.com | Admin | on | Error message, form preserved |
73
+ | Network offline | "Jane" | jane@x.com | Admin | on | Offline indicator, retry option |
74
+ | Double submit | "Jane" | jane@x.com | Admin | on | Only one user created |
75
+ | Unauthorized user | — | — | — | — | Redirected or 403 shown |
76
+
77
+ ```
78
+ MINIMUM COVERAGE PER FEATURE:
79
+ ├── 1 happy path per valid combination that produces different outcomes
80
+ ├── 1 validation test per field per validation rule
81
+ ├── 1 test per error type (400, 401, 403, 404, 409, 500, network)
82
+ ├── 1 empty state test
83
+ ├── 1 boundary test per field with limits (min, max, exact boundary)
84
+ ├── 1 test per role/permission that affects visibility or access
85
+ ├── 1 test per state transition in the feature's state machine
86
+ └── 1 concurrent/race condition test per form submission
87
+ ```
88
+
89
+ ---
90
+
91
+ ## 3. State Transition Testing
92
+
93
+ For features with multiple states (orders, subscriptions, tickets, etc.), test EVERY valid transition:
94
+
95
+ ```
96
+ MANDATORY:
97
+ ├── Map the state machine: identify all states and valid transitions
98
+ ├── Test each transition: action + assertion that new state is correct
99
+ ├── Test invalid transitions: verify they're rejected or unavailable
100
+ └── Test the full lifecycle: create → intermediate states → terminal state
101
+ ```
102
+
103
+ **Example: Order lifecycle**
104
+ ```typescript
105
+ test.describe('Order state transitions', () => {
106
+ test('new order starts as pending', async ({ page }) => {
107
+ await createOrder(page, testData.validOrder);
108
+ await expect(page.locator('[data-testid="order-status"]')).toHaveText('Pending');
109
+ });
110
+
111
+ test('pending → confirmed on payment', async ({ page }) => {
112
+ const order = await createPendingOrder(page);
113
+ await page.click('[data-testid="confirm-payment"]');
114
+ await expect(page.locator('[data-testid="order-status"]')).toHaveText('Confirmed');
115
+ });
116
+
117
+ test('confirmed → shipped on dispatch', async ({ page }) => {
118
+ const order = await createConfirmedOrder(page);
119
+ await page.click('[data-testid="mark-shipped"]');
120
+ await expect(page.locator('[data-testid="order-status"]')).toHaveText('Shipped');
121
+ await expect(page.locator('[data-testid="tracking-number"]')).not.toBeEmpty();
122
+ });
123
+
124
+ test('pending → cancelled is allowed', async ({ page }) => {
125
+ const order = await createPendingOrder(page);
126
+ await page.click('[data-testid="cancel-order"]');
127
+ await expect(page.locator('[data-testid="order-status"]')).toHaveText('Cancelled');
128
+ });
129
+
130
+ test('shipped → cancelled is NOT allowed', async ({ page }) => {
131
+ const order = await createShippedOrder(page);
132
+ await expect(page.locator('[data-testid="cancel-order"]')).toBeDisabled();
133
+ });
134
+ });
135
+ ```
136
+
137
+ ---
138
+
139
+ ## 4. Selectors — Resilient and Maintainable
140
+
141
+ ```
142
+ MANDATORY:
143
+ ├── Prefer user-facing selectors: getByRole, getByLabel, getByText, getByPlaceholder
144
+ ├── Use data-testid for elements with no accessible role or visible text
145
+ ├── NEVER use CSS class selectors (.btn-primary) — they break on styling changes
146
+ ├── NEVER use DOM structure selectors (div > span:nth-child(2)) — they break on layout changes
147
+ ├── NEVER use auto-generated IDs or dynamic class names
148
+ └── Combine selectors for precision: page.getByRole('button', { name: 'Submit' })
149
+ ```
150
+
151
+ **BAD**
152
+ ```typescript
153
+ await page.click('.btn.btn-primary.submit-form');
154
+ await page.locator('div.user-list > div:nth-child(3) > span').click();
155
+ ```
156
+
157
+ **GOOD**
158
+ ```typescript
159
+ await page.getByRole('button', { name: 'Submit' }).click();
160
+ await page.getByLabel('Email').fill('jane@example.com');
161
+ await page.locator('[data-testid="user-row-jane"]').click();
162
+ ```
163
+
164
+ ---
165
+
166
+ ## 5. Waiting and Assertions
167
+
168
+ ```
169
+ MANDATORY:
170
+ ├── Use Playwright auto-waiting — NEVER add manual sleep/setTimeout
171
+ ├── Assert on network completion for data-dependent tests: page.waitForResponse
172
+ ├── Use toHaveText, toContainText for content verification — not just toBeVisible
173
+ ├── Use web-first assertions (expect with auto-retry) — not page.evaluate checks
174
+ ├── Set assertion timeout for slow operations: expect(...).toHaveText('...', { timeout: 10000 })
175
+ └── Wait for navigation after clicks that change pages: page.waitForURL
176
+ ```
177
+
178
+ **BAD**
179
+ ```typescript
180
+ await page.click('#submit');
181
+ await page.waitForTimeout(3000); // arbitrary sleep!
182
+ const text = await page.locator('#result').innerText();
183
+ expect(text).toBe('Success'); // not auto-retrying
184
+ ```
185
+
186
+ **GOOD**
187
+ ```typescript
188
+ await page.click('#submit');
189
+ await page.waitForResponse(resp => resp.url().includes('/api/users') && resp.status() === 201);
190
+ await expect(page.locator('[data-testid="result"]')).toHaveText('User created successfully');
191
+ ```
192
+
193
+ ---
194
+
195
+ ## 6. Page Object Model
196
+
197
+ ```
198
+ MANDATORY for projects with 10+ tests:
199
+ ├── One page object per page or major component
200
+ ├── Page objects encapsulate selectors and actions — tests read like user stories
201
+ ├── Methods return data or other page objects (for navigation)
202
+ ├── NEVER put assertions in page objects — assertions belong in tests
203
+ └── Page objects live in a tests/pages/ or tests/pom/ directory
204
+ ```
205
+
206
+ **GOOD**
207
+ ```typescript
208
+ // tests/pages/LoginPage.ts
209
+ export class LoginPage {
210
+ constructor(private page: Page) {}
211
+
212
+ async goto() {
213
+ await this.page.goto('/login');
214
+ }
215
+
216
+ async login(email: string, password: string) {
217
+ await this.page.getByLabel('Email').fill(email);
218
+ await this.page.getByLabel('Password').fill(password);
219
+ await this.page.getByRole('button', { name: 'Sign in' }).click();
220
+ }
221
+
222
+ emailError() {
223
+ return this.page.locator('[data-testid="email-error"]');
224
+ }
225
+
226
+ passwordError() {
227
+ return this.page.locator('[data-testid="password-error"]');
228
+ }
229
+ }
230
+
231
+ // tests/login.spec.ts
232
+ test('successful login redirects to dashboard', async ({ page }) => {
233
+ const loginPage = new LoginPage(page);
234
+ await loginPage.goto();
235
+ await loginPage.login('jane@example.com', 'validpassword');
236
+ await expect(page).toHaveURL('/dashboard');
237
+ await expect(page.getByText('Welcome, Jane')).toBeVisible();
238
+ });
239
+
240
+ test('invalid email shows validation error', async ({ page }) => {
241
+ const loginPage = new LoginPage(page);
242
+ await loginPage.goto();
243
+ await loginPage.login('notanemail', 'password');
244
+ await expect(loginPage.emailError()).toHaveText('Enter a valid email address');
245
+ });
246
+ ```
247
+
248
+ ---
249
+
250
+ ## 7. API Mocking and Network Control
251
+
252
+ ```
253
+ MANDATORY for isolated, deterministic tests:
254
+ ├── Use page.route() to mock API responses — control what the UI receives
255
+ ├── Mock error responses to test error handling: route.fulfill({ status: 500 })
256
+ ├── Mock empty responses to test empty states
257
+ ├── Mock slow responses to test loading states: route.fulfill with delay
258
+ ├── Use page.waitForResponse to verify the app made the expected API call
259
+ └── For integration tests against real API: use a test/seed database, not mocks
260
+ ```
261
+
262
+ **GOOD**
263
+ ```typescript
264
+ test('shows error message on server failure', async ({ page }) => {
265
+ await page.route('**/api/users', route =>
266
+ route.fulfill({ status: 500, body: JSON.stringify({ error: 'Internal error' }) })
267
+ );
268
+ await page.goto('/users');
269
+ await expect(page.getByText('Failed to load users')).toBeVisible();
270
+ await expect(page.getByRole('button', { name: 'Retry' })).toBeVisible();
271
+ });
272
+
273
+ test('shows empty state when no users exist', async ({ page }) => {
274
+ await page.route('**/api/users', route =>
275
+ route.fulfill({ status: 200, body: JSON.stringify({ data: [], meta: { total: 0 } }) })
276
+ );
277
+ await page.goto('/users');
278
+ await expect(page.getByText('No users found')).toBeVisible();
279
+ });
280
+
281
+ test('shows loading skeleton while fetching', async ({ page }) => {
282
+ await page.route('**/api/users', async route => {
283
+ await new Promise(r => setTimeout(r, 2000));
284
+ await route.fulfill({ status: 200, body: JSON.stringify({ data: testUsers }) });
285
+ });
286
+ await page.goto('/users');
287
+ await expect(page.locator('[data-testid="loading-skeleton"]')).toBeVisible();
288
+ await expect(page.locator('.user-row').first()).toContainText('jane@example.com');
289
+ });
290
+ ```
291
+
292
+ ---
293
+
294
+ ## 8. Test Organization
295
+
296
+ ```
297
+ MANDATORY:
298
+ ├── One spec file per feature or page: login.spec.ts, user-management.spec.ts
299
+ ├── Group related tests with test.describe
300
+ ├── Use test.beforeEach for common setup (navigation, auth, seeding)
301
+ ├── Use test fixtures for reusable authenticated state
302
+ ├── Tag tests for selective runs: test('...', { tag: '@smoke' }, ...)
303
+ └── Keep individual tests independent — no test should depend on another's state
304
+ ```
305
+
306
+ **GOOD**
307
+ ```typescript
308
+ test.describe('User Management', () => {
309
+ test.beforeEach(async ({ page }) => {
310
+ await loginAsAdmin(page);
311
+ await page.goto('/users');
312
+ });
313
+
314
+ test.describe('List View', () => {
315
+ test('displays paginated user list', ...);
316
+ test('filters by role', ...);
317
+ test('searches by name or email', ...);
318
+ test('shows empty state when filter matches nothing', ...);
319
+ });
320
+
321
+ test.describe('Create User', () => {
322
+ test('creates user with valid data', ...);
323
+ test('shows validation errors for each invalid field', ...);
324
+ test('handles duplicate email conflict', ...);
325
+ test('handles server error gracefully', ...);
326
+ test('prevents double submission', ...);
327
+ });
328
+
329
+ test.describe('Edit User', () => {
330
+ test('pre-fills form with existing data', ...);
331
+ test('saves changes and shows confirmation', ...);
332
+ test('handles concurrent edit conflict', ...);
333
+ });
334
+
335
+ test.describe('Delete User', () => {
336
+ test('confirms before deleting', ...);
337
+ test('shows success after deletion', ...);
338
+ test('handles deletion of already-deleted user', ...);
339
+ });
340
+ });
341
+ ```
342
+
343
+ ---
344
+
345
+ ## 9. Test Data Management
346
+
347
+ ```
348
+ MANDATORY:
349
+ ├── Use factories or fixtures for test data — NEVER hardcode in test bodies
350
+ ├── Each test creates its own data — no shared mutable state between tests
351
+ ├── Use realistic data (not "test123") — catches encoding, truncation, display issues
352
+ ├── Include edge case data in fixtures: Unicode, long strings, special chars, empty strings
353
+ ├── Clean up test data after each test (or use isolated test databases)
354
+ └── Store reusable test data in tests/fixtures/ directory
355
+ ```
356
+
357
+ **GOOD**
358
+ ```typescript
359
+ // tests/fixtures/users.ts
360
+ export const testUsers = {
361
+ standard: { name: 'Jane Doe', email: 'jane@example.com', role: 'member' },
362
+ admin: { name: 'Admin User', email: 'admin@example.com', role: 'admin' },
363
+ unicode: { name: "José O'Brien-García", email: 'jose@example.com', role: 'member' },
364
+ longName: { name: 'A'.repeat(100), email: 'long@example.com', role: 'member' },
365
+ specialChars: { name: 'Test <script>alert(1)</script>', email: 'xss@example.com', role: 'member' },
366
+ };
367
+ ```
368
+
369
+ ---
370
+
371
+ ## 10. Combinatorial Testing Strategy
372
+
373
+ For features with multiple interacting inputs, use pairwise/combinatorial coverage:
374
+
375
+ ```
376
+ MANDATORY for forms with 3+ independent inputs:
377
+ ├── Identify all inputs and their valid values
378
+ ├── Test all single-field validations independently
379
+ ├── Use pairwise combinations for multi-field interactions (not full cartesian product)
380
+ ├── Always test the extreme corners: all-empty, all-max, all-invalid
381
+ └── Add specific combinations for known business rules
382
+ ```
383
+
384
+ **Example: Search with 3 filters (status, role, date range)**
385
+
386
+ Instead of testing all 4×3×5 = 60 combinations, use pairwise:
387
+
388
+ ```typescript
389
+ const filterCombinations = [
390
+ // Pairwise covers all 2-way interactions with fewer tests
391
+ { status: 'active', role: 'admin', dateRange: 'last7days' },
392
+ { status: 'active', role: 'viewer', dateRange: 'last30days' },
393
+ { status: 'active', role: 'member', dateRange: 'allTime' },
394
+ { status: 'inactive', role: 'admin', dateRange: 'last30days' },
395
+ { status: 'inactive', role: 'viewer', dateRange: 'allTime' },
396
+ { status: 'inactive', role: 'member', dateRange: 'last7days' },
397
+ { status: 'all', role: 'admin', dateRange: 'allTime' },
398
+ { status: 'all', role: 'viewer', dateRange: 'last7days' },
399
+ { status: 'all', role: 'member', dateRange: 'last30days' },
400
+ // Extremes
401
+ { status: 'all', role: undefined, dateRange: undefined }, // no filters
402
+ ];
403
+
404
+ for (const combo of filterCombinations) {
405
+ test(`filters: status=${combo.status}, role=${combo.role}, date=${combo.dateRange}`, async ({ page }) => {
406
+ await applyFilters(page, combo);
407
+ const results = await getVisibleResults(page);
408
+ // Assert each result matches ALL active filters
409
+ for (const result of results) {
410
+ if (combo.status !== 'all') expect(result.status).toBe(combo.status);
411
+ if (combo.role) expect(result.role).toBe(combo.role);
412
+ if (combo.dateRange) expect(isInDateRange(result.date, combo.dateRange)).toBe(true);
413
+ }
414
+ });
415
+ }
416
+ ```
417
+
418
+ ---
419
+
420
+ ## 11. Multi-Step Workflow Testing
421
+
422
+ ```
423
+ MANDATORY for multi-page or multi-step features:
424
+ ├── Test the complete end-to-end flow: start → each step → completion → verification
425
+ ├── Test backward navigation: step 3 → step 2 → step 3 (data preserved?)
426
+ ├── Test abandonment: start flow → navigate away → return (state preserved or reset?)
427
+ ├── Test each step's validation independently
428
+ ├── Test the flow with pre-filled data (edit mode vs create mode)
429
+ └── Verify the final result by reading it back (not just checking the success message)
430
+ ```
431
+
432
+ **GOOD**
433
+ ```typescript
434
+ test('complete checkout flow end-to-end', async ({ page }) => {
435
+ // Step 1: Add to cart
436
+ await page.goto('/products');
437
+ await page.getByRole('button', { name: 'Add Widget to cart' }).click();
438
+ await expect(page.locator('[data-testid="cart-count"]')).toHaveText('1');
439
+
440
+ // Step 2: Cart review
441
+ await page.goto('/cart');
442
+ await expect(page.getByText('Widget')).toBeVisible();
443
+ await page.getByRole('button', { name: 'Checkout' }).click();
444
+
445
+ // Step 3: Shipping
446
+ await page.getByLabel('Address').fill('123 Main St');
447
+ await page.getByLabel('City').fill('Springfield');
448
+ await page.getByRole('button', { name: 'Continue to payment' }).click();
449
+
450
+ // Step 4: Payment
451
+ await page.getByLabel('Card number').fill('4242424242424242');
452
+ await page.getByRole('button', { name: 'Place order' }).click();
453
+
454
+ // Verify: Check the confirmation AND read back the order
455
+ await expect(page).toHaveURL(/\/orders\/[a-z0-9-]+/);
456
+ await expect(page.getByText('Order confirmed')).toBeVisible();
457
+ await expect(page.getByText('Widget')).toBeVisible();
458
+ await expect(page.getByText('123 Main St')).toBeVisible();
459
+ });
460
+
461
+ test('checkout preserves data on backward navigation', async ({ page }) => {
462
+ // Fill shipping, go to payment, go BACK to shipping
463
+ // Assert: shipping fields still populated
464
+ });
465
+ ```
466
+
467
+ ---
468
+
469
+ ## 12. Cross-Browser and Responsive Testing
470
+
471
+ ```
472
+ RECOMMENDED:
473
+ ├── Configure projects in playwright.config for chromium, firefox, webkit
474
+ ├── Add mobile viewport project for responsive testing
475
+ ├── Run cross-browser in CI — chromium-only acceptable for local dev
476
+ ├── Test touch interactions on mobile viewports (tap, swipe)
477
+ └── Verify responsive breakpoints: mobile (375px), tablet (768px), desktop (1280px)
478
+ ```
479
+
480
+ **GOOD** — playwright.config.ts:
481
+ ```typescript
482
+ projects: [
483
+ { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
484
+ { name: 'firefox', use: { ...devices['Desktop Firefox'] } },
485
+ { name: 'webkit', use: { ...devices['Desktop Safari'] } },
486
+ { name: 'mobile-chrome', use: { ...devices['Pixel 5'] } },
487
+ { name: 'mobile-safari', use: { ...devices['iPhone 13'] } },
488
+ ],
489
+ ```
490
+
491
+ ---
492
+
493
+ ## 13. Anti-Patterns
494
+
495
+ ```
496
+ NEVER:
497
+ ├── Layout-only assertions (isVisible, toBeAttached without behavioral follow-up)
498
+ ├── waitForTimeout / manual sleep — use auto-waiting and waitForResponse
499
+ ├── CSS class selectors (.btn-primary) — use role, label, testid
500
+ ├── DOM structure selectors (div > span:nth-child) — breaks on any layout change
501
+ ├── Tests that depend on other tests' state — each test is independent
502
+ ├── Assertions in page objects — page objects encapsulate actions, tests assert
503
+ ├── Hardcoded test data in test bodies — use fixtures
504
+ ├── Skipping error/empty/loading state tests — they catch real bugs
505
+ ├── Testing only the happy path — most bugs live in edge cases
506
+ ├── Full cartesian product when pairwise suffices — wastes CI time
507
+ └── console.log for debugging — use Playwright trace viewer and screenshots
508
+ ```
509
+
510
+ ---
511
+
512
+ ## Playwright Verification Checklist
513
+
514
+ - [ ] Every assertion verifies behavior — not just element existence
515
+ - [ ] Coverage matrix built per feature (happy, validation, error, empty, edge, permissions, state transitions)
516
+ - [ ] All form fields tested: valid, invalid, empty, boundary, special chars
517
+ - [ ] Error states tested: 400, 401, 403, 404, 500, network failure
518
+ - [ ] Empty states tested: zero records, no search results
519
+ - [ ] State transitions tested: every valid + invalid transition
520
+ - [ ] Multi-step flows tested end-to-end with back-navigation
521
+ - [ ] Pairwise combinations for multi-input features
522
+ - [ ] Double-submit / concurrent action protection tested
523
+ - [ ] Selectors use role, label, testid — no CSS classes or DOM structure
524
+ - [ ] No manual waits — auto-waiting and waitForResponse only
525
+ - [ ] Page Object Model used (if 10+ tests)
526
+ - [ ] Test data in fixtures — not hardcoded
527
+ - [ ] Each test is independent — no shared mutable state
528
+ - [ ] API mocked for error/empty/loading scenarios