@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,360 @@
|
|
|
1
|
+
# Error & Edge Case Testing
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
|
|
5
|
+
1. [Error Boundaries](#error-boundaries)
|
|
6
|
+
2. [Network Failures](#network-failures)
|
|
7
|
+
3. [Offline Testing](#offline-testing)
|
|
8
|
+
4. [Loading States](#loading-states)
|
|
9
|
+
5. [Form Validation](#form-validation)
|
|
10
|
+
|
|
11
|
+
## Error Boundaries
|
|
12
|
+
|
|
13
|
+
### Test Component Errors
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
test("error boundary catches component error", async ({ page }) => {
|
|
17
|
+
// Trigger error via mock
|
|
18
|
+
await page.route("**/api/user", (route) => {
|
|
19
|
+
route.fulfill({
|
|
20
|
+
json: null, // Will cause component to throw
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
await page.goto("/profile");
|
|
25
|
+
|
|
26
|
+
// Error boundary should render fallback
|
|
27
|
+
await expect(page.getByText("Something went wrong")).toBeVisible();
|
|
28
|
+
await expect(page.getByRole("button", { name: "Try Again" })).toBeVisible();
|
|
29
|
+
});
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Test Error Recovery
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
test("recover from error state", async ({ page }) => {
|
|
36
|
+
let requestCount = 0;
|
|
37
|
+
|
|
38
|
+
await page.route("**/api/data", (route) => {
|
|
39
|
+
requestCount++;
|
|
40
|
+
if (requestCount === 1) {
|
|
41
|
+
return route.fulfill({ status: 500 });
|
|
42
|
+
}
|
|
43
|
+
return route.fulfill({
|
|
44
|
+
json: { data: "success" },
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
await page.goto("/dashboard");
|
|
49
|
+
|
|
50
|
+
// Error state
|
|
51
|
+
await expect(page.getByText("Failed to load")).toBeVisible();
|
|
52
|
+
|
|
53
|
+
// Retry
|
|
54
|
+
await page.getByRole("button", { name: "Retry" }).click();
|
|
55
|
+
|
|
56
|
+
// Success state
|
|
57
|
+
await expect(page.getByText("success")).toBeVisible();
|
|
58
|
+
});
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Test JavaScript Errors
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
test("handles runtime error gracefully", async ({ page }) => {
|
|
65
|
+
const errors: string[] = [];
|
|
66
|
+
|
|
67
|
+
page.on("pageerror", (error) => {
|
|
68
|
+
errors.push(error.message);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
await page.goto("/buggy-page");
|
|
72
|
+
|
|
73
|
+
// App should still be functional despite error
|
|
74
|
+
await expect(page.getByRole("navigation")).toBeVisible();
|
|
75
|
+
|
|
76
|
+
// Error was logged
|
|
77
|
+
expect(errors.length).toBeGreaterThan(0);
|
|
78
|
+
});
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Network Failures
|
|
82
|
+
|
|
83
|
+
### Test API Errors
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
test.describe("API error handling", () => {
|
|
87
|
+
const errorCodes = [400, 401, 403, 404, 500, 502, 503];
|
|
88
|
+
|
|
89
|
+
for (const status of errorCodes) {
|
|
90
|
+
test(`handles ${status} error`, async ({ page }) => {
|
|
91
|
+
await page.route("**/api/data", (route) =>
|
|
92
|
+
route.fulfill({
|
|
93
|
+
status,
|
|
94
|
+
json: { error: `Error ${status}` },
|
|
95
|
+
}),
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
await page.goto("/dashboard");
|
|
99
|
+
|
|
100
|
+
// Appropriate error message shown
|
|
101
|
+
await expect(page.getByRole("alert")).toBeVisible();
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Test Timeout
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
test("handles request timeout", async ({ page }) => {
|
|
111
|
+
await page.route("**/api/slow", async (route) => {
|
|
112
|
+
// Never respond - simulates timeout
|
|
113
|
+
await new Promise(() => {});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
await page.goto("/slow-page");
|
|
117
|
+
|
|
118
|
+
// Should show timeout message (app should have its own timeout)
|
|
119
|
+
await expect(page.getByText("Request timed out")).toBeVisible({
|
|
120
|
+
timeout: 15000,
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Test Connection Reset
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
test("handles connection failure", async ({ page }) => {
|
|
129
|
+
await page.route("**/api/data", (route) => {
|
|
130
|
+
route.abort("connectionfailed");
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
await page.goto("/dashboard");
|
|
134
|
+
|
|
135
|
+
await expect(page.getByText("Connection failed")).toBeVisible();
|
|
136
|
+
await expect(page.getByRole("button", { name: "Retry" })).toBeVisible();
|
|
137
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Test Mid-Request Failure
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
test("handles failure during request", async ({ page }) => {
|
|
144
|
+
let requestStarted = false;
|
|
145
|
+
|
|
146
|
+
await page.route("**/api/upload", async (route) => {
|
|
147
|
+
requestStarted = true;
|
|
148
|
+
// Abort after small delay (mid-request)
|
|
149
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
150
|
+
route.abort("failed");
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
await page.goto("/upload");
|
|
154
|
+
await page.getByLabel("File").setInputFiles("./fixtures/large-file.pdf");
|
|
155
|
+
await page.getByRole("button", { name: "Upload" }).click();
|
|
156
|
+
|
|
157
|
+
// Should show failure, not hang
|
|
158
|
+
await expect(page.getByText("Upload failed")).toBeVisible();
|
|
159
|
+
expect(requestStarted).toBe(true);
|
|
160
|
+
});
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Offline Testing
|
|
164
|
+
|
|
165
|
+
This section covers **unexpected network failures** and error recovery. For **offline-first apps (PWAs)** with service workers, caching, and background sync, see [service-workers.md](service-workers.md#offline-testing).
|
|
166
|
+
|
|
167
|
+
### Go Offline During Session
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
test("handles going offline", async ({ page, context }) => {
|
|
171
|
+
await page.goto("/dashboard");
|
|
172
|
+
await expect(page.getByTestId("data")).toBeVisible();
|
|
173
|
+
|
|
174
|
+
// Go offline unexpectedly
|
|
175
|
+
await context.setOffline(true);
|
|
176
|
+
|
|
177
|
+
// Try to refresh data
|
|
178
|
+
await page.getByRole("button", { name: "Refresh" }).click();
|
|
179
|
+
|
|
180
|
+
// Should show offline indicator
|
|
181
|
+
await expect(page.getByText("You're offline")).toBeVisible();
|
|
182
|
+
|
|
183
|
+
// Go back online
|
|
184
|
+
await context.setOffline(false);
|
|
185
|
+
|
|
186
|
+
// Should recover
|
|
187
|
+
await page.getByRole("button", { name: "Refresh" }).click();
|
|
188
|
+
await expect(page.getByText("You're offline")).toBeHidden();
|
|
189
|
+
});
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Test Network Recovery
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
test("recovers gracefully when connection returns", async ({
|
|
196
|
+
page,
|
|
197
|
+
context,
|
|
198
|
+
}) => {
|
|
199
|
+
await page.goto("/dashboard");
|
|
200
|
+
|
|
201
|
+
// Simulate connection drop
|
|
202
|
+
await context.setOffline(true);
|
|
203
|
+
|
|
204
|
+
// App should show degraded state
|
|
205
|
+
await expect(page.getByRole("alert")).toContainText(/offline|connection/i);
|
|
206
|
+
|
|
207
|
+
// Connection restored
|
|
208
|
+
await context.setOffline(false);
|
|
209
|
+
|
|
210
|
+
// Retry should work
|
|
211
|
+
await page.getByRole("button", { name: "Retry" }).click();
|
|
212
|
+
await expect(page.getByTestId("data")).toBeVisible();
|
|
213
|
+
});
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## Loading States
|
|
217
|
+
|
|
218
|
+
### Test Skeleton Loaders
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
test("shows skeleton during load", async ({ page }) => {
|
|
222
|
+
// Add delay to API response
|
|
223
|
+
await page.route("**/api/posts", async (route) => {
|
|
224
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
225
|
+
route.fulfill({
|
|
226
|
+
json: [{ id: 1, title: "Post 1" }],
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
await page.goto("/posts");
|
|
231
|
+
|
|
232
|
+
// Skeleton should appear immediately
|
|
233
|
+
await expect(page.getByTestId("skeleton")).toBeVisible();
|
|
234
|
+
|
|
235
|
+
// Then content replaces skeleton
|
|
236
|
+
await expect(page.getByText("Post 1")).toBeVisible();
|
|
237
|
+
await expect(page.getByTestId("skeleton")).toBeHidden();
|
|
238
|
+
});
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Test Loading Indicators
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
test("shows loading state for actions", async ({ page }) => {
|
|
245
|
+
await page.route("**/api/save", async (route) => {
|
|
246
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
247
|
+
route.fulfill({ json: { success: true } });
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
await page.goto("/editor");
|
|
251
|
+
await page.getByLabel("Content").fill("New content");
|
|
252
|
+
|
|
253
|
+
const saveButton = page.getByRole("button", { name: "Save" });
|
|
254
|
+
await saveButton.click();
|
|
255
|
+
|
|
256
|
+
// Button should show loading state
|
|
257
|
+
await expect(saveButton).toBeDisabled();
|
|
258
|
+
await expect(page.getByTestId("spinner")).toBeVisible();
|
|
259
|
+
|
|
260
|
+
// Then success state
|
|
261
|
+
await expect(saveButton).toBeEnabled();
|
|
262
|
+
await expect(page.getByText("Saved")).toBeVisible();
|
|
263
|
+
});
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### Test Empty States
|
|
267
|
+
|
|
268
|
+
```typescript
|
|
269
|
+
test("shows empty state when no data", async ({ page }) => {
|
|
270
|
+
await page.route("**/api/items", (route) => route.fulfill({ json: [] }));
|
|
271
|
+
|
|
272
|
+
await page.goto("/items");
|
|
273
|
+
|
|
274
|
+
await expect(page.getByText("No items yet")).toBeVisible();
|
|
275
|
+
await expect(
|
|
276
|
+
page.getByRole("button", { name: "Create First Item" }),
|
|
277
|
+
).toBeVisible();
|
|
278
|
+
});
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
## Form Validation
|
|
282
|
+
|
|
283
|
+
### Test Client-Side Validation
|
|
284
|
+
|
|
285
|
+
```typescript
|
|
286
|
+
test("validates required fields", async ({ page }) => {
|
|
287
|
+
await page.goto("/signup");
|
|
288
|
+
|
|
289
|
+
// Submit empty form
|
|
290
|
+
await page.getByRole("button", { name: "Sign Up" }).click();
|
|
291
|
+
|
|
292
|
+
// Should show validation errors
|
|
293
|
+
await expect(page.getByText("Email is required")).toBeVisible();
|
|
294
|
+
await expect(page.getByText("Password is required")).toBeVisible();
|
|
295
|
+
|
|
296
|
+
// Form should not submit
|
|
297
|
+
await expect(page).toHaveURL("/signup");
|
|
298
|
+
});
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### Test Format Validation
|
|
302
|
+
|
|
303
|
+
```typescript
|
|
304
|
+
test("validates email format", async ({ page }) => {
|
|
305
|
+
await page.goto("/signup");
|
|
306
|
+
|
|
307
|
+
await page.getByLabel("Email").fill("invalid-email");
|
|
308
|
+
await page.getByLabel("Email").blur();
|
|
309
|
+
|
|
310
|
+
await expect(page.getByText("Invalid email address")).toBeVisible();
|
|
311
|
+
|
|
312
|
+
// Fix the error
|
|
313
|
+
await page.getByLabel("Email").fill("valid@email.com");
|
|
314
|
+
await page.getByLabel("Email").blur();
|
|
315
|
+
|
|
316
|
+
await expect(page.getByText("Invalid email address")).toBeHidden();
|
|
317
|
+
});
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### Test Server-Side Validation
|
|
321
|
+
|
|
322
|
+
```typescript
|
|
323
|
+
test("handles server validation errors", async ({ page }) => {
|
|
324
|
+
await page.route("**/api/register", (route) =>
|
|
325
|
+
route.fulfill({
|
|
326
|
+
status: 422,
|
|
327
|
+
json: {
|
|
328
|
+
errors: {
|
|
329
|
+
email: "Email already exists",
|
|
330
|
+
username: "Username is taken",
|
|
331
|
+
},
|
|
332
|
+
},
|
|
333
|
+
}),
|
|
334
|
+
);
|
|
335
|
+
|
|
336
|
+
await page.goto("/signup");
|
|
337
|
+
await page.getByLabel("Email").fill("taken@email.com");
|
|
338
|
+
await page.getByLabel("Username").fill("takenuser");
|
|
339
|
+
await page.getByLabel("Password").fill("password123");
|
|
340
|
+
await page.getByRole("button", { name: "Sign Up" }).click();
|
|
341
|
+
|
|
342
|
+
// Server errors should display
|
|
343
|
+
await expect(page.getByText("Email already exists")).toBeVisible();
|
|
344
|
+
await expect(page.getByText("Username is taken")).toBeVisible();
|
|
345
|
+
});
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
## Anti-Patterns to Avoid
|
|
349
|
+
|
|
350
|
+
| Anti-Pattern | Problem | Solution |
|
|
351
|
+
| ------------------------ | ------------------------------ | -------------------------------------- |
|
|
352
|
+
| Only testing happy path | Misses error handling bugs | Test all error scenarios |
|
|
353
|
+
| No network failure tests | App crashes on poor connection | Test offline/slow/failed requests |
|
|
354
|
+
| Skipping loading states | Janky UX not caught | Assert loading UI appears |
|
|
355
|
+
| Ignoring validation | Form bugs slip through | Test both client and server validation |
|
|
356
|
+
|
|
357
|
+
## Related References
|
|
358
|
+
|
|
359
|
+
- **Network Mocking**: See [network-advanced.md](../advanced/network-advanced.md) for mock patterns
|
|
360
|
+
- **Assertions**: See [assertions-waiting.md](../core/assertions-waiting.md) for error assertions
|