| Dimension | Shared | Only in A | Only in B | Overlap |
|---|---|---|---|---|
| Sections | 0 | 12 | 25 | 0% |
| Commands | 0 | 1 | 38 | 0% |
| Section tags | 3 | 2 | 7 | 25% |
What each file covers
Sections
0 shared · 12 only in A · 25 only in B- − dashboard-new-layouts E2E Tests — Agent Guide
- − Purpose
- − Page Objects Reference
- − Base class & constructor
- − How to Write a New Test
- − Conventions
- − Page objects
- − Selector scoping
- − Specs
- − Adding a method to a page object
- − Canonical Example
- − Migration Status
- + AGENTS.md
- + Project Overview
- + Principles
- + Comments
- + Human Review Gates
- + Commands
- + Build & Run
- + Test
- + Backend
- + Frontend
- + E2E
- + Lint & Format
- + Code Generation
- + Dev Environment
- + Architecture
- + Backend (`pkg/`)
- + Frontend (`public/app/`)
- + Shared Packages (`packages/`)
- + Backend Apps (`apps/`)
- + Plugin Workspaces
- + Key Notes
- + Cursor Cloud specific instructions
- + Prerequisites
- + Running services
- + Testing gotchas
Commands
0 shared · 1 only in A · 38 only in B- − yarn e2e:pw --project dashboard-new-layouts --reporter list --repeat-each=3 -- <spec-filename>
- + make run
- + make build-backend
- + yarn start
- + yarn build
- + go test -run TestName ./pkg/services/myservice/
- + make test-go-unit
- + make test-go-integration
- + yarn test path/to/file
- + yarn test -t "pattern"
- + yarn test -u
- + yarn e2e:playwright path/to/test.spec.ts
- + make lint-go
- + yarn lint
- + yarn lint:fix
- + yarn prettier:write
- + yarn typecheck
- + make gen-go
- + make gen-cue
- + make gen-apps
- + make swagger-gen
- + make gen-feature-toggles
- + make i18n-extract
- + make update-workspace
- + yarn install --immutable
- + make devenv sources=influxdb
- + make devenv-down
- + make lefthook-install
- + git push
- + yarn workspace @grafana-plugins/<name> dev
- + go.work
- + make devenv sources=postgres_tests,mysql_tests
- + make test-go-integration-postgres
- + node
- + yarn
- + jest
- + go.mod
- + yarn test
- + yarn jest --no-watch
Section tags
3 shared · 2 only in A · 7 only in B- − code-style
- − database
- + setup
- + build
- + lint-format
- + architecture
- + git-pr
- + dependencies
- + docs
- test
- testing-strategy
- agent-behaviour
Line diff
grafana/grafana · e2e-playwright/dashboard-new-layouts/AGENTS.md
@@ −1 @@
1# dashboard-new-layouts E2E Tests — Agent Guide
2
3## Purpose
4
5This suite contains Playwright E2E tests for the V2 dashboard layout system. Tests use **page objects** to wrap raw selector chains behind user-intent methods. The full rationale is in [`_page_objects_strategy.md`](./_page_objects_strategy.md).
6
7## Page Objects Reference
8
9All page objects live in `page-objects/`. Only the top-level ones (`Controls`, `Sidebar`, `Panels`, `Rows`, `Tabs`, `Canvas`) are re-exported from `page-objects/index.ts` — import those in specs. Sidebar panes (`Toolbar`, `AddOptions`, `DashboardOptions`, `PanelOptions`, `TabOptions`, `VariableOptions`, `ContentOutline`) and shared sub-options (`ConditionalRenderingOptions`, `RepeatOptions` under `sidebar/shared/`) are not exported; reach them via `sidebar.*` (e.g. `sidebar.toolbar`, `sidebar.panelOptions.repeatOptions`). Every page object extends the abstract `PageObject` base class (`PageObject.ts`), which holds the shared `page`, `dashboardPage`, `selectors`, and `components` dependencies as `protected` fields.
10
11| Class | File | UI Region | Key Methods / Getters |
12| ----------------------------- | ----------------------------------------------- | ------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
13| `PageObject` | `PageObject.ts` | _(abstract base — not used directly)_ | Shared constructor taking a `PageObjectArgs` object (`page`, `dashboardPage`, `selectors`, `components`) |
14| `Controls` | `Controls.ts` | Top nav bar (edit, save, timepicker, share, ...) and variable submenu | `getContainer()`, `enterEditMode()`, `exitEditMode()`, `saveDashboard(title?)`, `clickBackToDashboard()`, `openControlsMenu()`, `openShareSnapshotDrawer()`; `timeRange` sub-object: `set(from, to)`, `selectPreset(presetLabel)`; `variables` sub-object: `getLabel(variableLabel)`, `getInput(variableLabel)`, `setValue(variableLabel, text)`, `getDropdownTrigger(variableLabel)`, `openDropdown(variableLabel)`, `getOption(optionLabel)`, `selectOption(variableLabel, optionLabel)`, `deselectOption(variableLabel, optionLabel)`, `addFilter(variableLabel, [label, operator, value])` |
15| `Sidebar` | `sidebar/Sidebar.ts` | Whole sidebar region (toolbar + open pane) | `.toolbar`, `.addOptions`, `.dashboardOptions`, `.panelOptions`, `.tabOptions`, `.variableOptions`, `.contentOutline` sub-objects; `getContainer()`, `clickGoBackButton()`, `getDockToggle()`, `clickCloseButton()`, `clickDeleteButton({ confirm? })` |
16| `Toolbar` | `sidebar/Toolbar.ts` | Icon strip — accessed via `sidebar.toolbar` | `getButton(name)`, `clickButton(name)`, `getVisibilityToggle()` |
17| `AddOptions` | `sidebar/AddOptions.ts` | "Add" pane (default pane on new dashboards) — via `sidebar.addOptions` | `clickNewPanelButton()`, `clickAddTabButton()`, `clickNewVariableButton()` |
18| `ContentOutline` | `sidebar/ContentOutline.ts` | Content outline pane — via `sidebar.contentOutline` | `getTree()`, `clickItem(name)`, `toggleNode(name)` |
19| `DashboardOptions` | `sidebar/DashboardOptions.ts` | Dashboard options pane — via `sidebar.dashboardOptions` | `getTitleInput()`, `getDescriptionTextarea()`, `switchLayout(layoutType, { confirm? })` |
20| `PanelOptions` | `sidebar/PanelOptions.ts` | Panel options pane — via `sidebar.panelOptions` | `.conditionalRenderingOptions`, `.repeatOptions`; `getTitleInput()`, `setTitle(title)`, `getDescriptionTextarea()`, `toggleTransparentBackground()` |
21| `TabOptions` | `sidebar/TabOptions.ts` | Tab options pane — via `sidebar.tabOptions` | `.conditionalRenderingOptions`, `.repeatOptions` |
22| `ConditionalRenderingOptions` | `sidebar/shared/ConditionalRenderingOptions.ts` | Shared conditional rendering rules — via `*.conditionalRenderingOptions` | `selectVisibility('show' \| 'hide')`, `selectMatch('all' \| 'any')`, `addVariableRule(name, operator, value)`, `addTimeRangeRule(lessThan)` |
23| `RepeatOptions` | `sidebar/shared/RepeatOptions.ts` | Shared repeat options — via `*.repeatOptions` | `repeatByVariable(variableName)`, `disableRepeatByVariable()` |
24| `VariableOptions` | `sidebar/VariableOptions.ts` | Variable sidebar — via `sidebar.variableOptions` | `selectVariableType(type)`, `setName(name)`, `setLabel(label)`, `selectDisplay(displayLabel)`; type-specific sub-objects: `datasource.selectType(dsType)`, `datasource.setNameFilter(filter)`, `custom.openEditor()`, `custom.selectFormat(format)`, `custom.setValues(values)`, `custom.getPreviewOfValues()`, `custom.getPreviewTable()`, `custom.clickApplyButton()`, `groupby.selectDatasource(ds)`, `adhoc.selectDatasource(ds)`, `query.openEditor()`, `query.selectTargetDatasource(ds)`, `query.setTestDataQuery(query)`, `query.runQuery()`, `query.getPreviewOfValues()`, `query.clickApplyButton()`, `constant.setValue(value)`, `textbox.setValue(value)`, `interval.toggleAuto()` |
25| `Panels` | `Panels.ts` | The dashboard panels in the edit canvas | `getPanels(title, scope?)`, `getPanel(title, scope?)`, `getHeaders(title?, scope?)` — string titles match exactly, RegExp filters by text, no argument returns all headers; `getHeader(title, scope?)`, `getBodies()`, `selectByTitle(title \| RegExp \| Array<title \| RegExp>)`, `selectByIndex(index)`, `selectMenuItem(panelTitle, menuPath[])` |
26| `Rows` | `Rows.ts` | A row of a rows layout in the dashboard canvas | `getTitle(rowTitle)`, `getContent(rowTitle)` — content wrapper (grid or nested tabs) right after the row header |
27| `Tabs` | `Tabs.ts` | Tab bar of a tabs layout (top-level or nested in a row) | `getTitle(tabTitle, scope?)` — pass `rows.getContent(rowTitle)` as `scope` to look up a tab inside a specific row; `getContent(tabTitle)` — the layout container holding the tab's content; `select(tabTitle)` — click the tab title |
28| `Canvas` | `Canvas.ts` | Edit canvas add-actions strip (per grid, revealed on hover) | `getContainer()`, `addPanel(panelsContainer?)`, `addTab()`, `addRow()`, `groupPanels('row' \| 'tab', panelsContainer?)` — pass `panelsContainer` (e.g. `tabs.getContent(...)`, `rows.getContent(...)`) to target the add-actions strip of a nested grid |
29
30> The show/hide visibility toggle is a **Toolbar** control (`sidebar.toolbar.getVisibilityToggle()`), even though its selector lives under `components.Sidebar.*`. `Toolbar.getButton(name)` resolves buttons by accessible name, scoped to the sidebar container.
31
32> This table grows as specs are migrated — only methods needed by migrated specs exist.
33
34### Base class & constructor
35
36All page objects inherit from `PageObject`, which provides the shared constructor. It takes a single `PageObjectArgs` object:
37
38```typescript
39// page-objects/PageObject.ts
40export interface PageObjectArgs {
41 page: Page;
42 dashboardPage: DashboardPage;
43 selectors: E2ESelectorGroups;
44 components: Components;
45}
46
47export abstract class PageObject {
48 constructor({ page, dashboardPage, selectors, components }: PageObjectArgs) {
49 // assigned to protected fields
50 }
51}
52```
53
54Simple page objects (e.g. `Controls`, `Toolbar`) inherit the constructor directly — no override needed. Page objects that compose sub-objects (e.g. `Sidebar`) declare `constructor(args: PageObjectArgs)`, call `super(args)`, and pass the same `args` to their children.
55
56All four dependencies come from the Playwright test arguments:
57
58```typescript
59test('example', async ({ gotoDashboardPage, selectors, page, components }) => {
60 const dashboardPage = await gotoDashboardPage({ uid: 'some-uid' });
61 const controls = new Controls({ page, dashboardPage, selectors, components });
62 // ...
63});
64```
65
66## How to Write a New Test
67
681. **Identify which page objects you need.** Check the table above. If the interaction you need isn't covered, add the method to the appropriate page object — only what the new test requires.
69
702. **Scaffold the spec** following this structure:
71
72```typescript
73import { test, expect } from '@grafana/plugin-e2e';
74
75import { Controls, Sidebar } from './page-objects';
76
77test.describe(
78 'Feature name',
79 {
80 tag: ['@dashboards'],
81 },
82 () => {
83 test('describes the user-visible behavior', async ({ gotoDashboardPage, selectors, page, components }) => {
84 const dashboardPage = await gotoDashboardPage({ uid: 'dashboard-uid' });
85
86 const controls = new Controls({ page, dashboardPage, selectors, components });
87 const sidebar = new Sidebar({ page, dashboardPage, selectors, components });
88
89 await controls.enterEditMode();
90 // ... test body using page objects (the toolbar is reached via sidebar.toolbar)
91 });
92 }
93);
94```
95
961. **Verify locally:**
97
98```bash
99yarn e2e:pw --project dashboard-new-layouts --reporter list --repeat-each=3 -- <spec-filename>
100```
101
102## Conventions
103
104### Page objects
105
106- **Locator getters** (e.g. `getTitleInput()`) return a Playwright `Locator` — for elements that specs assert on (or both act on and assert on). The test owns the assertion — never the page object.
107- **Action methods** (e.g. `enterEditMode()`, `clickCloseButton()`) wrap interactions — multi-step flows or single clicks on act-only elements — and use `test.step()` so the HTML report shows named steps.
108- **When a spec needs both**, pair them: the action method delegates to the getter (see `Toolbar.getButton()` / `clickButton()`).
109- **No speculative methods.** Only add methods needed by the spec being migrated.
110- **Plural vs singular getters** (e.g. `getPanels()` / `getPanel()`): plural getters return every match — assert counts or narrow (`.first()`, `.nth()`) in the spec; singular getters return the first match.
111- **Scoped lookups**: getters with a `scope?: Locator` parameter search inside that container — pass `rows.getContent(...)` or `tabs.getContent(...)` to look up elements in a specific row or tab.
112- **No waits or retries inside page objects** unless the pre-refactor code had them. Keep `toPass()` retries, drag-and-drop, scroll logic, and `boundingBox()` in the spec or in `utils.ts`.
113
114### Selector scoping
115
116- **Scope lookups to the owning container.** A bare `page.getByRole(...)` searches the whole page and can match an unrelated element with the same role and name — if not today, then after an unrelated UI change. Elements that belong to a region with a page object must be looked up through that region's container: e.g. radio buttons in the sidebar are `sidebar.getContainer().getByRole('radio', { name: '...' })`, the same way `Toolbar.getButton()` scopes button names to `Sidebar.container`. Inside a page object, chain from the container selector (`this.dashboardPage.getByGrafanaSelector(this.selectors.components.Sidebar.container).getByRole(...)`).
117- **Portalled UI is the only exception.** Select/Combobox option lists, modals, tooltips, and toasts render in a portal at the document root, outside their logical parent, so they cannot be scoped to it. Anchor them to the portal's own root instead: `page.getByRole('listbox').getByRole('option', { name })` (see `RepeatOptions`), or `page.getByRole('dialog', { name: 'Delete panel?' })`. A bare `page.getByRole('option', ...)` with no anchor is still too broad.
118
119### Specs
120
121- **One raw `getByGrafanaSelector` is allowed** for one-off assertions that aren't reusable interactions (e.g. a breadcrumb check).
122- **Timing-sensitive mechanics stay inline** — `toPass()`, `mouse` sequences, `page.evaluate()`.
123- **Test setup stays in the spec** — API calls, dashboard provisioning, navigation via `gotoDashboardPage()`.
124- **Each spec is fully migrated or untouched.** No file should mix page-object calls and raw selectors for the same UI region.
125
126### Adding a method to a page object
127
1281. Find the raw selector chain in the spec you're migrating.
1292. Copy it into the appropriate page object class — mechanical extraction, no rewrites. New page objects must extend `PageObject` from `PageObject.ts`.
1303. For interactions (multi-step flows or single clicks on act-only elements), wrap in `test.step('Human-readable name', async () => { ... })`.
1314. For elements the spec asserts on, return a `Locator` (getter pattern, no `test.step` needed).
1325. Run `--repeat-each=3` on the migrated spec.
133
134## Canonical Example
135
136`dashboards-title-description.spec.ts` — the seed spec demonstrating the full pattern:
137
138```typescript
139await controls.enterEditMode();
140await sidebar.toolbar.clickButton('Options');
141
142const titleInput = sidebar.dashboardOptions.getTitleInput();
143await expect(titleInput).toHaveValue('Annotation filtering');
144
145const newTitle = 'New dashboard title';
146await titleInput.fill(newTitle);
147await expect(titleInput).toHaveValue(newTitle);
148```
149
150## Migration Status
151
152**26 of 30 specs migrated.** Non-migrated specs are listed first by descending selectors usage count (a rough proxy for migration effort). "Selectors usage count" is the number of times the spec accesses the `selectors` object (`selectors.components...`, `selectors.pages...`, etc.).
153
154| Spec | Status | Lines of code | Selectors usage count |
155| ----------------------------------------------------- | ----------- | ------------- | --------------------- |
156| `dashboard-group-panels.spec.ts` | Not started | 918 | 224 |
157| `dashboards-repeats-tabs-layout.spec.ts` | Not started | 482 | 73 |
158| `dashboards-panel-layouts.spec.ts` | Not started | 425 | 70 |
159| `dashboard-repeats-row-layout.spec.ts` | Not started | 551 | 61 |
160| `dashboards-repeats-custom-grid.spec.ts` | Migrated | — | — |
161| `dashboards-repeats-auto-grid.spec.ts` | Migrated | — | — |
162| `dashboards-title-description.spec.ts` | Migrated | — | — |
163| `dashboards-edit-panel-title-description.spec.ts` | Migrated | — | — |
164| `dashboards-edit-panel-transparent-bg.spec.ts` | Migrated | — | — |
165| `dashboard-mobile-sidebar.spec.ts` | Migrated | — | — |
166| `dashboard-hide-sidebar.spec.ts` | Migrated | — | — |
167| `dashboards-remove-panel.spec.ts` | Migrated | — | — |
168| `dashboard-duplicate-panel.spec.ts` | Migrated | — | — |
169| `dashboard-sidepane.spec.ts` | Migrated | — | — |
170| `dashboard-outline.spec.ts` | Migrated | — | — |
171| `dashboards-conditional-rendering.spec.ts` | Migrated | — | — |
172| `dashboards-add-panel.spec.ts` | Migrated | — | — |
173| `dashboards-edit-variables.spec.ts` | Migrated | — | — |
174| `dashboard-tabs-scroll.spec.ts` | Migrated | — | — |
175| `dashboards-repeats-snapshots.spec.ts` | Migrated | — | — |
176| `dashboards-move-panel.spec.ts` | Migrated | — | — |
177| `dashboard-conditional-rendering-load-change.spec.ts` | Migrated | — | — |
178| `dashboards-edit-custom-variables.spec.ts` | Migrated | — | — |
179| `dashboards-edit-query-variables.spec.ts` | Migrated | — | — |
180| `dashboard-keybindings.spec.ts` | Migrated | — | — |
181| `dashboards-edit-adhoc-variables.spec.ts` | Migrated | — | — |
182| `dashboards-edit-group-by-variables.spec.ts` | Migrated | — | — |
183| `dashboards-edit-datasource-variables.spec.ts` | Migrated | — | — |
184| `dashboard-url-syncing.spec.ts` | Migrated | — | — |
185| `dashboard-tabs-drag-drop.spec.ts` | Migrated | — | — |
186
187See [`_page_objects_strategy.md`](./_page_objects_strategy.md) for the full migration plan.
188
grafana/grafana · AGENTS.md
@@ +1 @@
1# AGENTS.md
2
3<!-- version: 2.0.0 -->
4
5This file provides guidance to AI agents when working with code in the Grafana repository.
6
7**Directory-scoped agent files exist for specialized areas — read them when working in those directories:**
8
9- `docs/AGENTS.md` — Documentation style guide (for work under `docs/`)
10- `public/app/features/alerting/unified/AGENTS.md` — Alerting squad patterns
11- `pkg/storage/unified/AGENTS.md` — Unified storage/search compatibility rules (for work under `pkg/storage/unified/`)
12- `public/app/core/journeys/AGENTS.md` — Critical User Journey instrumentation
13
14## Project Overview
15
16Grafana is a monitoring and observability platform. Go backend, TypeScript/React frontend, monorepo with Yarn workspaces (frontend) and Go workspaces (backend).
17
18## Principles
19
20- Follow existing patterns in the surrounding code
21- Write tests for new functionality
22- Keep changes focused — avoid over-engineering
23- Separate PRs for frontend and backend changes (deployed at different cadences)
24- Security: prevent XSS, SQL injection, command injection
25
26## Comments
27
28- Only add a comment when it explains **why** something is done or reveals non-obvious logic that a reader must know to safely change the code. If the code is self-explanatory, no comment is needed.
29- Never include links (Slack, GitHub, Jira, etc.) in code comments.
30
31## Human Review Gates
32
33Before running `git push`, stop and get explicit human approval. When changes are ready, show a summary of changes and wait for instruction. "Open a PR" in a task description is intent, not permission to push without review.
34
35## Commands
36
37### Build & Run
38
39```bash
40make run # Backend with hot reload (localhost:3000, admin/admin)
41make build-backend # Backend only
42yarn start # Frontend dev server (watches for changes)
43yarn build # Frontend production build
44```
45
46### Test
47
48```bash
49# Backend
50go test -run TestName ./pkg/services/myservice/ # Specific test
51make test-go-unit # All unit tests
52make test-go-integration # Integration tests
53
54# Frontend
55yarn test path/to/file # Specific file
56yarn test -t "pattern" # By name pattern
57yarn test -u # Update snapshots
58
59# E2E
60yarn e2e:playwright path/to/test.spec.ts # Specific test
61```
62
63### Lint & Format
64
65```bash
66make lint-go # Go linter
67yarn lint # ESLint
68yarn lint:fix # ESLint auto-fix
69yarn prettier:write # Prettier auto-format
70yarn typecheck # TypeScript check
71```
72
73### Code Generation
74
75```bash
76make gen-go # Wire DI (after changing service init)
77make gen-cue # CUE schemas (after changing kinds/)
78make gen-apps # App SDK apps
79make swagger-gen # OpenAPI/Swagger specs
80make gen-feature-toggles # Feature flags (pkg/services/featuremgmt/)
81make i18n-extract # i18n strings
82make update-workspace # Go workspace (after adding modules)
83```
84
85### Dev Environment
86
87```bash
88yarn install --immutable # Install frontend deps
89make devenv sources=influxdb # Start backing services
90make devenv-down # Stop backing services
91make lefthook-install # Pre-commit hooks
92```
93
94## Architecture
95
96### Backend (`pkg/`)
97
98| Directory | Purpose |
99| ----------------- | ----------------------------------------------------------- |
100| `pkg/api/` | HTTP API handlers and routes |
101| `pkg/services/` | Business logic by domain (alerting, dashboards, auth, etc.) |
102| `pkg/server/` | Server init and Wire DI setup (`wire.go`) |
103| `pkg/tsdb/` | Time series database query backends |
104| `pkg/plugins/` | Plugin system and loader |
105| `pkg/infra/` | Logging, metrics, database access |
106| `pkg/middleware/` | HTTP middleware |
107| `pkg/setting/` | Configuration management |
108
109**Patterns**: Wire DI (regenerate with `make gen-go`), services implement interfaces in same package, business logic in `pkg/services/<domain>/` not in API handlers, database via `sqlstore`, plugin communication via gRPC/protobuf.
110
111### Frontend (`public/app/`)
112
113| Directory | Purpose |
114| ---------------------- | ----------------------------------------------------- |
115| `public/app/core/` | Shared services, components, utilities |
116| `public/app/features/` | Feature code by domain (dashboard, alerting, explore) |
117| `public/app/plugins/` | Built-in plugins (many are Yarn workspaces) |
118| `public/app/types/` | TypeScript type definitions |
119| `public/app/store/` | Redux store configuration |
120
121**Patterns**: Redux Toolkit with slices (not old Redux), function components with hooks, Emotion CSS-in-JS via `useStyles2`, RTK Query for data fetching, React Testing Library for tests.
122
123### Shared Packages (`packages/`)
124
125`@grafana/data` (data structures), `@grafana/ui` (components), `@grafana/runtime` (runtime services), `@grafana/schema` (CUE-generated types), `@grafana/scenes` (dashboard framework).
126
127### Backend Apps (`apps/`)
128
129Standalone Go apps using Grafana App SDK: `apps/dashboard/`, `apps/folder/`, `apps/alerting/`.
130
131### Plugin Workspaces
132
133These built-in plugins require separate build steps: `azuremonitor`, `loki`, `grafana-testdata-datasource`.
134
135Build a specific plugin: `yarn workspace @grafana-plugins/<name> dev`
136
137## Key Notes
138
139- **Wire DI**: Backend service init changes require `make gen-go`. Wire catches circular deps at compile time.
140- **CUE schemas**: Dashboard/panel schemas in `kinds/` generate both Go and TS code via `make gen-cue`.
141- **Feature toggles**: Defined in `pkg/services/featuremgmt/`, auto-generate code. Run `make gen-feature-toggles` after changes.
142- **Go workspace**: Defined in `go.work`. Run `make update-workspace` when adding Go modules.
143- **Build tags**: `oss` (default), `enterprise`, `pro`.
144- **Config**: Defaults in `conf/defaults.ini`, overrides in `conf/custom.ini`.
145- **Database migrations**: Live in `pkg/services/sqlstore/migrations/`. Test with `make devenv sources=postgres_tests,mysql_tests` then `make test-go-integration-postgres`.
146- **CI sharding**: Backend tests use `SHARD`/`SHARDS` env vars for parallelization.
147- **Service compatibility**: Unified storage/search (`pkg/storage/unified/`) can be deployed as separate services at a different cadence than the Grafana API layer. Changes spanning API-layer callers and `pkg/storage/unified/` must be backwards compatible in both directions — see `pkg/storage/unified/AGENTS.md`.
148
149## Cursor Cloud specific instructions
150
151### Prerequisites
152
153- **Node.js** — version pinned in `.nvmrc` (check that file for the exact version). Installed via nvm and set as the nvm default. **PATH gotcha:** the infra injects `/exec-daemon/node` ahead of nvm, so the plain non-login shell may resolve `node` to an older version — check it satisfies the `engines` range in `package.json` (it does today, so builds/tests work), but it is not the pinned version. Login shells (tmux sessions, `bash -lc '...'`) get the pinned version because `~/.bashrc` prepends the nvm bin. Run `yarn` / `yarn start` / `jest` / webpack via a login shell (tmux or `bash -lc`) to use the pinned Node.
154- **Go** — version pinned in `go.mod` (check that file for the exact version), installed at `/usr/local/go` and symlinked to `/usr/local/bin/go`. The distro `/usr/bin/go` is older; `/usr/local/bin` wins in PATH so `go` resolves correctly. If `go.mod` bumps Go, reinstall a matching toolchain into `/usr/local/go`.
155- **Yarn** via corepack — version pinned by `package.json` `packageManager` (check that field for the exact version). Run `corepack enable` if `yarn` is not found. `.yarnrc.yml` sets `enableScripts: false`, so dependency build/lifecycle scripts are disabled by default.
156- **GCC** required for CGo/SQLite compilation of the backend.
157- Repos in this environment live under `/agent/repos/<repo>` (e.g. `/agent/repos/grafana`); this is a multi-repo workspace, not the single `~/grafana` layout described in `grafana-enterprise/AGENTS.md`.
158
159### Running services
160
161- **Backend**: `make run` — builds and starts Grafana backend with hot-reload (air) on `localhost:3000`. Default login: `admin`/`admin`. First build takes ~3 minutes due to debug symbols (`-gcflags all=-N -l`); subsequent hot-reload rebuilds are faster.
162- **Frontend**: `yarn start` — starts webpack dev server that watches for changes. The backend proxies to it. First compile takes ~45s.
163- No external databases required — Grafana uses embedded SQLite by default.
164
165### Testing gotchas
166
167- **Frontend tests**: The `yarn test` script includes `--watch` by default. Always use `yarn jest --no-watch` or add `--watchAll=false` to run tests once and exit.
168- **Backend tests**: Some packages (e.g. `pkg/api/`) have slow test compilation (~2 min) due to large dependency graphs. Use targeted test runs with `-run TestName` where possible.
169- All standard build/test/lint commands are documented in the Commands section above.
170
@@ −1 +1 @@
1−# dashboard-new-layouts E2E Tests — Agent Guide
1+# AGENTS.md
22
3−## Purpose
3+<!-- version: 2.0.0 -->
44
5−This suite contains Playwright E2E tests for the V2 dashboard layout system. Tests use **page objects** to wrap raw selector chains behind user-intent methods. The full rationale is in [`_page_objects_strategy.md`](./_page_objects_strategy.md).
5+This file provides guidance to AI agents when working with code in the Grafana repository.
66
7−## Page Objects Reference
7+**Directory-scoped agent files exist for specialized areas — read them when working in those directories:**
88
9−All page objects live in `page-objects/`. Only the top-level ones (`Controls`, `Sidebar`, `Panels`, `Rows`, `Tabs`, `Canvas`) are re-exported from `page-objects/index.ts` — import those in specs. Sidebar panes (`Toolbar`, `AddOptions`, `DashboardOptions`, `PanelOptions`, `TabOptions`, `VariableOptions`, `ContentOutline`) and shared sub-options (`ConditionalRenderingOptions`, `RepeatOptions` under `sidebar/shared/`) are not exported; reach them via `sidebar.*` (e.g. `sidebar.toolbar`, `sidebar.panelOptions.repeatOptions`). Every page object extends the abstract `PageObject` base class (`PageObject.ts`), which holds the shared `page`, `dashboardPage`, `selectors`, and `components` dependencies as `protected` fields.
9+- `docs/AGENTS.md` — Documentation style guide (for work under `docs/`)
10+- `public/app/features/alerting/unified/AGENTS.md` — Alerting squad patterns
11+- `pkg/storage/unified/AGENTS.md` — Unified storage/search compatibility rules (for work under `pkg/storage/unified/`)
12+- `public/app/core/journeys/AGENTS.md` — Critical User Journey instrumentation
1013
11−| Class | File | UI Region | Key Methods / Getters |
12−| ----------------------------- | ----------------------------------------------- | ------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
13−| `PageObject` | `PageObject.ts` | _(abstract base — not used directly)_ | Shared constructor taking a `PageObjectArgs` object (`page`, `dashboardPage`, `selectors`, `components`) |
14−| `Controls` | `Controls.ts` | Top nav bar (edit, save, timepicker, share, ...) and variable submenu | `getContainer()`, `enterEditMode()`, `exitEditMode()`, `saveDashboard(title?)`, `clickBackToDashboard()`, `openControlsMenu()`, `openShareSnapshotDrawer()`; `timeRange` sub-object: `set(from, to)`, `selectPreset(presetLabel)`; `variables` sub-object: `getLabel(variableLabel)`, `getInput(variableLabel)`, `setValue(variableLabel, text)`, `getDropdownTrigger(variableLabel)`, `openDropdown(variableLabel)`, `getOption(optionLabel)`, `selectOption(variableLabel, optionLabel)`, `deselectOption(variableLabel, optionLabel)`, `addFilter(variableLabel, [label, operator, value])` |
15−| `Sidebar` | `sidebar/Sidebar.ts` | Whole sidebar region (toolbar + open pane) | `.toolbar`, `.addOptions`, `.dashboardOptions`, `.panelOptions`, `.tabOptions`, `.variableOptions`, `.contentOutline` sub-objects; `getContainer()`, `clickGoBackButton()`, `getDockToggle()`, `clickCloseButton()`, `clickDeleteButton({ confirm? })` |
16−| `Toolbar` | `sidebar/Toolbar.ts` | Icon strip — accessed via `sidebar.toolbar` | `getButton(name)`, `clickButton(name)`, `getVisibilityToggle()` |
17−| `AddOptions` | `sidebar/AddOptions.ts` | "Add" pane (default pane on new dashboards) — via `sidebar.addOptions` | `clickNewPanelButton()`, `clickAddTabButton()`, `clickNewVariableButton()` |
18−| `ContentOutline` | `sidebar/ContentOutline.ts` | Content outline pane — via `sidebar.contentOutline` | `getTree()`, `clickItem(name)`, `toggleNode(name)` |
19−| `DashboardOptions` | `sidebar/DashboardOptions.ts` | Dashboard options pane — via `sidebar.dashboardOptions` | `getTitleInput()`, `getDescriptionTextarea()`, `switchLayout(layoutType, { confirm? })` |
20−| `PanelOptions` | `sidebar/PanelOptions.ts` | Panel options pane — via `sidebar.panelOptions` | `.conditionalRenderingOptions`, `.repeatOptions`; `getTitleInput()`, `setTitle(title)`, `getDescriptionTextarea()`, `toggleTransparentBackground()` |
21−| `TabOptions` | `sidebar/TabOptions.ts` | Tab options pane — via `sidebar.tabOptions` | `.conditionalRenderingOptions`, `.repeatOptions` |
22−| `ConditionalRenderingOptions` | `sidebar/shared/ConditionalRenderingOptions.ts` | Shared conditional rendering rules — via `*.conditionalRenderingOptions` | `selectVisibility('show' \| 'hide')`, `selectMatch('all' \| 'any')`, `addVariableRule(name, operator, value)`, `addTimeRangeRule(lessThan)` |
23−| `RepeatOptions` | `sidebar/shared/RepeatOptions.ts` | Shared repeat options — via `*.repeatOptions` | `repeatByVariable(variableName)`, `disableRepeatByVariable()` |
24−| `VariableOptions` | `sidebar/VariableOptions.ts` | Variable sidebar — via `sidebar.variableOptions` | `selectVariableType(type)`, `setName(name)`, `setLabel(label)`, `selectDisplay(displayLabel)`; type-specific sub-objects: `datasource.selectType(dsType)`, `datasource.setNameFilter(filter)`, `custom.openEditor()`, `custom.selectFormat(format)`, `custom.setValues(values)`, `custom.getPreviewOfValues()`, `custom.getPreviewTable()`, `custom.clickApplyButton()`, `groupby.selectDatasource(ds)`, `adhoc.selectDatasource(ds)`, `query.openEditor()`, `query.selectTargetDatasource(ds)`, `query.setTestDataQuery(query)`, `query.runQuery()`, `query.getPreviewOfValues()`, `query.clickApplyButton()`, `constant.setValue(value)`, `textbox.setValue(value)`, `interval.toggleAuto()` |
25−| `Panels` | `Panels.ts` | The dashboard panels in the edit canvas | `getPanels(title, scope?)`, `getPanel(title, scope?)`, `getHeaders(title?, scope?)` — string titles match exactly, RegExp filters by text, no argument returns all headers; `getHeader(title, scope?)`, `getBodies()`, `selectByTitle(title \| RegExp \| Array<title \| RegExp>)`, `selectByIndex(index)`, `selectMenuItem(panelTitle, menuPath[])` |
26−| `Rows` | `Rows.ts` | A row of a rows layout in the dashboard canvas | `getTitle(rowTitle)`, `getContent(rowTitle)` — content wrapper (grid or nested tabs) right after the row header |
27−| `Tabs` | `Tabs.ts` | Tab bar of a tabs layout (top-level or nested in a row) | `getTitle(tabTitle, scope?)` — pass `rows.getContent(rowTitle)` as `scope` to look up a tab inside a specific row; `getContent(tabTitle)` — the layout container holding the tab's content; `select(tabTitle)` — click the tab title |
28−| `Canvas` | `Canvas.ts` | Edit canvas add-actions strip (per grid, revealed on hover) | `getContainer()`, `addPanel(panelsContainer?)`, `addTab()`, `addRow()`, `groupPanels('row' \| 'tab', panelsContainer?)` — pass `panelsContainer` (e.g. `tabs.getContent(...)`, `rows.getContent(...)`) to target the add-actions strip of a nested grid |
14+## Project Overview
2915
30−> The show/hide visibility toggle is a **Toolbar** control (`sidebar.toolbar.getVisibilityToggle()`), even though its selector lives under `components.Sidebar.*`. `Toolbar.getButton(name)` resolves buttons by accessible name, scoped to the sidebar container.
16+Grafana is a monitoring and observability platform. Go backend, TypeScript/React frontend, monorepo with Yarn workspaces (frontend) and Go workspaces (backend).
3117
32−> This table grows as specs are migrated — only methods needed by migrated specs exist.
18+## Principles
3319
34−### Base class & constructor
20+- Follow existing patterns in the surrounding code
21+- Write tests for new functionality
22+- Keep changes focused — avoid over-engineering
23+- Separate PRs for frontend and backend changes (deployed at different cadences)
24+- Security: prevent XSS, SQL injection, command injection
3525
36−All page objects inherit from `PageObject`, which provides the shared constructor. It takes a single `PageObjectArgs` object:
26+## Comments
3727
38−```typescript
39−// page-objects/PageObject.ts
40−export interface PageObjectArgs {
41− page: Page;
42− dashboardPage: DashboardPage;
43− selectors: E2ESelectorGroups;
44− components: Components;
45−}
28+- Only add a comment when it explains **why** something is done or reveals non-obvious logic that a reader must know to safely change the code. If the code is self-explanatory, no comment is needed.
29+- Never include links (Slack, GitHub, Jira, etc.) in code comments.
4630
47−export abstract class PageObject {
48− constructor({ page, dashboardPage, selectors, components }: PageObjectArgs) {
49− // assigned to protected fields
50− }
51−}
52−```
31+## Human Review Gates
5332
54−Simple page objects (e.g. `Controls`, `Toolbar`) inherit the constructor directly — no override needed. Page objects that compose sub-objects (e.g. `Sidebar`) declare `constructor(args: PageObjectArgs)`, call `super(args)`, and pass the same `args` to their children.
33+Before running `git push`, stop and get explicit human approval. When changes are ready, show a summary of changes and wait for instruction. "Open a PR" in a task description is intent, not permission to push without review.
5534
56−All four dependencies come from the Playwright test arguments:
35+## Commands
5736
58−```typescript
59−test('example', async ({ gotoDashboardPage, selectors, page, components }) => {
60− const dashboardPage = await gotoDashboardPage({ uid: 'some-uid' });
61− const controls = new Controls({ page, dashboardPage, selectors, components });
62− // ...
63−});
37+### Build & Run
38+
39+```bash
40+make run # Backend with hot reload (localhost:3000, admin/admin)
41+make build-backend # Backend only
42+yarn start # Frontend dev server (watches for changes)
43+yarn build # Frontend production build
6444 ```
6545
66−## How to Write a New Test
46+### Test
6747
68−1. **Identify which page objects you need.** Check the table above. If the interaction you need isn't covered, add the method to the appropriate page object — only what the new test requires.
48+```bash
49+# Backend
50+go test -run TestName ./pkg/services/myservice/ # Specific test
51+make test-go-unit # All unit tests
52+make test-go-integration # Integration tests
6953
70−2. **Scaffold the spec** following this structure:
54+# Frontend
55+yarn test path/to/file # Specific file
56+yarn test -t "pattern" # By name pattern
57+yarn test -u # Update snapshots
7158
72−```typescript
73−import { test, expect } from '@grafana/plugin-e2e';
59+# E2E
60+yarn e2e:playwright path/to/test.spec.ts # Specific test
61+```
7462
75−import { Controls, Sidebar } from './page-objects';
63+### Lint & Format
7664
77−test.describe(
78− 'Feature name',
79− {
80− tag: ['@dashboards'],
81− },
82− () => {
83− test('describes the user-visible behavior', async ({ gotoDashboardPage, selectors, page, components }) => {
84− const dashboardPage = await gotoDashboardPage({ uid: 'dashboard-uid' });
65+```bash
66+make lint-go # Go linter
67+yarn lint # ESLint
68+yarn lint:fix # ESLint auto-fix
69+yarn prettier:write # Prettier auto-format
70+yarn typecheck # TypeScript check
71+```
8572
86− const controls = new Controls({ page, dashboardPage, selectors, components });
87− const sidebar = new Sidebar({ page, dashboardPage, selectors, components });
73+### Code Generation
8874
89− await controls.enterEditMode();
90− // ... test body using page objects (the toolbar is reached via sidebar.toolbar)
91− });
92− }
93−);
75+```bash
76+make gen-go # Wire DI (after changing service init)
77+make gen-cue # CUE schemas (after changing kinds/)
78+make gen-apps # App SDK apps
79+make swagger-gen # OpenAPI/Swagger specs
80+make gen-feature-toggles # Feature flags (pkg/services/featuremgmt/)
81+make i18n-extract # i18n strings
82+make update-workspace # Go workspace (after adding modules)
9483 ```
9584
96−1. **Verify locally:**
85+### Dev Environment
9786
9887 ```bash
99−yarn e2e:pw --project dashboard-new-layouts --reporter list --repeat-each=3 -- <spec-filename>
88+yarn install --immutable # Install frontend deps
89+make devenv sources=influxdb # Start backing services
90+make devenv-down # Stop backing services
91+make lefthook-install # Pre-commit hooks
10092 ```
10193
102−## Conventions
94+## Architecture
10395
104−### Page objects
96+### Backend (`pkg/`)
10597
106−- **Locator getters** (e.g. `getTitleInput()`) return a Playwright `Locator` — for elements that specs assert on (or both act on and assert on). The test owns the assertion — never the page object.
107−- **Action methods** (e.g. `enterEditMode()`, `clickCloseButton()`) wrap interactions — multi-step flows or single clicks on act-only elements — and use `test.step()` so the HTML report shows named steps.
108−- **When a spec needs both**, pair them: the action method delegates to the getter (see `Toolbar.getButton()` / `clickButton()`).
109−- **No speculative methods.** Only add methods needed by the spec being migrated.
110−- **Plural vs singular getters** (e.g. `getPanels()` / `getPanel()`): plural getters return every match — assert counts or narrow (`.first()`, `.nth()`) in the spec; singular getters return the first match.
111−- **Scoped lookups**: getters with a `scope?: Locator` parameter search inside that container — pass `rows.getContent(...)` or `tabs.getContent(...)` to look up elements in a specific row or tab.
112−- **No waits or retries inside page objects** unless the pre-refactor code had them. Keep `toPass()` retries, drag-and-drop, scroll logic, and `boundingBox()` in the spec or in `utils.ts`.
98+| Directory | Purpose |
99+| ----------------- | ----------------------------------------------------------- |
100+| `pkg/api/` | HTTP API handlers and routes |
101+| `pkg/services/` | Business logic by domain (alerting, dashboards, auth, etc.) |
102+| `pkg/server/` | Server init and Wire DI setup (`wire.go`) |
103+| `pkg/tsdb/` | Time series database query backends |
104+| `pkg/plugins/` | Plugin system and loader |
105+| `pkg/infra/` | Logging, metrics, database access |
106+| `pkg/middleware/` | HTTP middleware |
107+| `pkg/setting/` | Configuration management |
113108
114−### Selector scoping
109+**Patterns**: Wire DI (regenerate with `make gen-go`), services implement interfaces in same package, business logic in `pkg/services/<domain>/` not in API handlers, database via `sqlstore`, plugin communication via gRPC/protobuf.
115110
116−- **Scope lookups to the owning container.** A bare `page.getByRole(...)` searches the whole page and can match an unrelated element with the same role and name — if not today, then after an unrelated UI change. Elements that belong to a region with a page object must be looked up through that region's container: e.g. radio buttons in the sidebar are `sidebar.getContainer().getByRole('radio', { name: '...' })`, the same way `Toolbar.getButton()` scopes button names to `Sidebar.container`. Inside a page object, chain from the container selector (`this.dashboardPage.getByGrafanaSelector(this.selectors.components.Sidebar.container).getByRole(...)`).
117−- **Portalled UI is the only exception.** Select/Combobox option lists, modals, tooltips, and toasts render in a portal at the document root, outside their logical parent, so they cannot be scoped to it. Anchor them to the portal's own root instead: `page.getByRole('listbox').getByRole('option', { name })` (see `RepeatOptions`), or `page.getByRole('dialog', { name: 'Delete panel?' })`. A bare `page.getByRole('option', ...)` with no anchor is still too broad.
111+### Frontend (`public/app/`)
118112
119−### Specs
113+| Directory | Purpose |
114+| ---------------------- | ----------------------------------------------------- |
115+| `public/app/core/` | Shared services, components, utilities |
116+| `public/app/features/` | Feature code by domain (dashboard, alerting, explore) |
117+| `public/app/plugins/` | Built-in plugins (many are Yarn workspaces) |
118+| `public/app/types/` | TypeScript type definitions |
119+| `public/app/store/` | Redux store configuration |
120120
121−- **One raw `getByGrafanaSelector` is allowed** for one-off assertions that aren't reusable interactions (e.g. a breadcrumb check).
122−- **Timing-sensitive mechanics stay inline** — `toPass()`, `mouse` sequences, `page.evaluate()`.
123−- **Test setup stays in the spec** — API calls, dashboard provisioning, navigation via `gotoDashboardPage()`.
124−- **Each spec is fully migrated or untouched.** No file should mix page-object calls and raw selectors for the same UI region.
121+**Patterns**: Redux Toolkit with slices (not old Redux), function components with hooks, Emotion CSS-in-JS via `useStyles2`, RTK Query for data fetching, React Testing Library for tests.
125122
126−### Adding a method to a page object
123+### Shared Packages (`packages/`)
127124
128−1. Find the raw selector chain in the spec you're migrating.
129−2. Copy it into the appropriate page object class — mechanical extraction, no rewrites. New page objects must extend `PageObject` from `PageObject.ts`.
130−3. For interactions (multi-step flows or single clicks on act-only elements), wrap in `test.step('Human-readable name', async () => { ... })`.
131−4. For elements the spec asserts on, return a `Locator` (getter pattern, no `test.step` needed).
132−5. Run `--repeat-each=3` on the migrated spec.
125+`@grafana/data` (data structures), `@grafana/ui` (components), `@grafana/runtime` (runtime services), `@grafana/schema` (CUE-generated types), `@grafana/scenes` (dashboard framework).
133126
134−## Canonical Example
127+### Backend Apps (`apps/`)
135128
136−`dashboards-title-description.spec.ts` — the seed spec demonstrating the full pattern:
129+Standalone Go apps using Grafana App SDK: `apps/dashboard/`, `apps/folder/`, `apps/alerting/`.
137130
138−```typescript
139−await controls.enterEditMode();
140−await sidebar.toolbar.clickButton('Options');
131+### Plugin Workspaces
141132
142−const titleInput = sidebar.dashboardOptions.getTitleInput();
143−await expect(titleInput).toHaveValue('Annotation filtering');
133+These built-in plugins require separate build steps: `azuremonitor`, `loki`, `grafana-testdata-datasource`.
144134
145−const newTitle = 'New dashboard title';
146−await titleInput.fill(newTitle);
147−await expect(titleInput).toHaveValue(newTitle);
148−```
135+Build a specific plugin: `yarn workspace @grafana-plugins/<name> dev`
149136
150−## Migration Status
137+## Key Notes
151138
152−**26 of 30 specs migrated.** Non-migrated specs are listed first by descending selectors usage count (a rough proxy for migration effort). "Selectors usage count" is the number of times the spec accesses the `selectors` object (`selectors.components...`, `selectors.pages...`, etc.).
139+- **Wire DI**: Backend service init changes require `make gen-go`. Wire catches circular deps at compile time.
140+- **CUE schemas**: Dashboard/panel schemas in `kinds/` generate both Go and TS code via `make gen-cue`.
141+- **Feature toggles**: Defined in `pkg/services/featuremgmt/`, auto-generate code. Run `make gen-feature-toggles` after changes.
142+- **Go workspace**: Defined in `go.work`. Run `make update-workspace` when adding Go modules.
143+- **Build tags**: `oss` (default), `enterprise`, `pro`.
144+- **Config**: Defaults in `conf/defaults.ini`, overrides in `conf/custom.ini`.
145+- **Database migrations**: Live in `pkg/services/sqlstore/migrations/`. Test with `make devenv sources=postgres_tests,mysql_tests` then `make test-go-integration-postgres`.
146+- **CI sharding**: Backend tests use `SHARD`/`SHARDS` env vars for parallelization.
147+- **Service compatibility**: Unified storage/search (`pkg/storage/unified/`) can be deployed as separate services at a different cadence than the Grafana API layer. Changes spanning API-layer callers and `pkg/storage/unified/` must be backwards compatible in both directions — see `pkg/storage/unified/AGENTS.md`.
153148
154−| Spec | Status | Lines of code | Selectors usage count |
155−| ----------------------------------------------------- | ----------- | ------------- | --------------------- |
156−| `dashboard-group-panels.spec.ts` | Not started | 918 | 224 |
157−| `dashboards-repeats-tabs-layout.spec.ts` | Not started | 482 | 73 |
158−| `dashboards-panel-layouts.spec.ts` | Not started | 425 | 70 |
159−| `dashboard-repeats-row-layout.spec.ts` | Not started | 551 | 61 |
160−| `dashboards-repeats-custom-grid.spec.ts` | Migrated | — | — |
161−| `dashboards-repeats-auto-grid.spec.ts` | Migrated | — | — |
162−| `dashboards-title-description.spec.ts` | Migrated | — | — |
163−| `dashboards-edit-panel-title-description.spec.ts` | Migrated | — | — |
164−| `dashboards-edit-panel-transparent-bg.spec.ts` | Migrated | — | — |
165−| `dashboard-mobile-sidebar.spec.ts` | Migrated | — | — |
166−| `dashboard-hide-sidebar.spec.ts` | Migrated | — | — |
167−| `dashboards-remove-panel.spec.ts` | Migrated | — | — |
168−| `dashboard-duplicate-panel.spec.ts` | Migrated | — | — |
169−| `dashboard-sidepane.spec.ts` | Migrated | — | — |
170−| `dashboard-outline.spec.ts` | Migrated | — | — |
171−| `dashboards-conditional-rendering.spec.ts` | Migrated | — | — |
172−| `dashboards-add-panel.spec.ts` | Migrated | — | — |
173−| `dashboards-edit-variables.spec.ts` | Migrated | — | — |
174−| `dashboard-tabs-scroll.spec.ts` | Migrated | — | — |
175−| `dashboards-repeats-snapshots.spec.ts` | Migrated | — | — |
176−| `dashboards-move-panel.spec.ts` | Migrated | — | — |
177−| `dashboard-conditional-rendering-load-change.spec.ts` | Migrated | — | — |
178−| `dashboards-edit-custom-variables.spec.ts` | Migrated | — | — |
179−| `dashboards-edit-query-variables.spec.ts` | Migrated | — | — |
180−| `dashboard-keybindings.spec.ts` | Migrated | — | — |
181−| `dashboards-edit-adhoc-variables.spec.ts` | Migrated | — | — |
182−| `dashboards-edit-group-by-variables.spec.ts` | Migrated | — | — |
183−| `dashboards-edit-datasource-variables.spec.ts` | Migrated | — | — |
184−| `dashboard-url-syncing.spec.ts` | Migrated | — | — |
185−| `dashboard-tabs-drag-drop.spec.ts` | Migrated | — | — |
149+## Cursor Cloud specific instructions
186150
187−See [`_page_objects_strategy.md`](./_page_objects_strategy.md) for the full migration plan.
151+### Prerequisites
152+
153+- **Node.js** — version pinned in `.nvmrc` (check that file for the exact version). Installed via nvm and set as the nvm default. **PATH gotcha:** the infra injects `/exec-daemon/node` ahead of nvm, so the plain non-login shell may resolve `node` to an older version — check it satisfies the `engines` range in `package.json` (it does today, so builds/tests work), but it is not the pinned version. Login shells (tmux sessions, `bash -lc '...'`) get the pinned version because `~/.bashrc` prepends the nvm bin. Run `yarn` / `yarn start` / `jest` / webpack via a login shell (tmux or `bash -lc`) to use the pinned Node.
154+- **Go** — version pinned in `go.mod` (check that file for the exact version), installed at `/usr/local/go` and symlinked to `/usr/local/bin/go`. The distro `/usr/bin/go` is older; `/usr/local/bin` wins in PATH so `go` resolves correctly. If `go.mod` bumps Go, reinstall a matching toolchain into `/usr/local/go`.
155+- **Yarn** via corepack — version pinned by `package.json` `packageManager` (check that field for the exact version). Run `corepack enable` if `yarn` is not found. `.yarnrc.yml` sets `enableScripts: false`, so dependency build/lifecycle scripts are disabled by default.
156+- **GCC** required for CGo/SQLite compilation of the backend.
157+- Repos in this environment live under `/agent/repos/<repo>` (e.g. `/agent/repos/grafana`); this is a multi-repo workspace, not the single `~/grafana` layout described in `grafana-enterprise/AGENTS.md`.
158+
159+### Running services
160+
161+- **Backend**: `make run` — builds and starts Grafana backend with hot-reload (air) on `localhost:3000`. Default login: `admin`/`admin`. First build takes ~3 minutes due to debug symbols (`-gcflags all=-N -l`); subsequent hot-reload rebuilds are faster.
162+- **Frontend**: `yarn start` — starts webpack dev server that watches for changes. The backend proxies to it. First compile takes ~45s.
163+- No external databases required — Grafana uses embedded SQLite by default.
164+
165+### Testing gotchas
166+
167+- **Frontend tests**: The `yarn test` script includes `--watch` by default. Always use `yarn jest --no-watch` or add `--watchAll=false` to run tests once and exit.
168+- **Backend tests**: Some packages (e.g. `pkg/api/`) have slow test compilation (~2 min) due to large dependency graphs. Use targeted test runs with `-run TestName` where possible.
169+- All standard build/test/lint commands are documented in the Commands section above.
188170
