@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,452 @@
|
|
|
1
|
+
# Advanced Network Interception
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
|
|
5
|
+
1. [Request Modification](#request-modification)
|
|
6
|
+
2. [GraphQL Mocking](#graphql-mocking)
|
|
7
|
+
3. [HAR Recording & Playback](#har-recording--playback)
|
|
8
|
+
4. [Conditional Mocking](#conditional-mocking)
|
|
9
|
+
5. [Network Throttling](#network-throttling)
|
|
10
|
+
|
|
11
|
+
## Request Modification
|
|
12
|
+
|
|
13
|
+
### Modify Request Headers
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
test("add auth header to requests", async ({ page }) => {
|
|
17
|
+
await page.route("**/api/**", (route) => {
|
|
18
|
+
const headers = {
|
|
19
|
+
...route.request().headers(),
|
|
20
|
+
Authorization: "Bearer test-token",
|
|
21
|
+
"X-Test-Header": "test-value",
|
|
22
|
+
};
|
|
23
|
+
route.continue({ headers });
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
await page.goto("/dashboard");
|
|
27
|
+
});
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Modify Request Body
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
test("modify POST body", async ({ page }) => {
|
|
34
|
+
await page.route("**/api/orders", async (route) => {
|
|
35
|
+
if (route.request().method() === "POST") {
|
|
36
|
+
const postData = route.request().postDataJSON();
|
|
37
|
+
|
|
38
|
+
// Add test metadata
|
|
39
|
+
const modifiedData = {
|
|
40
|
+
...postData,
|
|
41
|
+
testMode: true,
|
|
42
|
+
testTimestamp: Date.now(),
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
await route.continue({
|
|
46
|
+
postData: JSON.stringify(modifiedData),
|
|
47
|
+
});
|
|
48
|
+
} else {
|
|
49
|
+
await route.continue();
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
await page.goto("/checkout");
|
|
54
|
+
await page.getByRole("button", { name: "Place Order" }).click();
|
|
55
|
+
});
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Transform Response
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
test("modify API response", async ({ page }) => {
|
|
62
|
+
await page.route("**/api/products", async (route) => {
|
|
63
|
+
// Fetch real response
|
|
64
|
+
const response = await route.fetch();
|
|
65
|
+
const json = await response.json();
|
|
66
|
+
|
|
67
|
+
// Modify response
|
|
68
|
+
const modified = json.map((product: any) => ({
|
|
69
|
+
...product,
|
|
70
|
+
price: product.price * 0.9, // 10% discount
|
|
71
|
+
testMode: true,
|
|
72
|
+
}));
|
|
73
|
+
|
|
74
|
+
await route.fulfill({
|
|
75
|
+
response,
|
|
76
|
+
json: modified,
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
await page.goto("/products");
|
|
81
|
+
});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## GraphQL Mocking
|
|
85
|
+
|
|
86
|
+
### Mock by Operation Name
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
test("mock GraphQL query", async ({ page }) => {
|
|
90
|
+
await page.route("**/graphql", async (route) => {
|
|
91
|
+
const postData = route.request().postDataJSON();
|
|
92
|
+
|
|
93
|
+
if (postData.operationName === "GetUser") {
|
|
94
|
+
return route.fulfill({
|
|
95
|
+
json: {
|
|
96
|
+
data: {
|
|
97
|
+
user: {
|
|
98
|
+
id: "1",
|
|
99
|
+
name: "Test User",
|
|
100
|
+
email: "test@example.com",
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (postData.operationName === "GetProducts") {
|
|
108
|
+
return route.fulfill({
|
|
109
|
+
json: {
|
|
110
|
+
data: {
|
|
111
|
+
products: [
|
|
112
|
+
{ id: "1", name: "Product A", price: 29.99 },
|
|
113
|
+
{ id: "2", name: "Product B", price: 49.99 },
|
|
114
|
+
],
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Pass through unmocked operations
|
|
121
|
+
return route.continue();
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
await page.goto("/dashboard");
|
|
125
|
+
});
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### GraphQL Mock Fixture
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
// fixtures/graphql.fixture.ts
|
|
132
|
+
type GraphQLMock = {
|
|
133
|
+
operation: string;
|
|
134
|
+
variables?: Record<string, any>;
|
|
135
|
+
response: { data?: any; errors?: any[] };
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
type GraphQLFixtures = {
|
|
139
|
+
mockGraphQL: (mocks: GraphQLMock[]) => Promise<void>;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
export const test = base.extend<GraphQLFixtures>({
|
|
143
|
+
mockGraphQL: async ({ page }, use) => {
|
|
144
|
+
await use(async (mocks) => {
|
|
145
|
+
await page.route("**/graphql", async (route) => {
|
|
146
|
+
const postData = route.request().postDataJSON();
|
|
147
|
+
|
|
148
|
+
const mock = mocks.find((m) => {
|
|
149
|
+
if (m.operation !== postData.operationName) return false;
|
|
150
|
+
|
|
151
|
+
// Optionally match variables
|
|
152
|
+
if (m.variables) {
|
|
153
|
+
return (
|
|
154
|
+
JSON.stringify(m.variables) === JSON.stringify(postData.variables)
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
return true;
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
if (mock) {
|
|
161
|
+
return route.fulfill({ json: mock.response });
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return route.continue();
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// Usage
|
|
171
|
+
test("dashboard with mocked GraphQL", async ({ page, mockGraphQL }) => {
|
|
172
|
+
await mockGraphQL([
|
|
173
|
+
{
|
|
174
|
+
operation: "GetDashboardStats",
|
|
175
|
+
response: {
|
|
176
|
+
data: { stats: { users: 100, revenue: 50000 } },
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
operation: "GetUser",
|
|
181
|
+
variables: { id: "1" },
|
|
182
|
+
response: {
|
|
183
|
+
data: { user: { id: "1", name: "John" } },
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
]);
|
|
187
|
+
|
|
188
|
+
await page.goto("/dashboard");
|
|
189
|
+
await expect(page.getByText("100 users")).toBeVisible();
|
|
190
|
+
});
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Mock GraphQL Mutations
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
test("mock GraphQL mutation", async ({ page }) => {
|
|
197
|
+
await page.route("**/graphql", async (route) => {
|
|
198
|
+
const postData = route.request().postDataJSON();
|
|
199
|
+
|
|
200
|
+
if (postData.operationName === "CreateOrder") {
|
|
201
|
+
const { input } = postData.variables;
|
|
202
|
+
|
|
203
|
+
return route.fulfill({
|
|
204
|
+
json: {
|
|
205
|
+
data: {
|
|
206
|
+
createOrder: {
|
|
207
|
+
id: "order-123",
|
|
208
|
+
status: "PENDING",
|
|
209
|
+
items: input.items,
|
|
210
|
+
total: input.items.reduce(
|
|
211
|
+
(sum: number, item: any) => sum + item.price * item.quantity,
|
|
212
|
+
0,
|
|
213
|
+
),
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return route.continue();
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
await page.goto("/checkout");
|
|
224
|
+
await page.getByRole("button", { name: "Place Order" }).click();
|
|
225
|
+
|
|
226
|
+
await expect(page.getByText("Order #order-123")).toBeVisible();
|
|
227
|
+
});
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## HAR Recording & Playback
|
|
231
|
+
|
|
232
|
+
### Record HAR File
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
// Record network traffic
|
|
236
|
+
test("record HAR", async ({ page, context }) => {
|
|
237
|
+
// Start recording
|
|
238
|
+
await context.routeFromHAR("./recordings/checkout.har", {
|
|
239
|
+
update: true, // Create/update HAR file
|
|
240
|
+
url: "**/api/**",
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
await page.goto("/checkout");
|
|
244
|
+
await page.getByRole("button", { name: "Place Order" }).click();
|
|
245
|
+
|
|
246
|
+
// HAR file is saved automatically
|
|
247
|
+
});
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### Playback HAR File
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
// Use recorded HAR for offline testing
|
|
254
|
+
test("playback HAR", async ({ page, context }) => {
|
|
255
|
+
await context.routeFromHAR("./recordings/checkout.har", {
|
|
256
|
+
url: "**/api/**",
|
|
257
|
+
update: false, // Don't update, just playback
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
await page.goto("/checkout");
|
|
261
|
+
|
|
262
|
+
// All API calls served from HAR file
|
|
263
|
+
await expect(page.getByText("Order confirmed")).toBeVisible();
|
|
264
|
+
});
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### HAR with Fallback
|
|
268
|
+
|
|
269
|
+
```typescript
|
|
270
|
+
test("HAR with live fallback", async ({ page, context }) => {
|
|
271
|
+
await context.routeFromHAR("./recordings/api.har", {
|
|
272
|
+
url: "**/api/**",
|
|
273
|
+
update: false,
|
|
274
|
+
notFound: "fallback", // Use real network if not in HAR
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
await page.goto("/dashboard");
|
|
278
|
+
});
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
## Conditional Mocking
|
|
282
|
+
|
|
283
|
+
### Mock Based on Request Body
|
|
284
|
+
|
|
285
|
+
```typescript
|
|
286
|
+
test("conditional mock by body", async ({ page }) => {
|
|
287
|
+
await page.route("**/api/search", async (route) => {
|
|
288
|
+
const body = route.request().postDataJSON();
|
|
289
|
+
|
|
290
|
+
if (body.query === "error") {
|
|
291
|
+
return route.fulfill({
|
|
292
|
+
status: 500,
|
|
293
|
+
json: { error: "Search failed" },
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (body.query === "empty") {
|
|
298
|
+
return route.fulfill({
|
|
299
|
+
json: { results: [] },
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Default response
|
|
304
|
+
return route.fulfill({
|
|
305
|
+
json: {
|
|
306
|
+
results: [{ id: 1, title: `Result for: ${body.query}` }],
|
|
307
|
+
},
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
await page.goto("/search");
|
|
312
|
+
|
|
313
|
+
// Test different scenarios
|
|
314
|
+
await page.getByLabel("Search").fill("error");
|
|
315
|
+
await page.getByLabel("Search").press("Enter");
|
|
316
|
+
await expect(page.getByText("Search failed")).toBeVisible();
|
|
317
|
+
});
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### Mock Nth Request
|
|
321
|
+
|
|
322
|
+
```typescript
|
|
323
|
+
test("different response on retry", async ({ page }) => {
|
|
324
|
+
let callCount = 0;
|
|
325
|
+
|
|
326
|
+
await page.route("**/api/status", (route) => {
|
|
327
|
+
callCount++;
|
|
328
|
+
|
|
329
|
+
if (callCount < 3) {
|
|
330
|
+
return route.fulfill({
|
|
331
|
+
status: 503,
|
|
332
|
+
json: { error: "Service unavailable" },
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Succeed on 3rd attempt
|
|
337
|
+
return route.fulfill({
|
|
338
|
+
json: { status: "ok" },
|
|
339
|
+
});
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
await page.goto("/dashboard");
|
|
343
|
+
|
|
344
|
+
// App should retry and eventually succeed
|
|
345
|
+
await expect(page.getByText("Connected")).toBeVisible();
|
|
346
|
+
});
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### Mock with Delay
|
|
350
|
+
|
|
351
|
+
```typescript
|
|
352
|
+
test("slow network simulation", async ({ page }) => {
|
|
353
|
+
await page.route("**/api/data", async (route) => {
|
|
354
|
+
// Simulate 2 second delay
|
|
355
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
356
|
+
|
|
357
|
+
return route.fulfill({
|
|
358
|
+
json: { data: "loaded" },
|
|
359
|
+
});
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
await page.goto("/dashboard");
|
|
363
|
+
|
|
364
|
+
// Loading state should appear
|
|
365
|
+
await expect(page.getByText("Loading...")).toBeVisible();
|
|
366
|
+
|
|
367
|
+
// Then data appears
|
|
368
|
+
await expect(page.getByText("loaded")).toBeVisible();
|
|
369
|
+
});
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
## Network Throttling
|
|
373
|
+
|
|
374
|
+
### Slow 3G Simulation
|
|
375
|
+
|
|
376
|
+
```typescript
|
|
377
|
+
test("slow network experience", async ({ page, context }) => {
|
|
378
|
+
// Create CDP session for network throttling
|
|
379
|
+
const client = await context.newCDPSession(page);
|
|
380
|
+
|
|
381
|
+
await client.send("Network.emulateNetworkConditions", {
|
|
382
|
+
offline: false,
|
|
383
|
+
downloadThroughput: (500 * 1024) / 8, // 500 Kbps
|
|
384
|
+
uploadThroughput: (500 * 1024) / 8,
|
|
385
|
+
latency: 400, // 400ms
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
await page.goto("/");
|
|
389
|
+
|
|
390
|
+
// Test loading states appear
|
|
391
|
+
await expect(page.getByTestId("skeleton-loader")).toBeVisible();
|
|
392
|
+
});
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
### Offline Mode
|
|
396
|
+
|
|
397
|
+
Use `context.setOffline(true/false)` to simulate network connectivity changes.
|
|
398
|
+
|
|
399
|
+
> **For comprehensive offline testing patterns:**
|
|
400
|
+
>
|
|
401
|
+
> - **Network failure simulation** (error recovery, graceful degradation): See [error-testing.md](error-testing.md#offline-testing)
|
|
402
|
+
> - **Offline-first/PWA testing** (service workers, caching, background sync): See [service-workers.md](service-workers.md#offline-testing)
|
|
403
|
+
|
|
404
|
+
### Network Throttling Fixture
|
|
405
|
+
|
|
406
|
+
```typescript
|
|
407
|
+
// fixtures/network.fixture.ts
|
|
408
|
+
type NetworkCondition = "slow3g" | "fast3g" | "offline";
|
|
409
|
+
|
|
410
|
+
const conditions = {
|
|
411
|
+
slow3g: { downloadThroughput: 50000, uploadThroughput: 50000, latency: 2000 },
|
|
412
|
+
fast3g: { downloadThroughput: 180000, uploadThroughput: 75000, latency: 150 },
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
type NetworkFixtures = {
|
|
416
|
+
setNetworkCondition: (condition: NetworkCondition) => Promise<void>;
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
export const test = base.extend<NetworkFixtures>({
|
|
420
|
+
setNetworkCondition: async ({ page, context }, use) => {
|
|
421
|
+
const client = await context.newCDPSession(page);
|
|
422
|
+
|
|
423
|
+
await use(async (condition) => {
|
|
424
|
+
if (condition === "offline") {
|
|
425
|
+
await context.setOffline(true);
|
|
426
|
+
} else {
|
|
427
|
+
await client.send("Network.emulateNetworkConditions", {
|
|
428
|
+
offline: false,
|
|
429
|
+
...conditions[condition],
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
// Reset
|
|
435
|
+
await context.setOffline(false);
|
|
436
|
+
},
|
|
437
|
+
});
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
## Anti-Patterns to Avoid
|
|
441
|
+
|
|
442
|
+
| Anti-Pattern | Problem | Solution |
|
|
443
|
+
| ------------------------ | ------------------------------ | -------------------------------- |
|
|
444
|
+
| Mocking all requests | Tests don't reflect reality | Mock only what's necessary |
|
|
445
|
+
| No cleanup of routes | Routes persist across tests | Use fixtures with cleanup |
|
|
446
|
+
| Ignoring request method | Mock applies to wrong requests | Check `route.request().method()` |
|
|
447
|
+
| Hardcoded mock responses | Brittle, hard to maintain | Use factories for mock data |
|
|
448
|
+
|
|
449
|
+
## Related References
|
|
450
|
+
|
|
451
|
+
- **Basic Mocking**: See [test-suite-structure.md](../core/test-suite-structure.md) for simple mocking
|
|
452
|
+
- **WebSockets**: See [websockets.md](../browser-apis/websockets.md) for real-time mocking
|