CtrlK
BlogDocsLog inGet started
Tessl Logo

testland/chart-render-tests

Chart-render regression testing across the three chart-library families - Canvas (Chart.js: locator screenshot snapshot + `canvas.toDataURL()` diff with animations disabled), SVG (D3: `outerHTML` structural snapshot with generated-ID normalization + per-element data-binding tests), and declarative specs (Vega / Vega-Lite: JSON Schema validation + Vega-Lite → Vega compile test). Detects the family from package.json imports (chart.js / d3 / vega-lite), then applies the matching recipe; full per-library depth with citations in references/chartjs.md, references/d3.md, references/vega.md. Use when a dashboard or data product renders charts and their output needs regression coverage - before a chart-library major upgrade, after a theming change, or when runtime-generated Vega specs must be proven valid before render.

74

Quality

93%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Passed

No findings from the security scan

Overview
Quality
Evals
Security
Files

chartjs.mdreferences/

Chart.js (Canvas) snapshot tests

Chart.js reference for chart-render-tests: 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.

Per the Chart.js docs, Chart.js renders to <canvas>, testable via canvas.toDataURL() snapshot diff.

When to use

  • Dashboards or analytics products where chart accuracy is product surface.
  • Library upgrade gate (Chart.js v4 → v5 changes default styles).
  • Custom theme integration - verify the brand styling renders correctly.

Step 1 - Disable animations for stable snapshots

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.

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 chartjs-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-patternWhy it failsFix
Skip animation: falseSnapshots flakyStep 1 mandatory
Snapshot whole pageLayout shifts unrelated to chart break testsSnapshot the canvas locator only (Step 2)
maxDiffPixels: 0Anti-aliasing flakeAllow ~50 pixels (Step 2)
Test only static dataDynamic data behavior untestedSnapshot per scenario (filter, range)
Skip DPR pinningCI machines vs dev machines render differentlyStep 4

Limitations

  • Canvas snapshots can't catch SVG-only regressions (Chart.js is canvas-only); for SVG charts see d3.md.
  • Chart.js plugins (annotation, datalabels) may have separate init paths; verify they render before snapshotting.
  • Tooltips render in DOM (not canvas), so canvas snapshot misses them - test interactions separately (Step 3).

References

  • Chart.js docs - install, basic config, options
  • d3.md - sister reference for SVG-based charts
  • vega.md - sister reference for declarative-spec validation
  • node-canvas - github.com/Automattic/node-canvas (Node-native Canvas implementation for unit tests)

SKILL.md

tile.json