-
Notifications
You must be signed in to change notification settings - Fork 47
Plugin E2E: Add components fixture with dataSourcePicker and timeRangePicker #2583
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
a039713
6660cf5
73c6eef
a932d84
dac332a
54335c9
c2b8f0c
a5d3561
49782b1
9d975d7
d5d2f85
676fd69
49f821b
d4fa2ea
fce7fb6
db6b14b
d249845
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| import { TestFixture } from '@playwright/test'; | ||
| import { PlaywrightArgs } from '../types'; | ||
| import { Components } from '../models/Components'; | ||
|
|
||
| type ComponentsFixture = TestFixture<Components, PlaywrightArgs>; | ||
|
|
||
| export const components: ComponentsFixture = async ( | ||
| { request, page, selectors, grafanaVersion }, | ||
| use, | ||
| testInfo | ||
| ) => { | ||
| await use(new Components({ page, selectors, grafanaVersion, request, testInfo })); | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| import { PluginTestCtx } from '../types'; | ||
| import { DataSourcePicker } from './components/DataSourcePicker'; | ||
| import { TimeRange } from './components/TimeRange'; | ||
|
|
||
| /** | ||
| * Factory for components that are not attached to a specific page. | ||
| * | ||
| * Use this when you need to interact with a Grafana UI component on a page | ||
| * that is not covered by one of the page fixtures (e.g. {@link PanelEditPage} | ||
| * or {@link ExplorePage}). | ||
| * | ||
| * To scope a component to a sub-tree of the DOM, use `within(root)`: | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * await components.dataSourcePicker.set('prom'); | ||
| * await components.dataSourcePicker.within(panel).set('prom'); | ||
| * ``` | ||
| */ | ||
| export class Components { | ||
| readonly dataSourcePicker: DataSourcePicker; | ||
| readonly timeRangePicker: TimeRange; | ||
|
|
||
| constructor(ctx: PluginTestCtx) { | ||
| this.dataSourcePicker = new DataSourcePicker(ctx); | ||
|
mckn marked this conversation as resolved.
|
||
| this.timeRangePicker = new TimeRange(ctx); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| import { Locator } from '@playwright/test'; | ||
| import { PluginTestCtx } from '../../types'; | ||
| import { GrafanaPage } from '../pages/GrafanaPage'; | ||
|
|
||
| /** | ||
| * Base class for components that live at the page level but can optionally | ||
| * be scoped to a sub-tree of the DOM via `within()`. | ||
| */ | ||
| export abstract class ScopedComponent extends GrafanaPage { | ||
| constructor( | ||
| ctx: PluginTestCtx, | ||
| protected readonly root?: Locator | ||
| ) { | ||
| super(ctx); | ||
| } | ||
|
|
||
| /** | ||
| * Returns a new instance of this component scoped to the given root locator. | ||
| * Mirrors Playwright's `locator.locator(...)` chaining pattern. | ||
| */ | ||
| within(root: Locator): this { | ||
| const Ctor = this.constructor as new (ctx: PluginTestCtx, root?: Locator) => this; | ||
| return new Ctor(this.ctx, root); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,19 +1,15 @@ | ||
| import * as semver from 'semver'; | ||
| import { PluginTestCtx, TimeRangeArgs } from '../../types'; | ||
| import { GrafanaPage } from '../pages/GrafanaPage'; | ||
|
|
||
| export class TimeRange extends GrafanaPage { | ||
| constructor(ctx: PluginTestCtx) { | ||
| super(ctx); | ||
| } | ||
| import { TimeRangeArgs } from '../../types'; | ||
| import { ScopedComponent } from './ScopedComponent'; | ||
|
|
||
| export class TimeRange extends ScopedComponent { | ||
| /** | ||
| * Opens the time picker and sets the time range to the provided values | ||
| */ | ||
| async set({ from, to, zone }: TimeRangeArgs) { | ||
| const { TimeZonePicker, TimePicker, Select } = this.ctx.selectors.components; | ||
| try { | ||
| await this.getByGrafanaSelector(TimePicker.openButton).click(); | ||
| await this.getByGrafanaSelector(TimePicker.openButton, { root: this.root }).click(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Curious why we need this change?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is because you can do the Does this make sense? |
||
| } catch (e) { | ||
| // seems like in older versions of Grafana the time picker markup is rendered twice | ||
| await this.ctx.page.locator('[aria-controls="TimePickerContent"]').last().click(); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| import { expect, test } from '../../../src'; | ||
|
|
||
| test('components.dataSourcePicker should set the data source', async ({ | ||
| panelEditPage, | ||
| components, | ||
| readProvisionedDataSource, | ||
| }) => { | ||
| const ds = await readProvisionedDataSource({ fileName: 'testdatasource.yaml' }); | ||
| await components.dataSourcePicker.set(ds.name); | ||
| await expect(panelEditPage.getQueryEditorRow('A').getByRole('textbox', { name: 'Query Text' })).toBeVisible(); | ||
| }); | ||
|
|
||
| test('components.dataSourcePicker.within should set the data source when scoped to a root locator', async ({ | ||
| panelEditPage, | ||
| components, | ||
| readProvisionedDataSource, | ||
| selectors, | ||
| }) => { | ||
| const ds = await readProvisionedDataSource({ fileName: 'testdatasource.yaml' }); | ||
| const root = panelEditPage.getByGrafanaSelector(selectors.components.PanelEditor.General.content); | ||
| await components.dataSourcePicker.within(root).set(ds.name); | ||
| await expect(panelEditPage.getQueryEditorRow('A').getByRole('textbox', { name: 'Query Text' })).toBeVisible(); | ||
| }); | ||
|
|
||
| test('components.timeRangePicker should set the time range', async ({ | ||
| panelEditPage, | ||
| components, | ||
| selectors, | ||
| }) => { | ||
| await components.timeRangePicker.set({ from: '2020-01-01 00:00:00', to: '2020-01-02 00:00:00' }); | ||
| // older Grafana versions render the time picker twice, so we use .first() to avoid strict mode violations | ||
| const openButton = panelEditPage.getByGrafanaSelector(selectors.components.TimePicker.openButton).first(); | ||
| await expect(openButton).toContainText('2020-01-01 00:00:00'); | ||
| }); | ||
|
|
||
| test('components.timeRangePicker.within should set the time range when scoped to a root locator', async ({ | ||
| gotoDashboardPage, | ||
| readProvisionedDashboard, | ||
| components, | ||
| selectors, | ||
| }) => { | ||
| const dashboard = await readProvisionedDashboard({ fileName: 'testdatasource.json' }); | ||
| const dashboardPage = await gotoDashboardPage(dashboard); | ||
| await components.timeRangePicker.within(dashboardPage.toolbar).set({ from: '2020-01-01 00:00:00', to: '2020-01-02 00:00:00' }); | ||
| const openButton = dashboardPage.getByGrafanaSelector(selectors.components.TimePicker.openButton).first(); | ||
| await expect(openButton).toContainText('2020-01-01 00:00:00'); | ||
| }); |
Uh oh!
There was an error while loading. Please reload this page.