@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,493 @@
|
|
|
1
|
+
# Canvas & WebGL Testing
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
|
|
5
|
+
1. [Canvas Basics](#canvas-basics)
|
|
6
|
+
2. [Visual Comparison](#visual-comparison)
|
|
7
|
+
3. [Interaction Testing](#interaction-testing)
|
|
8
|
+
4. [WebGL Testing](#webgl-testing)
|
|
9
|
+
5. [Chart Libraries](#chart-libraries)
|
|
10
|
+
6. [Game & Animation Testing](#game--animation-testing)
|
|
11
|
+
|
|
12
|
+
## Canvas Basics
|
|
13
|
+
|
|
14
|
+
### Locating Canvas Elements
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
test("find canvas", async ({ page }) => {
|
|
18
|
+
await page.goto("/canvas-app");
|
|
19
|
+
|
|
20
|
+
// By tag
|
|
21
|
+
const canvas = page.locator("canvas");
|
|
22
|
+
|
|
23
|
+
// By ID or class
|
|
24
|
+
const gameCanvas = page.locator("canvas#game");
|
|
25
|
+
const chartCanvas = page.locator("canvas.chart-canvas");
|
|
26
|
+
|
|
27
|
+
// Verify canvas is present and visible
|
|
28
|
+
await expect(canvas).toBeVisible();
|
|
29
|
+
|
|
30
|
+
// Get canvas dimensions
|
|
31
|
+
const box = await canvas.boundingBox();
|
|
32
|
+
console.log(`Canvas size: ${box?.width}x${box?.height}`);
|
|
33
|
+
});
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Canvas Screenshot Testing
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
test("canvas renders correctly", async ({ page }) => {
|
|
40
|
+
await page.goto("/chart");
|
|
41
|
+
|
|
42
|
+
// Wait for canvas to be ready (check for specific content)
|
|
43
|
+
await page.waitForFunction(() => {
|
|
44
|
+
const canvas = document.querySelector("canvas");
|
|
45
|
+
const ctx = canvas?.getContext("2d");
|
|
46
|
+
// Check if canvas has been drawn to
|
|
47
|
+
return ctx && !isCanvasBlank(canvas);
|
|
48
|
+
|
|
49
|
+
function isCanvasBlank(canvas) {
|
|
50
|
+
const ctx = canvas.getContext("2d");
|
|
51
|
+
const data = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
|
|
52
|
+
return !data.some((channel) => channel !== 0);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Screenshot just the canvas
|
|
57
|
+
const canvas = page.locator("canvas");
|
|
58
|
+
await expect(canvas).toHaveScreenshot("chart.png");
|
|
59
|
+
});
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Extracting Canvas Data
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
test("verify canvas content", async ({ page }) => {
|
|
66
|
+
await page.goto("/drawing-app");
|
|
67
|
+
|
|
68
|
+
// Get canvas image data
|
|
69
|
+
const imageData = await page.evaluate(() => {
|
|
70
|
+
const canvas = document.querySelector("canvas") as HTMLCanvasElement;
|
|
71
|
+
return canvas.toDataURL("image/png");
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Verify it's not empty
|
|
75
|
+
expect(imageData).toMatch(/^data:image\/png;base64,.+/);
|
|
76
|
+
|
|
77
|
+
// Get pixel data at specific location
|
|
78
|
+
const pixelColor = await page.evaluate(() => {
|
|
79
|
+
const canvas = document.querySelector("canvas") as HTMLCanvasElement;
|
|
80
|
+
const ctx = canvas.getContext("2d")!;
|
|
81
|
+
const pixel = ctx.getImageData(100, 100, 1, 1).data;
|
|
82
|
+
return { r: pixel[0], g: pixel[1], b: pixel[2], a: pixel[3] };
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// Verify specific pixel color
|
|
86
|
+
expect(pixelColor.r).toBeGreaterThan(200); // Expecting red-ish
|
|
87
|
+
});
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Visual Comparison
|
|
91
|
+
|
|
92
|
+
### Screenshot Assertions
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
test("chart matches baseline", async ({ page }) => {
|
|
96
|
+
await page.goto("/dashboard");
|
|
97
|
+
|
|
98
|
+
// Wait for chart animation to complete
|
|
99
|
+
await page.waitForTimeout(1000); // Or better: wait for specific state
|
|
100
|
+
|
|
101
|
+
// Full page screenshot
|
|
102
|
+
await expect(page).toHaveScreenshot("dashboard.png", {
|
|
103
|
+
maxDiffPixels: 100, // Allow small differences
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// Just the canvas
|
|
107
|
+
const chart = page.locator("canvas#sales-chart");
|
|
108
|
+
await expect(chart).toHaveScreenshot("sales-chart.png", {
|
|
109
|
+
maxDiffPixelRatio: 0.01, // 1% difference allowed
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Handling Animation
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
test("animated canvas", async ({ page }) => {
|
|
118
|
+
await page.goto("/animated-chart");
|
|
119
|
+
|
|
120
|
+
// Pause animation before screenshot
|
|
121
|
+
await page.evaluate(() => {
|
|
122
|
+
// Common pattern: chart libraries expose pause method
|
|
123
|
+
window.chartInstance?.stop?.();
|
|
124
|
+
|
|
125
|
+
// Or override requestAnimationFrame
|
|
126
|
+
window.requestAnimationFrame = () => 0;
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
await expect(page.locator("canvas")).toHaveScreenshot();
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test("wait for animation complete", async ({ page }) => {
|
|
133
|
+
await page.goto("/chart-with-animation");
|
|
134
|
+
|
|
135
|
+
// Wait for animation complete event
|
|
136
|
+
await page.evaluate(() => {
|
|
137
|
+
return new Promise<void>((resolve) => {
|
|
138
|
+
if (window.chart?.isAnimating === false) {
|
|
139
|
+
resolve();
|
|
140
|
+
} else {
|
|
141
|
+
window.chart?.on("animationComplete", resolve);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
await expect(page.locator("canvas")).toHaveScreenshot();
|
|
147
|
+
});
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Threshold Configuration
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
// playwright.config.ts
|
|
154
|
+
export default defineConfig({
|
|
155
|
+
expect: {
|
|
156
|
+
toHaveScreenshot: {
|
|
157
|
+
// Increased threshold for canvas (anti-aliasing differences)
|
|
158
|
+
maxDiffPixelRatio: 0.02,
|
|
159
|
+
threshold: 0.3, // Per-pixel color threshold
|
|
160
|
+
animations: "disabled",
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Interaction Testing
|
|
167
|
+
|
|
168
|
+
### Click on Canvas
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
test("click on canvas element", async ({ page }) => {
|
|
172
|
+
await page.goto("/interactive-map");
|
|
173
|
+
|
|
174
|
+
const canvas = page.locator("canvas");
|
|
175
|
+
|
|
176
|
+
// Click at specific coordinates
|
|
177
|
+
await canvas.click({ position: { x: 150, y: 200 } });
|
|
178
|
+
|
|
179
|
+
// Verify click was registered
|
|
180
|
+
await expect(page.locator("#info-panel")).toContainText("Location: Paris");
|
|
181
|
+
});
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Drawing on Canvas
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
test("draw on canvas", async ({ page }) => {
|
|
188
|
+
await page.goto("/whiteboard");
|
|
189
|
+
|
|
190
|
+
const canvas = page.locator("canvas");
|
|
191
|
+
const box = await canvas.boundingBox();
|
|
192
|
+
|
|
193
|
+
// Draw a line using mouse
|
|
194
|
+
await page.mouse.move(box!.x + 50, box!.y + 50);
|
|
195
|
+
await page.mouse.down();
|
|
196
|
+
await page.mouse.move(box!.x + 200, box!.y + 200, { steps: 10 });
|
|
197
|
+
await page.mouse.up();
|
|
198
|
+
|
|
199
|
+
// Verify something was drawn
|
|
200
|
+
const hasDrawing = await page.evaluate(() => {
|
|
201
|
+
const canvas = document.querySelector("canvas") as HTMLCanvasElement;
|
|
202
|
+
const ctx = canvas.getContext("2d")!;
|
|
203
|
+
const data = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
|
|
204
|
+
return data.some((v, i) => i % 4 !== 3 && v !== 255); // Non-white pixels
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
expect(hasDrawing).toBe(true);
|
|
208
|
+
});
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### Drag and Drop
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
test("drag canvas element", async ({ page }) => {
|
|
215
|
+
await page.goto("/diagram-editor");
|
|
216
|
+
|
|
217
|
+
const canvas = page.locator("canvas");
|
|
218
|
+
const box = await canvas.boundingBox();
|
|
219
|
+
|
|
220
|
+
// Drag shape from position A to B
|
|
221
|
+
await page.mouse.move(box!.x + 100, box!.y + 100);
|
|
222
|
+
await page.mouse.down();
|
|
223
|
+
await page.mouse.move(box!.x + 300, box!.y + 200, { steps: 20 });
|
|
224
|
+
await page.mouse.up();
|
|
225
|
+
|
|
226
|
+
// Verify via screenshot or state check
|
|
227
|
+
await expect(canvas).toHaveScreenshot("shape-moved.png");
|
|
228
|
+
});
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Touch Gestures on Canvas
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
test("pinch zoom on canvas", async ({ page }) => {
|
|
235
|
+
await page.goto("/map");
|
|
236
|
+
|
|
237
|
+
const canvas = page.locator("canvas");
|
|
238
|
+
const box = await canvas.boundingBox();
|
|
239
|
+
const centerX = box!.x + box!.width / 2;
|
|
240
|
+
const centerY = box!.y + box!.height / 2;
|
|
241
|
+
|
|
242
|
+
// Simulate pinch zoom using two touch points
|
|
243
|
+
await page.touchscreen.tap(centerX, centerY);
|
|
244
|
+
|
|
245
|
+
// Use evaluate for complex gestures
|
|
246
|
+
await page.evaluate(
|
|
247
|
+
async ({ x, y }) => {
|
|
248
|
+
const target = document.querySelector("canvas")!;
|
|
249
|
+
|
|
250
|
+
// Simulate pinch start
|
|
251
|
+
const touch1 = new Touch({
|
|
252
|
+
identifier: 1,
|
|
253
|
+
target,
|
|
254
|
+
clientX: x - 50,
|
|
255
|
+
clientY: y,
|
|
256
|
+
});
|
|
257
|
+
const touch2 = new Touch({
|
|
258
|
+
identifier: 2,
|
|
259
|
+
target,
|
|
260
|
+
clientX: x + 50,
|
|
261
|
+
clientY: y,
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
target.dispatchEvent(
|
|
265
|
+
new TouchEvent("touchstart", {
|
|
266
|
+
touches: [touch1, touch2],
|
|
267
|
+
targetTouches: [touch1, touch2],
|
|
268
|
+
bubbles: true,
|
|
269
|
+
}),
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
// Simulate pinch out
|
|
273
|
+
const touch1End = new Touch({
|
|
274
|
+
identifier: 1,
|
|
275
|
+
target,
|
|
276
|
+
clientX: x - 100,
|
|
277
|
+
clientY: y,
|
|
278
|
+
});
|
|
279
|
+
const touch2End = new Touch({
|
|
280
|
+
identifier: 2,
|
|
281
|
+
target,
|
|
282
|
+
clientX: x + 100,
|
|
283
|
+
clientY: y,
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
target.dispatchEvent(
|
|
287
|
+
new TouchEvent("touchmove", {
|
|
288
|
+
touches: [touch1End, touch2End],
|
|
289
|
+
targetTouches: [touch1End, touch2End],
|
|
290
|
+
bubbles: true,
|
|
291
|
+
}),
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
target.dispatchEvent(new TouchEvent("touchend", { bubbles: true }));
|
|
295
|
+
},
|
|
296
|
+
{ x: centerX, y: centerY },
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
// Verify zoom level changed
|
|
300
|
+
const zoomLevel = await page.locator("#zoom-indicator").textContent();
|
|
301
|
+
expect(parseFloat(zoomLevel!)).toBeGreaterThan(1);
|
|
302
|
+
});
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
## WebGL Testing
|
|
306
|
+
|
|
307
|
+
### Checking WebGL Support
|
|
308
|
+
|
|
309
|
+
```typescript
|
|
310
|
+
test("WebGL is supported", async ({ page }) => {
|
|
311
|
+
await page.goto("/3d-viewer");
|
|
312
|
+
|
|
313
|
+
const hasWebGL = await page.evaluate(() => {
|
|
314
|
+
const canvas = document.createElement("canvas");
|
|
315
|
+
const gl =
|
|
316
|
+
canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
|
|
317
|
+
return !!gl;
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
expect(hasWebGL).toBe(true);
|
|
321
|
+
});
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### WebGL Screenshot Testing
|
|
325
|
+
|
|
326
|
+
```typescript
|
|
327
|
+
test("3D scene renders", async ({ page }) => {
|
|
328
|
+
await page.goto("/3d-model-viewer");
|
|
329
|
+
|
|
330
|
+
// Wait for WebGL scene to render
|
|
331
|
+
await page.waitForFunction(() => {
|
|
332
|
+
const canvas = document.querySelector("canvas");
|
|
333
|
+
if (!canvas) return false;
|
|
334
|
+
|
|
335
|
+
const gl = canvas.getContext("webgl") || canvas.getContext("webgl2");
|
|
336
|
+
if (!gl) return false;
|
|
337
|
+
|
|
338
|
+
// Check if something has been drawn
|
|
339
|
+
const pixels = new Uint8Array(4);
|
|
340
|
+
gl.readPixels(
|
|
341
|
+
canvas.width / 2,
|
|
342
|
+
canvas.height / 2,
|
|
343
|
+
1,
|
|
344
|
+
1,
|
|
345
|
+
gl.RGBA,
|
|
346
|
+
gl.UNSIGNED_BYTE,
|
|
347
|
+
pixels,
|
|
348
|
+
);
|
|
349
|
+
return pixels.some((p) => p > 0);
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
// Screenshot comparison (higher threshold for WebGL)
|
|
353
|
+
await expect(page.locator("canvas")).toHaveScreenshot("3d-scene.png", {
|
|
354
|
+
maxDiffPixelRatio: 0.05, // WebGL can have more variation
|
|
355
|
+
});
|
|
356
|
+
});
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
### Testing Three.js Applications
|
|
360
|
+
|
|
361
|
+
```typescript
|
|
362
|
+
test("Three.js scene interaction", async ({ page }) => {
|
|
363
|
+
await page.goto("/three-demo");
|
|
364
|
+
|
|
365
|
+
// Wait for scene to be ready
|
|
366
|
+
await page.waitForFunction(() => window.scene?.children?.length > 0);
|
|
367
|
+
|
|
368
|
+
// Interact with scene (orbit controls)
|
|
369
|
+
const canvas = page.locator("canvas");
|
|
370
|
+
const box = await canvas.boundingBox();
|
|
371
|
+
|
|
372
|
+
// Rotate camera by dragging
|
|
373
|
+
await page.mouse.move(box!.x + box!.width / 2, box!.y + box!.height / 2);
|
|
374
|
+
await page.mouse.down();
|
|
375
|
+
await page.mouse.move(
|
|
376
|
+
box!.x + box!.width / 2 + 100,
|
|
377
|
+
box!.y + box!.height / 2,
|
|
378
|
+
{
|
|
379
|
+
steps: 10,
|
|
380
|
+
},
|
|
381
|
+
);
|
|
382
|
+
await page.mouse.up();
|
|
383
|
+
|
|
384
|
+
// Verify camera position changed
|
|
385
|
+
const cameraRotation = await page.evaluate(() => {
|
|
386
|
+
return window.camera?.rotation?.y;
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
expect(cameraRotation).not.toBe(0);
|
|
390
|
+
});
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
## Chart Libraries
|
|
394
|
+
|
|
395
|
+
### Chart.js Testing
|
|
396
|
+
|
|
397
|
+
```typescript
|
|
398
|
+
test("Chart.js renders data", async ({ page }) => {
|
|
399
|
+
await page.goto("/chartjs-demo");
|
|
400
|
+
|
|
401
|
+
// Wait for Chart.js to initialize
|
|
402
|
+
await page.waitForFunction(() => {
|
|
403
|
+
return window.Chart && document.querySelector("canvas")?.__chart__;
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
// Get chart data via Chart.js API
|
|
407
|
+
const chartData = await page.evaluate(() => {
|
|
408
|
+
const canvas = document.querySelector("canvas") as any;
|
|
409
|
+
const chart = canvas.__chart__;
|
|
410
|
+
return chart.data.datasets[0].data;
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
expect(chartData).toEqual([12, 19, 3, 5, 2, 3]);
|
|
414
|
+
|
|
415
|
+
// Screenshot test
|
|
416
|
+
await expect(page.locator("canvas")).toHaveScreenshot();
|
|
417
|
+
});
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
### D3.js / ECharts Testing
|
|
421
|
+
|
|
422
|
+
```typescript
|
|
423
|
+
test("chart library interaction", async ({ page }) => {
|
|
424
|
+
await page.goto("/chart-demo");
|
|
425
|
+
|
|
426
|
+
// Wait for chart to render
|
|
427
|
+
await page.waitForFunction(() => document.querySelector("canvas, svg.chart"));
|
|
428
|
+
|
|
429
|
+
// For SVG charts (D3)
|
|
430
|
+
const bars = page.locator("svg.chart rect.bar");
|
|
431
|
+
if ((await bars.count()) > 0) {
|
|
432
|
+
await bars.first().hover();
|
|
433
|
+
await expect(page.locator(".tooltip")).toBeVisible();
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// For canvas charts (ECharts, Chart.js)
|
|
437
|
+
const canvas = page.locator("canvas");
|
|
438
|
+
await canvas.click({ position: { x: 200, y: 150 } });
|
|
439
|
+
});
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
## Game & Animation Testing
|
|
443
|
+
|
|
444
|
+
### Frame-by-Frame Testing
|
|
445
|
+
|
|
446
|
+
```typescript
|
|
447
|
+
test("game frame control", async ({ page }) => {
|
|
448
|
+
await page.goto("/game");
|
|
449
|
+
|
|
450
|
+
// Pause and step through frames
|
|
451
|
+
await page.evaluate(() => window.gameLoop?.pause());
|
|
452
|
+
await page.evaluate(() => window.gameLoop?.tick());
|
|
453
|
+
await expect(page.locator("canvas")).toHaveScreenshot("frame-1.png");
|
|
454
|
+
|
|
455
|
+
for (let i = 0; i < 10; i++) {
|
|
456
|
+
await page.evaluate(() => window.gameLoop?.tick());
|
|
457
|
+
}
|
|
458
|
+
await expect(page.locator("canvas")).toHaveScreenshot("frame-11.png");
|
|
459
|
+
});
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
### Testing Game State
|
|
463
|
+
|
|
464
|
+
```typescript
|
|
465
|
+
test("game state changes", async ({ page }) => {
|
|
466
|
+
await page.goto("/game");
|
|
467
|
+
|
|
468
|
+
const initialScore = await page.evaluate(() => window.game?.score);
|
|
469
|
+
expect(initialScore).toBe(0);
|
|
470
|
+
|
|
471
|
+
await page.keyboard.press("Space"); // Action
|
|
472
|
+
await page.waitForTimeout(500);
|
|
473
|
+
|
|
474
|
+
const newScore = await page.evaluate(() => window.game?.score);
|
|
475
|
+
expect(newScore).toBeGreaterThan(0);
|
|
476
|
+
});
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
## Anti-Patterns to Avoid
|
|
480
|
+
|
|
481
|
+
| Anti-Pattern | Problem | Solution |
|
|
482
|
+
| ------------------------ | ------------------------ | ----------------------------------- |
|
|
483
|
+
| Pixel-perfect assertions | Fails across browsers/OS | Use maxDiffPixelRatio threshold |
|
|
484
|
+
| Not waiting for render | Blank canvas screenshots | Wait for draw completion |
|
|
485
|
+
| Testing raw pixel data | Brittle and slow | Use visual comparison |
|
|
486
|
+
| Ignoring animation | Flaky screenshots | Pause/disable animations |
|
|
487
|
+
| Hardcoded coordinates | Breaks on resize | Calculate relative to canvas bounds |
|
|
488
|
+
|
|
489
|
+
## Related References
|
|
490
|
+
|
|
491
|
+
- **Visual Testing**: See [test-suite-structure.md](../core/test-suite-structure.md) for visual regression setup
|
|
492
|
+
- **Mobile Gestures**: See [mobile-testing.md](../advanced/mobile-testing.md) for touch interactions
|
|
493
|
+
- **Performance**: See [performance-testing.md](performance-testing.md) for FPS monitoring
|