thevoidforge 21.0.11 → 21.0.12

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 (107) hide show
  1. package/dist/.claude/commands/ai.md +69 -0
  2. package/dist/.claude/commands/architect.md +121 -0
  3. package/dist/.claude/commands/assemble.md +201 -0
  4. package/dist/.claude/commands/assess.md +75 -0
  5. package/dist/.claude/commands/blueprint.md +135 -0
  6. package/dist/.claude/commands/build.md +116 -0
  7. package/dist/.claude/commands/campaign.md +201 -0
  8. package/dist/.claude/commands/cultivation.md +166 -0
  9. package/dist/.claude/commands/current.md +128 -0
  10. package/dist/.claude/commands/dangerroom.md +74 -0
  11. package/dist/.claude/commands/debrief.md +178 -0
  12. package/dist/.claude/commands/deploy.md +99 -0
  13. package/dist/.claude/commands/devops.md +143 -0
  14. package/dist/.claude/commands/gauntlet.md +140 -0
  15. package/dist/.claude/commands/git.md +104 -0
  16. package/dist/.claude/commands/grow.md +146 -0
  17. package/dist/.claude/commands/imagine.md +126 -0
  18. package/dist/.claude/commands/portfolio.md +50 -0
  19. package/dist/.claude/commands/prd.md +113 -0
  20. package/dist/.claude/commands/qa.md +107 -0
  21. package/dist/.claude/commands/review.md +151 -0
  22. package/dist/.claude/commands/security.md +100 -0
  23. package/dist/.claude/commands/test.md +96 -0
  24. package/dist/.claude/commands/thumper.md +116 -0
  25. package/dist/.claude/commands/treasury.md +100 -0
  26. package/dist/.claude/commands/ux.md +118 -0
  27. package/dist/.claude/commands/vault.md +189 -0
  28. package/dist/.claude/commands/void.md +108 -0
  29. package/dist/CHANGELOG.md +1918 -0
  30. package/dist/CLAUDE.md +250 -0
  31. package/dist/HOLOCRON.md +856 -0
  32. package/dist/VERSION.md +123 -0
  33. package/dist/docs/NAMING_REGISTRY.md +478 -0
  34. package/dist/docs/methods/AI_INTELLIGENCE.md +276 -0
  35. package/dist/docs/methods/ASSEMBLER.md +142 -0
  36. package/dist/docs/methods/BACKEND_ENGINEER.md +165 -0
  37. package/dist/docs/methods/BUILD_JOURNAL.md +185 -0
  38. package/dist/docs/methods/BUILD_PROTOCOL.md +426 -0
  39. package/dist/docs/methods/CAMPAIGN.md +568 -0
  40. package/dist/docs/methods/CONTEXT_MANAGEMENT.md +189 -0
  41. package/dist/docs/methods/DEEP_CURRENT.md +184 -0
  42. package/dist/docs/methods/DEVOPS_ENGINEER.md +295 -0
  43. package/dist/docs/methods/FIELD_MEDIC.md +261 -0
  44. package/dist/docs/methods/FORGE_ARTIST.md +108 -0
  45. package/dist/docs/methods/FORGE_KEEPER.md +268 -0
  46. package/dist/docs/methods/GAUNTLET.md +344 -0
  47. package/dist/docs/methods/GROWTH_STRATEGIST.md +466 -0
  48. package/dist/docs/methods/HEARTBEAT.md +168 -0
  49. package/dist/docs/methods/MCP_INTEGRATION.md +139 -0
  50. package/dist/docs/methods/MUSTER.md +148 -0
  51. package/dist/docs/methods/PRD_GENERATOR.md +186 -0
  52. package/dist/docs/methods/PRODUCT_DESIGN_FRONTEND.md +250 -0
  53. package/dist/docs/methods/QA_ENGINEER.md +337 -0
  54. package/dist/docs/methods/RELEASE_MANAGER.md +145 -0
  55. package/dist/docs/methods/SECURITY_AUDITOR.md +320 -0
  56. package/dist/docs/methods/SUB_AGENTS.md +335 -0
  57. package/dist/docs/methods/SYSTEMS_ARCHITECT.md +171 -0
  58. package/dist/docs/methods/TESTING.md +359 -0
  59. package/dist/docs/methods/THUMPER.md +175 -0
  60. package/dist/docs/methods/TIME_VAULT.md +120 -0
  61. package/dist/docs/methods/TREASURY.md +184 -0
  62. package/dist/docs/methods/TROUBLESHOOTING.md +265 -0
  63. package/dist/docs/patterns/README.md +52 -0
  64. package/dist/docs/patterns/ad-billing-adapter.ts +537 -0
  65. package/dist/docs/patterns/ad-platform-adapter.ts +421 -0
  66. package/dist/docs/patterns/ai-classifier.ts +195 -0
  67. package/dist/docs/patterns/ai-eval.ts +272 -0
  68. package/dist/docs/patterns/ai-orchestrator.ts +341 -0
  69. package/dist/docs/patterns/ai-router.ts +194 -0
  70. package/dist/docs/patterns/ai-tool-schema.ts +237 -0
  71. package/dist/docs/patterns/api-route.ts +241 -0
  72. package/dist/docs/patterns/backtest-engine.ts +499 -0
  73. package/dist/docs/patterns/browser-review.ts +292 -0
  74. package/dist/docs/patterns/combobox.tsx +300 -0
  75. package/dist/docs/patterns/component.tsx +262 -0
  76. package/dist/docs/patterns/daemon-process.ts +338 -0
  77. package/dist/docs/patterns/data-pipeline.ts +297 -0
  78. package/dist/docs/patterns/database-migration.ts +466 -0
  79. package/dist/docs/patterns/e2e-test.ts +629 -0
  80. package/dist/docs/patterns/error-handling.ts +312 -0
  81. package/dist/docs/patterns/execution-safety.ts +601 -0
  82. package/dist/docs/patterns/financial-transaction.ts +342 -0
  83. package/dist/docs/patterns/funding-plan.ts +462 -0
  84. package/dist/docs/patterns/game-entity.ts +137 -0
  85. package/dist/docs/patterns/game-loop.ts +113 -0
  86. package/dist/docs/patterns/game-state.ts +143 -0
  87. package/dist/docs/patterns/job-queue.ts +225 -0
  88. package/dist/docs/patterns/kongo-integration.ts +164 -0
  89. package/dist/docs/patterns/middleware.ts +363 -0
  90. package/dist/docs/patterns/mobile-screen.tsx +139 -0
  91. package/dist/docs/patterns/mobile-service.ts +167 -0
  92. package/dist/docs/patterns/multi-tenant.ts +382 -0
  93. package/dist/docs/patterns/oauth-token-lifecycle.ts +223 -0
  94. package/dist/docs/patterns/outbound-rate-limiter.ts +260 -0
  95. package/dist/docs/patterns/prompt-template.ts +195 -0
  96. package/dist/docs/patterns/revenue-source-adapter.ts +311 -0
  97. package/dist/docs/patterns/service.ts +224 -0
  98. package/dist/docs/patterns/sse-endpoint.ts +118 -0
  99. package/dist/docs/patterns/stablecoin-adapter.ts +511 -0
  100. package/dist/docs/patterns/third-party-script.ts +68 -0
  101. package/dist/scripts/thumper/gom-jabbar.sh +241 -0
  102. package/dist/scripts/thumper/relay.sh +610 -0
  103. package/dist/scripts/thumper/scan.sh +359 -0
  104. package/dist/scripts/thumper/thumper.sh +190 -0
  105. package/dist/scripts/thumper/water-rings.sh +76 -0
  106. package/package.json +1 -1
  107. package/dist/tsconfig.tsbuildinfo +0 -1
@@ -0,0 +1,629 @@
1
+ /**
2
+ * Pattern: E2E Testing with Playwright
3
+ *
4
+ * Key principles:
5
+ * - Page Object Model for test organization
6
+ * - axe-core a11y scan as default fixture (every page gets a free a11y check)
7
+ * - Deterministic state: clean temp dir per test, explicit waits only
8
+ * - Network mocking for external APIs
9
+ * - Auth helper for authenticated flows
10
+ * - CWV measurement (opt-in)
11
+ * - Flaky test management: retry, quarantine, no waitForTimeout
12
+ *
13
+ * Testing pyramid position: Unit (many, fast) → Integration (moderate) → E2E (few, slow, critical paths only)
14
+ * Target: 10-15 E2E tests for a typical full-stack app. Max 2 min CI time.
15
+ *
16
+ * Agents: Batman (test strategy), Samwise (a11y), Éowyn (enchantment), Nightwing (regression)
17
+ * Hawkeye (Gauntlet Round 2.5 smoke tests)
18
+ *
19
+ * Framework adaptations:
20
+ * Next.js: webServer.command = 'next dev -p 3199' (or 'next build && next start')
21
+ * Express: webServer.command = 'PORT=3199 npx tsx src/server.ts'
22
+ * Django: webServer.command = 'python manage.py runserver 3199 --settings=project.settings.test'
23
+ * Rails: webServer.command = 'RAILS_ENV=test bin/rails server -p 3199'
24
+ *
25
+ * === Django Adaptation ===
26
+ *
27
+ * # playwright.config.ts — same structure, different webServer
28
+ * webServer: {
29
+ * command: 'python manage.py runserver 3199 --settings=myapp.settings.test',
30
+ * port: 3199,
31
+ * reuseExistingServer: !process.env.CI,
32
+ * }
33
+ *
34
+ * # Auth helper: POST to Django login endpoint or use session cookie directly
35
+ * # Network mocking: same Playwright route.fulfill() — framework doesn't matter
36
+ * # a11y: same axe-core fixture — it runs in the browser, not the server
37
+ *
38
+ * === Key Rules ===
39
+ *
40
+ * 1. NEVER use waitForTimeout() — it's a flake factory. Use explicit waits:
41
+ * await page.waitForSelector(), await expect(locator).toBeVisible()
42
+ * 2. NEVER use networkidle — it's non-deterministic. Wait for specific elements.
43
+ * 3. ONE assertion concept per test. Split journeys into focused test cases.
44
+ * 4. Clean state per test. Use beforeEach to reset, not afterAll to teardown.
45
+ * 5. E2E tests are expensive — only test critical user journeys.
46
+ * Unit/integration tests cover edge cases, validation, and error handling.
47
+ */
48
+
49
+ import { test as base, expect, type Page, type BrowserContext } from '@playwright/test';
50
+ import AxeBuilder from '@axe-core/playwright';
51
+
52
+ // ── Types ───────────────────────────────────────────
53
+
54
+ /** Auth session info returned from login helper. */
55
+ interface AuthSession {
56
+ token: string;
57
+ userId: string;
58
+ cookies: Array<{
59
+ name: string;
60
+ value: string;
61
+ domain: string;
62
+ path: string;
63
+ }>;
64
+ }
65
+
66
+ /** Core Web Vitals measurement result. */
67
+ interface CWVResult {
68
+ lcp: number | null; // Largest Contentful Paint (ms)
69
+ fid: number | null; // First Input Delay (ms)
70
+ cls: number | null; // Cumulative Layout Shift (score)
71
+ ttfb: number | null; // Time to First Byte (ms)
72
+ }
73
+
74
+ // ── axe-core Fixture ────────────────────────────────
75
+ // Every test that uses { page } also gets a free a11y scan helper.
76
+ // Import `test` from this file instead of @playwright/test.
77
+
78
+ /**
79
+ * Known pre-existing a11y violations. Track these for resolution —
80
+ * excluding them prevents false failures while still catching regressions.
81
+ * Remove rules from this list as they're fixed.
82
+ */
83
+ const KNOWN_A11Y_EXCLUSIONS: string[] = [
84
+ // 'color-contrast', // Example: dark theme needs design review
85
+ // 'landmark-one-main', // Example: some pages lack <main>
86
+ ];
87
+
88
+ export const test = base.extend<{
89
+ axe: AxeBuilder;
90
+ expectAccessible: (page: Page) => Promise<void>;
91
+ }>({
92
+ axe: async ({ page }, use) => {
93
+ await use(new AxeBuilder({ page }));
94
+ },
95
+
96
+ expectAccessible: async ({}, use) => {
97
+ await use(async (targetPage: Page) => {
98
+ const results = await new AxeBuilder({ page: targetPage })
99
+ .disableRules(KNOWN_A11Y_EXCLUSIONS)
100
+ .analyze();
101
+
102
+ if (results.violations.length > 0) {
103
+ const summary = results.violations
104
+ .map(
105
+ (v) =>
106
+ `[${v.impact}] ${v.id}: ${v.description} (${v.nodes.length} node${v.nodes.length === 1 ? '' : 's'})`
107
+ )
108
+ .join('\n ');
109
+ throw new Error(`Accessibility violations:\n ${summary}`);
110
+ }
111
+ });
112
+ },
113
+ });
114
+
115
+ export { expect };
116
+
117
+ // ── Page Object Model ───────────────────────────────
118
+ // Encapsulate page interaction behind a stable API.
119
+ // When selectors change, update the POM — not every test.
120
+
121
+ export class LoginPage {
122
+ constructor(private page: Page) {}
123
+
124
+ async goto(): Promise<void> {
125
+ await this.page.goto('/login');
126
+ }
127
+
128
+ async fillEmail(email: string): Promise<void> {
129
+ await this.page.getByLabel('Email').fill(email);
130
+ }
131
+
132
+ async fillPassword(password: string): Promise<void> {
133
+ await this.page.getByLabel('Password').fill(password);
134
+ }
135
+
136
+ async submit(): Promise<void> {
137
+ await this.page.getByRole('button', { name: /sign in|log in/i }).click();
138
+ }
139
+
140
+ async login(email: string, password: string): Promise<void> {
141
+ await this.fillEmail(email);
142
+ await this.fillPassword(password);
143
+ await this.submit();
144
+ }
145
+
146
+ async expectError(message: string | RegExp): Promise<void> {
147
+ await expect(this.page.getByRole('alert')).toContainText(message);
148
+ }
149
+
150
+ async expectRedirectToDashboard(): Promise<void> {
151
+ await this.page.waitForURL('**/dashboard**');
152
+ }
153
+ }
154
+
155
+ export class DashboardPage {
156
+ constructor(private page: Page) {}
157
+
158
+ async goto(): Promise<void> {
159
+ await this.page.goto('/dashboard');
160
+ }
161
+
162
+ async expectLoaded(): Promise<void> {
163
+ // Wait for a specific element, not networkidle
164
+ await expect(this.page.getByRole('heading', { level: 1 })).toBeVisible();
165
+ }
166
+
167
+ async expectProjectVisible(name: string): Promise<void> {
168
+ await expect(this.page.getByText(name)).toBeVisible();
169
+ }
170
+
171
+ async createProject(name: string): Promise<void> {
172
+ await this.page.getByRole('button', { name: /create|new project/i }).click();
173
+ await this.page.getByLabel('Project name').fill(name);
174
+ await this.page.getByRole('button', { name: /create|save/i }).click();
175
+ // Wait for the project to appear — explicit, not timeout
176
+ await expect(this.page.getByText(name)).toBeVisible();
177
+ }
178
+ }
179
+
180
+ // ── Auth Helper ─────────────────────────────────────
181
+ // Login via API (fast), not via UI (slow). Reuse session across tests.
182
+ // Store auth state to a file so Playwright can load it without re-logging in.
183
+
184
+ const AUTH_STATE_PATH = 'e2e/.auth/session.json';
185
+
186
+ /**
187
+ * Authenticate via API and save session state for reuse.
188
+ * Call once in a setup project, then reuse in test projects.
189
+ *
190
+ * playwright.config.ts:
191
+ * projects: [
192
+ * { name: 'setup', testMatch: /auth\.setup\.ts/ },
193
+ * { name: 'tests', dependencies: ['setup'], use: { storageState: AUTH_STATE_PATH } },
194
+ * ]
195
+ */
196
+ export async function loginViaAPI(
197
+ context: BrowserContext,
198
+ baseURL: string,
199
+ credentials: { email: string; password: string }
200
+ ): Promise<AuthSession> {
201
+ const response = await context.request.post(`${baseURL}/api/auth/login`, {
202
+ data: credentials,
203
+ });
204
+
205
+ if (!response.ok()) {
206
+ throw new Error(`Auth failed: ${response.status()} ${await response.text()}`);
207
+ }
208
+
209
+ const body = await response.json();
210
+
211
+ // Save browser state (cookies, localStorage) for reuse
212
+ await context.storageState({ path: AUTH_STATE_PATH });
213
+
214
+ return {
215
+ token: body.token,
216
+ userId: body.userId,
217
+ cookies: await context.cookies(),
218
+ };
219
+ }
220
+
221
+ /**
222
+ * Auth setup file — run once before all tests.
223
+ *
224
+ * // e2e/auth.setup.ts
225
+ * import { test as setup } from '@playwright/test';
226
+ * import { loginViaAPI } from './fixtures';
227
+ *
228
+ * setup('authenticate', async ({ context, baseURL }) => {
229
+ * await loginViaAPI(context, baseURL!, {
230
+ * email: 'test@example.com',
231
+ * password: 'test-password-123',
232
+ * });
233
+ * });
234
+ */
235
+
236
+ // ── Network Mocking ─────────────────────────────────
237
+ // Intercept external API calls to make tests deterministic.
238
+ // Mock at the network level, not the application level.
239
+
240
+ /**
241
+ * Mock an external API endpoint.
242
+ *
243
+ * Usage:
244
+ * await mockAPI(page, 'https://api.stripe.com/v1/charges', {
245
+ * status: 200,
246
+ * body: { id: 'ch_123', amount: 1000, status: 'succeeded' },
247
+ * });
248
+ */
249
+ export async function mockAPI(
250
+ page: Page,
251
+ urlPattern: string | RegExp,
252
+ response: {
253
+ status?: number;
254
+ body: Record<string, unknown>;
255
+ headers?: Record<string, string>;
256
+ delay?: number; // Simulate network latency (ms) — use sparingly
257
+ }
258
+ ): Promise<void> {
259
+ await page.route(urlPattern, async (route) => {
260
+ if (response.delay) {
261
+ await new Promise((resolve) => setTimeout(resolve, response.delay));
262
+ }
263
+ await route.fulfill({
264
+ status: response.status ?? 200,
265
+ contentType: 'application/json',
266
+ headers: response.headers ?? {},
267
+ body: JSON.stringify(response.body),
268
+ });
269
+ });
270
+ }
271
+
272
+ /**
273
+ * Mock a failing external API.
274
+ *
275
+ * Usage:
276
+ * await mockAPIError(page, /api\.stripe\.com/, 503, 'Service Unavailable');
277
+ */
278
+ export async function mockAPIError(
279
+ page: Page,
280
+ urlPattern: string | RegExp,
281
+ status: number,
282
+ message: string
283
+ ): Promise<void> {
284
+ await page.route(urlPattern, (route) =>
285
+ route.fulfill({
286
+ status,
287
+ contentType: 'application/json',
288
+ body: JSON.stringify({ error: { message } }),
289
+ })
290
+ );
291
+ }
292
+
293
+ /**
294
+ * Abort external requests entirely — simulates network failure.
295
+ *
296
+ * Usage:
297
+ * await mockNetworkFailure(page, /api\.external\.com/);
298
+ */
299
+ export async function mockNetworkFailure(
300
+ page: Page,
301
+ urlPattern: string | RegExp
302
+ ): Promise<void> {
303
+ await page.route(urlPattern, (route) => route.abort('connectionrefused'));
304
+ }
305
+
306
+ // ── WebSocket Mock ──────────────────────────────────
307
+ // Intercept WebSocket connections for real-time feature testing.
308
+
309
+ /**
310
+ * Mock a WebSocket connection.
311
+ *
312
+ * Usage:
313
+ * const wsMock = await mockWebSocket(page, 'wss://api.example.com/ws');
314
+ * wsMock.send({ type: 'message', data: 'hello' });
315
+ * wsMock.close();
316
+ *
317
+ * Note: Playwright's native WebSocket mocking (page.routeWebSocket) was added
318
+ * in Playwright 1.48. For older versions, use a local WebSocket test server.
319
+ */
320
+ export async function mockWebSocket(
321
+ page: Page,
322
+ url: string | RegExp
323
+ ): Promise<{
324
+ send: (data: Record<string, unknown>) => void;
325
+ close: () => void;
326
+ onMessage: (handler: (data: string) => void) => void;
327
+ }> {
328
+ const handlers: Array<(data: string) => void> = [];
329
+
330
+ // Playwright 1.48+ — use page.routeWebSocket
331
+ const ws = await page.routeWebSocket(url, (ws) => {
332
+ ws.onMessage((message) => {
333
+ for (const handler of handlers) {
334
+ handler(typeof message === 'string' ? message : message.toString());
335
+ }
336
+ });
337
+ });
338
+
339
+ return {
340
+ send: (data) => {
341
+ // Send data from the "server" side to the client
342
+ ws.send(JSON.stringify(data));
343
+ },
344
+ close: () => {
345
+ ws.close();
346
+ },
347
+ onMessage: (handler) => {
348
+ handlers.push(handler);
349
+ },
350
+ };
351
+ }
352
+
353
+ // ── Core Web Vitals Measurement ─────────────────────
354
+ // Opt-in CWV measurement for performance-critical pages.
355
+ // Not a pass/fail gate by default — use for tracking and regression detection.
356
+
357
+ /**
358
+ * Measure Core Web Vitals on a page.
359
+ *
360
+ * Usage:
361
+ * const cwv = await measureCWV(page);
362
+ * expect(cwv.lcp).toBeLessThan(2500); // Good LCP threshold
363
+ * expect(cwv.cls).toBeLessThan(0.1); // Good CLS threshold
364
+ *
365
+ * Note: FID requires user interaction — use INP (Interaction to Next Paint) for
366
+ * automated measurement. This helper measures LCP, CLS, and TTFB reliably.
367
+ * FID is returned as null unless explicit interaction triggers it.
368
+ */
369
+ export async function measureCWV(page: Page): Promise<CWVResult> {
370
+ return await page.evaluate(() => {
371
+ return new Promise<CWVResult>((resolve) => {
372
+ const result: CWVResult = { lcp: null, fid: null, cls: null, ttfb: null };
373
+
374
+ // TTFB — available immediately from Navigation Timing API
375
+ const navEntry = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming;
376
+ if (navEntry) {
377
+ result.ttfb = navEntry.responseStart - navEntry.requestStart;
378
+ }
379
+
380
+ // LCP — observe until we get a stable value
381
+ const lcpObserver = new PerformanceObserver((list) => {
382
+ const entries = list.getEntries();
383
+ if (entries.length > 0) {
384
+ result.lcp = entries[entries.length - 1]!.startTime;
385
+ }
386
+ });
387
+ lcpObserver.observe({ type: 'largest-contentful-paint', buffered: true });
388
+
389
+ // CLS — accumulate layout shift scores
390
+ let clsScore = 0;
391
+ const clsObserver = new PerformanceObserver((list) => {
392
+ for (const entry of list.getEntries()) {
393
+ const layoutShift = entry as PerformanceEntry & { hadRecentInput: boolean; value: number };
394
+ if (!layoutShift.hadRecentInput) {
395
+ clsScore += layoutShift.value;
396
+ }
397
+ }
398
+ result.cls = clsScore;
399
+ });
400
+ clsObserver.observe({ type: 'layout-shift', buffered: true });
401
+
402
+ // Collect after a short stabilization period
403
+ setTimeout(() => {
404
+ lcpObserver.disconnect();
405
+ clsObserver.disconnect();
406
+ resolve(result);
407
+ }, 3000);
408
+ });
409
+ });
410
+ }
411
+
412
+ /**
413
+ * CWV thresholds per Google's "Good" classification.
414
+ * Use with expect: expect(cwv.lcp).toBeLessThan(CWV_THRESHOLDS.lcp.good)
415
+ */
416
+ export const CWV_THRESHOLDS = {
417
+ lcp: { good: 2500, needsImprovement: 4000 }, // ms
418
+ fid: { good: 100, needsImprovement: 300 }, // ms
419
+ cls: { good: 0.1, needsImprovement: 0.25 }, // score
420
+ ttfb: { good: 800, needsImprovement: 1800 }, // ms
421
+ } as const;
422
+
423
+ // ── Flaky Test Annotation ───────────────────────────
424
+ // Use the @flaky tag to mark tests with known intermittent failures.
425
+ // Quarantined tests run in a separate CI job and don't block the main pipeline.
426
+ //
427
+ // Protocol:
428
+ // 1. Test fails intermittently (3+ times in a week) → add @flaky tag
429
+ // 2. Huntress (stability monitor) tracks flake rate
430
+ // 3. Quarantined tests run in a separate "flaky" CI job
431
+ // 4. Fix the root cause → remove @flaky tag → test returns to main suite
432
+ // 5. If unfixable after 2 weeks → rewrite the test or demote to manual
433
+ //
434
+ // NEVER use @flaky to suppress real bugs. The test must pass consistently
435
+ // when the feature works correctly — flakiness comes from test infrastructure,
436
+ // timing, or external dependencies, not from product bugs.
437
+
438
+ /**
439
+ * Mark a test as flaky with a tracking reason.
440
+ *
441
+ * Usage:
442
+ * test('payment flow completes', {
443
+ * tag: ['@flaky'],
444
+ * annotation: { type: 'flaky', description: 'Stripe webhook timing — tracking in #234' },
445
+ * }, async ({ page }) => { ... });
446
+ *
447
+ * In playwright.config.ts, separate flaky tests into their own project:
448
+ * projects: [
449
+ * { name: 'stable', grep: /^(?!.*@flaky)/, retries: 1 },
450
+ * { name: 'flaky', grep: /@flaky/, retries: 3 },
451
+ * ]
452
+ */
453
+
454
+ // ── Example Tests ───────────────────────────────────
455
+ // These show the complete pattern for common E2E scenarios.
456
+
457
+ /*
458
+ // ── Example 1: Login flow with Page Object Model ───
459
+
460
+ import { test, expect } from './fixtures';
461
+ import { LoginPage, DashboardPage } from './fixtures';
462
+
463
+ test.describe('Authentication', () => {
464
+ test('user can log in with valid credentials', async ({ page, expectAccessible }) => {
465
+ const loginPage = new LoginPage(page);
466
+ const dashboard = new DashboardPage(page);
467
+
468
+ await loginPage.goto();
469
+ await loginPage.login('user@example.com', 'password123');
470
+ await dashboard.expectLoaded();
471
+
472
+ // Every page gets a free a11y check
473
+ await expectAccessible(page);
474
+ });
475
+
476
+ test('shows error on invalid credentials', async ({ page, expectAccessible }) => {
477
+ const loginPage = new LoginPage(page);
478
+
479
+ await loginPage.goto();
480
+ await loginPage.login('user@example.com', 'wrong-password');
481
+ await loginPage.expectError(/invalid credentials/i);
482
+
483
+ await expectAccessible(page);
484
+ });
485
+ });
486
+
487
+ // ── Example 2: CRUD flow with network mocking ──────
488
+
489
+ import { test, expect, mockAPI } from './fixtures';
490
+
491
+ test.describe('Projects', () => {
492
+ test('creates a project and sees it in the list', async ({ page, expectAccessible }) => {
493
+ const dashboard = new DashboardPage(page);
494
+ await dashboard.goto();
495
+ await dashboard.createProject('My New Project');
496
+ await dashboard.expectProjectVisible('My New Project');
497
+
498
+ await expectAccessible(page);
499
+ });
500
+
501
+ test('handles API failure gracefully', async ({ page, expectAccessible }) => {
502
+ // Mock the project creation endpoint to fail
503
+ await mockAPI(page, '**/api/projects', {
504
+ status: 500,
505
+ body: { error: { code: 'INTERNAL_ERROR', message: 'Something went wrong' } },
506
+ });
507
+
508
+ const dashboard = new DashboardPage(page);
509
+ await dashboard.goto();
510
+
511
+ await page.getByRole('button', { name: /create|new project/i }).click();
512
+ await page.getByLabel('Project name').fill('Doomed Project');
513
+ await page.getByRole('button', { name: /create|save/i }).click();
514
+
515
+ // Verify error state is shown — not a blank screen
516
+ await expect(page.getByRole('alert')).toBeVisible();
517
+
518
+ await expectAccessible(page);
519
+ });
520
+ });
521
+
522
+ // ── Example 3: WebSocket real-time updates ──────────
523
+
524
+ import { test, expect, mockWebSocket } from './fixtures';
525
+
526
+ test('receives real-time notifications via WebSocket', async ({ page }) => {
527
+ const ws = await mockWebSocket(page, /\/ws\/notifications/);
528
+
529
+ await page.goto('/dashboard');
530
+
531
+ // Server pushes a notification
532
+ ws.send({ type: 'notification', title: 'Build complete', level: 'success' });
533
+
534
+ // Verify it appears in the UI
535
+ await expect(page.getByText('Build complete')).toBeVisible();
536
+
537
+ ws.close();
538
+ });
539
+
540
+ // ── Example 4: CWV measurement (opt-in) ────────────
541
+
542
+ import { test, expect, measureCWV, CWV_THRESHOLDS } from './fixtures';
543
+
544
+ test('landing page meets CWV thresholds', async ({ page }) => {
545
+ await page.goto('/');
546
+
547
+ const cwv = await measureCWV(page);
548
+
549
+ // These are aspirational — adjust thresholds to your project
550
+ if (cwv.lcp !== null) {
551
+ expect(cwv.lcp).toBeLessThan(CWV_THRESHOLDS.lcp.good);
552
+ }
553
+ if (cwv.cls !== null) {
554
+ expect(cwv.cls).toBeLessThan(CWV_THRESHOLDS.cls.good);
555
+ }
556
+ if (cwv.ttfb !== null) {
557
+ expect(cwv.ttfb).toBeLessThan(CWV_THRESHOLDS.ttfb.good);
558
+ }
559
+ });
560
+ */
561
+
562
+ // ── Playwright Config Reference ─────────────────────
563
+ // playwright.config.ts — adapt webServer.command to your framework.
564
+ //
565
+ // import { defineConfig, devices } from '@playwright/test';
566
+ //
567
+ // export default defineConfig({
568
+ // testDir: './e2e',
569
+ // forbidOnly: !!process.env.CI,
570
+ // retries: 1,
571
+ // reporter: process.env.CI ? 'github' : 'list',
572
+ //
573
+ // use: {
574
+ // baseURL: 'http://127.0.0.1:3199',
575
+ // // Network isolation — block external requests in browser
576
+ // launchOptions: {
577
+ // args: ['--host-resolver-rules=MAP * ~NOTFOUND, EXCLUDE 127.0.0.1'],
578
+ // },
579
+ // trace: 'on-first-retry',
580
+ // },
581
+ //
582
+ // projects: [
583
+ // // Auth setup runs first, saves session state
584
+ // { name: 'setup', testMatch: /auth\.setup\.ts/ },
585
+ //
586
+ // // Stable tests use saved auth, retry once
587
+ // {
588
+ // name: 'stable',
589
+ // dependencies: ['setup'],
590
+ // use: { storageState: 'e2e/.auth/session.json' },
591
+ // grep: /^(?!.*@flaky)/,
592
+ // retries: 1,
593
+ // },
594
+ //
595
+ // // Flaky tests get more retries, don't block the pipeline
596
+ // {
597
+ // name: 'flaky',
598
+ // dependencies: ['setup'],
599
+ // use: { storageState: 'e2e/.auth/session.json' },
600
+ // grep: /@flaky/,
601
+ // retries: 3,
602
+ // },
603
+ // ],
604
+ //
605
+ // // Framework-specific webServer commands:
606
+ // webServer: {
607
+ // // Next.js: command: 'next dev -p 3199',
608
+ // // Express: command: 'PORT=3199 npx tsx src/server.ts',
609
+ // // Django: command: 'python manage.py runserver 3199 --settings=project.settings.test',
610
+ // // Rails: command: 'RAILS_ENV=test bin/rails server -p 3199',
611
+ // command: 'PORT=3199 npx tsx src/server.ts',
612
+ // port: 3199,
613
+ // reuseExistingServer: !process.env.CI,
614
+ // timeout: 30_000,
615
+ // },
616
+ // });
617
+
618
+ // ── Setup Checklist ─────────────────────────────────
619
+ // When adding E2E tests to a project:
620
+ //
621
+ // - [ ] npm i -D @playwright/test @axe-core/playwright
622
+ // - [ ] npx playwright install chromium
623
+ // - [ ] Create playwright.config.ts (adapt webServer command)
624
+ // - [ ] Create e2e/fixtures.ts (copy axe fixture + auth helper from this pattern)
625
+ // - [ ] Create e2e/auth.setup.ts (login via API, save session)
626
+ // - [ ] Write first smoke test: page loads, no a11y violations
627
+ // - [ ] Add to CI: npx playwright test (separate job, max 2 min budget)
628
+ // - [ ] Add e2e/.auth/ to .gitignore
629
+ // - [ ] Add test-results/ and playwright-report/ to .gitignore