Skip to content

Plugin E2E: Add components fixture with dataSourcePicker and timeRangePicker#2583

Merged
mckn merged 17 commits intomainfrom
mckn/expose-ds-picker-cmd
May 7, 2026
Merged

Plugin E2E: Add components fixture with dataSourcePicker and timeRangePicker#2583
mckn merged 17 commits intomainfrom
mckn/expose-ds-picker-cmd

Conversation

@mckn
Copy link
Copy Markdown
Collaborator

@mckn mckn commented Apr 16, 2026

What this PR does / why we need it:

Adds a components fixture that exposes eagerly-constructed Grafana UI components for use in tests on arbitrary pages — not just the ones covered by page-specific fixtures like PanelEditPage or ExplorePage.

Two components are exposed:

  • components.dataSourcePicker — useful for tests that interact with a data source picker on any page (e.g. Grafana's exemplars test)
  • components.timeRangePicker — useful for tests that set a time range on any page

Both components support a Playwright-style within(root) method for scoping to a specific DOM container (mirroring locator.locator(...)). The within() method is provided by a new shared ScopedComponent base class.

test('my test', async ({ components, panelEditPage }) => {
  await components.dataSourcePicker.set('gdev-prometheus');
  await components.timeRangePicker.set({ from: 'now-1h', to: 'now' });

  // or scoped to a container
  const panel = panelEditPage.getByGrafanaSelector(selectors.components.PanelEditor.General.content);
  await components.dataSourcePicker.within(panel).set('gdev-tempo');
});

Which issue(s) this PR fixes:

Special notes for your reviewer:

ScopedComponent is exported as part of the public API so plugin authors can subclass it for their own page-level components. Additional components can be added to Components following the same pattern.

📦 Published PR as canary version: Canary Versions

✨ Test out this PR locally via:

npm install website@5.6.0-canary.2583.25382670310.0
npm install @grafana/create-plugin@7.4.0-canary.2583.25382670310.0
npm install @grafana/plugin-e2e@3.8.0-canary.2583.25382670310.0
# or 
yarn add website@5.6.0-canary.2583.25382670310.0
yarn add @grafana/create-plugin@7.4.0-canary.2583.25382670310.0
yarn add @grafana/plugin-e2e@3.8.0-canary.2583.25382670310.0

@mckn mckn requested a review from a team as a code owner April 16, 2026 20:13
@mckn mckn requested review from Copilot and leventebalogh and removed request for Copilot April 16, 2026 20:13
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 16, 2026

Hello! 👋 This repository uses Auto for releasing packages using PR labels.

✨ This PR can be merged and will trigger a new minor release.
NOTE: When merging a PR with the release label please avoid merging another PR. For further information see here.

@mckn mckn requested a review from sunker April 16, 2026 20:14
@mckn mckn self-assigned this Apr 16, 2026
@mckn mckn moved this from 📬 Triage to 🔬 In review in Grafana Catalog Team Apr 16, 2026
@mckn mckn added the minor Increment the minor version when merged label Apr 16, 2026
Copilot AI review requested due to automatic review settings April 17, 2026 11:10
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new Playwright fixture for creating UI component model instances (starting with DataSourcePicker) without requiring page-specific fixtures or manual PluginTestCtx construction.

Changes:

  • Extends PluginFixture with a new components: Components fixture.
  • Introduces a Components factory model exposing getDataSourcePicker(root?).
  • Wires the new components fixture into the exported test fixture set.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

File Description
packages/plugin-e2e/src/types.ts Adds components: Components to the public fixture type and documents usage.
packages/plugin-e2e/src/models/Components.ts New component factory class that constructs DataSourcePicker instances (optionally scoped by a root locator).
packages/plugin-e2e/src/index.ts Exports Components and registers the new components fixture in test.extend(...).
packages/plugin-e2e/src/fixtures/components.ts Implements the Playwright fixture that instantiates Components with PluginTestCtx.

Comment thread packages/plugin-e2e/src/types.ts
Comment thread packages/plugin-e2e/src/index.ts
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Comment thread packages/plugin-e2e/src/types.ts
Copy link
Copy Markdown
Contributor

@sunker sunker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice Marcus!

It would be quite nice to instantiate all components under /models/components in the Components class constructor. That way the API would be closer to other plugin-e2e APIs:

test('my test', async ({ components }) => {
  await components.dataSourcePicker.set('prom');
});

Then to scope the component to another root than page, you'd use the same pattern as Playwright's locator.locator(...) - a within() helper that returns a scoped clone:

test('my test', async ({ components, panelEditPage }) => {
  const panel = panelEditPage.getPanelByTitle('My panel').locator;
  await components.dataSourcePicker.within(panel).set('prom');
});

To avoid repeating the within() boilerplate in every component, we could pull it into a small ScopedComponent base class that each component extends. Something like:

export abstract class ScopedComponent extends GrafanaPage {
  constructor(
    ctx: PluginTestCtx,
    protected readonly root?: Locator
  ) {
    super(ctx);
  }

  within(root: Locator): this {
    const Ctor = this.constructor as new (ctx: PluginTestCtx, root?: Locator) => this;
    return new Ctor(this.ctx, root);
  }
}

WDYT?

mckn and others added 4 commits May 4, 2026 14:38
Expose a fixture that returns a DataSourcePicker instance without
requiring manual PluginTestCtx construction. Useful for tests that
interact with a datasource picker on pages not covered by existing
page fixtures.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Expose a `components` fixture that returns a Components factory object.
This groups component factories (currently just `getDataSourcePicker`)
in one place and makes it easier to add more in the future without
bloating the top-level test fixtures.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Verify that components.getDataSourcePicker() sets the data source on
a panel edit page, both without a root locator and when scoped to the
panel editor content.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Expose components as eagerly constructed properties on the Components
class (e.g. components.dataSourcePicker.set(...)) and introduce a
ScopedComponent base class providing a Playwright-style within(root)
method for scoping, mirroring locator.locator(...).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@mckn mckn force-pushed the mckn/expose-ds-picker-cmd branch from 0b0ca89 to a932d84 Compare May 4, 2026 12:39
@mckn mckn requested a review from sunker May 4, 2026 12:53
Comment thread packages/plugin-e2e/src/models/Components.ts
Migrate TimeRange to ScopedComponent and expose it as
components.timeRangePicker, following the same pattern as
components.dataSourcePicker.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@mckn mckn changed the title Plugin E2E: Add getDataSourcePicker fixture command Plugin E2E: Add components fixture with dataSourcePicker and timeRangePicker May 5, 2026
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@sunker sunker added the release Create a release when this pr is merged label May 5, 2026
sunker
sunker previously approved these changes May 5, 2026
Copy link
Copy Markdown
Contributor

@sunker sunker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved, but there's one nit to fix

Comment thread packages/plugin-e2e/src/types.ts Outdated
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
mckn and others added 3 commits May 5, 2026 14:09
Use .first() on the openButton assertion to avoid strict mode
violations on Grafana versions that render the time picker twice.
Remove the within() test for timeRangePicker - the within() pattern
is covered by dataSourcePicker.within() tests and there is no
natural scope for a time range picker in the test environment.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…root

Use gotoDashboardPage for a stable time picker location. Resolve the
root locator based on Grafana version: NavToolbar.container for >=9.4.0
(when it was introduced), PageToolbar.container for older versions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Expose a version-aware toolbar locator on DashboardPage. Resolves to
NavToolbar.container on Grafana >=9.4.0 and falls back to .page-toolbar
for older versions. Use it in the timeRangePicker.within() test to
remove the version logic from the test itself.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
mckn and others added 7 commits May 5, 2026 15:10
In Grafana 11.1.0+ (scenes-based dashboards) the time range controls
live inside the `Dashboard.Controls` container, not `NavToolbar.container`
(which is the app-level navigation bar). Scope the toolbar getter to
`Dashboard.Controls` for >= 11.1.0, keeping NavToolbar for 9.4.0–11.0.x
and the CSS fallback for older versions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- DataSourcePicker.set(): fall back to page-level input search when
  the scoped root doesn't contain the inputV2 element (Grafana 13
  Enterprise alerting renders the picker outside the query-editor row)

- AlertRuleEditPage.evaluate() route handler: guard against
  body.results being undefined/differently shaped in Grafana 13
  Enterprise; wrap in try/catch so the route is always fulfilled and
  waitForResponse never hangs

- page.ts: merge featureToggles into the OFREP interceptor flags for
  Grafana >= 12.1.0. The server's OFREP bulk-evaluation response was
  overriding bootData values (e.g. tlsEnabled, alertingQueryAndExpressionsStepMode)
  after app load; both bootData and OFREP now carry the same overrides.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Merge featureToggles into OFREP flags so Grafana 13's server-side
  bulk-eval response no longer overrides bootData values (fixes tlsEnabled
  and alertingQueryAndExpressionsStepMode toggle tests)
- Null-safe route handler in AlertRuleEditPage.evaluate() to handle
  undefined results in Grafana 13 Enterprise responses
- Patch QueryEditorRows/QueryEditorRow selector boundary from 13.1.0 to
  13.0.1 to match when the data-testid attributes were actually introduced

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The patch incorrectly moved the data-testid query editor row selector
boundary from 13.1.0 to 13.0.1. Grafana 13.0.1 still uses the old
aria-label="Query editor row" format; @grafana/e2e-selectors already
has the correct 13.1.0 boundary.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 6, 2026

Playwright test results

Image Name Version Result Report
grafana-enterprise nightly
grafana-enterprise dev-preview-react19
grafana-enterprise 13.0.1
grafana-enterprise 12.1.10
grafana-enterprise 11.0.11
grafana-enterprise 9.3.16
grafana-enterprise 8.5.27
Troubleshooting

404 when clicking on View report

By default, the deploy-report-pages Action deploys reports to the gh-pages branch. However, you need to take an extra step to ensure that GitHub Pages can build and serve the site from this branch. To do so:

  1. Go to the Settings tab of your repository.
  2. In the left-hand sidebar, click on Pages.
  3. Under Source, select Deploy from a branch, then choose the gh-pages branch.

This action needs to be completed manually in order for your GitHub Pages site to be built and accessible from the gh-pages branch. Once configured, GitHub will automatically build and serve the site whenever new reports are deployed.

Copy link
Copy Markdown
Contributor

@sunker sunker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀

const { TimeZonePicker, TimePicker, Select } = this.ctx.selectors.components;
try {
await this.getByGrafanaSelector(TimePicker.openButton).click();
await this.getByGrafanaSelector(TimePicker.openButton, { root: this.root }).click();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious why we need this change?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is because you can do the within which change the scope of the time picker. It should try to find the time picker within that new root element instead of the page.

Does this make sense?

@mckn mckn merged commit 634c110 into main May 7, 2026
46 of 47 checks passed
@mckn mckn deleted the mckn/expose-ds-picker-cmd branch May 7, 2026 06:16
@github-project-automation github-project-automation Bot moved this from 🔬 In review to 🚀 Shipped in Grafana Catalog Team May 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

minor Increment the minor version when merged release Create a release when this pr is merged

Projects

Status: 🚀 Shipped

Development

Successfully merging this pull request may close these issues.

3 participants