Skip to content
Testing
Skill

/frontend-testing-strategy

Write unit and E2E tests for Grafana frontend code (React/TypeScript, any package or feature area) to the conventions this repo expects. Use when adding, backfilling, or reviewing frontend tests; when a test only asserts "it rendered" or "it's defined"; when reviewing

From plugin
grafana
77k3 skills
Install
$ npx -y skills add grafana/grafana --skill frontend-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/frontend-testing-strategy

Context preview

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

Write unit and E2E tests for Grafana frontend code (React/TypeScript, any package or feature area) to the conventions this repo expects. Use when adding, backfilling, or reviewing frontend tests; when a test only asserts "it rendered" or "it's defined"; when reviewing

SKILL.md

frontend-testing-strategy.SKILL.md
name: frontend-testing-strategy
description: Write unit and E2E tests for Grafana frontend code (React/TypeScript, any package or feature area) to the conventions this repo expects. Use when adding, backfilling, or reviewing frontend tests; when a test only asserts "it rendered" or "it's defined"; when reviewing AI-generated tests for slop; or when a frontend test is flaky. For visualization panels and grafana-ui viz components specifically, also load the `panel-testing-strategy` skill.

Frontend testing strategy

Write tests for Grafana frontend 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; and stabilize the known flake classes. Several 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 / component / module 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 file/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.

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 2).

**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`.

The same "assert nothing" failure mode applies to what you choose to test, not only what you assert. A test earns its place only if you can name a code change it would catch — skip "renders without crashing" (unless there's a known crash bug), "initial value is empty string", or a boolean branch with no real logic behind it.

**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 addi

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,747
Stars
14,734
Forks
Active
Maintenance
TypeScript
Language
AGPL-3.0
License
1h ago
Last commit
12y ago
Created

Repo: grafana/grafana

Other skills on grafana.