@assertrx/dino 1.0.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 (89) hide show
  1. package/README.md +293 -0
  2. package/extensions/dino.ts +70 -0
  3. package/extensions/mabl-mcp.ts +306 -0
  4. package/extensions/playwright-mcp.ts +142 -0
  5. package/package.json +47 -0
  6. package/prompts/create-pr.md +110 -0
  7. package/prompts/generate-bdd-from-url.md +65 -0
  8. package/prompts/generate-pom-from-url-cli.md +48 -0
  9. package/prompts/generate-pom-from-url.md +45 -0
  10. package/prompts/generate-spec-from-feature.md +64 -0
  11. package/prompts/heal-playwright-failures.md +106 -0
  12. package/prompts/setup-playwright.md +81 -0
  13. package/scripts/start-dino.cmd +8 -0
  14. package/scripts/start-dino.mjs +226 -0
  15. package/scripts/start-dino.ps1 +9 -0
  16. package/scripts/start-dino.sh +10 -0
  17. package/skills/acli/SKILL.md +292 -0
  18. package/skills/bdd-gherkin/SKILL.md +552 -0
  19. package/skills/build-review-interface/SKILL.md +96 -0
  20. package/skills/error-analysis/SKILL.md +164 -0
  21. package/skills/eval-audit/SKILL.md +183 -0
  22. package/skills/evaluate-rag/SKILL.md +177 -0
  23. package/skills/generate-synthetic-data/SKILL.md +131 -0
  24. package/skills/mabl-mcp/SKILL.md +79 -0
  25. package/skills/playwright-cli/SKILL.md +246 -0
  26. package/skills/playwright-cli/advanced-workflows.md +679 -0
  27. package/skills/playwright-cli/core-commands.md +375 -0
  28. package/skills/playwright-cli/device-emulation.md +475 -0
  29. package/skills/playwright-cli/request-mocking.md +373 -0
  30. package/skills/playwright-cli/running-custom-code.md +553 -0
  31. package/skills/playwright-cli/screenshots-and-media.md +457 -0
  32. package/skills/playwright-cli/session-management.md +425 -0
  33. package/skills/playwright-cli/storage-and-auth.md +487 -0
  34. package/skills/playwright-cli/test-generation.md +336 -0
  35. package/skills/playwright-cli/tracing-and-debugging.md +501 -0
  36. package/skills/playwright-core/SKILL.md +101 -0
  37. package/skills/playwright-core/accessibility.md +1517 -0
  38. package/skills/playwright-core/angular.md +1086 -0
  39. package/skills/playwright-core/api-testing.md +1623 -0
  40. package/skills/playwright-core/assertions-and-waiting.md +703 -0
  41. package/skills/playwright-core/auth-flows.md +1081 -0
  42. package/skills/playwright-core/authentication.md +1409 -0
  43. package/skills/playwright-core/browser-apis.md +673 -0
  44. package/skills/playwright-core/browser-extensions.md +319 -0
  45. package/skills/playwright-core/canvas-and-webgl.md +494 -0
  46. package/skills/playwright-core/clock-and-time-mocking.md +427 -0
  47. package/skills/playwright-core/common-pitfalls.md +1318 -0
  48. package/skills/playwright-core/component-testing.md +1179 -0
  49. package/skills/playwright-core/configuration.md +731 -0
  50. package/skills/playwright-core/crud-testing.md +945 -0
  51. package/skills/playwright-core/debugging.md +827 -0
  52. package/skills/playwright-core/drag-and-drop.md +992 -0
  53. package/skills/playwright-core/electron-testing.md +622 -0
  54. package/skills/playwright-core/error-and-edge-cases.md +1181 -0
  55. package/skills/playwright-core/error-index.md +1903 -0
  56. package/skills/playwright-core/file-operations.md +749 -0
  57. package/skills/playwright-core/file-upload-download.md +1012 -0
  58. package/skills/playwright-core/fixtures-and-hooks.md +1072 -0
  59. package/skills/playwright-core/flaky-tests.md +883 -0
  60. package/skills/playwright-core/forms-and-validation.md +1056 -0
  61. package/skills/playwright-core/i18n-and-localization.md +622 -0
  62. package/skills/playwright-core/iframes-and-shadow-dom.md +488 -0
  63. package/skills/playwright-core/locator-strategy.md +588 -0
  64. package/skills/playwright-core/locators.md +743 -0
  65. package/skills/playwright-core/mobile-and-responsive.md +1669 -0
  66. package/skills/playwright-core/multi-context-and-popups.md +581 -0
  67. package/skills/playwright-core/multi-user-and-collaboration.md +477 -0
  68. package/skills/playwright-core/network-mocking.md +1395 -0
  69. package/skills/playwright-core/nextjs.md +1013 -0
  70. package/skills/playwright-core/performance-testing.md +608 -0
  71. package/skills/playwright-core/react.md +1082 -0
  72. package/skills/playwright-core/search-and-filter.md +1366 -0
  73. package/skills/playwright-core/security-testing.md +624 -0
  74. package/skills/playwright-core/service-workers-and-pwa.md +524 -0
  75. package/skills/playwright-core/test-architecture.md +569 -0
  76. package/skills/playwright-core/test-data-management.md +1209 -0
  77. package/skills/playwright-core/test-organization.md +947 -0
  78. package/skills/playwright-core/third-party-integrations.md +754 -0
  79. package/skills/playwright-core/trace-analysis.md +161 -0
  80. package/skills/playwright-core/visual-regression.md +1027 -0
  81. package/skills/playwright-core/vue.md +1162 -0
  82. package/skills/playwright-core/websockets-and-realtime.md +623 -0
  83. package/skills/playwright-core/when-to-mock.md +827 -0
  84. package/skills/playwright-pom/SKILL.md +94 -0
  85. package/skills/playwright-pom/page-object-model.md +921 -0
  86. package/skills/playwright-pom/pom-vs-fixtures-vs-helpers.md +937 -0
  87. package/skills/validate-evaluator/SKILL.md +215 -0
  88. package/skills/write-judge-prompt/SKILL.md +144 -0
  89. package/themes/dino.json +66 -0
@@ -0,0 +1,494 @@
1
+ # Canvas and WebGL Testing
2
+
3
+ > **When to use**: When your application renders content on `<canvas>` elements -- charts (Chart.js, D3), maps (Mapbox, Leaflet), games, image editors, WebGL visualizations, drawing tools, signature pads.
4
+ > **Prerequisites**: [core/assertions-and-waiting.md](assertions-and-waiting.md), [core/locators.md](locators.md)
5
+
6
+ ## Quick Reference
7
+
8
+ ```typescript
9
+ // Screenshot comparison — the primary strategy for canvas
10
+ await expect(page.locator('canvas#chart')).toHaveScreenshot('revenue-chart.png');
11
+
12
+ // Click at specific coordinates on canvas
13
+ await page.locator('canvas').click({ position: { x: 200, y: 150 } });
14
+
15
+ // Read canvas state via page.evaluate
16
+ const pixelColor = await page.evaluate(() => {
17
+ const canvas = document.querySelector('canvas') as HTMLCanvasElement;
18
+ const ctx = canvas.getContext('2d')!;
19
+ const pixel = ctx.getImageData(100, 100, 1, 1).data;
20
+ return { r: pixel[0], g: pixel[1], b: pixel[2], a: pixel[3] };
21
+ });
22
+ ```
23
+
24
+ ## Patterns
25
+
26
+ ### Screenshot Comparison (Visual Regression)
27
+
28
+ **Use when**: Verifying the visual output of canvas-rendered content -- charts, graphs, maps, drawings. This is the most reliable approach because canvas pixels are not queryable via DOM.
29
+ **Avoid when**: The canvas content is dynamic on every render (animations, timestamps). Use threshold or mask options.
30
+
31
+ **TypeScript**
32
+ ```typescript
33
+ import { test, expect } from '@playwright/test';
34
+
35
+ test('revenue chart renders correctly', async ({ page }) => {
36
+ await page.goto('/dashboard');
37
+
38
+ // Wait for the chart to finish rendering
39
+ await expect(page.locator('canvas#revenue-chart')).toBeVisible();
40
+
41
+ // Optionally wait for a loading indicator to disappear
42
+ await expect(page.getByTestId('chart-loading')).toBeHidden();
43
+
44
+ // Screenshot comparison against a baseline
45
+ await expect(page.locator('canvas#revenue-chart')).toHaveScreenshot('revenue-chart.png', {
46
+ maxDiffPixelRatio: 0.01, // Allow 1% pixel difference for anti-aliasing
47
+ });
48
+ });
49
+
50
+ test('chart updates after date range change', async ({ page }) => {
51
+ await page.goto('/dashboard');
52
+
53
+ // Change date range
54
+ await page.getByRole('combobox', { name: 'Date range' }).selectOption('Last 30 days');
55
+
56
+ // Wait for chart to re-render
57
+ await expect(page.getByTestId('chart-loading')).toBeHidden();
58
+
59
+ // Compare against a different baseline
60
+ await expect(page.locator('canvas#revenue-chart')).toHaveScreenshot('revenue-chart-30d.png', {
61
+ maxDiffPixelRatio: 0.01,
62
+ });
63
+ });
64
+
65
+ test('mask dynamic areas in canvas screenshot', async ({ page }) => {
66
+ await page.goto('/dashboard');
67
+
68
+ await expect(page.locator('canvas#chart')).toHaveScreenshot('chart-stable.png', {
69
+ // Mask the timestamp area that changes every render
70
+ mask: [page.locator('.chart-timestamp')],
71
+ maxDiffPixelRatio: 0.02,
72
+ });
73
+ });
74
+ ```
75
+
76
+ **JavaScript**
77
+ ```javascript
78
+ const { test, expect } = require('@playwright/test');
79
+
80
+ test('revenue chart renders correctly', async ({ page }) => {
81
+ await page.goto('/dashboard');
82
+ await expect(page.locator('canvas#revenue-chart')).toBeVisible();
83
+ await expect(page.getByTestId('chart-loading')).toBeHidden();
84
+
85
+ await expect(page.locator('canvas#revenue-chart')).toHaveScreenshot('revenue-chart.png', {
86
+ maxDiffPixelRatio: 0.01,
87
+ });
88
+ });
89
+
90
+ test('chart updates after date range change', async ({ page }) => {
91
+ await page.goto('/dashboard');
92
+ await page.getByRole('combobox', { name: 'Date range' }).selectOption('Last 30 days');
93
+ await expect(page.getByTestId('chart-loading')).toBeHidden();
94
+
95
+ await expect(page.locator('canvas#revenue-chart')).toHaveScreenshot('revenue-chart-30d.png', {
96
+ maxDiffPixelRatio: 0.01,
97
+ });
98
+ });
99
+ ```
100
+
101
+ ### Interacting with Canvas via Coordinates
102
+
103
+ **Use when**: Testing user interactions on canvas -- clicking chart data points, dragging on a drawing tool, selecting map regions.
104
+ **Avoid when**: The element has an accessible DOM overlay (many chart libraries render tooltips as HTML). Interact with the overlay instead.
105
+
106
+ **TypeScript**
107
+ ```typescript
108
+ import { test, expect } from '@playwright/test';
109
+
110
+ test('click on chart data point shows tooltip', async ({ page }) => {
111
+ await page.goto('/analytics');
112
+
113
+ const canvas = page.locator('canvas#line-chart');
114
+ await expect(canvas).toBeVisible();
115
+
116
+ // Click at a specific coordinate on the canvas
117
+ await canvas.click({ position: { x: 200, y: 100 } });
118
+
119
+ // Tooltip appears as an HTML overlay (most chart libraries)
120
+ await expect(page.getByTestId('chart-tooltip')).toContainText('Revenue: $12,400');
121
+ });
122
+
123
+ test('draw a line on the canvas', async ({ page }) => {
124
+ await page.goto('/drawing-tool');
125
+
126
+ const canvas = page.locator('canvas#drawing-area');
127
+
128
+ // Simulate a drag to draw a line
129
+ await canvas.hover({ position: { x: 50, y: 50 } });
130
+ await page.mouse.down();
131
+ await page.mouse.move(200, 200, { steps: 10 }); // Smooth drag
132
+ await page.mouse.up();
133
+
134
+ // Verify via screenshot
135
+ await expect(canvas).toHaveScreenshot('drawn-line.png');
136
+ });
137
+
138
+ test('pinch-to-zoom on a map canvas', async ({ page }) => {
139
+ await page.goto('/map');
140
+
141
+ const canvas = page.locator('canvas#map');
142
+
143
+ // Scroll to zoom (common in map libraries)
144
+ await canvas.hover({ position: { x: 300, y: 300 } });
145
+ await page.mouse.wheel(0, -500); // Scroll up = zoom in
146
+
147
+ // Verify zoom level changed
148
+ await expect(page.getByTestId('zoom-level')).toHaveText('Zoom: 12');
149
+ });
150
+ ```
151
+
152
+ **JavaScript**
153
+ ```javascript
154
+ const { test, expect } = require('@playwright/test');
155
+
156
+ test('click on chart data point shows tooltip', async ({ page }) => {
157
+ await page.goto('/analytics');
158
+
159
+ const canvas = page.locator('canvas#line-chart');
160
+ await expect(canvas).toBeVisible();
161
+
162
+ await canvas.click({ position: { x: 200, y: 100 } });
163
+ await expect(page.getByTestId('chart-tooltip')).toContainText('Revenue: $12,400');
164
+ });
165
+
166
+ test('draw a line on the canvas', async ({ page }) => {
167
+ await page.goto('/drawing-tool');
168
+
169
+ const canvas = page.locator('canvas#drawing-area');
170
+ await canvas.hover({ position: { x: 50, y: 50 } });
171
+ await page.mouse.down();
172
+ await page.mouse.move(200, 200, { steps: 10 });
173
+ await page.mouse.up();
174
+
175
+ await expect(canvas).toHaveScreenshot('drawn-line.png');
176
+ });
177
+ ```
178
+
179
+ ### Canvas API Testing via `page.evaluate()`
180
+
181
+ **Use when**: You need to inspect canvas pixel data, read the rendering context state, or verify programmatic canvas operations.
182
+ **Avoid when**: A screenshot comparison is sufficient. Pixel-level assertions are brittle.
183
+
184
+ **TypeScript**
185
+ ```typescript
186
+ import { test, expect } from '@playwright/test';
187
+
188
+ test('verify specific pixel color on canvas', async ({ page }) => {
189
+ await page.goto('/color-picker');
190
+
191
+ // Click the red swatch
192
+ await page.getByRole('button', { name: 'Red' }).click();
193
+
194
+ // Read the pixel color at the canvas center
195
+ const color = await page.evaluate(() => {
196
+ const canvas = document.querySelector('canvas#preview') as HTMLCanvasElement;
197
+ const ctx = canvas.getContext('2d')!;
198
+ const centerX = Math.floor(canvas.width / 2);
199
+ const centerY = Math.floor(canvas.height / 2);
200
+ const pixel = ctx.getImageData(centerX, centerY, 1, 1).data;
201
+ return { r: pixel[0], g: pixel[1], b: pixel[2], a: pixel[3] };
202
+ });
203
+
204
+ expect(color.r).toBeGreaterThan(200); // Red channel high
205
+ expect(color.g).toBeLessThan(50); // Green channel low
206
+ expect(color.b).toBeLessThan(50); // Blue channel low
207
+ });
208
+
209
+ test('verify canvas dimensions match expected size', async ({ page }) => {
210
+ await page.goto('/editor');
211
+
212
+ const dimensions = await page.evaluate(() => {
213
+ const canvas = document.querySelector('canvas#main') as HTMLCanvasElement;
214
+ return { width: canvas.width, height: canvas.height };
215
+ });
216
+
217
+ expect(dimensions).toEqual({ width: 1920, height: 1080 });
218
+ });
219
+
220
+ test('canvas has content (is not blank)', async ({ page }) => {
221
+ await page.goto('/chart');
222
+
223
+ // Wait for rendering
224
+ await expect(page.getByTestId('chart-loading')).toBeHidden();
225
+
226
+ const isBlank = await page.evaluate(() => {
227
+ const canvas = document.querySelector('canvas') as HTMLCanvasElement;
228
+ const ctx = canvas.getContext('2d')!;
229
+ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
230
+ // Check if all pixels are transparent (blank canvas)
231
+ return imageData.data.every((value, index) => {
232
+ return index % 4 === 3 ? value === 0 : true; // Check alpha channel
233
+ });
234
+ });
235
+
236
+ expect(isBlank).toBe(false);
237
+ });
238
+ ```
239
+
240
+ **JavaScript**
241
+ ```javascript
242
+ const { test, expect } = require('@playwright/test');
243
+
244
+ test('verify specific pixel color on canvas', async ({ page }) => {
245
+ await page.goto('/color-picker');
246
+ await page.getByRole('button', { name: 'Red' }).click();
247
+
248
+ const color = await page.evaluate(() => {
249
+ const canvas = document.querySelector('canvas#preview');
250
+ const ctx = canvas.getContext('2d');
251
+ const centerX = Math.floor(canvas.width / 2);
252
+ const centerY = Math.floor(canvas.height / 2);
253
+ const pixel = ctx.getImageData(centerX, centerY, 1, 1).data;
254
+ return { r: pixel[0], g: pixel[1], b: pixel[2], a: pixel[3] };
255
+ });
256
+
257
+ expect(color.r).toBeGreaterThan(200);
258
+ expect(color.g).toBeLessThan(50);
259
+ expect(color.b).toBeLessThan(50);
260
+ });
261
+
262
+ test('canvas has content (is not blank)', async ({ page }) => {
263
+ await page.goto('/chart');
264
+ await expect(page.getByTestId('chart-loading')).toBeHidden();
265
+
266
+ const isBlank = await page.evaluate(() => {
267
+ const canvas = document.querySelector('canvas');
268
+ const ctx = canvas.getContext('2d');
269
+ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
270
+ return imageData.data.every((value, index) => {
271
+ return index % 4 === 3 ? value === 0 : true;
272
+ });
273
+ });
274
+
275
+ expect(isBlank).toBe(false);
276
+ });
277
+ ```
278
+
279
+ ### WebGL Rendering Verification
280
+
281
+ **Use when**: Your app uses WebGL for 3D visualizations, data plots, or games.
282
+ **Avoid when**: The canvas uses 2D context only.
283
+
284
+ WebGL canvas cannot be read with `getImageData` from a 2D context. Use `toDataURL()` or screenshot comparison.
285
+
286
+ **TypeScript**
287
+ ```typescript
288
+ import { test, expect } from '@playwright/test';
289
+
290
+ test('WebGL scene renders a 3D model', async ({ page }) => {
291
+ await page.goto('/3d-viewer');
292
+
293
+ // Wait for WebGL to finish rendering
294
+ await page.waitForFunction(() => {
295
+ const canvas = document.querySelector('canvas#scene') as HTMLCanvasElement;
296
+ const gl = canvas.getContext('webgl2') || canvas.getContext('webgl');
297
+ return gl !== null;
298
+ });
299
+
300
+ // Give the renderer time to complete the first frame
301
+ await expect(page.getByTestId('render-status')).toHaveText('Ready');
302
+
303
+ // Screenshot comparison is the most reliable approach for WebGL
304
+ await expect(page.locator('canvas#scene')).toHaveScreenshot('3d-model.png', {
305
+ maxDiffPixelRatio: 0.02, // WebGL has more rendering variance
306
+ });
307
+ });
308
+
309
+ test('WebGL canvas is not blank', async ({ page }) => {
310
+ await page.goto('/3d-viewer');
311
+ await expect(page.getByTestId('render-status')).toHaveText('Ready');
312
+
313
+ // Check that the WebGL canvas has drawn something
314
+ const hasContent = await page.evaluate(() => {
315
+ const canvas = document.querySelector('canvas#scene') as HTMLCanvasElement;
316
+ // Convert canvas to data URL and check it's not a blank image
317
+ const dataUrl = canvas.toDataURL();
318
+ // A blank canvas produces a very short data URL
319
+ return dataUrl.length > 1000;
320
+ });
321
+
322
+ expect(hasContent).toBe(true);
323
+ });
324
+
325
+ test('rotate 3D model and verify new angle', async ({ page }) => {
326
+ await page.goto('/3d-viewer');
327
+ await expect(page.getByTestId('render-status')).toHaveText('Ready');
328
+
329
+ // Take baseline screenshot
330
+ const canvas = page.locator('canvas#scene');
331
+
332
+ // Drag to rotate
333
+ await canvas.hover({ position: { x: 300, y: 300 } });
334
+ await page.mouse.down();
335
+ await page.mouse.move(450, 300, { steps: 20 });
336
+ await page.mouse.up();
337
+
338
+ // Screenshot should differ from default angle
339
+ await expect(canvas).toHaveScreenshot('3d-model-rotated.png', {
340
+ maxDiffPixelRatio: 0.02,
341
+ });
342
+ });
343
+ ```
344
+
345
+ **JavaScript**
346
+ ```javascript
347
+ const { test, expect } = require('@playwright/test');
348
+
349
+ test('WebGL scene renders a 3D model', async ({ page }) => {
350
+ await page.goto('/3d-viewer');
351
+
352
+ await page.waitForFunction(() => {
353
+ const canvas = document.querySelector('canvas#scene');
354
+ const gl = canvas.getContext('webgl2') || canvas.getContext('webgl');
355
+ return gl !== null;
356
+ });
357
+
358
+ await expect(page.getByTestId('render-status')).toHaveText('Ready');
359
+ await expect(page.locator('canvas#scene')).toHaveScreenshot('3d-model.png', {
360
+ maxDiffPixelRatio: 0.02,
361
+ });
362
+ });
363
+
364
+ test('WebGL canvas is not blank', async ({ page }) => {
365
+ await page.goto('/3d-viewer');
366
+ await expect(page.getByTestId('render-status')).toHaveText('Ready');
367
+
368
+ const hasContent = await page.evaluate(() => {
369
+ const canvas = document.querySelector('canvas#scene');
370
+ const dataUrl = canvas.toDataURL();
371
+ return dataUrl.length > 1000;
372
+ });
373
+
374
+ expect(hasContent).toBe(true);
375
+ });
376
+ ```
377
+
378
+ ### Chart Library Testing Strategies
379
+
380
+ **Use when**: Testing Chart.js, D3, Recharts, Highcharts, or similar chart libraries.
381
+ **Avoid when**: Charts have full HTML/SVG DOM output (D3 with SVG). Use standard locators for SVG elements.
382
+
383
+ **TypeScript**
384
+ ```typescript
385
+ import { test, expect } from '@playwright/test';
386
+
387
+ test('bar chart shows correct number of bars (SVG-based chart)', async ({ page }) => {
388
+ await page.goto('/analytics');
389
+
390
+ // SVG-based charts (D3, Recharts) render DOM elements — use locators
391
+ const bars = page.locator('svg.chart rect.bar');
392
+ await expect(bars).toHaveCount(12); // 12 months
393
+
394
+ // Check a specific bar's aria-label or tooltip
395
+ await bars.nth(0).hover();
396
+ await expect(page.getByTestId('chart-tooltip')).toContainText('January: $8,200');
397
+ });
398
+
399
+ test('canvas-based chart displays data (Chart.js)', async ({ page }) => {
400
+ await page.goto('/analytics');
401
+
402
+ // Canvas charts — no DOM elements to query, use screenshot
403
+ await expect(page.getByTestId('chart-loading')).toBeHidden();
404
+ await expect(page.locator('canvas#monthly-chart')).toHaveScreenshot('monthly-chart.png');
405
+ });
406
+
407
+ test('chart legend toggles data series', async ({ page }) => {
408
+ await page.goto('/analytics');
409
+
410
+ // Click legend item (usually HTML, not canvas)
411
+ await page.getByRole('button', { name: 'Revenue' }).click();
412
+
413
+ // Revenue series hidden — chart should look different
414
+ await expect(page.locator('canvas#chart')).toHaveScreenshot('chart-no-revenue.png');
415
+
416
+ // Click again to re-enable
417
+ await page.getByRole('button', { name: 'Revenue' }).click();
418
+ await expect(page.locator('canvas#chart')).toHaveScreenshot('chart-with-revenue.png');
419
+ });
420
+
421
+ test('export chart as image', async ({ page }) => {
422
+ await page.goto('/analytics');
423
+
424
+ // Many chart libraries offer "download as PNG"
425
+ const downloadPromise = page.waitForEvent('download');
426
+ await page.getByRole('button', { name: 'Export as PNG' }).click();
427
+ const download = await downloadPromise;
428
+
429
+ expect(download.suggestedFilename()).toMatch(/chart.*\.png$/);
430
+ });
431
+ ```
432
+
433
+ **JavaScript**
434
+ ```javascript
435
+ const { test, expect } = require('@playwright/test');
436
+
437
+ test('bar chart shows correct number of bars (SVG-based)', async ({ page }) => {
438
+ await page.goto('/analytics');
439
+
440
+ const bars = page.locator('svg.chart rect.bar');
441
+ await expect(bars).toHaveCount(12);
442
+
443
+ await bars.nth(0).hover();
444
+ await expect(page.getByTestId('chart-tooltip')).toContainText('January: $8,200');
445
+ });
446
+
447
+ test('canvas-based chart displays data', async ({ page }) => {
448
+ await page.goto('/analytics');
449
+ await expect(page.getByTestId('chart-loading')).toBeHidden();
450
+ await expect(page.locator('canvas#monthly-chart')).toHaveScreenshot('monthly-chart.png');
451
+ });
452
+ ```
453
+
454
+ ## Decision Guide
455
+
456
+ | Scenario | Best Approach | Why |
457
+ |---|---|---|
458
+ | Verify chart looks correct | `toHaveScreenshot()` on canvas element | Canvas pixels are not DOM; screenshot is the source of truth |
459
+ | Click a data point on chart | `canvas.click({ position: { x, y } })` | Canvas does not have clickable child elements |
460
+ | Verify canvas is not blank | `page.evaluate` + `getImageData` or `toDataURL` | Quick programmatic check without baseline image |
461
+ | Test SVG-based chart (D3) | Standard locators (`svg rect`, `svg path`) | SVG elements are in the DOM; use locator queries |
462
+ | Read specific pixel color | `page.evaluate` + `getImageData` | Direct access to pixel data |
463
+ | Test WebGL rendering | `toHaveScreenshot()` with higher `maxDiffPixelRatio` | WebGL has rendering variance; pixel assertions are unreliable |
464
+ | Test canvas drag/draw | `mouse.down()` + `mouse.move()` + `mouse.up()` | Simulates real drawing interactions |
465
+ | Chart tooltip after hover | `canvas.hover({ position })` then assert tooltip DOM | Tooltips are usually HTML overlays |
466
+
467
+ ## Anti-Patterns
468
+
469
+ | Don't Do This | Problem | Do This Instead |
470
+ |---|---|---|
471
+ | `page.getByRole('button')` inside a canvas | Canvas content has no DOM elements | Use `canvas.click({ position })` for coordinates |
472
+ | Assert pixel colors with exact RGB values | Anti-aliasing and GPU differences cause 1-2 value variance | Use ranges (`toBeGreaterThan(200)`) or `toHaveScreenshot` |
473
+ | Skip `maxDiffPixelRatio` in canvas screenshots | Different GPUs and OS versions render slightly differently | Set `maxDiffPixelRatio: 0.01` to `0.02` |
474
+ | `waitForTimeout` to wait for chart render | Arbitrary; too slow or too fast | Wait for a loading indicator to disappear or use `waitForFunction` |
475
+ | Read WebGL pixels via 2D context `getImageData` | WebGL and 2D contexts are mutually exclusive on the same canvas | Use `canvas.toDataURL()` or screenshot comparison |
476
+ | Take full-page screenshots for canvas tests | Captures unrelated content; more brittle baselines | Scope screenshot to `page.locator('canvas#specific')` |
477
+
478
+ ## Troubleshooting
479
+
480
+ | Symptom | Cause | Fix |
481
+ |---|---|---|
482
+ | Screenshot baseline always differs | GPU rendering differences across CI and local machine | Use Docker with consistent GPU settings, or increase `maxDiffPixelRatio` |
483
+ | Canvas click at coordinates hits wrong element | Coordinates are relative to element, but viewport changed | Use `position` relative to the canvas element, not the page |
484
+ | `getImageData` returns all transparent pixels | Canvas has not finished rendering when evaluated | Wait for a render-complete signal or use `waitForFunction` |
485
+ | `toDataURL` throws `SecurityError` | Canvas is tainted by cross-origin image | Serve images from the same origin or use CORS headers |
486
+ | WebGL context is null | Browser does not support WebGL or it is disabled in CI | Use `--enable-webgl` flag or run tests on a GPU-capable CI runner |
487
+ | Screenshot test passes locally, fails in CI | Different font rendering, DPI, or OS | Pin the Docker image, use `fonts` config, or increase threshold |
488
+
489
+ ## Related
490
+
491
+ - [core/assertions-and-waiting.md](assertions-and-waiting.md) -- auto-retrying assertions and visual comparison options
492
+ - [core/configuration.md](configuration.md) -- configure screenshot thresholds and update baselines
493
+ - [core/iframes-and-shadow-dom.md](iframes-and-shadow-dom.md) -- canvas elements inside iframes
494
+ - [core/debugging.md](debugging.md) -- debugging visual regression failures with trace viewer