Skip to content
Testing
Skill

/frontend-testing-strategy

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

From plugin
grafana
78k3 skills

Claude Code

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

SKILL.md

frontend-testing-strategy.SKILL.md
name: frontend-testing-strategy
description: Write and run unit and E2E tests for Grafana frontend code (React/TypeScript, any package or feature area) to the conventions this repo expects. Use when running Playwright E2E tests; 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.

Running Playwright E2E tests

Use `pnpm e2e:playwright <spec>` with `GRAFANA_URL` unset by default. The harness starts its own lightweight Grafana server on port 3001; this is more efficient than starting a separate development instance. Do not start `make run` or another Grafana server just to run E2E tests.

Only set `GRAFANA_URL` to use a custom server when the task specifically needs one, such as debugging a particular running instance or testing a deployment. State that reason before using the override. Setting `GRAFANA_URL` disables the harness-managed server, so the target instance must already be running and accessible.

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, except for theme colors (see below).** 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:

// ❌ 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 literal — a change in the formula now fails the test
expect(getDefaultRowHeight(theme, [])).toBe(34);

**Theme colors are an explicit exception.** When testing that a component or consumer uses a theme color, derive the expected color from the theme supplied to that component (or its theme provider), rather than hardcoding the current hex/RGB value. Assert the specific semantic token or named theme color the element should use. Palette changes should not break these tests; using the wrong token should.

For example, the JSON highlighting assertions in `packages/grafana-ui/src/components/Table/TableNG/TableNG.test.tsx` should express the string and number token choices:

// Before: pins today's palette
expect(await screen.findByText('"us-east-1"')).toHaveStyle({ color: '#6CCF8E' });
expect(screen.getByText('3')).toHaveStyle({ color: '#FBAD37' });

// After: checks the intended theme tokens
expect(await screen.findByText('"us-east-1"')).toHaveStyle({ color: theme.components.codeEditor.string });
expect(screen.getByText('3')).toHaveStyle({ color: theme.components.codeEditor.number });

For theme-switching tests, assert against each supplied theme after rerendering. This exception does not apply when the color resolver, transformation, or palette definition itself is under test: keep independent literal expectations there rather than calling the implementation to produce its own expected result.

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 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
panel-testing-strategy

panel-testing-strategy

Write unit and E2E tests for Grafana visualization panels and viz utilities to the conventions this repo expects. Use when…

@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…

playwrighttoday100k
@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 frontend-testing-strategy.

Which coding agents can use frontend-testing-strategy?
frontend-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.