Snapshot-test Chart.js charts - render via headless Chromium / jsdom + canvas mock, capture canvas pixels via `canvas.toDataURL()` + image-diff, disable animations (`options.animation = false`) for stable snapshots, test tooltip + legend interactions. Use when a project renders Chart.js charts and needs regression coverage of their rendered output.
79
99%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Per the Chart.js docs, Chart.js renders to <canvas>, testable via
canvas.toDataURL() snapshot diff.
Per the Chart.js docs, the basic config object accepts options:
new Chart(ctx, {
type: 'bar',
data: {...},
options: {
animation: false, // disable for snapshots
responsive: false, // fix the canvas dimensions
plugins: {
legend: { display: true },
},
scales: { y: { beginAtZero: true } },
},
});Without animation: false, snapshots capture mid-animation frames
randomly.
import { test, expect } from '@playwright/test';
test('revenue bar chart matches snapshot', async ({ page }) => {
await page.goto('https://localhost:3000/dashboard');
// Wait for chart to render (no animation, just initial draw)
await page.waitForFunction(() => {
const canvas = document.querySelector('canvas#revenue-chart');
return canvas && canvas.toDataURL().length > 1000;
});
const canvas = page.locator('canvas#revenue-chart');
await expect(canvas).toHaveScreenshot('revenue-chart.png', {
maxDiffPixels: 50,
});
});maxDiffPixels allows for sub-pixel anti-aliasing variance across
runs.
For alternatives to the Playwright screenshot helper - a programmatic
toDataURL() diff, jsdom + canvas-mock unit tests, and non-visual
data-driven assertions - see
references/alternative-approaches.md.
test('tooltip shows data point value on hover', async ({ page }) => {
await page.goto('https://localhost:3000/dashboard');
await waitForChartReady(page);
// Hover over a known data point coordinate
await page.mouse.move(150, 200);
await page.waitForSelector('.chartjs-tooltip', { state: 'visible' });
const tooltipText = await page.locator('.chartjs-tooltip').textContent();
expect(tooltipText).toContain('Q1: 10');
});
test('legend click toggles dataset visibility', async ({ page }) => {
await page.goto('https://localhost:3000/dashboard');
await waitForChartReady(page);
await page.click('.chartjs-legend-item:has-text("Revenue")');
// Re-snapshot; revenue dataset should be hidden
await expect(page.locator('canvas#revenue-chart')).toHaveScreenshot(
'revenue-chart-revenue-hidden.png'
);
});Pin device pixel ratio so CI and dev machines produce identical snapshots:
// playwright.config.ts
use: {
deviceScaleFactor: 1, // pin to 1× for snapshot stability
}| Anti-pattern | Why it fails | Fix |
|---|---|---|
Skip animation: false | Snapshots flaky | Step 1 mandatory |
| Snapshot whole page | Layout shifts unrelated to chart break tests | Snapshot the canvas locator only (Step 2) |
maxDiffPixels: 0 | Anti-aliasing flake | Allow ~50 pixels (Step 2) |
| Test only static data | Dynamic data behavior untested | Snapshot per scenario (filter, range) |
| Skip DPR pinning | CI machines vs dev machines render differently | Step 4 |
d3-snapshot-tests.d3-snapshot-tests - sister
skill for SVG-based chartsvega-spec-validator - sister
skill for declarative-spec validation