@arcadialdev/arcality 2.2.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 (97) hide show
  1. package/.agents/skills/e2e-testing-expert/SKILL.md +28 -0
  2. package/.agents/skills/frontend-design/LICENSE.txt +177 -0
  3. package/.agents/skills/frontend-design/SKILL.md +42 -0
  4. package/.agents/skills/nodejs-backend-patterns/SKILL.md +639 -0
  5. package/.agents/skills/nodejs-backend-patterns/references/advanced-patterns.md +430 -0
  6. package/.agents/skills/playwright-best-practices/LICENSE.md +7 -0
  7. package/.agents/skills/playwright-best-practices/README.md +147 -0
  8. package/.agents/skills/playwright-best-practices/SKILL.md +303 -0
  9. package/.agents/skills/playwright-best-practices/advanced/authentication-flows.md +360 -0
  10. package/.agents/skills/playwright-best-practices/advanced/authentication.md +871 -0
  11. package/.agents/skills/playwright-best-practices/advanced/clock-mocking.md +364 -0
  12. package/.agents/skills/playwright-best-practices/advanced/mobile-testing.md +409 -0
  13. package/.agents/skills/playwright-best-practices/advanced/multi-context.md +288 -0
  14. package/.agents/skills/playwright-best-practices/advanced/multi-user.md +393 -0
  15. package/.agents/skills/playwright-best-practices/advanced/network-advanced.md +452 -0
  16. package/.agents/skills/playwright-best-practices/advanced/third-party.md +464 -0
  17. package/.agents/skills/playwright-best-practices/architecture/pom-vs-fixtures.md +363 -0
  18. package/.agents/skills/playwright-best-practices/architecture/test-architecture.md +369 -0
  19. package/.agents/skills/playwright-best-practices/architecture/when-to-mock.md +383 -0
  20. package/.agents/skills/playwright-best-practices/browser-apis/browser-apis.md +391 -0
  21. package/.agents/skills/playwright-best-practices/browser-apis/iframes.md +403 -0
  22. package/.agents/skills/playwright-best-practices/browser-apis/service-workers.md +504 -0
  23. package/.agents/skills/playwright-best-practices/browser-apis/websockets.md +403 -0
  24. package/.agents/skills/playwright-best-practices/core/annotations.md +424 -0
  25. package/.agents/skills/playwright-best-practices/core/assertions-waiting.md +361 -0
  26. package/.agents/skills/playwright-best-practices/core/configuration.md +452 -0
  27. package/.agents/skills/playwright-best-practices/core/fixtures-hooks.md +417 -0
  28. package/.agents/skills/playwright-best-practices/core/global-setup.md +434 -0
  29. package/.agents/skills/playwright-best-practices/core/locators.md +242 -0
  30. package/.agents/skills/playwright-best-practices/core/page-object-model.md +315 -0
  31. package/.agents/skills/playwright-best-practices/core/projects-dependencies.md +453 -0
  32. package/.agents/skills/playwright-best-practices/core/test-data.md +492 -0
  33. package/.agents/skills/playwright-best-practices/core/test-suite-structure.md +361 -0
  34. package/.agents/skills/playwright-best-practices/core/test-tags.md +298 -0
  35. package/.agents/skills/playwright-best-practices/debugging/console-errors.md +420 -0
  36. package/.agents/skills/playwright-best-practices/debugging/debugging.md +504 -0
  37. package/.agents/skills/playwright-best-practices/debugging/error-testing.md +360 -0
  38. package/.agents/skills/playwright-best-practices/debugging/flaky-tests.md +496 -0
  39. package/.agents/skills/playwright-best-practices/frameworks/angular.md +530 -0
  40. package/.agents/skills/playwright-best-practices/frameworks/nextjs.md +469 -0
  41. package/.agents/skills/playwright-best-practices/frameworks/react.md +531 -0
  42. package/.agents/skills/playwright-best-practices/frameworks/vue.md +574 -0
  43. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/ci-cd.md +468 -0
  44. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/docker.md +283 -0
  45. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/github-actions.md +546 -0
  46. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/gitlab.md +397 -0
  47. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/other-providers.md +521 -0
  48. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/parallel-sharding.md +371 -0
  49. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/performance.md +453 -0
  50. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/reporting.md +424 -0
  51. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/test-coverage.md +497 -0
  52. package/.agents/skills/playwright-best-practices/testing-patterns/accessibility.md +359 -0
  53. package/.agents/skills/playwright-best-practices/testing-patterns/api-testing.md +719 -0
  54. package/.agents/skills/playwright-best-practices/testing-patterns/browser-extensions.md +506 -0
  55. package/.agents/skills/playwright-best-practices/testing-patterns/canvas-webgl.md +493 -0
  56. package/.agents/skills/playwright-best-practices/testing-patterns/component-testing.md +500 -0
  57. package/.agents/skills/playwright-best-practices/testing-patterns/drag-drop.md +576 -0
  58. package/.agents/skills/playwright-best-practices/testing-patterns/electron.md +509 -0
  59. package/.agents/skills/playwright-best-practices/testing-patterns/file-operations.md +377 -0
  60. package/.agents/skills/playwright-best-practices/testing-patterns/file-upload-download.md +562 -0
  61. package/.agents/skills/playwright-best-practices/testing-patterns/forms-validation.md +561 -0
  62. package/.agents/skills/playwright-best-practices/testing-patterns/graphql-testing.md +331 -0
  63. package/.agents/skills/playwright-best-practices/testing-patterns/i18n.md +508 -0
  64. package/.agents/skills/playwright-best-practices/testing-patterns/performance-testing.md +476 -0
  65. package/.agents/skills/playwright-best-practices/testing-patterns/security-testing.md +430 -0
  66. package/.agents/skills/playwright-best-practices/testing-patterns/visual-regression.md +634 -0
  67. package/.env.example +21 -0
  68. package/README.md +30 -0
  69. package/bin/arcality.mjs +86 -0
  70. package/package.json +66 -0
  71. package/playwright.config.ts +12 -0
  72. package/scripts/cleanup-qmsdev.mjs +63 -0
  73. package/scripts/discover-view.mjs +52 -0
  74. package/scripts/extract-view.mjs +64 -0
  75. package/scripts/gen-and-run.mjs +838 -0
  76. package/scripts/init.mjs +290 -0
  77. package/scripts/migrate-to-central-out.mjs +157 -0
  78. package/scripts/postinstall.mjs +63 -0
  79. package/scripts/rebrand-report.mjs +241 -0
  80. package/scripts/setup.mjs +166 -0
  81. package/src/KnowledgeService.ts +239 -0
  82. package/src/arcalityClient.mjs +266 -0
  83. package/src/configLoader.mjs +179 -0
  84. package/src/configManager.mjs +172 -0
  85. package/src/consoleBanner.ts +32 -0
  86. package/src/envSetup.ts +205 -0
  87. package/src/index.ts +25 -0
  88. package/src/projectInspector.ts +42 -0
  89. package/src/services/collectiveMemoryService.ts +178 -0
  90. package/src/testRunner.ts +201 -0
  91. package/tests/_helpers/ArcalityReporter.ts +490 -0
  92. package/tests/_helpers/agentic-runner.spec.ts +741 -0
  93. package/tests/_helpers/ai-agent-helper.ts +1573 -0
  94. package/tests/_helpers/discover-view.spec.ts +238 -0
  95. package/tests/_helpers/extract-view.spec.ts +118 -0
  96. package/tests/_helpers/qa-tools.ts +333 -0
  97. package/tests/_helpers/smart-action.spec.ts +1458 -0
@@ -0,0 +1,531 @@
1
+ # React Application Testing
2
+
3
+ ## Table of Contents
4
+
5
+ 1. [Patterns](#patterns)
6
+ 2. [Setup](#setup)
7
+ 3. [Framework Tips](#framework-tips)
8
+ 4. [Anti-Patterns](#anti-patterns)
9
+ 5. [Related](#related)
10
+
11
+ > **When to use**: Testing React apps built with Vite, Create React App, or custom bundlers. Covers E2E testing, component testing, React Router navigation, form libraries, portals, error boundaries, and context/state verification.
12
+ > **Prerequisites**: [configuration.md](../core/configuration.md), [locators.md](../core/locators.md)
13
+
14
+ ## Patterns
15
+
16
+ ### Testing Context and Global State
17
+
18
+ **Use when**: Verifying React context (theme, auth, locale) and state management (Redux, Zustand) produce correct UI changes.
19
+ **Avoid when**: You want to assert on raw state objects—test the UI, not internal state.
20
+
21
+ ```typescript
22
+ import { test, expect } from '@playwright/test';
23
+
24
+ test.describe('theme switching', () => {
25
+ test('toggle applies dark mode across pages', async ({ page }) => {
26
+ await page.goto('/preferences');
27
+
28
+ const root = page.locator('html');
29
+ await expect(root).not.toHaveClass(/dark-mode/);
30
+
31
+ await page.getByRole('switch', { name: 'Enable dark theme' }).click();
32
+ await expect(root).toHaveClass(/dark-mode/);
33
+
34
+ await page.getByRole('link', { name: 'Dashboard' }).click();
35
+ await expect(page.locator('html')).toHaveClass(/dark-mode/);
36
+ });
37
+ });
38
+
39
+ test.describe('cart state persistence', () => {
40
+ test('item count updates globally', async ({ page }) => {
41
+ await page.goto('/catalog');
42
+
43
+ const badge = page.getByTestId('cart-badge');
44
+
45
+ await page.getByRole('listitem')
46
+ .filter({ hasText: 'Wireless Headphones' })
47
+ .getByRole('button', { name: 'Add' })
48
+ .click();
49
+ await expect(badge).toHaveText('1');
50
+
51
+ await page.getByRole('link', { name: 'Contact' }).click();
52
+ await expect(badge).toHaveText('1');
53
+ });
54
+ });
55
+
56
+ test.describe('auth state', () => {
57
+ test('login updates header across components', async ({ page }) => {
58
+ await page.goto('/');
59
+
60
+ await expect(page.getByRole('link', { name: 'Login' })).toBeVisible();
61
+
62
+ await page.getByRole('link', { name: 'Login' }).click();
63
+ await page.getByLabel('Username').fill('testuser');
64
+ await page.getByLabel('Password').fill('secret123');
65
+ await page.getByRole('button', { name: 'Submit' }).click();
66
+
67
+ await expect(page.getByRole('link', { name: 'Login' })).toBeHidden();
68
+ await expect(page.getByText('testuser')).toBeVisible();
69
+ });
70
+ });
71
+ ```
72
+
73
+ ### React Router Navigation
74
+
75
+ **Use when**: Testing client-side routing with React Router v6+—route transitions, URL parameters, protected routes, browser history.
76
+ **Avoid when**: Server-side routing (Next.js App Router—see [nextjs.md](nextjs.md)).
77
+
78
+ ```typescript
79
+ import { test, expect } from '@playwright/test';
80
+
81
+ test.describe('client routing', () => {
82
+ test('navigation preserves SPA state', async ({ page }) => {
83
+ await page.goto('/');
84
+
85
+ await page.evaluate(() => {
86
+ (window as any).__spaMarker = 'active';
87
+ });
88
+
89
+ await page.getByRole('link', { name: 'Inventory' }).click();
90
+ await page.waitForURL('/inventory');
91
+
92
+ const marker = await page.evaluate(() => (window as any).__spaMarker);
93
+ expect(marker).toBe('active');
94
+ });
95
+
96
+ test('query params filter content', async ({ page }) => {
97
+ await page.goto('/items?type=books');
98
+
99
+ await expect(page.getByRole('heading', { name: 'Books' })).toBeVisible();
100
+
101
+ await page.getByRole('link', { name: 'Music' }).click();
102
+ await page.waitForURL('/items?type=music');
103
+
104
+ await expect(page.getByRole('heading', { name: 'Music' })).toBeVisible();
105
+ });
106
+
107
+ test('nested routes render layouts', async ({ page }) => {
108
+ await page.goto('/account/security');
109
+
110
+ await expect(page.getByRole('heading', { name: 'Account' })).toBeVisible();
111
+ await expect(page.getByRole('heading', { name: 'Security', level: 2 })).toBeVisible();
112
+
113
+ await page.getByRole('link', { name: 'Privacy' }).click();
114
+ await page.waitForURL('/account/privacy');
115
+
116
+ await expect(page.getByRole('heading', { name: 'Account' })).toBeVisible();
117
+ await expect(page.getByRole('heading', { name: 'Privacy', level: 2 })).toBeVisible();
118
+ });
119
+
120
+ test('history navigation works', async ({ page }) => {
121
+ await page.goto('/');
122
+ await page.getByRole('link', { name: 'Inventory' }).click();
123
+ await page.waitForURL('/inventory');
124
+ await page.getByRole('link', { name: 'Help' }).click();
125
+ await page.waitForURL('/help');
126
+
127
+ await page.goBack();
128
+ await expect(page).toHaveURL(/\/inventory/);
129
+
130
+ await page.goBack();
131
+ await expect(page).toHaveURL(/\/$/);
132
+ });
133
+
134
+ test('protected route redirects', async ({ page }) => {
135
+ await page.goto('/admin/users');
136
+
137
+ await expect(page).toHaveURL(/\/login/);
138
+ });
139
+
140
+ test('unknown route shows 404', async ({ page }) => {
141
+ await page.goto('/nonexistent-path');
142
+
143
+ await expect(page.getByRole('heading', { name: 'Not Found' })).toBeVisible();
144
+ });
145
+ });
146
+ ```
147
+
148
+ ### Testing Hooks Through UI
149
+
150
+ **Use when**: Verifying custom hooks produce correct UI behavior—Playwright cannot call hooks directly.
151
+ **Avoid when**: Hook logic is pure computation—use unit tests instead.
152
+
153
+ ```typescript
154
+ import { test, expect } from '@playwright/test';
155
+
156
+ test.describe('useDebounce via SearchBox', () => {
157
+ test('batches rapid input', async ({ page }) => {
158
+ await page.goto('/search');
159
+
160
+ const apiCalls: string[] = [];
161
+ await page.route('**/api/query*', async (route) => {
162
+ apiCalls.push(route.request().url());
163
+ await route.continue();
164
+ });
165
+
166
+ await page.getByRole('textbox', { name: 'Search' }).pressSequentially('testing', {
167
+ delay: 40,
168
+ });
169
+
170
+ await expect(page.getByRole('listitem')).toHaveCount(3);
171
+ expect(apiCalls.length).toBeLessThanOrEqual(2);
172
+ });
173
+ });
174
+
175
+ test.describe('usePagination via DataGrid', () => {
176
+ test('page controls work', async ({ page }) => {
177
+ await page.goto('/records');
178
+
179
+ await expect(page.getByText('Page 1 of 10')).toBeVisible();
180
+
181
+ await page.getByRole('button', { name: 'Next' }).click();
182
+ await expect(page.getByText('Page 2 of 10')).toBeVisible();
183
+
184
+ await page.getByRole('button', { name: 'Previous' }).click();
185
+ await expect(page.getByText('Page 1 of 10')).toBeVisible();
186
+ await expect(page.getByRole('button', { name: 'Previous' })).toBeDisabled();
187
+ });
188
+ });
189
+ ```
190
+
191
+ ### Form Libraries (React Hook Form, Formik)
192
+
193
+ **Use when**: Testing forms built with react-hook-form or Formik—Playwright interacts with DOM, form library is transparent.
194
+
195
+ ```typescript
196
+ import { test, expect } from '@playwright/test';
197
+
198
+ test.describe('signup form', () => {
199
+ test.beforeEach(async ({ page }) => {
200
+ await page.goto('/signup');
201
+ });
202
+
203
+ test('validation on empty submit', async ({ page }) => {
204
+ await page.getByRole('button', { name: 'Register' }).click();
205
+
206
+ await expect(page.getByText('Email required')).toBeVisible();
207
+ await expect(page.getByText('Password required')).toBeVisible();
208
+ });
209
+
210
+ test('inline validation on blur', async ({ page }) => {
211
+ const email = page.getByLabel('Email');
212
+ await email.fill('invalid');
213
+ await email.blur();
214
+
215
+ await expect(page.getByText('Invalid email format')).toBeVisible();
216
+ });
217
+
218
+ test('password strength indicator', async ({ page }) => {
219
+ const pwd = page.getByLabel('Password', { exact: true });
220
+
221
+ await pwd.fill('weak');
222
+ await expect(page.getByText('Minimum 8 characters')).toHaveClass(/invalid/);
223
+
224
+ await pwd.fill('StrongPass1!');
225
+ await expect(page.getByText('Minimum 8 characters')).toHaveClass(/valid/);
226
+ });
227
+
228
+ test('successful submission redirects', async ({ page }) => {
229
+ await page.getByLabel('Name').fill('Alice');
230
+ await page.getByLabel('Email').fill('alice@test.com');
231
+ await page.getByLabel('Password', { exact: true }).fill('Secure123!');
232
+ await page.getByLabel('Confirm').fill('Secure123!');
233
+ await page.getByLabel('Accept terms').check();
234
+
235
+ await page.getByRole('button', { name: 'Register' }).click();
236
+
237
+ await page.waitForURL('/welcome');
238
+ await expect(page.getByText('Hello, Alice')).toBeVisible();
239
+ });
240
+
241
+ test('submit button disabled during request', async ({ page }) => {
242
+ await page.route('**/api/signup', async (route) => {
243
+ await new Promise((r) => setTimeout(r, 800));
244
+ await route.fulfill({ status: 201, json: { id: 1 } });
245
+ });
246
+
247
+ await page.getByLabel('Name').fill('Bob');
248
+ await page.getByLabel('Email').fill('bob@test.com');
249
+ await page.getByLabel('Password', { exact: true }).fill('Secure123!');
250
+ await page.getByLabel('Confirm').fill('Secure123!');
251
+ await page.getByLabel('Accept terms').check();
252
+
253
+ await page.getByRole('button', { name: 'Register' }).click();
254
+
255
+ await expect(page.getByRole('button', { name: /Registering|Loading/ })).toBeDisabled();
256
+ });
257
+ });
258
+ ```
259
+
260
+ ### Portals (Modals, Tooltips, Dropdowns)
261
+
262
+ **Use when**: Testing components rendered via `ReactDOM.createPortal()`—modals, dialogs, tooltips, menus. These render outside parent DOM but Playwright sees the full document.
263
+
264
+ ```typescript
265
+ import { test, expect } from '@playwright/test';
266
+
267
+ test.describe('portal components', () => {
268
+ test('modal interaction', async ({ page }) => {
269
+ await page.goto('/items');
270
+
271
+ await page.getByRole('button', { name: 'Remove' }).first().click();
272
+
273
+ const dialog = page.getByRole('dialog', { name: 'Confirm removal' });
274
+ await expect(dialog).toBeVisible();
275
+ await expect(dialog.getByRole('button', { name: 'Cancel' })).toBeFocused();
276
+
277
+ await dialog.getByRole('button', { name: 'Remove' }).click();
278
+ await expect(dialog).toBeHidden();
279
+ });
280
+
281
+ test('escape closes modal', async ({ page }) => {
282
+ await page.goto('/items');
283
+ await page.getByRole('button', { name: 'Remove' }).first().click();
284
+
285
+ const dialog = page.getByRole('dialog');
286
+ await expect(dialog).toBeVisible();
287
+
288
+ await page.keyboard.press('Escape');
289
+ await expect(dialog).toBeHidden();
290
+ });
291
+
292
+ test('tooltip on hover', async ({ page }) => {
293
+ await page.goto('/panel');
294
+
295
+ await page.getByRole('button', { name: 'Help' }).hover();
296
+ await expect(page.getByRole('tooltip')).toBeVisible();
297
+
298
+ await page.mouse.move(0, 0);
299
+ await expect(page.getByRole('tooltip')).toBeHidden();
300
+ });
301
+
302
+ test('dropdown menu', async ({ page }) => {
303
+ await page.goto('/panel');
304
+
305
+ await page.getByRole('button', { name: 'Actions' }).click();
306
+
307
+ const menu = page.getByRole('menu');
308
+ await expect(menu).toBeVisible();
309
+
310
+ await menu.getByRole('menuitem', { name: 'Rename' }).click();
311
+ await expect(menu).toBeHidden();
312
+ });
313
+
314
+ test('toast auto-dismisses', async ({ page }) => {
315
+ await page.goto('/preferences');
316
+
317
+ await page.getByRole('button', { name: 'Save' }).click();
318
+ await expect(page.getByText('Preferences saved')).toBeVisible();
319
+
320
+ await expect(page.getByText('Preferences saved')).toBeHidden({ timeout: 8000 });
321
+ });
322
+ });
323
+ ```
324
+
325
+ ### Error Boundaries
326
+
327
+ **Use when**: Verifying error boundaries catch rendering errors and show fallback UI.
328
+ **Avoid when**: Testing error handling in event handlers or async code—error boundaries only catch render errors.
329
+
330
+ ```typescript
331
+ import { test, expect } from '@playwright/test';
332
+
333
+ test.describe('error boundary', () => {
334
+ test('shows fallback on crash', async ({ page }) => {
335
+ await page.route('**/api/widgets', (route) => {
336
+ route.fulfill({
337
+ status: 200,
338
+ json: { widgets: null },
339
+ });
340
+ });
341
+
342
+ await page.goto('/panel');
343
+
344
+ await expect(page.getByText('Something went wrong')).toBeVisible();
345
+ await expect(page.getByRole('button', { name: 'Retry' })).toBeVisible();
346
+ await expect(page.getByRole('navigation')).toBeVisible();
347
+ });
348
+
349
+ test('retry recovers component', async ({ page }) => {
350
+ let calls = 0;
351
+ await page.route('**/api/widgets', (route) => {
352
+ calls++;
353
+ if (calls === 1) {
354
+ route.fulfill({ status: 200, json: { widgets: null } });
355
+ } else {
356
+ route.fulfill({ status: 200, json: { widgets: [{ id: 1, name: 'Chart' }] } });
357
+ }
358
+ });
359
+
360
+ await page.goto('/panel');
361
+
362
+ await expect(page.getByText('Something went wrong')).toBeVisible();
363
+
364
+ await page.getByRole('button', { name: 'Retry' }).click();
365
+
366
+ await expect(page.getByText('Something went wrong')).toBeHidden();
367
+ await expect(page.getByText('Chart')).toBeVisible();
368
+ });
369
+ });
370
+ ```
371
+
372
+ ### Component Testing (Experimental)
373
+
374
+ **Use when**: Testing complex interactive components in isolation—data tables, form wizards, rich editors. Needs real browser but not full app.
375
+ **Avoid when**: Component depends heavily on backend data or routing—use E2E instead.
376
+
377
+ ```typescript
378
+ // playwright-ct.config.ts
379
+ import { defineConfig, devices } from '@playwright/experimental-ct-react';
380
+
381
+ export default defineConfig({
382
+ testDir: './tests/components',
383
+ testMatch: '**/*.ct.ts',
384
+ use: {
385
+ trace: 'on-first-retry',
386
+ ctPort: 3100,
387
+ },
388
+ projects: [
389
+ { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
390
+ ],
391
+ });
392
+ ```
393
+
394
+ ```typescript
395
+ // tests/components/Stepper.ct.ts
396
+ import { test, expect } from '@playwright/experimental-ct-react';
397
+ import Stepper from '../../src/components/Stepper';
398
+
399
+ test('increments on click', async ({ mount }) => {
400
+ const component = await mount(<Stepper initial={0} />);
401
+
402
+ await expect(component.getByText('Value: 0')).toBeVisible();
403
+ await component.getByRole('button', { name: '+' }).click();
404
+ await expect(component.getByText('Value: 1')).toBeVisible();
405
+ });
406
+
407
+ test('fires onChange callback', async ({ mount }) => {
408
+ const values: number[] = [];
409
+ const component = await mount(
410
+ <Stepper initial={0} onChange={(v) => values.push(v)} />
411
+ );
412
+
413
+ await component.getByRole('button', { name: '+' }).click();
414
+ await component.getByRole('button', { name: '+' }).click();
415
+
416
+ expect(values).toEqual([1, 2]);
417
+ });
418
+
419
+ test('respects min boundary', async ({ mount }) => {
420
+ const component = await mount(<Stepper initial={0} min={0} />);
421
+
422
+ await expect(component.getByRole('button', { name: '-' })).toBeDisabled();
423
+ });
424
+ ```
425
+
426
+ ## Setup
427
+
428
+ ### E2E Config (Vite)
429
+
430
+ ```typescript
431
+ // playwright.config.ts
432
+ import { defineConfig, devices } from '@playwright/test';
433
+
434
+ export default defineConfig({
435
+ testDir: './tests',
436
+ fullyParallel: true,
437
+ forbidOnly: !!process.env.CI,
438
+ retries: process.env.CI ? 2 : 0,
439
+ workers: process.env.CI ? '50%' : undefined,
440
+
441
+ use: {
442
+ baseURL: 'http://localhost:5173',
443
+ trace: 'on-first-retry',
444
+ screenshot: 'only-on-failure',
445
+ },
446
+
447
+ projects: [
448
+ { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
449
+ { name: 'firefox', use: { ...devices['Desktop Firefox'] } },
450
+ { name: 'mobile', use: { ...devices['iPhone 14'] } },
451
+ ],
452
+
453
+ webServer: {
454
+ command: process.env.CI ? 'npm run build && npx vite preview --port 5173' : 'npm run dev',
455
+ url: 'http://localhost:5173',
456
+ reuseExistingServer: !process.env.CI,
457
+ timeout: 120_000,
458
+ },
459
+ });
460
+ ```
461
+
462
+ ### CRA vs Vite Differences
463
+
464
+ | Aspect | Create React App | Vite |
465
+ |---|---|---|
466
+ | Default port | `3000` | `5173` |
467
+ | Build output | `build/` | `dist/` |
468
+ | Serve production | `npx serve -s build -l 3000` | `npx vite preview --port 5173` |
469
+ | Env var prefix | `REACT_APP_*` | `VITE_*` |
470
+
471
+ ## Framework Tips
472
+
473
+ ### Strict Mode Double Effects
474
+
475
+ React Strict Mode runs effects twice in development. Tests should be resilient:
476
+
477
+ - Don't assert exact API call counts in dev mode
478
+ - Run against production build for call count assertions, or account for double invocations
479
+
480
+ ### Suspense and Lazy Components
481
+
482
+ ```typescript
483
+ test('lazy route loads content', async ({ page }) => {
484
+ await page.goto('/');
485
+
486
+ await page.getByRole('link', { name: 'Analytics' }).click();
487
+
488
+ await expect(page.getByRole('heading', { name: 'Analytics' })).toBeVisible();
489
+ });
490
+ ```
491
+
492
+ ### Detecting Memory Leaks
493
+
494
+ ```typescript
495
+ test('no unmounted state warnings', async ({ page }) => {
496
+ const warnings: string[] = [];
497
+ page.on('console', (msg) => {
498
+ if (msg.type() === 'warning' && msg.text().includes('unmounted')) {
499
+ warnings.push(msg.text());
500
+ }
501
+ });
502
+
503
+ await page.goto('/panel');
504
+ await page.getByRole('link', { name: 'Settings' }).click();
505
+ await page.goBack();
506
+ await page.getByRole('link', { name: 'Profile' }).click();
507
+
508
+ expect(warnings).toEqual([]);
509
+ });
510
+ ```
511
+
512
+ ## Anti-Patterns
513
+
514
+ | Don't | Problem | Do Instead |
515
+ |---|---|---|
516
+ | `page.evaluate(() => store.getState())` | Couples tests to implementation | Assert on UI: `expect(badge).toHaveText('3')` |
517
+ | Import components in E2E tests | E2E runs in Node, not browser | Use `@playwright/experimental-ct-react` for components |
518
+ | `page.waitForTimeout(500)` after state changes | Timing varies across machines | `expect(locator).toHaveText('value')` auto-retries |
519
+ | `page.locator('.MuiButton-root')` | Class names change between versions | `page.getByRole('button', { name: 'Submit' })` |
520
+ | Test every component with CT | Overhead for simple components | CT for complex widgets, unit tests for logic, E2E for flows |
521
+ | Skip keyboard navigation tests | Accessibility regressions common | Test Tab, Enter, Escape, Arrow interactions |
522
+ | Assert on `__REACT_FIBER__` internals | Not stable across versions | Only interact with rendered DOM |
523
+
524
+ ## Related
525
+
526
+ - [locators.md](../core/locators.md) — locator strategies for any React component library
527
+ - [assertions-waiting.md](../core/assertions-waiting.md) — auto-waiting for React state changes
528
+ - [forms-validation.md](../testing-patterns/forms-validation.md) — form testing patterns
529
+ - [component-testing.md](../testing-patterns/component-testing.md) — in-depth component testing
530
+ - [test-architecture.md](../architecture/test-architecture.md) — E2E vs component vs unit decisions
531
+ - [nextjs.md](nextjs.md) — Next.js-specific patterns for SSR