/add-e2e-selectors
Add reliable @grafana/e2e-selectors to interactive elements and key containers in the Grafana frontend. Use when adding e2e selectors, data-testid attributes, or test selectors to React components, when a file or component lacks selectors for testing, or when asked to make
$ npx -y skills add grafana/grafana --skill add-e2e-selectors --agent claude-codeHow 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
/add-e2e-selectors
Context preview
The summary Claude sees to decide when to auto-load this skill.
Add reliable @grafana/e2e-selectors to interactive elements and key containers in the Grafana frontend. Use when adding e2e selectors, data-testid attributes, or test selectors to React components, when a file or component lacks selectors for testing, or when asked to make
SKILL.md
add-e2e-selectors.SKILL.mdname: add-e2e-selectors
description: Add reliable @grafana/e2e-selectors to interactive elements and key containers in the Grafana frontend. Use when adding e2e selectors, data-testid attributes, or test selectors to React components, when a file or component lacks selectors for testing, or when asked to make elements testable. Also use when given a Grafana Pathfinder interactive guide (a guide directory or content.json) to audit or fix — it extracts the guide's reftarget selectors, identifies weak ones, and fixes them at the source in Grafana's JSX. Defines versioned selectors in the e2e-selectors package and wires data-testid into JSX. Accepts a file path, a list of targets, a directory, a pathfinder guide, or the current/open file.
Add e2e-selectors
Find interactive elements and key containers in the Grafana frontend that lack a stable test selector, define a **versioned** selector in the `@grafana/e2e-selectors` package, and wire it into the JSX as `data-testid`. This encodes the package's layout (`pages` vs `components`), its semver versioning scheme, and its strict reuse / never-delete rules so selectors are added correctly and don't break plugin end-to-end tests.
Resolve the target
Interpret the argument to decide scope:
- **A file path** → only that file.
- **A list of files or described elements** → each named target, one by one.
- **A directory** → all `.tsx` files under it.
- **A Grafana Pathfinder interactive guide** — a guide directory or its `content.json` (e.g.
under `grafana-pathfinder-app/src/bundled-interactives/`) → see "Pathfinder guide as target".
- **"current file" / "open file" / no path but a file is open** → the open file.
- **No argument and no open file** → ask for a target; never scan the whole frontend.
Pathfinder guide as target
Interactive guides drive Grafana's UI through CSS selectors (`reftarget` fields), so a weak selector breaks a guide the same way it breaks a test. Process the guide, then fix the weak targets at the source:
1. Extract targets: `grep -o '"reftarget": *"[^"]*"' <guide>/content.json` 2. Skip navigation targets — blocks with `"action": "navigate"` or URL-shaped values (starting with `/` or `http`). 3. Classify the rest. **Strong** (leave alone): `data-testid`/`data-cy`-based selectors and `grafana:` / `{grafana:...}` tokens. **Weak** (fix): text matching (`:contains`, `:text`), `aria-label` / `placeholder` / `title` attributes, `href`, bare-id compounds, and positional selectors (`:nth-of-type`, `:nth-child`, `:nth-match`). 4. Locate the JSX in this repo rendering each weak target (search by id, testid fragment, button text, aria-label). Some targets are not fixable here — external npm packages (`@grafana/plugin-ui`, `@grafana/prometheus`), Monaco editor internals, instance data (dashboard titles, datasource names) — report those instead of forcing a change. 5. Run Steps 1–6 below for the located elements. Updating the guide itself is out of scope — include a weak-selector → new-selector mapping in your summary so the guide author can adopt them.
Step 1 — Find the version key
All new selectors added in this run use a single version key: the current `main` version with `-pre` and build tags stripped.
grep -m1 '"version"' package.json
`13.2.0-pre` → use `'13.2.0'`. On a release branch, read main's copy instead (`git show main:package.json | grep -m1 '"version"'` — the local `main` ref can be stale, so fetch first if in doubt). If you know the change will be backported, use the lowest release version instead. Never hardcode — always compute it.
Step 2 — Identify elements
Target these in the file:
- **Interactive controls** a test would click or type into: `button`, `input`, `select`,
`textarea`, `a`, links, toggles/switches, checkboxes, radios, menu items, tabs, and the grafana-ui components that render them (`Button`, `IconButton`, `Input`, `Select`, `Switch`, `Checkbox`, `Tab`, etc.).
- **Key containers** tests scope queries to: modals, panels, page sections, dialogs.
Skip any element that already has a `data-testid` or an existing selector — don't duplicate.
**Exception — a static testid on a repeated item.** A hardcoded literal inside a `.map()` (every card rendering the same `data-testid="data-source-card"`) identifies nothing: consumers are forced into text matching or positional hacks to pick one item, so it's as weak as no selector at all. Migrate it:
1. Define a parameterized selector keyed by a stable per-item value. 2. Preserve the legacy literal as the `MIN_GRAFANA_VERSION` entry so version-resolved consumers keep working against older Grafana, keeping the signature compatible:
dataSourceCard: {
'13.2.0': (name: string) => `data-testid data source card ${name}`,
[MIN_GRAFANA_VERSION]: (_name: string) => 'data-source-card',
},3. Update every in-repo usage of the old literal (jest and Playwright — grep `public/` and `e2e-playwright/`), and note in your summary that external code hardcoding the literal will need the same one-line update.
Loop rule (important)
Inside a `.map()` / list render, **do not** put a selector on each inner interactive element — that bloats the DOM and degrades render performance. Instead attach **one parameterized container selector to the repeated row/wrapper**, keyed by a unique value. Tests then scope their queries within the matched row.
Canonical example — `public/app/features/browse-dashboards/components/DashboardsTree.tsx`:
<div
key={key}
{...rowProps}
data-testid={selectors.pages.BrowseDashboards.table.row(
'title' in dashboardItem ? dashboardItem.title : dashboardItem.uid
)}
>
{row.cells.map((cell) => /* inner cells get NO individual selector */)}
</div>Step 3 — Reuse check (never duplicate)
Before defining anything, search the package for an existing selector covering this UI:
grep -rn "<keyword>" packages/grafan
Read more
name: add-e2e-selectors description: Add reliable @grafana/e2e-selectors to interactive elements and key containers in the Grafana frontend. Use when adding e2e selectors, data-testid attributes, or test selectors to React components, when a file or component lacks selectors for testing, or when asked to make elements testable. Also use when given a Grafana Pathfinder interactive guide (a guide directory or content.json) to audit or fix — it extracts the guide's reftarget selectors, identifies weak ones, and fixes them at the source in Grafana's JSX. Defines versioned selectors in the e2e-selectors package and wires data-testid into JSX. Accepts a file path, a list of targets, a directory, a pathfinder guide, or the current/open file.
Add e2e-selectors
Find interactive elements and key containers in the Grafana frontend that lack a stable test selector, define a **versioned** selector in the `@grafana/e2e-selectors` package, and wire it into the JSX as `data-testid`. This encodes the package's layout (`pages` vs `components`), its semver versioning scheme, and its strict reuse / never-delete rules so selectors are added correctly and don't break plugin end-to-end tests.
Resolve the target
Interpret the argument to decide scope:
- **A file path** → only that file.
- **A list of files or described elements** → each named target, one by one.
- **A directory** → all `.tsx` files under it.
- **A Grafana Pathfinder interactive guide** — a guide directory or its `content.json` (e.g.
under `grafana-pathfinder-app/src/bundled-interactives/`) → see "Pathfinder guide as target".
- **"current file" / "open file" / no path but a file is open** → the open file.
- **No argument and no open file** → ask for a target; never scan the whole frontend.
Pathfinder guide as target
Interactive guides drive Grafana's UI through CSS selectors (`reftarget` fields), so a weak selector breaks a guide the same way it breaks a test. Process the guide, then fix the weak targets at the source:
1. Extract targets: `grep -o '"reftarget": *"[^"]*"' <guide>/content.json` 2. Skip navigation targets — blocks with `"action": "navigate"` or URL-shaped values (starting with `/` or `http`). 3. Classify the rest. **Strong** (leave alone): `data-testid`/`data-cy`-based selectors and `grafana:` / `{grafana:...}` tokens. **Weak** (fix): text matching (`:contains`, `:text`), `aria-label` / `placeholder` / `title` attributes, `href`, bare-id compounds, and positional selectors (`:nth-of-type`, `:nth-child`, `:nth-match`). 4. Locate the JSX in this repo rendering each weak target (search by id, testid fragment, button text, aria-label). Some targets are not fixable here — external npm packages (`@grafana/plugin-ui`, `@grafana/prometheus`), Monaco editor internals, instance data (dashboard titles, datasource names) — report those instead of forcing a change. 5. Run Steps 1–6 below for the located elements. Updating the guide itself is out of scope — include a weak-selector → new-selector mapping in your summary so the guide author can adopt them.
Step 1 — Find the version key
All new selectors added in this run use a single version key: the current `main` version with `-pre` and build tags stripped.
grep -m1 '"version"' package.json
`13.2.0-pre` → use `'13.2.0'`. On a release branch, read main's copy instead (`git show main:package.json | grep -m1 '"version"'` — the local `main` ref can be stale, so fetch first if in doubt). If you know the change will be backported, use the lowest release version instead. Never hardcode — always compute it.
Step 2 — Identify elements
Target these in the file:
- **Interactive controls** a test would click or type into: `button`, `input`, `select`,
`textarea`, `a`, links, toggles/switches, checkboxes, radios, menu items, tabs, and the grafana-ui components that render them (`Button`, `IconButton`, `Input`, `Select`, `Switch`, `Checkbox`, `Tab`, etc.).
- **Key containers** tests scope queries to: modals, panels, page sections, dialogs.
Skip any element that already has a `data-testid` or an existing selector — don't duplicate.
**Exception — a static testid on a repeated item.** A hardcoded literal inside a `.map()` (every card rendering the same `data-testid="data-source-card"`) identifies nothing: consumers are forced into text matching or positional hacks to pick one item, so it's as weak as no selector at all. Migrate it:
1. Define a parameterized selector keyed by a stable per-item value. 2. Preserve the legacy literal as the `MIN_GRAFANA_VERSION` entry so version-resolved consumers keep working against older Grafana, keeping the signature compatible:
dataSourceCard: {
'13.2.0': (name: string) => `data-testid data source card ${name}`,
[MIN_GRAFANA_VERSION]: (_name: string) => 'data-source-card',
},3. Update every in-repo usage of the old literal (jest and Playwright — grep `public/` and `e2e-playwright/`), and note in your summary that external code hardcoding the literal will need the same one-line update.
Loop rule (important)
Inside a `.map()` / list render, **do not** put a selector on each inner interactive element — that bloats the DOM and degrades render performance. Instead attach **one parameterized container selector to the repeated row/wrapper**, keyed by a unique value. Tests then scope their queries within the matched row.
Canonical example — `public/app/features/browse-dashboards/components/DashboardsTree.tsx`:
<div
key={key}
{...rowProps}
data-testid={selectors.pages.BrowseDashboards.table.row(
'title' in dashboardItem ? dashboardItem.title : dashboardItem.uid
)}
>
{row.cells.map((cell) => /* inner cells get NO individual selector */)}
</div>Step 3 — Reuse check (never duplicate)
Before defining anything, search the package for an existing selector covering this UI:
grep -rn "<keyword>" packages/grafan
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.
Repo: grafana/grafana

