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
76k2 skills
Install
$ npx -y skills add grafana/grafana --skill panel-testing-strategy --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/panel-testing-strategy

Context preview

The summary Claude sees to decide when to auto-load this skill.

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,

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

Write tests for Grafana visualization code that pass review on the first pass. Goals: **assert concrete behavior, not existence**; keep test descriptions honest; verify the test actually exercises the target code path; use the repo's data-frame and panel-props builders; snapshot HTML5 canvas based panels via the draw-call harness; and stabilize the known flake classes. The visualization codeowner paths are opted into the gating `check-frontend-test-coverage.yml` check, so coverage that drops fails CI.

Resolve the target

Interpret the argument to decide scope:

  • **A file path** → test that file (create or extend its co-located `*.test.ts(x)`).
  • **A directory / panel name** → the source files under it lacking meaningful coverage.
  • **"current file" / no path but a file is open** → the open file.
  • **No argument** → ask which panel/area; don't blanket-generate.

Prefer extending an existing co-located test file over adding a new one. Match the surrounding test file's imports and idiom.

Principle 1 — Where each test fits: the (inverted) testing diamond

Testing model, top to bottom:

  • **E2E** (pinnacle) — validate the system via real user flows; powerful but slow, so keep

it targeted and few.

  • **Unit** (base) — cheap, plentiful specs documenting behavior for logic/utils.
  • **Static analysis** (foundation) — lint + strong TypeScript interfaces.

Pick the layer that matches the job: logic/IO → unit; how pieces fit together → integration/visual; a key user journey → E2E. **Favour speed and feedback** — unit tests are cheap, so make them small and plentiful; reserve the expensive layers for what only they can cover. (Steps 1–4 below are the unit craft; Step 5 is E2E.)

Principle 2 — Assert real behavior, not existence

This is the bar reviewers hold every test to, at every layer. They reject tests that only prove a function ran. Never land these as the whole test:

expect(result).toBeDefined(); // ❌ proves nothing about correctness
expect(result).toBeInstanceOf(Foo); // ❌ (unless the type itself is the contract)
expect(() => fn(input)).not.toThrow(); // ❌ "didn't crash" is not a behavior
expect(result).toHaveLength(input.length); // ❌ if it just mirrors the input

Instead assert the **concrete computed value**, so a failure points at the real bug:

// diffperc: 10 -> 20 is a +100% change
const results = getDisplayValuesForCalcs(/* … */);
expect(results[0].numeric).toBe(100); // ✅ assert the math
expect(results[0].text).toBe('100%'); // (formatting is secondary)

If the function mostly delegates, assert the delegation with exact arguments (see Step 3).

**Expected values are literals, not recomputations.** Never derive the expected side by calling the code under test, a collaborator it calls internally, or by re-typing the production formula — the test then passes whenever the code and the expectation share the same bug, and comparing a value to _itself_ asserts nothing at all. Freeze the expected value as a literal, computed once by hand or captured from a known-good run:

// ❌ circular: `expected` is produced the same way the code produces its result
const expected = theme.visualization.getColorByName('red');
expect(dim.value()).toBe(expected);
// ❌ re-derives the production formula — a bug in the formula is copied into `expected`
const expected = TABLE.CELL_PADDING * 2 + theme.typography.fontSize * theme.typography.body.lineHeight;
expect(getDefaultRowHeight(theme, [])).toBe(expected);

// ✅ frozen literals — a change in the resolver or the formula now fails the test
expect(dim.value()).toBe('#F2495C');
expect(getDefaultRowHeight(theme, [])).toBe(34);

For values awkward to write by hand (projected coordinates, hashes), assert an **independent readback** rather than re-running the same path — e.g. project lng/lat, read it back in WGS84, and compare to the literal input — or freeze it with `toMatchInlineSnapshot`.

**Prove the assertion has teeth.** Before landing, mutate the asserted value (or the source it derives from) and confirm the test goes **red**. A test that stays green — because its expectation tracks the code, or checks a value against itself — is a tautology dressed as coverage. Make this a habit, not just the final Verify step.

Principle 3 — Authoring with AI: no slop tests

This skill exists so AI-proposed tests meet the bar above. The failure mode to avoid is the **slop test**:

  • **Unfocused** — a wide blast of assertions that doesn't preserve the intent of the code

under test.

  • **Verbose** — unnecessary steps/mocks for a simple goal; brittle to implementation

changes, and can silently mask real regressions.

  • **Limiting** — so many, or so coupled to implementation, that a later refactor breaks them

without telling you whether behavior actually broke. (Unreadable DOM snapshot tests are the classic example — never add them.)

Review AI output _thoroughly_ before opening a PR; expect to amend it for readability/maintainability. If reviewing the AI output costs more than writing the test by hand, write it by hand. A test is a specification a teammate — and future-you — must read easily; value refactoring for readability over a raw coverage percentage.

Principle 4 - Do not simply update failing tests to pass after changing behaviour, or adding a feature

When a test fails after updating functionality, behaviour or features, this is a warning that a regression was cau

Read more
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.

Get the whole plugin
Stats
76,176
Stars
14,535
Forks
Active
Maintenance
TypeScript
Language
AGPL-3.0
License
23m ago
Last commit
12y ago
Created

Repo: grafana/grafana