@assertrx/dino 1.0.0

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 (89) hide show
  1. package/README.md +293 -0
  2. package/extensions/dino.ts +70 -0
  3. package/extensions/mabl-mcp.ts +306 -0
  4. package/extensions/playwright-mcp.ts +142 -0
  5. package/package.json +47 -0
  6. package/prompts/create-pr.md +110 -0
  7. package/prompts/generate-bdd-from-url.md +65 -0
  8. package/prompts/generate-pom-from-url-cli.md +48 -0
  9. package/prompts/generate-pom-from-url.md +45 -0
  10. package/prompts/generate-spec-from-feature.md +64 -0
  11. package/prompts/heal-playwright-failures.md +106 -0
  12. package/prompts/setup-playwright.md +81 -0
  13. package/scripts/start-dino.cmd +8 -0
  14. package/scripts/start-dino.mjs +226 -0
  15. package/scripts/start-dino.ps1 +9 -0
  16. package/scripts/start-dino.sh +10 -0
  17. package/skills/acli/SKILL.md +292 -0
  18. package/skills/bdd-gherkin/SKILL.md +552 -0
  19. package/skills/build-review-interface/SKILL.md +96 -0
  20. package/skills/error-analysis/SKILL.md +164 -0
  21. package/skills/eval-audit/SKILL.md +183 -0
  22. package/skills/evaluate-rag/SKILL.md +177 -0
  23. package/skills/generate-synthetic-data/SKILL.md +131 -0
  24. package/skills/mabl-mcp/SKILL.md +79 -0
  25. package/skills/playwright-cli/SKILL.md +246 -0
  26. package/skills/playwright-cli/advanced-workflows.md +679 -0
  27. package/skills/playwright-cli/core-commands.md +375 -0
  28. package/skills/playwright-cli/device-emulation.md +475 -0
  29. package/skills/playwright-cli/request-mocking.md +373 -0
  30. package/skills/playwright-cli/running-custom-code.md +553 -0
  31. package/skills/playwright-cli/screenshots-and-media.md +457 -0
  32. package/skills/playwright-cli/session-management.md +425 -0
  33. package/skills/playwright-cli/storage-and-auth.md +487 -0
  34. package/skills/playwright-cli/test-generation.md +336 -0
  35. package/skills/playwright-cli/tracing-and-debugging.md +501 -0
  36. package/skills/playwright-core/SKILL.md +101 -0
  37. package/skills/playwright-core/accessibility.md +1517 -0
  38. package/skills/playwright-core/angular.md +1086 -0
  39. package/skills/playwright-core/api-testing.md +1623 -0
  40. package/skills/playwright-core/assertions-and-waiting.md +703 -0
  41. package/skills/playwright-core/auth-flows.md +1081 -0
  42. package/skills/playwright-core/authentication.md +1409 -0
  43. package/skills/playwright-core/browser-apis.md +673 -0
  44. package/skills/playwright-core/browser-extensions.md +319 -0
  45. package/skills/playwright-core/canvas-and-webgl.md +494 -0
  46. package/skills/playwright-core/clock-and-time-mocking.md +427 -0
  47. package/skills/playwright-core/common-pitfalls.md +1318 -0
  48. package/skills/playwright-core/component-testing.md +1179 -0
  49. package/skills/playwright-core/configuration.md +731 -0
  50. package/skills/playwright-core/crud-testing.md +945 -0
  51. package/skills/playwright-core/debugging.md +827 -0
  52. package/skills/playwright-core/drag-and-drop.md +992 -0
  53. package/skills/playwright-core/electron-testing.md +622 -0
  54. package/skills/playwright-core/error-and-edge-cases.md +1181 -0
  55. package/skills/playwright-core/error-index.md +1903 -0
  56. package/skills/playwright-core/file-operations.md +749 -0
  57. package/skills/playwright-core/file-upload-download.md +1012 -0
  58. package/skills/playwright-core/fixtures-and-hooks.md +1072 -0
  59. package/skills/playwright-core/flaky-tests.md +883 -0
  60. package/skills/playwright-core/forms-and-validation.md +1056 -0
  61. package/skills/playwright-core/i18n-and-localization.md +622 -0
  62. package/skills/playwright-core/iframes-and-shadow-dom.md +488 -0
  63. package/skills/playwright-core/locator-strategy.md +588 -0
  64. package/skills/playwright-core/locators.md +743 -0
  65. package/skills/playwright-core/mobile-and-responsive.md +1669 -0
  66. package/skills/playwright-core/multi-context-and-popups.md +581 -0
  67. package/skills/playwright-core/multi-user-and-collaboration.md +477 -0
  68. package/skills/playwright-core/network-mocking.md +1395 -0
  69. package/skills/playwright-core/nextjs.md +1013 -0
  70. package/skills/playwright-core/performance-testing.md +608 -0
  71. package/skills/playwright-core/react.md +1082 -0
  72. package/skills/playwright-core/search-and-filter.md +1366 -0
  73. package/skills/playwright-core/security-testing.md +624 -0
  74. package/skills/playwright-core/service-workers-and-pwa.md +524 -0
  75. package/skills/playwright-core/test-architecture.md +569 -0
  76. package/skills/playwright-core/test-data-management.md +1209 -0
  77. package/skills/playwright-core/test-organization.md +947 -0
  78. package/skills/playwright-core/third-party-integrations.md +754 -0
  79. package/skills/playwright-core/trace-analysis.md +161 -0
  80. package/skills/playwright-core/visual-regression.md +1027 -0
  81. package/skills/playwright-core/vue.md +1162 -0
  82. package/skills/playwright-core/websockets-and-realtime.md +623 -0
  83. package/skills/playwright-core/when-to-mock.md +827 -0
  84. package/skills/playwright-pom/SKILL.md +94 -0
  85. package/skills/playwright-pom/page-object-model.md +921 -0
  86. package/skills/playwright-pom/pom-vs-fixtures-vs-helpers.md +937 -0
  87. package/skills/validate-evaluator/SKILL.md +215 -0
  88. package/skills/write-judge-prompt/SKILL.md +144 -0
  89. package/themes/dino.json +66 -0
@@ -0,0 +1,947 @@
1
+ # Test Organization
2
+
3
+ > **When to use**: Structuring test files, naming tests, grouping with `describe`, tagging, filtering, and deciding parallel vs serial execution.
4
+ > **Prerequisites**: [core/configuration.md](foundations/configuration.md)
5
+
6
+ ## Quick Reference
7
+
8
+ | Concept | Rule |
9
+ |---|---|
10
+ | File suffix | `.spec.ts` / `.spec.js` — always |
11
+ | Grouping | By feature/domain, not by page or URL |
12
+ | Test names | `test('should ...')` or `test('user can ...')` — describe behavior |
13
+ | Nesting | Max 2 levels of `test.describe()` |
14
+ | Default execution | `fullyParallel: true` — tests run in parallel by default |
15
+ | Serial tests | Almost never use `test.describe.serial()` |
16
+ | Test dependencies | Avoid — each test sets up its own state |
17
+ | Tags | `@smoke`, `@regression`, `@slow` — filter with `--grep` |
18
+
19
+ ## Patterns
20
+
21
+ ### Pattern 1: Feature-Based File Structure
22
+
23
+ **Use when**: Any project — this is the default layout.
24
+ **Avoid when**: Never. This is always correct.
25
+
26
+ Group tests by feature or domain, not by page class or test type.
27
+
28
+ **Small project (< 20 tests):**
29
+
30
+ ```
31
+ tests/
32
+ ├── auth.spec.ts
33
+ ├── dashboard.spec.ts
34
+ ├── settings.spec.ts
35
+ └── checkout.spec.ts
36
+ playwright.config.ts
37
+ ```
38
+
39
+ **Medium project (20–200 tests):**
40
+
41
+ ```
42
+ tests/
43
+ ├── auth/
44
+ │ ├── login.spec.ts
45
+ │ ├── signup.spec.ts
46
+ │ ├── password-reset.spec.ts
47
+ │ └── mfa.spec.ts
48
+ ├── dashboard/
49
+ │ ├── widgets.spec.ts
50
+ │ ├── filters.spec.ts
51
+ │ └── export.spec.ts
52
+ ├── checkout/
53
+ │ ├── cart.spec.ts
54
+ │ ├── payment.spec.ts
55
+ │ └── confirmation.spec.ts
56
+ ├── settings/
57
+ │ ├── profile.spec.ts
58
+ │ └── notifications.spec.ts
59
+ └── fixtures/
60
+ ├── auth.fixture.ts
61
+ └── test-data.ts
62
+ playwright.config.ts
63
+ ```
64
+
65
+ **Large project (200+ tests):**
66
+
67
+ ```
68
+ tests/
69
+ ├── e2e/
70
+ │ ├── auth/
71
+ │ │ ├── login.spec.ts
72
+ │ │ ├── signup.spec.ts
73
+ │ │ ├── password-reset.spec.ts
74
+ │ │ └── mfa.spec.ts
75
+ │ ├── checkout/
76
+ │ │ ├── cart.spec.ts
77
+ │ │ ├── payment.spec.ts
78
+ │ │ ├── promo-codes.spec.ts
79
+ │ │ └── confirmation.spec.ts
80
+ │ ├── admin/
81
+ │ │ ├── user-management.spec.ts
82
+ │ │ ├── reports.spec.ts
83
+ │ │ └── audit-log.spec.ts
84
+ │ └── inventory/
85
+ │ ├── product-list.spec.ts
86
+ │ ├── product-detail.spec.ts
87
+ │ └── stock-management.spec.ts
88
+ ├── api/
89
+ │ ├── users.spec.ts
90
+ │ ├── orders.spec.ts
91
+ │ └── products.spec.ts
92
+ ├── visual/
93
+ │ ├── homepage.spec.ts
94
+ │ └── product-page.spec.ts
95
+ ├── fixtures/
96
+ │ ├── auth.fixture.ts
97
+ │ ├── db.fixture.ts
98
+ │ └── base.fixture.ts
99
+ ├── page-objects/
100
+ │ ├── login.page.ts
101
+ │ ├── dashboard.page.ts
102
+ │ └── checkout.page.ts
103
+ └── helpers/
104
+ ├── test-data.ts
105
+ └── api-client.ts
106
+ playwright.config.ts
107
+ ```
108
+
109
+ ---
110
+
111
+ ### Pattern 2: Naming Conventions
112
+
113
+ **Use when**: Writing any test or file.
114
+ **Avoid when**: Never.
115
+
116
+ **TypeScript:**
117
+
118
+ ```typescript
119
+ // tests/checkout/cart.spec.ts
120
+ import { test, expect } from '@playwright/test';
121
+
122
+ // Good: group by feature, describe behavior
123
+ test.describe('Shopping Cart', () => {
124
+ test('should add item to empty cart', async ({ page }) => {
125
+ await page.goto('/products/widget-a');
126
+ await page.getByRole('button', { name: 'Add to cart' }).click();
127
+ await expect(page.getByTestId('cart-count')).toHaveText('1');
128
+ });
129
+
130
+ test('should update quantity when same item added twice', async ({ page }) => {
131
+ await page.goto('/products/widget-a');
132
+ await page.getByRole('button', { name: 'Add to cart' }).click();
133
+ await page.getByRole('button', { name: 'Add to cart' }).click();
134
+ await expect(page.getByTestId('cart-count')).toHaveText('2');
135
+ });
136
+
137
+ test('user can remove item from cart', async ({ page }) => {
138
+ await page.goto('/cart');
139
+ // ... setup item in cart via API or fixture
140
+ await page.getByRole('button', { name: 'Remove' }).first().click();
141
+ await expect(page.getByText('Your cart is empty')).toBeVisible();
142
+ });
143
+ });
144
+ ```
145
+
146
+ **JavaScript:**
147
+
148
+ ```javascript
149
+ // tests/checkout/cart.spec.js
150
+ const { test, expect } = require('@playwright/test');
151
+
152
+ test.describe('Shopping Cart', () => {
153
+ test('should add item to empty cart', async ({ page }) => {
154
+ await page.goto('/products/widget-a');
155
+ await page.getByRole('button', { name: 'Add to cart' }).click();
156
+ await expect(page.getByTestId('cart-count')).toHaveText('1');
157
+ });
158
+
159
+ test('should update quantity when same item added twice', async ({ page }) => {
160
+ await page.goto('/products/widget-a');
161
+ await page.getByRole('button', { name: 'Add to cart' }).click();
162
+ await page.getByRole('button', { name: 'Add to cart' }).click();
163
+ await expect(page.getByTestId('cart-count')).toHaveText('2');
164
+ });
165
+
166
+ test('user can remove item from cart', async ({ page }) => {
167
+ await page.goto('/cart');
168
+ await page.getByRole('button', { name: 'Remove' }).first().click();
169
+ await expect(page.getByText('Your cart is empty')).toBeVisible();
170
+ });
171
+ });
172
+ ```
173
+
174
+ **Naming rules:**
175
+
176
+ | Element | Convention | Example |
177
+ |---|---|---|
178
+ | File name | `kebab-case.spec.ts` | `password-reset.spec.ts` |
179
+ | `test.describe()` | Title Case, feature name | `'Password Reset'` |
180
+ | `test()` | Sentence starting with `should` or `user can` | `'should send reset email'` |
181
+ | Page objects | `PascalCase.page.ts` | `login.page.ts` / `LoginPage` |
182
+ | Fixtures | `kebab-case.fixture.ts` | `auth.fixture.ts` |
183
+
184
+ ---
185
+
186
+ ### Pattern 3: `test.describe()` Grouping
187
+
188
+ **Use when**: A file has multiple related tests that share context or setup.
189
+ **Avoid when**: A file has only 1–3 tests with no shared setup — skip the `describe` wrapper.
190
+
191
+ **TypeScript:**
192
+
193
+ ```typescript
194
+ // tests/auth/login.spec.ts
195
+ import { test, expect } from '@playwright/test';
196
+
197
+ test.describe('Login', () => {
198
+ // Shared setup for all tests in this describe block
199
+ test.beforeEach(async ({ page }) => {
200
+ await page.goto('/login');
201
+ });
202
+
203
+ test('should login with valid credentials', async ({ page }) => {
204
+ await page.getByLabel('Email').fill('user@example.com');
205
+ await page.getByLabel('Password').fill('securepass123');
206
+ await page.getByRole('button', { name: 'Sign in' }).click();
207
+ await expect(page).toHaveURL('/dashboard');
208
+ });
209
+
210
+ test('should show error for invalid password', async ({ page }) => {
211
+ await page.getByLabel('Email').fill('user@example.com');
212
+ await page.getByLabel('Password').fill('wrongpassword');
213
+ await page.getByRole('button', { name: 'Sign in' }).click();
214
+ await expect(page.getByRole('alert')).toHaveText('Invalid credentials');
215
+ });
216
+
217
+ // One level of nesting — acceptable
218
+ test.describe('Rate Limiting', () => {
219
+ test('should lock account after 5 failed attempts', async ({ page }) => {
220
+ for (let i = 0; i < 5; i++) {
221
+ await page.getByLabel('Email').fill('user@example.com');
222
+ await page.getByLabel('Password').fill('wrong');
223
+ await page.getByRole('button', { name: 'Sign in' }).click();
224
+ }
225
+ await expect(page.getByRole('alert')).toContainText('Account locked');
226
+ });
227
+ });
228
+ });
229
+ ```
230
+
231
+ **JavaScript:**
232
+
233
+ ```javascript
234
+ // tests/auth/login.spec.js
235
+ const { test, expect } = require('@playwright/test');
236
+
237
+ test.describe('Login', () => {
238
+ test.beforeEach(async ({ page }) => {
239
+ await page.goto('/login');
240
+ });
241
+
242
+ test('should login with valid credentials', async ({ page }) => {
243
+ await page.getByLabel('Email').fill('user@example.com');
244
+ await page.getByLabel('Password').fill('securepass123');
245
+ await page.getByRole('button', { name: 'Sign in' }).click();
246
+ await expect(page).toHaveURL('/dashboard');
247
+ });
248
+
249
+ test('should show error for invalid password', async ({ page }) => {
250
+ await page.getByLabel('Email').fill('user@example.com');
251
+ await page.getByLabel('Password').fill('wrongpassword');
252
+ await page.getByRole('button', { name: 'Sign in' }).click();
253
+ await expect(page.getByRole('alert')).toHaveText('Invalid credentials');
254
+ });
255
+
256
+ test.describe('Rate Limiting', () => {
257
+ test('should lock account after 5 failed attempts', async ({ page }) => {
258
+ for (let i = 0; i < 5; i++) {
259
+ await page.getByLabel('Email').fill('user@example.com');
260
+ await page.getByLabel('Password').fill('wrong');
261
+ await page.getByRole('button', { name: 'Sign in' }).click();
262
+ }
263
+ await expect(page.getByRole('alert')).toContainText('Account locked');
264
+ });
265
+ });
266
+ });
267
+ ```
268
+
269
+ **Nesting limit: 2 levels max.** If you need a third level, split into a separate file.
270
+
271
+ ---
272
+
273
+ ### Pattern 4: Tags and Annotations
274
+
275
+ **Use when**: You need to categorize tests for selective runs (smoke suites, CI pipelines, skip known issues).
276
+ **Avoid when**: All tests always run together and you have < 20 tests.
277
+
278
+ **TypeScript:**
279
+
280
+ ```typescript
281
+ // tests/checkout/payment.spec.ts
282
+ import { test, expect } from '@playwright/test';
283
+
284
+ // Tag in the test title — simplest approach
285
+ test('should process credit card payment @smoke', async ({ page }) => {
286
+ await page.goto('/checkout');
287
+ await page.getByLabel('Card number').fill('4242424242424242');
288
+ await page.getByLabel('Expiry').fill('12/28');
289
+ await page.getByLabel('CVC').fill('123');
290
+ await page.getByRole('button', { name: 'Pay now' }).click();
291
+ await expect(page.getByText('Payment successful')).toBeVisible();
292
+ });
293
+
294
+ test('should handle declined card @regression', async ({ page }) => {
295
+ await page.goto('/checkout');
296
+ await page.getByLabel('Card number').fill('4000000000000002');
297
+ await page.getByLabel('Expiry').fill('12/28');
298
+ await page.getByLabel('CVC').fill('123');
299
+ await page.getByRole('button', { name: 'Pay now' }).click();
300
+ await expect(page.getByRole('alert')).toContainText('Card declined');
301
+ });
302
+
303
+ // Tag via test.describe for group-level tagging
304
+ test.describe('Payment Edge Cases @regression', () => {
305
+ test('should handle network timeout during payment', async ({ page }) => {
306
+ await page.goto('/checkout');
307
+ // ... test implementation
308
+ await expect(page.getByText('Please try again')).toBeVisible();
309
+ });
310
+ });
311
+
312
+ // Annotations for test lifecycle control
313
+ test('should render 3D Secure iframe @slow', async ({ page }) => {
314
+ test.slow(); // Triples the default timeout
315
+ await page.goto('/checkout/3ds');
316
+ // ... long-running 3D Secure flow
317
+ await expect(page.getByText('Verified')).toBeVisible();
318
+ });
319
+
320
+ test('should apply loyalty points discount', async ({ page }) => {
321
+ test.skip(process.env.CI !== 'true', 'Only run in CI — needs loyalty service');
322
+ await page.goto('/checkout');
323
+ // ...
324
+ });
325
+
326
+ test('should handle Apple Pay on Safari', async ({ page, browserName }) => {
327
+ test.skip(browserName !== 'webkit', 'Apple Pay only works in Safari');
328
+ await page.goto('/checkout');
329
+ // ...
330
+ });
331
+
332
+ test('should display PayPal button', async ({ page }) => {
333
+ test.fixme(); // Known broken — tracked in JIRA-1234
334
+ await page.goto('/checkout');
335
+ await expect(page.getByRole('button', { name: 'PayPal' })).toBeVisible();
336
+ });
337
+
338
+ test('should fail when submitting empty card form', async ({ page }) => {
339
+ test.fail(); // This test is expected to fail — documents known bug
340
+ await page.goto('/checkout');
341
+ await page.getByRole('button', { name: 'Pay now' }).click();
342
+ // Bug: currently no validation shown — this assertion will fail
343
+ await expect(page.getByRole('alert')).toBeVisible();
344
+ });
345
+ ```
346
+
347
+ **JavaScript:**
348
+
349
+ ```javascript
350
+ // tests/checkout/payment.spec.js
351
+ const { test, expect } = require('@playwright/test');
352
+
353
+ test('should process credit card payment @smoke', async ({ page }) => {
354
+ await page.goto('/checkout');
355
+ await page.getByLabel('Card number').fill('4242424242424242');
356
+ await page.getByLabel('Expiry').fill('12/28');
357
+ await page.getByLabel('CVC').fill('123');
358
+ await page.getByRole('button', { name: 'Pay now' }).click();
359
+ await expect(page.getByText('Payment successful')).toBeVisible();
360
+ });
361
+
362
+ test('should handle declined card @regression', async ({ page }) => {
363
+ await page.goto('/checkout');
364
+ await page.getByLabel('Card number').fill('4000000000000002');
365
+ await page.getByLabel('Expiry').fill('12/28');
366
+ await page.getByLabel('CVC').fill('123');
367
+ await page.getByRole('button', { name: 'Pay now' }).click();
368
+ await expect(page.getByRole('alert')).toContainText('Card declined');
369
+ });
370
+
371
+ test.describe('Payment Edge Cases @regression', () => {
372
+ test('should handle network timeout during payment', async ({ page }) => {
373
+ await page.goto('/checkout');
374
+ await expect(page.getByText('Please try again')).toBeVisible();
375
+ });
376
+ });
377
+
378
+ test('should render 3D Secure iframe @slow', async ({ page }) => {
379
+ test.slow();
380
+ await page.goto('/checkout/3ds');
381
+ await expect(page.getByText('Verified')).toBeVisible();
382
+ });
383
+
384
+ test('should apply loyalty points discount', async ({ page }) => {
385
+ test.skip(process.env.CI !== 'true', 'Only run in CI — needs loyalty service');
386
+ await page.goto('/checkout');
387
+ });
388
+
389
+ test('should handle Apple Pay on Safari', async ({ page, browserName }) => {
390
+ test.skip(browserName !== 'webkit', 'Apple Pay only works in Safari');
391
+ await page.goto('/checkout');
392
+ });
393
+
394
+ test('should display PayPal button', async ({ page }) => {
395
+ test.fixme(); // Known broken — tracked in JIRA-1234
396
+ await page.goto('/checkout');
397
+ await expect(page.getByRole('button', { name: 'PayPal' })).toBeVisible();
398
+ });
399
+
400
+ test('should fail when submitting empty card form', async ({ page }) => {
401
+ test.fail();
402
+ await page.goto('/checkout');
403
+ await page.getByRole('button', { name: 'Pay now' }).click();
404
+ await expect(page.getByRole('alert')).toBeVisible();
405
+ });
406
+ ```
407
+
408
+ **Annotation cheat sheet:**
409
+
410
+ | Annotation | Effect | When to use |
411
+ |---|---|---|
412
+ | `test.skip()` | Skips the test entirely | Feature not available in this env/browser |
413
+ | `test.skip(condition, reason)` | Conditional skip | Browser-specific or env-specific tests |
414
+ | `test.fixme()` | Skips and marks as "needs fix" | Known bug, not yet fixed |
415
+ | `test.slow()` | Triples the timeout | Tests with inherently slow workflows |
416
+ | `test.fail()` | Expects the test to fail; fails if it passes | Documenting a known bug with a regression guard |
417
+ | `test.info().annotations` | Add custom annotations | Custom metadata for reports |
418
+
419
+ ---
420
+
421
+ ### Pattern 5: Test Filtering
422
+
423
+ **Use when**: Running a subset of tests from the CLI or CI.
424
+ **Avoid when**: Never — know these commands.
425
+
426
+ ```bash
427
+ # By tag (in test title)
428
+ npx playwright test --grep @smoke
429
+ npx playwright test --grep @regression
430
+ npx playwright test --grep-invert @slow # everything except @slow
431
+ npx playwright test -G @slow # same — -G is the 1.61+ shorthand
432
+
433
+ # By file
434
+ npx playwright test tests/auth/
435
+ npx playwright test tests/checkout/payment.spec.ts
436
+
437
+ # By test name
438
+ npx playwright test --grep "should login"
439
+
440
+ # By project (from playwright.config)
441
+ npx playwright test --project=chromium
442
+ npx playwright test --project=mobile-safari
443
+
444
+ # Combine filters
445
+ npx playwright test --grep @smoke --project=chromium
446
+ npx playwright test tests/checkout/ --grep @smoke
447
+
448
+ # By line number — run a single test
449
+ npx playwright test tests/auth/login.spec.ts:15
450
+ ```
451
+
452
+ **Using tags with `test.describe.configure()`:**
453
+
454
+ **TypeScript:**
455
+
456
+ ```typescript
457
+ // playwright.config.ts
458
+ import { defineConfig } from '@playwright/test';
459
+
460
+ export default defineConfig({
461
+ projects: [
462
+ {
463
+ name: 'smoke',
464
+ testMatch: '**/*.spec.ts',
465
+ grep: /@smoke/,
466
+ },
467
+ {
468
+ name: 'regression',
469
+ testMatch: '**/*.spec.ts',
470
+ grep: /@regression/,
471
+ },
472
+ {
473
+ name: 'all',
474
+ testMatch: '**/*.spec.ts',
475
+ grepInvert: /@slow/,
476
+ },
477
+ ],
478
+ });
479
+ ```
480
+
481
+ **JavaScript:**
482
+
483
+ ```javascript
484
+ // playwright.config.js
485
+ const { defineConfig } = require('@playwright/test');
486
+
487
+ module.exports = defineConfig({
488
+ projects: [
489
+ {
490
+ name: 'smoke',
491
+ testMatch: '**/*.spec.js',
492
+ grep: /@smoke/,
493
+ },
494
+ {
495
+ name: 'regression',
496
+ testMatch: '**/*.spec.js',
497
+ grep: /@regression/,
498
+ },
499
+ {
500
+ name: 'all',
501
+ testMatch: '**/*.spec.js',
502
+ grepInvert: /@slow/,
503
+ },
504
+ ],
505
+ });
506
+ ```
507
+
508
+ ---
509
+
510
+ ### Pattern 6: Parallel vs Serial Execution
511
+
512
+ **Use when**: Deciding how tests run relative to each other.
513
+ **Avoid when**: You have < 5 tests (parallelism doesn't matter).
514
+
515
+ **Default: always parallel.** Set `fullyParallel: true` in config.
516
+
517
+ **TypeScript:**
518
+
519
+ ```typescript
520
+ // playwright.config.ts
521
+ import { defineConfig } from '@playwright/test';
522
+
523
+ export default defineConfig({
524
+ fullyParallel: true, // All tests run in parallel by default
525
+ workers: process.env.CI ? 1 : undefined, // Use all cores locally, 1 in CI (or adjust)
526
+ });
527
+ ```
528
+
529
+ **JavaScript:**
530
+
531
+ ```javascript
532
+ // playwright.config.js
533
+ const { defineConfig } = require('@playwright/test');
534
+
535
+ module.exports = defineConfig({
536
+ fullyParallel: true,
537
+ workers: process.env.CI ? 1 : undefined,
538
+ });
539
+ ```
540
+
541
+ **Serial execution — use only when tests share external state you cannot isolate:**
542
+
543
+ **TypeScript:**
544
+
545
+ ```typescript
546
+ // tests/onboarding/wizard.spec.ts
547
+ import { test, expect } from '@playwright/test';
548
+
549
+ // ONLY use serial when steps truly depend on prior state that cannot be set up independently
550
+ test.describe.serial('Onboarding Wizard', () => {
551
+ test('step 1: user enters company name', async ({ page }) => {
552
+ await page.goto('/onboarding');
553
+ await page.getByLabel('Company name').fill('Acme Corp');
554
+ await page.getByRole('button', { name: 'Next' }).click();
555
+ await expect(page).toHaveURL('/onboarding/step-2');
556
+ });
557
+
558
+ test('step 2: user selects plan', async ({ page }) => {
559
+ await page.goto('/onboarding/step-2');
560
+ await page.getByRole('radio', { name: 'Pro plan' }).check();
561
+ await page.getByRole('button', { name: 'Next' }).click();
562
+ await expect(page).toHaveURL('/onboarding/step-3');
563
+ });
564
+
565
+ test('step 3: user confirms and completes', async ({ page }) => {
566
+ await page.goto('/onboarding/step-3');
567
+ await page.getByRole('button', { name: 'Complete setup' }).click();
568
+ await expect(page.getByText('Welcome to Acme Corp')).toBeVisible();
569
+ });
570
+ });
571
+ ```
572
+
573
+ **JavaScript:**
574
+
575
+ ```javascript
576
+ // tests/onboarding/wizard.spec.js
577
+ const { test, expect } = require('@playwright/test');
578
+
579
+ test.describe.serial('Onboarding Wizard', () => {
580
+ test('step 1: user enters company name', async ({ page }) => {
581
+ await page.goto('/onboarding');
582
+ await page.getByLabel('Company name').fill('Acme Corp');
583
+ await page.getByRole('button', { name: 'Next' }).click();
584
+ await expect(page).toHaveURL('/onboarding/step-2');
585
+ });
586
+
587
+ test('step 2: user selects plan', async ({ page }) => {
588
+ await page.goto('/onboarding/step-2');
589
+ await page.getByRole('radio', { name: 'Pro plan' }).check();
590
+ await page.getByRole('button', { name: 'Next' }).click();
591
+ await expect(page).toHaveURL('/onboarding/step-3');
592
+ });
593
+
594
+ test('step 3: user confirms and completes', async ({ page }) => {
595
+ await page.goto('/onboarding/step-3');
596
+ await page.getByRole('button', { name: 'Complete setup' }).click();
597
+ await expect(page.getByText('Welcome to Acme Corp')).toBeVisible();
598
+ });
599
+ });
600
+ ```
601
+
602
+ **Better alternative: test the full flow in one test with `test.step()`:**
603
+
604
+ **TypeScript:**
605
+
606
+ ```typescript
607
+ // tests/onboarding/wizard.spec.ts
608
+ import { test, expect } from '@playwright/test';
609
+
610
+ test('user completes full onboarding wizard', async ({ page }) => {
611
+ await test.step('enter company name', async () => {
612
+ await page.goto('/onboarding');
613
+ await page.getByLabel('Company name').fill('Acme Corp');
614
+ await page.getByRole('button', { name: 'Next' }).click();
615
+ await expect(page).toHaveURL('/onboarding/step-2');
616
+ });
617
+
618
+ await test.step('select plan', async () => {
619
+ await page.getByRole('radio', { name: 'Pro plan' }).check();
620
+ await page.getByRole('button', { name: 'Next' }).click();
621
+ await expect(page).toHaveURL('/onboarding/step-3');
622
+ });
623
+
624
+ await test.step('confirm and complete', async () => {
625
+ await page.getByRole('button', { name: 'Complete setup' }).click();
626
+ await expect(page.getByText('Welcome to Acme Corp')).toBeVisible();
627
+ });
628
+ });
629
+ ```
630
+
631
+ **JavaScript:**
632
+
633
+ ```javascript
634
+ // tests/onboarding/wizard.spec.js
635
+ const { test, expect } = require('@playwright/test');
636
+
637
+ test('user completes full onboarding wizard', async ({ page }) => {
638
+ await test.step('enter company name', async () => {
639
+ await page.goto('/onboarding');
640
+ await page.getByLabel('Company name').fill('Acme Corp');
641
+ await page.getByRole('button', { name: 'Next' }).click();
642
+ await expect(page).toHaveURL('/onboarding/step-2');
643
+ });
644
+
645
+ await test.step('select plan', async () => {
646
+ await page.getByRole('radio', { name: 'Pro plan' }).check();
647
+ await page.getByRole('button', { name: 'Next' }).click();
648
+ await expect(page).toHaveURL('/onboarding/step-3');
649
+ });
650
+
651
+ await test.step('confirm and complete', async () => {
652
+ await page.getByRole('button', { name: 'Complete setup' }).click();
653
+ await expect(page.getByText('Welcome to Acme Corp')).toBeVisible();
654
+ });
655
+ });
656
+ ```
657
+
658
+ ---
659
+
660
+ ### Pattern 7: Monorepo Testing
661
+
662
+ **Use when**: A single repository contains multiple apps or packages that each need E2E tests.
663
+ **Avoid when**: Single app repo.
664
+
665
+ **TypeScript:**
666
+
667
+ ```typescript
668
+ // playwright.config.ts (root of monorepo)
669
+ import { defineConfig } from '@playwright/test';
670
+
671
+ export default defineConfig({
672
+ projects: [
673
+ {
674
+ name: 'web-app',
675
+ testDir: './apps/web/tests',
676
+ use: {
677
+ baseURL: 'http://localhost:3000',
678
+ },
679
+ },
680
+ {
681
+ name: 'admin-panel',
682
+ testDir: './apps/admin/tests',
683
+ use: {
684
+ baseURL: 'http://localhost:3001',
685
+ },
686
+ },
687
+ {
688
+ name: 'marketing-site',
689
+ testDir: './apps/marketing/tests',
690
+ use: {
691
+ baseURL: 'http://localhost:4000',
692
+ },
693
+ },
694
+ ],
695
+ });
696
+ ```
697
+
698
+ **JavaScript:**
699
+
700
+ ```javascript
701
+ // playwright.config.js (root of monorepo)
702
+ const { defineConfig } = require('@playwright/test');
703
+
704
+ module.exports = defineConfig({
705
+ projects: [
706
+ {
707
+ name: 'web-app',
708
+ testDir: './apps/web/tests',
709
+ use: {
710
+ baseURL: 'http://localhost:3000',
711
+ },
712
+ },
713
+ {
714
+ name: 'admin-panel',
715
+ testDir: './apps/admin/tests',
716
+ use: {
717
+ baseURL: 'http://localhost:3001',
718
+ },
719
+ },
720
+ {
721
+ name: 'marketing-site',
722
+ testDir: './apps/marketing/tests',
723
+ use: {
724
+ baseURL: 'http://localhost:4000',
725
+ },
726
+ },
727
+ ],
728
+ });
729
+ ```
730
+
731
+ **Monorepo directory layout:**
732
+
733
+ ```
734
+ monorepo/
735
+ ├── apps/
736
+ │ ├── web/
737
+ │ │ ├── src/
738
+ │ │ └── tests/
739
+ │ │ ├── auth/
740
+ │ │ │ └── login.spec.ts
741
+ │ │ └── dashboard/
742
+ │ │ └── widgets.spec.ts
743
+ │ ├── admin/
744
+ │ │ ├── src/
745
+ │ │ └── tests/
746
+ │ │ └── user-management.spec.ts
747
+ │ └── marketing/
748
+ │ ├── src/
749
+ │ └── tests/
750
+ │ └── landing-page.spec.ts
751
+ ├── packages/
752
+ │ └── shared-fixtures/
753
+ │ ├── auth.fixture.ts
754
+ │ └── index.ts
755
+ └── playwright.config.ts
756
+ ```
757
+
758
+ Run only one app's tests:
759
+
760
+ ```bash
761
+ npx playwright test --project=web-app
762
+ npx playwright test --project=admin-panel
763
+ ```
764
+
765
+ ## Decision Guide
766
+
767
+ ```
768
+ How many tests do you have?
769
+ │
770
+ ├── < 20 tests (small)
771
+ │ ├── Flat structure: all .spec.ts files in tests/
772
+ │ ├── No subdirectories needed
773
+ │ ├── Tags optional — just run everything
774
+ │ └── fullyParallel: true, single config project
775
+ │
776
+ ├── 20–200 tests (medium)
777
+ │ ├── Feature subdirectories: tests/auth/, tests/checkout/
778
+ │ ├── Use @smoke tag for critical-path subset (< 15 min)
779
+ │ ├── Shared fixtures in tests/fixtures/
780
+ │ ├── Page objects in tests/page-objects/ (if you use them)
781
+ │ └── Consider separate projects for different browsers
782
+ │
783
+ └── 200+ tests (large)
784
+ ├── Top-level split: tests/e2e/, tests/api/, tests/visual/
785
+ ├── Feature subdirectories under each
786
+ ├── @smoke, @regression, @slow tags — multiple CI pipelines
787
+ ├── Sharding in CI for faster runs
788
+ ├── Project-based filtering: smoke project, full project
789
+ └── Shared fixtures as a package in monorepo
790
+ ```
791
+
792
+ ```
793
+ Should I use test.describe.serial()?
794
+ │
795
+ ├── Can each test set up its own state via API/fixture?
796
+ │ └── YES → Do NOT use serial. Use independent parallel tests.
797
+ │
798
+ ├── Is this a multi-step wizard where each step changes server state
799
+ │ that cannot be reset between tests?
800
+ │ └── Write it as ONE test with test.step() instead.
801
+ │
802
+ └── Is this genuinely stateful (e.g., database migration sequence)?
803
+ └── Use serial as last resort. Add a comment explaining why.
804
+ ```
805
+
806
+ ## Anti-Patterns
807
+
808
+ ### One giant test file
809
+
810
+ ```typescript
811
+ // BAD: tests/all-tests.spec.ts with 80 tests and 2000+ lines
812
+ test.describe('Everything', () => {
813
+ test('login works', async ({ page }) => { /* ... */ });
814
+ test('signup works', async ({ page }) => { /* ... */ });
815
+ test('cart works', async ({ page }) => { /* ... */ });
816
+ // ... 77 more tests
817
+ });
818
+ ```
819
+
820
+ **Fix:** Split by feature — one file per feature area, 5–15 tests per file.
821
+
822
+ ---
823
+
824
+ ### Meaningless test names
825
+
826
+ ```typescript
827
+ // BAD
828
+ test('test1', async ({ page }) => { /* ... */ });
829
+ test('test2', async ({ page }) => { /* ... */ });
830
+ test('payment test', async ({ page }) => { /* ... */ });
831
+ test('it works', async ({ page }) => { /* ... */ });
832
+ ```
833
+
834
+ **Fix:** Describe behavior. When a test fails, the name should tell you what broke.
835
+
836
+ ```typescript
837
+ // GOOD
838
+ test('should reject expired credit card', async ({ page }) => { /* ... */ });
839
+ test('user can update shipping address during checkout', async ({ page }) => { /* ... */ });
840
+ ```
841
+
842
+ ---
843
+
844
+ ### Deep describe nesting
845
+
846
+ ```typescript
847
+ // BAD: 3+ levels deep — hard to read, hard to find tests in reports
848
+ test.describe('Auth', () => {
849
+ test.describe('Login', () => {
850
+ test.describe('With MFA', () => {
851
+ test.describe('SMS', () => {
852
+ test('should send code', async ({ page }) => { /* ... */ });
853
+ });
854
+ });
855
+ });
856
+ });
857
+ ```
858
+
859
+ **Fix:** Max 2 levels. Split deeper nesting into separate files.
860
+
861
+ ```typescript
862
+ // GOOD: tests/auth/login-mfa-sms.spec.ts
863
+ test.describe('Login with SMS MFA', () => {
864
+ test('should send verification code', async ({ page }) => { /* ... */ });
865
+ test('should reject invalid code', async ({ page }) => { /* ... */ });
866
+ });
867
+ ```
868
+
869
+ ---
870
+
871
+ ### Using `test.describe.serial()` as the default
872
+
873
+ ```typescript
874
+ // BAD: serial for no reason — kills parallelism, creates hidden dependencies
875
+ test.describe.serial('User Profile', () => {
876
+ test('should display profile page', async ({ page }) => { /* ... */ });
877
+ test('should update display name', async ({ page }) => { /* ... */ });
878
+ test('should upload avatar', async ({ page }) => { /* ... */ });
879
+ });
880
+ ```
881
+
882
+ **Fix:** Each test should be independent. Use `beforeEach` or fixtures to set up state.
883
+
884
+ ```typescript
885
+ // GOOD
886
+ test.describe('User Profile', () => {
887
+ test.beforeEach(async ({ page }) => {
888
+ await page.goto('/profile');
889
+ });
890
+
891
+ test('should display profile page', async ({ page }) => { /* ... */ });
892
+ test('should update display name', async ({ page }) => { /* ... */ });
893
+ test('should upload avatar', async ({ page }) => { /* ... */ });
894
+ });
895
+ ```
896
+
897
+ ---
898
+
899
+ ### Relying on test execution order
900
+
901
+ ```typescript
902
+ // BAD: test 2 depends on test 1 creating data
903
+ test('should create a product', async ({ page }) => {
904
+ // creates "Widget X" in the database
905
+ });
906
+
907
+ test('should edit the product', async ({ page }) => {
908
+ // assumes "Widget X" exists — breaks if run alone or in parallel
909
+ });
910
+ ```
911
+
912
+ **Fix:** Each test creates its own data via API calls or fixtures.
913
+
914
+ ```typescript
915
+ // GOOD
916
+ test('should edit a product', async ({ page, request }) => {
917
+ // Set up test data independently
918
+ const response = await request.post('/api/products', {
919
+ data: { name: 'Widget X', price: 9.99 },
920
+ });
921
+ const product = await response.json();
922
+
923
+ await page.goto(`/products/${product.id}/edit`);
924
+ await page.getByLabel('Name').fill('Widget X Updated');
925
+ await page.getByRole('button', { name: 'Save' }).click();
926
+ await expect(page.getByText('Widget X Updated')).toBeVisible();
927
+ });
928
+ ```
929
+
930
+ ## Troubleshooting
931
+
932
+ | Problem | Cause | Fix |
933
+ |---|---|---|
934
+ | Tests pass alone but fail together | Shared state between tests (cookies, DB rows, global variables) | Isolate each test — use fixtures for setup/teardown |
935
+ | `--grep @smoke` matches nothing | Tag not in test title string | Verify the tag appears literally in `test('... @smoke', ...)` |
936
+ | Serial tests cascade-fail | One test fails, all subsequent tests skip | Rewrite as independent tests or use `test.step()` in a single test |
937
+ | Tests run slower than expected | `fullyParallel` not set | Add `fullyParallel: true` in `playwright.config` |
938
+ | Wrong tests run in CI | `testMatch` or `testDir` misconfigured | Check `playwright.config` — print resolved config with `npx playwright test --list` |
939
+ | Monorepo tests pick up wrong files | Default `testDir` is `.` | Set explicit `testDir` per project |
940
+
941
+ ## Related
942
+
943
+ - [core/configuration.md](foundations/configuration.md) — `testMatch`, `testDir`, `fullyParallel`, `workers`
944
+ - [core/fixtures-and-hooks.md](foundations/fixtures-and-hooks.md) — shared setup via fixtures instead of `beforeAll`
945
+ - [core/test-architecture.md](decisions/test-architecture.md) — when to write E2E vs API vs component tests
946
+ - [ci/parallel-and-sharding.md](infrastructure/parallel-and-sharding.md) — CI sharding for large suites
947
+ - [ci/projects-and-dependencies.md](infrastructure/projects-and-dependencies.md) — multi-project config