AGENTS.md
e2e-playwright/dashboard-new-layouts/AGENTS.mdAGENTS.md
Quality
62/100
Scores the file, not the repository.Length
1,777 words
12 headings · 5 code blocksRepository
76k
— · pushed 0 days agoLast changed
today
First indexed 3 days ago.1# dashboard-new-layouts E2E Tests — Agent Guide23## Purpose45This 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).67## Page Objects Reference89All 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.1011| 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 |2930> 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.3132> This table grows as specs are migrated — only methods needed by migrated specs exist.3334### Base class & constructor3536All page objects inherit from `PageObject`, which provides the shared constructor. It takes a single `PageObjectArgs` object:3738```typescript39// page-objects/PageObject.ts40export interface PageObjectArgs {41 page: Page;42 dashboardPage: DashboardPage;43 selectors: E2ESelectorGroups;44 components: Components;45}4647export abstract class PageObject {48 constructor({ page, dashboardPage, selectors, components }: PageObjectArgs) {49 // assigned to protected fields50 }51}52```5354Simple 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.5556All four dependencies come from the Playwright test arguments:5758```typescript59test('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```6566## How to Write a New Test67681. **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.69702. **Scaffold the spec** following this structure:7172```typescript73import { test, expect } from '@grafana/plugin-e2e';7475import { Controls, Sidebar } from './page-objects';7677test.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' });8586 const controls = new Controls({ page, dashboardPage, selectors, components });87 const sidebar = new Sidebar({ page, dashboardPage, selectors, components });8889 await controls.enterEditMode();90 // ... test body using page objects (the toolbar is reached via sidebar.toolbar)91 });92 }93);94```95961. **Verify locally:**9798```bash99yarn e2e:pw --project dashboard-new-layouts --reporter list --repeat-each=3 -- <spec-filename>100```101102## Conventions103104### Page objects105106- **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`.113114### Selector scoping115116- **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.118119### Specs120121- **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.125126### Adding a method to a page object1271281. 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.133134## Canonical Example135136`dashboards-title-description.spec.ts` — the seed spec demonstrating the full pattern:137138```typescript139await controls.enterEditMode();140await sidebar.toolbar.clickButton('Options');141142const titleInput = sidebar.dashboardOptions.getTitleInput();143await expect(titleInput).toHaveValue('Annotation filtering');144145const newTitle = 'New dashboard title';146await titleInput.fill(newTitle);147await expect(titleInput).toHaveValue(newTitle);148```149150## Migration Status151152**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.).153154| 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 | — | — |186187See [`_page_objects_strategy.md`](./_page_objects_strategy.md) for the full migration plan.188
Also in grafana/grafana
Diff this repo’s formatsOne repository carrying more than one format is the comparison this product exists for: does anyone actually write different content in each file, or is one a copy of the other?
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| grafana/grafanaAGENTS.md · 76k | AGENTS.md | setupbuildtestlint-format+6 | 89/100 | 3 days ago | |
| grafana/grafanae2e-playwright/alerting-suite/AGENTS.md · 76k | AGENTS.md | teststylearchtesting-strategy+3 | 81/100 | 3 days ago | |
| grafana/grafanae2e-playwright/plugin-e2e/plugin-e2e-api-tests/AGENTS.md · 76k | AGENTS.md | teststyletesting-strategygit+4 | 70/100 | 3 days ago | |
| grafana/grafanapackages/grafana-ui/AGENTS.md · 76k | AGENTS.md | uiagent-behaviour | 16/100 | 3 days ago | |
| grafana/grafanapkg/storage/unified/AGENTS.md · 76k | AGENTS.md | do-not | 46/100 | 3 days ago | |
| grafana/grafanapublic/app/core/journeys/AGENTS.md · 76k | AGENTS.md | testtesting-strategygitagent-behaviour | 73/100 | 3 days ago | |
| grafana/grafanapublic/app/features/AGENTS.md · 76k | AGENTS.md | agent-behaviour | 16/100 | 3 days ago | |
| grafana/grafanapublic/app/features/alerting/unified/AGENTS.md · 76k | AGENTS.md | setuptestlint-formatstyle+11 | 76/100 | 3 days ago | |
| grafana/grafanapublic/app/features/expressions/components/SqlExpressions/SqlEditor/AGENTS.md · 76k | AGENTS.md | styleagent-behaviour | 43/100 | 3 days ago | |
| grafana/grafanapublic/app/plugins/panel/AGENTS.md · 76k | AGENTS.md | agent-behaviour | 16/100 | 3 days ago |
Diff against AGENTS.md Diff against e2e-playwright/alerting-suite/AGENTS.md Diff against e2e-playwright/plugin-e2e/plugin-e2e-api-tests/AGENTS.md Diff against packages/grafana-ui/AGENTS.md Diff against pkg/storage/unified/AGENTS.md Diff against public/app/core/journeys/AGENTS.md Diff against public/app/features/AGENTS.md Diff against public/app/features/alerting/unified/AGENTS.md Diff against public/app/features/expressions/components/SqlExpressions/SqlEditor/AGENTS.md Diff against public/app/plugins/panel/AGENTS.md
Similar configs
Same format, overlapping stack, ranked by quality.
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| code-yeongyu/oh-my-openagentpackages/web/AGENTS.md · 67k | AGENTS.md | setupbuildtestlint-format+6 | 100/100 | 2 days ago | |
| TryGhost/Ghoste2e/AGENTS.md · 55k | AGENTS.md | setupteststylearch+2 | 100/100 | 3 days ago | |
| mui/material-uiAGENTS.md · 99k | AGENTS.md | setupbuildtestlint-format+9 | 100/100 | 3 days ago | |
| n8n-io/n8npackages/@n8n/agents/AGENTS.md · 199k | AGENTS.md | buildteststylearch+3 | 100/100 | 3 days ago | |
| duckduckgo/content-scope-scriptsspecial-pages/AGENTS.md · 70 | AGENTS.md | buildteststylearch+3 | 100/100 | 3 days ago | |
| aaif-goose/gooseAGENTS.md · 52k | AGENTS.md | setupbuildtestlint-format+6 | 100/100 | 3 days ago | |
| trick77/agents-md-syncAGENTS.md · 2 | AGENTS.md | setupbuildteststyle+5 | 100/100 | 3 days ago | |
| ethereum/go-ethereumAGENTS.md · 51k | AGENTS.md | buildtestlint-formatgit+1 | 100/100 | 3 days ago |
