Skip to content
Testing
Skill

/panel-testing-strategy

Write unit and E2E tests for Grafana visualization panels and viz utilities to the conventions this repo expects. Use when adding, backfilling, or reviewing tests for panels (barchart, timeseries, table, xychart, heatmap, canvas, etc.), grafana-ui viz components (Table, uPlot,

From plugin
grafana
78k3 skills

Claude Code

$ npx -y skills add grafana/grafana --skill panel-testing-strategy --agent claude-code

SKILL.md

panel-testing-strategy.SKILL.md
name: panel-testing-strategy
description: Write unit and E2E tests for Grafana visualization panels and viz utilities to the conventions this repo expects. Use when adding, backfilling, or reviewing tests for panels (barchart, timeseries, table, xychart, heatmap, canvas, etc.), grafana-ui viz components (Table, uPlot, VizLegend, VizTooltip), or grafana-data viz utils; when a panel test only asserts "it rendered" or "it's defined"; when reviewing AI-generated panel tests for slop; or when a canvas/rendering test is flaky.

Panel testing strategy

This skill builds on **`frontend-testing-strategy`** — read that first for the general principles every Grafana frontend test is held to (the inverted testing diamond, asserting real behavior instead of existence, avoiding AI slop, verifying a test reaches its target branch, generic anti-flake rules, and the SDLC-phase gating). This skill covers what's specific to visualization code on top of that: data-frame/panel-prop builders, the canvas draw-call snapshot harness, panel accessibility and interaction-snapshot E2E, and canvas/uPlot-specific anti-flake rules. The visualization codeowner paths are opted into the gating `check-frontend-test-coverage.yml` check, so coverage that drops fails CI.

Step 1 — Set up data with the repo's builders

Build data frames with the `@grafana/data` builders — **pick one and don't mix** `toDataFrame` and `createDataFrame` in the same file:

import { createDataFrame, toDataFrame, arrayToDataFrame, FieldType, LoadingState } from '@grafana/data';

Use a **single canonical builder per file** with a `Partial<>` overrides object, rather than bespoke frames per test:

function makeFrame(overrides: Partial<Options> = {}) {
  /* … */
}

To render a panel component, use the shared panel-props builder instead of hand-rolling props:

import { getPanelProps } from '../test-utils'; // public/app/plugins/panel/test-utils.ts
render(<BarChartPanel {...getPanelProps(defaultOptions, { fieldConfig })} />);

> **Gotcha — field config.** A panel unit test must call `applyFieldOverrides` itself with a > `createFieldConfigRegistry`; the panel framework normally does this, so without it your > custom `fieldConfig.custom` never reaches the render and every case looks identical.

> **Gotcha — type inference.** If you're testing `guessFieldTypes` (or any inference), feed > **untyped** raw fields (`as unknown as DataFrameDTO`). `createDataFrame` pre-sets `type`, so > the function under test becomes a no-op and the test gives false confidence.

Step 2 — HTML5 canvas / rendering panels: use the draw-call snapshot harness

Panels that draw to canvas (timeseries, heatmap, xychart, timeline, piechart, sparkline) are tested by **capturing the ctx draw-call stream**, not by pixel-diffing. Follow the established harness:

// In the harness (public/app/plugins/panel/timeseries/TimeSeriesPanel.canvasTestUtils.tsx):
import {
  applyDefaultUPlotAxisMeasureTextMock,
  installCanvasPath2DShim,
  removeCanvasTransforms,
} from '@grafana/test-utils/canvas';

// In each *.canvas.test.tsx, mock grafana-ui's text measurement so layout is deterministic:
jest.mock('@grafana/ui/src/utils/measureText', () =>
  require('@grafana/test-utils/canvas').createGrafanaUiMeasureTextJestMock(() =>
    require('./TimeSeriesPanel.canvasTestUtils').getUPlotInstance()
  )
);
  • Split suites by concern: `*.lines.canvas.test.tsx`, `*.fills.…`, `*.annotations.…`,

`*.axisPlacement.…`, `*.axisRange.…` — each a focused `it.each` of cases.

  • Assert with the custom matcher: `expect(events).toMatchCanvasSnapshot(context, { width, height })`.
  • **Keep it deterministic** (this is where flake comes from): fixed `width`/`height`, UTC

timestamps (`Date.UTC(...)`, `timeZone: 'utc'`), and wait for the renderer to be ready before asserting — `await waitFor(() => expect(uPlotInstance?.status).toBe(1))` (a `waitFor` callback must **throw** to retry, so it needs `expect`, not a bare boolean).

Step 3 — E2E for interaction, accessibility, and interaction snapshots

The DataViz strategy is **unit-first**. Reserve Playwright for cross-component interaction and per-panel smoke coverage. When you do write E2E:

  • Add the selector to the **versioned `@grafana/e2e-selectors` package first**, wire

`data-testid` into the JSX, then query it (use the `add-e2e-selectors` skill).

  • Query by selector, never brittle CSS — in E2E `dashboardPage.getByGrafanaSelector(...)`,

in unit `screen.getByTestId(selectors.components...)`.

import { test, expect } from '@grafana/plugin-e2e';

test.describe('Panels test: BarChart render', { tag: ['@panels', '@barchart'] }, () => {
  test('renders without error', async ({ gotoDashboardPage, selectors }) => {
    const page = await gotoDashboardPage({ uid: DASHBOARD_UID }); // provisioned devenv dashboard
    await expect(page.getByGrafanaSelector(selectors.components.Panels.Panel.headerCornerInfo('error'))).toBeHidden();
  });
});

Accessibility — every panel gets an a11y check

**Every panel must have an E2E accessibility test.** Use the `scanForA11yViolations` fixture and the `toHaveNoA11yViolations()` matcher, in a `describe`/test tagged `@a11y`. Load the panel, wait for it to actually render (assert the panel title and the chart element are visible — an empty panel trivially passes), then scan:

test.describe('a11y', { tag: ['@a11y'] }, () => {
  test('run a11y report', async ({ gotoDashboardPage, scanForA11yViolations, selectors, page }) => {
    const dashboardPage = await gotoDashboardPage({
      uid: DASHBOARD_UID,
      queryParams: new URLSearchParams({ viewPanel: 'panel-4' }),
    });
    await expect(dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.title('…'))).toBeVisible();
    await expect(page.locator('.uplot')).toBeVisible(); // panel has drawn

    const report = await scanForA11yViolations({
      options: { runOnly: ['wcag2a', 'wcag2aa', 'wcag21a', '
Read more
@grafana
Ships withgrafana

The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.

Stars on GitHub

Momentum

stars in total
77.8k
Star history of grafana/grafana on GitHub, a chart by star-history.com

Other skills on grafana.

add-e2e-selectors

add-e2e-selectors

Add reliable @grafana/e2e-selectors to interactive elements and key containers in the Grafana frontend. Use when adding e2e…

@grafana@grafanaInstall Skill
frontend-testing-strategy

frontend-testing-strat…

Write and run unit and E2E tests for Grafana frontend code (React/TypeScript, any package or feature area) to the conventions…

@grafana@grafanaInstall Skill

More skills from other plugins.

api-review

api-review

Produce the "Playwright 1.xx API Review" artifact for a range of commits - the public API additions and the breaking or behavior…

@microsoft@microsoftInstall Skill
promptfoo-evals

promptfoo-evals

Write, refine, run, and QA promptfoo evaluation suites: promptfooconfig.yaml, prompts, providers, vars, tests, assertions,…

@promptfoo@promptfooInstall Skill
deepeval

deepeval

DeepEval evaluation workflow for AI agents and LLM applications. TRIGGER when the user wants to evaluate or improve an AI agent,…

@confident-ai@confident-aiInstall Skill
dev

dev

Development workflows for the playwright-cli repository. Use when the user asks about rolling dependencies, releasing, or other…

@microsoft@microsoftInstall Skill

Questions about panel-testing-strategy.

Which coding agents can use panel-testing-strategy?
panel-testing-strategy installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.