chartjs-snapshot-tests
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.
Install with skills.sh (any agent)
npx skills add testland/qa --skill chartjs-snapshot-testschartjs-snapshot-tests
Per the Chart.js docs (opens in new window), Chart.js renders to <canvas>, testable via canvas.toDataURL() snapshot diff.
When to use
Step 1 - Disable animations for stable snapshots
Per the Chart.js docs (opens in new window), 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.
Step 2 - Playwright canvas snapshot
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.
Step 3 - Tooltip + legend interaction
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'
);
});Step 4 - Multi-DPI handling
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-patterns
| 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 |
Limitations
References
chartjs-snapshot-tests - alternative approaches
View source (opens in new window)chartjs-snapshot-tests - alternative approaches
Secondary approaches beyond the core Playwright canvas snapshot (Step 2 in SKILL.md). Reach for these when the primary screenshot workflow does not fit.
Programmatic canvas dataURL diff
For finer control without Playwright's screenshot helper:
test('chart canvas data URL is stable', async ({ page }) => {
await page.goto('https://localhost:3000/dashboard');
await page.waitForFunction(() => /* render complete */);
const dataUrl = await page.evaluate(() => {
const canvas = document.querySelector('canvas#revenue-chart') as HTMLCanvasElement;
return canvas.toDataURL('image/png');
});
// Compare to baseline saved as PNG
const baseline = await readBaseline('revenue-chart.png');
const diff = imagePixelDiff(dataUrl, baseline);
expect(diff.diffRatio).toBeLessThan(0.005);
});jsdom + canvas-mock unit testing
For unit-test-speed feedback (no browser):
// jest.setup.js
import 'canvas'; // node-canvas packageimport { Chart } from 'chart.js/auto';
test('chart renders with expected dataset count', () => {
const canvas = document.createElement('canvas');
document.body.appendChild(canvas);
const chart = new Chart(canvas, {
type: 'bar',
data: { labels: ['Q1', 'Q2'], datasets: [{ data: [10, 20] }] },
options: { animation: false, responsive: false },
});
expect(chart.data.datasets).toHaveLength(1);
expect(chart.data.labels).toEqual(['Q1', 'Q2']);
});The canvas package (Node native) lets jsdom render Chart.js output without a browser. Use for fast assertions on dataset shape + config; rely on the core snapshot workflow for visual regression.
Data-driven assertion (without snapshot)
For non-visual assertions, query Chart.js internal state via the chart instance:
test('chart shows all 12 months', async ({ page }) => {
const labels = await page.evaluate(() => {
const chart = (window as any).Chart.getChart('revenue-chart');
return chart.data.labels;
});
expect(labels).toHaveLength(12);
});Related skills
d3-snapshot-tests
Snapshot-test D3.js charts - D3 generates SVG (not Canvas, per d3js.org getting-started); use `outerHTML` snapshot for static structure, `toHaveScreenshot` for rendered SVG; jsdom for headless render in unit tests; disable transitions for stable snapshots; per-element data-binding correctness tests. Use when a project renders charts with `d3.create('svg')` or D3 selections and the emitted SVG structure or update join needs regression coverage - including before a D3 major-version upgrade.
vega-spec-validator
Validate Vega + Vega-Lite specifications against the JSON Schema (vega.github.io/schema), test cross-engine compatibility (Vega-Lite compiles to Vega per the canonical compiler), and verify data-binding correctness. Pair with d3-snapshot-tests when Vega specs render to SVG; pair with chartjs-snapshot-tests when rendered to Canvas. Use when application code generates Vega/Vega-Lite JSON specs at runtime (BI builders, spec templating) and those specs must be proven valid and correctly encoded before render, or before a Vega major-version upgrade.