@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.
- package/.agents/skills/e2e-testing-expert/SKILL.md +28 -0
- package/.agents/skills/frontend-design/LICENSE.txt +177 -0
- package/.agents/skills/frontend-design/SKILL.md +42 -0
- package/.agents/skills/nodejs-backend-patterns/SKILL.md +639 -0
- package/.agents/skills/nodejs-backend-patterns/references/advanced-patterns.md +430 -0
- package/.agents/skills/playwright-best-practices/LICENSE.md +7 -0
- package/.agents/skills/playwright-best-practices/README.md +147 -0
- package/.agents/skills/playwright-best-practices/SKILL.md +303 -0
- package/.agents/skills/playwright-best-practices/advanced/authentication-flows.md +360 -0
- package/.agents/skills/playwright-best-practices/advanced/authentication.md +871 -0
- package/.agents/skills/playwright-best-practices/advanced/clock-mocking.md +364 -0
- package/.agents/skills/playwright-best-practices/advanced/mobile-testing.md +409 -0
- package/.agents/skills/playwright-best-practices/advanced/multi-context.md +288 -0
- package/.agents/skills/playwright-best-practices/advanced/multi-user.md +393 -0
- package/.agents/skills/playwright-best-practices/advanced/network-advanced.md +452 -0
- package/.agents/skills/playwright-best-practices/advanced/third-party.md +464 -0
- package/.agents/skills/playwright-best-practices/architecture/pom-vs-fixtures.md +363 -0
- package/.agents/skills/playwright-best-practices/architecture/test-architecture.md +369 -0
- package/.agents/skills/playwright-best-practices/architecture/when-to-mock.md +383 -0
- package/.agents/skills/playwright-best-practices/browser-apis/browser-apis.md +391 -0
- package/.agents/skills/playwright-best-practices/browser-apis/iframes.md +403 -0
- package/.agents/skills/playwright-best-practices/browser-apis/service-workers.md +504 -0
- package/.agents/skills/playwright-best-practices/browser-apis/websockets.md +403 -0
- package/.agents/skills/playwright-best-practices/core/annotations.md +424 -0
- package/.agents/skills/playwright-best-practices/core/assertions-waiting.md +361 -0
- package/.agents/skills/playwright-best-practices/core/configuration.md +452 -0
- package/.agents/skills/playwright-best-practices/core/fixtures-hooks.md +417 -0
- package/.agents/skills/playwright-best-practices/core/global-setup.md +434 -0
- package/.agents/skills/playwright-best-practices/core/locators.md +242 -0
- package/.agents/skills/playwright-best-practices/core/page-object-model.md +315 -0
- package/.agents/skills/playwright-best-practices/core/projects-dependencies.md +453 -0
- package/.agents/skills/playwright-best-practices/core/test-data.md +492 -0
- package/.agents/skills/playwright-best-practices/core/test-suite-structure.md +361 -0
- package/.agents/skills/playwright-best-practices/core/test-tags.md +298 -0
- package/.agents/skills/playwright-best-practices/debugging/console-errors.md +420 -0
- package/.agents/skills/playwright-best-practices/debugging/debugging.md +504 -0
- package/.agents/skills/playwright-best-practices/debugging/error-testing.md +360 -0
- package/.agents/skills/playwright-best-practices/debugging/flaky-tests.md +496 -0
- package/.agents/skills/playwright-best-practices/frameworks/angular.md +530 -0
- package/.agents/skills/playwright-best-practices/frameworks/nextjs.md +469 -0
- package/.agents/skills/playwright-best-practices/frameworks/react.md +531 -0
- package/.agents/skills/playwright-best-practices/frameworks/vue.md +574 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/ci-cd.md +468 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/docker.md +283 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/github-actions.md +546 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/gitlab.md +397 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/other-providers.md +521 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/parallel-sharding.md +371 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/performance.md +453 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/reporting.md +424 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/test-coverage.md +497 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/accessibility.md +359 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/api-testing.md +719 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/browser-extensions.md +506 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/canvas-webgl.md +493 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/component-testing.md +500 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/drag-drop.md +576 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/electron.md +509 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/file-operations.md +377 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/file-upload-download.md +562 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/forms-validation.md +561 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/graphql-testing.md +331 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/i18n.md +508 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/performance-testing.md +476 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/security-testing.md +430 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/visual-regression.md +634 -0
- package/.env.example +21 -0
- package/README.md +30 -0
- package/bin/arcality.mjs +86 -0
- package/package.json +66 -0
- package/playwright.config.ts +12 -0
- package/scripts/cleanup-qmsdev.mjs +63 -0
- package/scripts/discover-view.mjs +52 -0
- package/scripts/extract-view.mjs +64 -0
- package/scripts/gen-and-run.mjs +838 -0
- package/scripts/init.mjs +290 -0
- package/scripts/migrate-to-central-out.mjs +157 -0
- package/scripts/postinstall.mjs +63 -0
- package/scripts/rebrand-report.mjs +241 -0
- package/scripts/setup.mjs +166 -0
- package/src/KnowledgeService.ts +239 -0
- package/src/arcalityClient.mjs +266 -0
- package/src/configLoader.mjs +179 -0
- package/src/configManager.mjs +172 -0
- package/src/consoleBanner.ts +32 -0
- package/src/envSetup.ts +205 -0
- package/src/index.ts +25 -0
- package/src/projectInspector.ts +42 -0
- package/src/services/collectiveMemoryService.ts +178 -0
- package/src/testRunner.ts +201 -0
- package/tests/_helpers/ArcalityReporter.ts +490 -0
- package/tests/_helpers/agentic-runner.spec.ts +741 -0
- package/tests/_helpers/ai-agent-helper.ts +1573 -0
- package/tests/_helpers/discover-view.spec.ts +238 -0
- package/tests/_helpers/extract-view.spec.ts +118 -0
- package/tests/_helpers/qa-tools.ts +333 -0
- package/tests/_helpers/smart-action.spec.ts +1458 -0
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
# Component Testing
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
|
|
5
|
+
1. [Setup & Configuration](#setup--configuration)
|
|
6
|
+
2. [Mounting Components](#mounting-components)
|
|
7
|
+
3. [Props & State Testing](#props--state-testing)
|
|
8
|
+
4. [Events & Interactions](#events--interactions)
|
|
9
|
+
5. [Slots & Children](#slots--children)
|
|
10
|
+
6. [Mocking Dependencies](#mocking-dependencies)
|
|
11
|
+
7. [Framework-Specific Patterns](#framework-specific-patterns)
|
|
12
|
+
|
|
13
|
+
## Setup & Configuration
|
|
14
|
+
|
|
15
|
+
### Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# React
|
|
19
|
+
npm init playwright@latest -- --ct
|
|
20
|
+
|
|
21
|
+
# Vue
|
|
22
|
+
npm init playwright@latest -- --ct
|
|
23
|
+
|
|
24
|
+
# Svelte
|
|
25
|
+
npm init playwright@latest -- --ct
|
|
26
|
+
|
|
27
|
+
# Solid
|
|
28
|
+
npm init playwright@latest -- --ct
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Configuration
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
// playwright-ct.config.ts
|
|
35
|
+
import { defineConfig, devices } from "@playwright/experimental-ct-react";
|
|
36
|
+
|
|
37
|
+
export default defineConfig({
|
|
38
|
+
testDir: "./tests/components",
|
|
39
|
+
snapshotDir: "./tests/components/__snapshots__",
|
|
40
|
+
|
|
41
|
+
use: {
|
|
42
|
+
ctPort: 3100,
|
|
43
|
+
ctViteConfig: {
|
|
44
|
+
resolve: {
|
|
45
|
+
alias: {
|
|
46
|
+
"@": "/src",
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
projects: [
|
|
53
|
+
{ name: "chromium", use: { ...devices["Desktop Chrome"] } },
|
|
54
|
+
{ name: "firefox", use: { ...devices["Desktop Firefox"] } },
|
|
55
|
+
{ name: "webkit", use: { ...devices["Desktop Safari"] } },
|
|
56
|
+
],
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Project Structure
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
src/
|
|
64
|
+
components/
|
|
65
|
+
Button.tsx
|
|
66
|
+
Modal.tsx
|
|
67
|
+
tests/
|
|
68
|
+
components/
|
|
69
|
+
Button.spec.tsx
|
|
70
|
+
Modal.spec.tsx
|
|
71
|
+
playwright/
|
|
72
|
+
index.html # CT entry point
|
|
73
|
+
index.tsx # CT setup (providers, styles)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Mounting Components
|
|
77
|
+
|
|
78
|
+
### Basic Mount
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
// Button.spec.tsx
|
|
82
|
+
import { test, expect } from "@playwright/experimental-ct-react";
|
|
83
|
+
import { Button } from "@/components/Button";
|
|
84
|
+
|
|
85
|
+
test("renders button with text", async ({ mount }) => {
|
|
86
|
+
const component = await mount(<Button>Click me</Button>);
|
|
87
|
+
|
|
88
|
+
await expect(component).toContainText("Click me");
|
|
89
|
+
await expect(component).toBeVisible();
|
|
90
|
+
});
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Mount with Props
|
|
94
|
+
|
|
95
|
+
```tsx
|
|
96
|
+
test("renders with all props", async ({ mount }) => {
|
|
97
|
+
const component = await mount(
|
|
98
|
+
<Button variant="primary" size="large" disabled={false} icon="check">
|
|
99
|
+
Submit
|
|
100
|
+
</Button>,
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
await expect(component).toHaveClass(/primary/);
|
|
104
|
+
await expect(component).toHaveClass(/large/);
|
|
105
|
+
await expect(component.locator("svg")).toBeVisible(); // icon
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Mount with Wrapper/Provider
|
|
110
|
+
|
|
111
|
+
```tsx
|
|
112
|
+
// playwright/index.tsx - Global providers
|
|
113
|
+
import { ThemeProvider } from "@/providers/theme";
|
|
114
|
+
import { QueryClientProvider } from "@tanstack/react-query";
|
|
115
|
+
import "@/styles/globals.css";
|
|
116
|
+
|
|
117
|
+
export default function PlaywrightWrapper({ children }) {
|
|
118
|
+
return (
|
|
119
|
+
<QueryClientProvider client={queryClient}>
|
|
120
|
+
<ThemeProvider>{children}</ThemeProvider>
|
|
121
|
+
</QueryClientProvider>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
```tsx
|
|
127
|
+
// Or per-test wrapper
|
|
128
|
+
test("with custom provider", async ({ mount }) => {
|
|
129
|
+
const component = await mount(
|
|
130
|
+
<AuthProvider initialUser={{ name: "Test" }}>
|
|
131
|
+
<UserProfile />
|
|
132
|
+
</AuthProvider>,
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
await expect(component.getByText("Test")).toBeVisible();
|
|
136
|
+
});
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Props & State Testing
|
|
140
|
+
|
|
141
|
+
### Testing Prop Variations
|
|
142
|
+
|
|
143
|
+
```tsx
|
|
144
|
+
test.describe("Button variants", () => {
|
|
145
|
+
const variants = ["primary", "secondary", "danger", "ghost"] as const;
|
|
146
|
+
|
|
147
|
+
for (const variant of variants) {
|
|
148
|
+
test(`renders ${variant} variant`, async ({ mount }) => {
|
|
149
|
+
const component = await mount(<Button variant={variant}>Button</Button>);
|
|
150
|
+
await expect(component).toHaveClass(new RegExp(variant));
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Updating Props
|
|
157
|
+
|
|
158
|
+
```tsx
|
|
159
|
+
test("responds to prop changes", async ({ mount }) => {
|
|
160
|
+
const component = await mount(<Counter initialCount={0} />);
|
|
161
|
+
|
|
162
|
+
await expect(component.getByTestId("count")).toHaveText("0");
|
|
163
|
+
|
|
164
|
+
// Update props
|
|
165
|
+
await component.update(<Counter initialCount={10} />);
|
|
166
|
+
await expect(component.getByTestId("count")).toHaveText("10");
|
|
167
|
+
});
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Testing Controlled Components
|
|
171
|
+
|
|
172
|
+
```tsx
|
|
173
|
+
test("controlled input", async ({ mount }) => {
|
|
174
|
+
let externalValue = "";
|
|
175
|
+
|
|
176
|
+
const component = await mount(
|
|
177
|
+
<Input
|
|
178
|
+
value={externalValue}
|
|
179
|
+
onChange={(e) => {
|
|
180
|
+
externalValue = e.target.value;
|
|
181
|
+
}}
|
|
182
|
+
/>,
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
await component.locator("input").fill("hello");
|
|
186
|
+
|
|
187
|
+
// For controlled components, update with new value
|
|
188
|
+
await component.update(
|
|
189
|
+
<Input value="hello" onChange={(e) => (externalValue = e.target.value)} />,
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
await expect(component.locator("input")).toHaveValue("hello");
|
|
193
|
+
});
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Testing Internal State
|
|
197
|
+
|
|
198
|
+
```tsx
|
|
199
|
+
test("internal state updates", async ({ mount }) => {
|
|
200
|
+
const component = await mount(<Toggle defaultChecked={false} />);
|
|
201
|
+
|
|
202
|
+
// Initial state
|
|
203
|
+
await expect(component.locator('[role="switch"]')).toHaveAttribute(
|
|
204
|
+
"aria-checked",
|
|
205
|
+
"false",
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
// Trigger state change
|
|
209
|
+
await component.click();
|
|
210
|
+
|
|
211
|
+
// Verify state updated
|
|
212
|
+
await expect(component.locator('[role="switch"]')).toHaveAttribute(
|
|
213
|
+
"aria-checked",
|
|
214
|
+
"true",
|
|
215
|
+
);
|
|
216
|
+
});
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Events & Interactions
|
|
220
|
+
|
|
221
|
+
### Testing Click Events
|
|
222
|
+
|
|
223
|
+
```tsx
|
|
224
|
+
test("click event fires", async ({ mount }) => {
|
|
225
|
+
let clicked = false;
|
|
226
|
+
|
|
227
|
+
const component = await mount(
|
|
228
|
+
<Button onClick={() => (clicked = true)}>Click</Button>,
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
await component.click();
|
|
232
|
+
|
|
233
|
+
expect(clicked).toBe(true);
|
|
234
|
+
});
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### Testing Event Payloads
|
|
238
|
+
|
|
239
|
+
```tsx
|
|
240
|
+
test("onChange provides correct value", async ({ mount }) => {
|
|
241
|
+
const values: string[] = [];
|
|
242
|
+
|
|
243
|
+
const component = await mount(
|
|
244
|
+
<Select
|
|
245
|
+
options={["a", "b", "c"]}
|
|
246
|
+
onChange={(value) => values.push(value)}
|
|
247
|
+
/>,
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
await component.getByRole("combobox").click();
|
|
251
|
+
await component.getByRole("option", { name: "b" }).click();
|
|
252
|
+
|
|
253
|
+
expect(values).toEqual(["b"]);
|
|
254
|
+
});
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### Testing Form Submission
|
|
258
|
+
|
|
259
|
+
```tsx
|
|
260
|
+
test("form submission", async ({ mount }) => {
|
|
261
|
+
let submittedData: FormData | null = null;
|
|
262
|
+
|
|
263
|
+
const component = await mount(
|
|
264
|
+
<LoginForm
|
|
265
|
+
onSubmit={(data) => {
|
|
266
|
+
submittedData = data;
|
|
267
|
+
}}
|
|
268
|
+
/>,
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
await component.getByLabel("Email").fill("test@example.com");
|
|
272
|
+
await component.getByLabel("Password").fill("secret123");
|
|
273
|
+
await component.getByRole("button", { name: "Sign in" }).click();
|
|
274
|
+
|
|
275
|
+
expect(submittedData).toEqual({
|
|
276
|
+
email: "test@example.com",
|
|
277
|
+
password: "secret123",
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### Testing Keyboard Interactions
|
|
283
|
+
|
|
284
|
+
```tsx
|
|
285
|
+
test("keyboard navigation", async ({ mount }) => {
|
|
286
|
+
const component = await mount(
|
|
287
|
+
<Dropdown options={["Apple", "Banana", "Cherry"]} />,
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
// Open dropdown
|
|
291
|
+
await component.getByRole("button").click();
|
|
292
|
+
|
|
293
|
+
// Navigate with keyboard
|
|
294
|
+
await component.press("ArrowDown");
|
|
295
|
+
await component.press("ArrowDown");
|
|
296
|
+
await component.press("Enter");
|
|
297
|
+
|
|
298
|
+
await expect(component.getByRole("button")).toHaveText("Banana");
|
|
299
|
+
});
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
## Slots & Children
|
|
303
|
+
|
|
304
|
+
### Testing Children Content
|
|
305
|
+
|
|
306
|
+
```tsx
|
|
307
|
+
test("renders children", async ({ mount }) => {
|
|
308
|
+
const component = await mount(
|
|
309
|
+
<Card>
|
|
310
|
+
<h2>Title</h2>
|
|
311
|
+
<p>Description</p>
|
|
312
|
+
</Card>,
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
await expect(component.getByRole("heading")).toHaveText("Title");
|
|
316
|
+
await expect(component.getByText("Description")).toBeVisible();
|
|
317
|
+
});
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### Testing Named Slots (Vue)
|
|
321
|
+
|
|
322
|
+
```tsx
|
|
323
|
+
// Vue component with slots
|
|
324
|
+
test("renders named slots", async ({ mount }) => {
|
|
325
|
+
const component = await mount(Modal, {
|
|
326
|
+
slots: {
|
|
327
|
+
header: "<h2>Modal Title</h2>",
|
|
328
|
+
default: "<p>Modal content</p>",
|
|
329
|
+
footer: "<button>Close</button>",
|
|
330
|
+
},
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
await expect(component.getByRole("heading")).toHaveText("Modal Title");
|
|
334
|
+
await expect(component.getByRole("button")).toHaveText("Close");
|
|
335
|
+
});
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### Testing Render Props
|
|
339
|
+
|
|
340
|
+
```tsx
|
|
341
|
+
test("render prop pattern", async ({ mount }) => {
|
|
342
|
+
const component = await mount(
|
|
343
|
+
<DataFetcher url="/api/users">
|
|
344
|
+
{({ data, loading }) =>
|
|
345
|
+
loading ? <span>Loading...</span> : <span>{data.name}</span>
|
|
346
|
+
}
|
|
347
|
+
</DataFetcher>,
|
|
348
|
+
);
|
|
349
|
+
|
|
350
|
+
// Initially loading
|
|
351
|
+
await expect(component.getByText("Loading...")).toBeVisible();
|
|
352
|
+
|
|
353
|
+
// After data loads
|
|
354
|
+
await expect(component.getByText(/User/)).toBeVisible();
|
|
355
|
+
});
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
## Mocking Dependencies
|
|
359
|
+
|
|
360
|
+
### Mocking Imports
|
|
361
|
+
|
|
362
|
+
```tsx
|
|
363
|
+
// playwright/index.tsx - Mock at setup level
|
|
364
|
+
import { beforeMount } from "@playwright/experimental-ct-react/hooks";
|
|
365
|
+
|
|
366
|
+
beforeMount(async ({ hooksConfig }) => {
|
|
367
|
+
// Mock analytics
|
|
368
|
+
window.analytics = {
|
|
369
|
+
track: () => {},
|
|
370
|
+
identify: () => {},
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
// Mock feature flags
|
|
374
|
+
if (hooksConfig?.featureFlags) {
|
|
375
|
+
window.__FEATURE_FLAGS__ = hooksConfig.featureFlags;
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
```tsx
|
|
381
|
+
// Test with mocked config
|
|
382
|
+
test("with feature flag", async ({ mount }) => {
|
|
383
|
+
const component = await mount(<FeatureComponent />, {
|
|
384
|
+
hooksConfig: {
|
|
385
|
+
featureFlags: { newFeature: true },
|
|
386
|
+
},
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
await expect(component.getByText("New Feature")).toBeVisible();
|
|
390
|
+
});
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
### Mocking API Calls
|
|
394
|
+
|
|
395
|
+
```tsx
|
|
396
|
+
test("component with API", async ({ mount, page }) => {
|
|
397
|
+
// Mock API before mounting
|
|
398
|
+
await page.route("**/api/user", (route) => {
|
|
399
|
+
route.fulfill({
|
|
400
|
+
json: { id: 1, name: "Test User" },
|
|
401
|
+
});
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
const component = await mount(<UserProfile userId={1} />);
|
|
405
|
+
|
|
406
|
+
await expect(component.getByText("Test User")).toBeVisible();
|
|
407
|
+
});
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
### Mocking Hooks
|
|
411
|
+
|
|
412
|
+
```tsx
|
|
413
|
+
// Mock custom hook via module mock
|
|
414
|
+
test("with mocked hook", async ({ mount }) => {
|
|
415
|
+
const component = await mount(<Dashboard />, {
|
|
416
|
+
hooksConfig: {
|
|
417
|
+
mockAuth: { user: { name: "Admin" }, isAdmin: true },
|
|
418
|
+
},
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
await expect(component.getByText("Admin Panel")).toBeVisible();
|
|
422
|
+
});
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
## Framework-Specific Patterns
|
|
426
|
+
|
|
427
|
+
### React Testing
|
|
428
|
+
|
|
429
|
+
```tsx
|
|
430
|
+
// React with refs
|
|
431
|
+
test("exposes ref methods", async ({ mount }) => {
|
|
432
|
+
let inputRef: HTMLInputElement | null = null;
|
|
433
|
+
|
|
434
|
+
const component = await mount(<Input ref={(el) => (inputRef = el)} />);
|
|
435
|
+
|
|
436
|
+
await component.locator("input").fill("test");
|
|
437
|
+
expect(inputRef?.value).toBe("test");
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
// React with context
|
|
441
|
+
test("uses context", async ({ mount }) => {
|
|
442
|
+
const component = await mount(
|
|
443
|
+
<UserContext.Provider value={{ name: "Test" }}>
|
|
444
|
+
<UserGreeting />
|
|
445
|
+
</UserContext.Provider>,
|
|
446
|
+
);
|
|
447
|
+
|
|
448
|
+
await expect(component).toContainText("Hello, Test");
|
|
449
|
+
});
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
### Vue Testing
|
|
453
|
+
|
|
454
|
+
```tsx
|
|
455
|
+
import { test, expect } from "@playwright/experimental-ct-vue";
|
|
456
|
+
import MyInput from "@/components/MyInput.vue";
|
|
457
|
+
|
|
458
|
+
// With v-model
|
|
459
|
+
test("v-model binding", async ({ mount }) => {
|
|
460
|
+
let modelValue = "";
|
|
461
|
+
const component = await mount(MyInput, {
|
|
462
|
+
props: {
|
|
463
|
+
modelValue,
|
|
464
|
+
"onUpdate:modelValue": (v: string) => (modelValue = v),
|
|
465
|
+
},
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
await component.locator("input").fill("test");
|
|
469
|
+
expect(modelValue).toBe("test");
|
|
470
|
+
});
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
### Svelte Testing
|
|
474
|
+
|
|
475
|
+
```tsx
|
|
476
|
+
import { test, expect } from "@playwright/experimental-ct-svelte";
|
|
477
|
+
import Counter from "./Counter.svelte";
|
|
478
|
+
|
|
479
|
+
test("Svelte component", async ({ mount }) => {
|
|
480
|
+
const component = await mount(Counter, { props: { initialCount: 5 } });
|
|
481
|
+
await expect(component.getByTestId("count")).toHaveText("5");
|
|
482
|
+
await component.getByRole("button", { name: "+" }).click();
|
|
483
|
+
await expect(component.getByTestId("count")).toHaveText("6");
|
|
484
|
+
});
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
## Anti-Patterns to Avoid
|
|
488
|
+
|
|
489
|
+
| Anti-Pattern | Problem | Solution |
|
|
490
|
+
| ------------------------------ | ------------------- | --------------------------------- |
|
|
491
|
+
| Testing implementation details | Brittle tests | Test behavior, not internal state |
|
|
492
|
+
| Snapshot testing everything | Maintenance burden | Use for visual regression only |
|
|
493
|
+
| Not isolating components | Hidden dependencies | Mock all external dependencies |
|
|
494
|
+
| Testing framework behavior | Redundant | Focus on your component logic |
|
|
495
|
+
| Skipping accessibility | Misses real issues | Include a11y checks in CT |
|
|
496
|
+
|
|
497
|
+
## Related References
|
|
498
|
+
|
|
499
|
+
- **Accessibility**: See [accessibility.md](accessibility.md) for a11y testing in components
|
|
500
|
+
- **Fixtures**: See [fixtures-hooks.md](../core/fixtures-hooks.md) for shared test setup
|