Skip to content

Commit

Permalink
Plugin E2E: Rename getByTestIdOrAriaLabel to getByGrafanaSelector (#832)
Browse files Browse the repository at this point in the history
  • Loading branch information
sunker authored Apr 3, 2024
1 parent ab952cd commit cc16775
Show file tree
Hide file tree
Showing 28 changed files with 73 additions and 76 deletions.
2 changes: 1 addition & 1 deletion docusaurus/docs/e2e-test-a-plugin/feature-toggles.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ test('valid credentials should return a 200 status code', async ({
isFeatureToggleEnabled,
}) => {
const configPage = await createDataSourceConfigPage({ type: 'grafana-snowflake-datasource' });
await configPage.getByTestIdOrAriaLabel('Data source connection URL').fill('http://localhost:9090');
await configPage.getByGrafanaSelector('Data source connection URL').fill('http://localhost:9090');
const isSecureSocksDSProxyEnabled = await isFeatureToggleEnabled('secureSocksDSProxyEnabled');
if (isSecureSocksDSProxyEnabled) {
page.getByLabel('Enabled').check();
Expand Down
12 changes: 5 additions & 7 deletions docusaurus/docs/e2e-test-a-plugin/selecting-ui-elements.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ Selecting Grafana UI elements can be challenging because the selector may be def

## Playwright locator for Grafana UI elements

All [pages](https://github.com/grafana/plugin-tools/tree/main/packages/plugin-e2e/src/models/pages) defined by `@grafana/plugin-e2e` expose a `getByTestIdOrAriaLabel` method. This method returns a Playwright [locator](https://playwright.dev/docs/locators) that resolves to one or more elements, using the appropriate HTML attribute as defined on the element. Whenever you want to resolve a Playwright locator based on a [grafana/e2e-selectors](https://github.com/grafana/grafana/tree/main/packages/grafana-e2e-selectors), you should always use this method.
All [pages](https://github.com/grafana/plugin-tools/tree/main/packages/plugin-e2e/src/models/pages) defined by `@grafana/plugin-e2e` expose a `getByGrafanaSelector` method. This method returns a Playwright [locator](https://playwright.dev/docs/locators) that resolves to one or more elements, using the appropriate HTML attribute as defined on the element. Whenever you want to resolve a Playwright locator based on a [grafana/e2e-selectors](https://github.com/grafana/grafana/tree/main/packages/grafana-e2e-selectors), you should always use this method.

```ts
panelEditPage.getByTestIdOrAriaLabel(selectors.components.CodeEditor.container).click();
panelEditPage.getByGrafanaSelector(selectors.components.CodeEditor.container).click();
```

## The selectors fixture
Expand All @@ -41,16 +41,14 @@ import { test, expect } from '@grafana/plugin-e2e';

test('annotation query should be OK when query is valid', async ({ annotationEditPage, page, selectors }) => {
await annotationEditPage.datasource.set('E2E Test Data Source');
await annotationEditPage
.getByTestIdOrAriaLabel(selectors.components.CodeEditor.container)
.fill('SELECT * FROM users');
await annotationEditPage.getByGrafanaSelector(selectors.components.CodeEditor.container).fill('SELECT * FROM users');
await expect(annotationEditPage.runQuery()).toBeOK();
});
```

## Interact with UI elements defined in the plugin code

As stated above, you should always use the `getByTestIdOrAriaLabel` method when the UI element you want to interact with has an associated end-to-end selector. However, many Grafana UI elements don't have end-to-end selectors. If that's the case, we recommended following Grafana's best practices for [testing](https://github.com/grafana/grafana/blob/401265522e584e4e71a1d92d5af311564b1ec33e/contribute/style-guides/testing.md) and the [testing with accessibility in mind](https://github.com/grafana/grafana/blob/401265522e584e4e71a1d92d5af311564b1ec33e/contribute/style-guides/accessibility.md#writing-tests-with-accessibility-in-mind) guide when composing your UI and writing tests.
As stated above, you should always use the `getByGrafanaSelector` method when the UI element you want to interact with has an associated end-to-end selector. However, many Grafana UI elements don't have end-to-end selectors. If that's the case, we recommended following Grafana's best practices for [testing](https://github.com/grafana/grafana/blob/401265522e584e4e71a1d92d5af311564b1ec33e/contribute/style-guides/testing.md) and the [testing with accessibility in mind](https://github.com/grafana/grafana/blob/401265522e584e4e71a1d92d5af311564b1ec33e/contribute/style-guides/accessibility.md#writing-tests-with-accessibility-in-mind) guide when composing your UI and writing tests.

### Scope locators

Expand Down Expand Up @@ -105,7 +103,7 @@ test('testing select component', async ({ page, selectors }) => {
const configPage = await createDataSourceConfigPage({ type: 'test-datasource' });
await page.getByRole('combobox', { name: 'Auth type' }).click();
const option = selectors.components.Select.option;
await expect(configPage.getByTestIdOrAriaLabel(option)).toHaveText(['val1', 'val2']);
await expect(configPage.getByGrafanaSelector(option)).toHaveText(['val1', 'val2']);
});
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ test('should run successfully and display a success alert box when query is vali
const ds = await readProvisionedDataSource({ fileName: 'datasources.yml' });
await annotationEditPage.datasource.set(ds.name);
await page.waitForFunction(() => window.monaco);
await annotationEditPage.getByTestIdOrAriaLabel(selectors.components.CodeEditor.container).click();
await annotationEditPage.getByGrafanaSelector(selectors.components.CodeEditor.container).click();
await page.keyboard.insertText(`select time as time, humidity as text
from dataset
where $__timeFilter(time) and humidity > 95`);
Expand All @@ -71,7 +71,7 @@ test('should fail and display an error alert box when time field is missing in t
const ds = await readProvisionedDataSource({ fileName: 'datasources.yml' });
await annotationEditPage.datasource.set(ds.name);
await page.waitForFunction(() => window.monaco);
await annotationEditPage.getByTestIdOrAriaLabel(selectors.components.CodeEditor.container).click();
await annotationEditPage.getByGrafanaSelector(selectors.components.CodeEditor.container).click();
await page.keyboard.insertText(`select humidity as text
from dataset
where humidity > 95`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ test('should display Dimensions field only if ListByDimensions is selected', asy
const ds = await readProvisionedDataSource({ fileName: 'datasources.yaml' });
await variableEditPage.setVariableType('Query');
await variableEditPage.datasource.set(ds.name);
const dimensionField = variableEditPage.getByTestIdOrAriaLabel('Dimensions');
const dimensionField = variableEditPage.getByGrafanaSelector('Dimensions');
await expect(dimensionField).not.toBeVisible();
await variableEditPage.getByLabel('Query type').fill('ListByDimensions');
await page.keyboard.press('Enter');
Expand Down Expand Up @@ -61,7 +61,7 @@ test('custom variable query runner should return data when query is valid', asyn
await variableEditPage.setVariableType('Query');
await variableEditPage.datasource.set(ds.name);
const codeEditorSelector = selectors.components.CodeEditor.container;
await variableEditPage.getByTestIdOrAriaLabel(codeEditorSelector).click();
await variableEditPage.getByGrafanaSelector(codeEditorSelector).click();
await page.keyboard.insertText('select distinct(environment) from dataset');
const queryDataRequest = variableEditPage.waitForQueryDataRequest();
await variableEditPage.runQuery();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ test('should filter out govcloud regions', async ({ panelEditPage, selectors, re
await panelEditPage.datasource.set(ds.name);
await panelEditPage.mockResourceResponse('regions', regionsMock);
await panelEditPage.getQueryEditorRow('A').getByText('Regions').click();
await expect(panelEditPage.getByTestIdOrAriaLabel(selectors.components.Select.option)).toHaveText(expectedRegions);
await expect(panelEditPage.getByGrafanaSelector(selectors.components.Select.option)).toHaveText(expectedRegions);
});
```

Expand Down
6 changes: 3 additions & 3 deletions packages/plugin-e2e/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,10 @@ If for example the UI for adding a panel to a dashboard is being changed complet
if (gte(this.ctx.grafanaVersion, '10.4.0')) {
// logic that ensures adding new panels work in Grafana versions greater than or equals to 10.4.0
} else if (gte(this.ctx.grafanaVersion, '10.0.0')) {
await this.getByTestIdOrAriaLabel(components.PageToolbar.itemButton(components.PageToolbar.itemButtonTitle)).click();
await this.getByTestIdOrAriaLabel(pages.AddDashboard.itemButton(pages.AddDashboard.itemButtonAddViz)).click();
await this.getByGrafanaSelector(components.PageToolbar.itemButton(components.PageToolbar.itemButtonTitle)).click();
await this.getByGrafanaSelector(pages.AddDashboard.itemButton(pages.AddDashboard.itemButtonAddViz)).click();
} else {
await this.getByTestIdOrAriaLabel(pages.AddDashboard.addNewPanel).click();
await this.getByGrafanaSelector(pages.AddDashboard.addNewPanel).click();
}
```

Expand Down
2 changes: 1 addition & 1 deletion packages/plugin-e2e/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export const expect = baseExpect.extend({

/** Register a custom selector engine that resolves locators for Grafana E2E selectors
*
* The same functionality is available in the {@link GrafanaPage.getByTestIdOrAriaLabel} method. However,
* The same functionality is available in the {@link GrafanaPage.getByGrafanaSelector} method. However,
* by registering the selector engine, one can resolve locators by Grafana E2E selectors also within a locator.
*
* Example:
Expand Down
2 changes: 1 addition & 1 deletion packages/plugin-e2e/src/matchers/toDisplayPreviews.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const toDisplayPreviews = async (

try {
await expect(
variableEditPage.getByTestIdOrAriaLabel(
variableEditPage.getByGrafanaSelector(
variableEditPage.ctx.selectors.pages.Dashboard.Settings.Variables.Edit.General.previewOfValuesOption
)
).toContainText(previewTexts, options);
Expand Down
2 changes: 1 addition & 1 deletion packages/plugin-e2e/src/matchers/toHaveAlert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export const toHaveAlert = async (grafanaPage: GrafanaPage, severity: AlertVaria

try {
const filteredAlerts = grafanaPage
.getByTestIdOrAriaLabel(grafanaPage.ctx.selectors.components.Alert.alertV2(severity))
.getByGrafanaSelector(grafanaPage.ctx.selectors.components.Alert.alertV2(severity))
.filter({
hasText: options?.hasText,
hasNotText: options?.hasNotText,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export class DataSourcePicker extends GrafanaPage {
* Sets the data source picker to the provided name
*/
async set(name: string) {
await this.getByTestIdOrAriaLabel(this.ctx.selectors.components.DataSourcePicker.container)
await this.getByGrafanaSelector(this.ctx.selectors.components.DataSourcePicker.container)
.locator('input')
.fill(name);

Expand Down
2 changes: 1 addition & 1 deletion packages/plugin-e2e/src/models/components/Panel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export class Panel extends GrafanaPage {
selector = this.ctx.selectors.components.Panels.Panel.headerCornerInfo(ERROR_STATUS);
}

return this.getByTestIdOrAriaLabel(selector, {
return this.getByGrafanaSelector(selector, {
root: this.locator,
});
}
Expand Down
14 changes: 7 additions & 7 deletions packages/plugin-e2e/src/models/components/TimeRange.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,27 +13,27 @@ export class TimeRange extends GrafanaPage {
async set({ from, to, zone }: TimeRangeArgs) {
const { TimeZonePicker, TimePicker } = this.ctx.selectors.components;
try {
await this.getByTestIdOrAriaLabel(TimePicker.openButton).click();
await this.getByGrafanaSelector(TimePicker.openButton).click();
} 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();
}

if (zone) {
const changeTimeSettingsButton = semver.gte(this.ctx.grafanaVersion, '11.0.0')
? this.getByTestIdOrAriaLabel(TimeZonePicker.changeTimeSettingsButton)
? this.getByGrafanaSelector(TimeZonePicker.changeTimeSettingsButton)
: this.ctx.page.getByRole('button', { name: 'Change time settings' });

await changeTimeSettingsButton.click();
await this.getByTestIdOrAriaLabel(TimeZonePicker.containerV2).fill(zone);
await this.getByGrafanaSelector(TimeZonePicker.containerV2).fill(zone);
}
await this.getByTestIdOrAriaLabel(TimePicker.absoluteTimeRangeTitle).click();
const fromField = await this.getByTestIdOrAriaLabel(TimePicker.fromField);
await this.getByGrafanaSelector(TimePicker.absoluteTimeRangeTitle).click();
const fromField = await this.getByGrafanaSelector(TimePicker.fromField);
await fromField.clear();
await fromField.fill(from);
const toField = await this.getByTestIdOrAriaLabel(TimePicker.toField);
const toField = await this.getByGrafanaSelector(TimePicker.toField);
await toField.clear();
await toField.fill(to);
await this.getByTestIdOrAriaLabel(TimePicker.applyTimeRange).click();
await this.getByGrafanaSelector(TimePicker.applyTimeRange).click();
}
}
2 changes: 1 addition & 1 deletion packages/plugin-e2e/src/models/pages/AnnotationEditPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export class AnnotationEditPage extends GrafanaPage {
);

const testButton = semver.gte(this.ctx.grafanaVersion, '11.0.0')
? this.getByTestIdOrAriaLabel(this.ctx.selectors.components.Annotations.editor.testButton)
? this.getByGrafanaSelector(this.ctx.selectors.components.Annotations.editor.testButton)
: this.ctx.page.getByRole('button', { name: 'TEST' });
await testButton.click();
return responsePromise;
Expand Down
6 changes: 3 additions & 3 deletions packages/plugin-e2e/src/models/pages/AnnotationPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,14 @@ export class AnnotationPage extends GrafanaPage {
if (!this.dashboard?.uid) {
//the dashboard doesn't have any annotations yet (except for the built-in one)
if (semver.gte(this.ctx.grafanaVersion, '8.3.0')) {
await this.getByTestIdOrAriaLabel(addAnnotationCTAV2).click();
await this.getByGrafanaSelector(addAnnotationCTAV2).click();
} else {
await this.getByTestIdOrAriaLabel(addAnnotationCTA).click();
await this.getByGrafanaSelector(addAnnotationCTA).click();
}
} else {
//the dashboard already has annotations
const newQueryButton = semver.gte(this.ctx.grafanaVersion, '11.0.0')
? this.getByTestIdOrAriaLabel(addAnnotationCTAV2)
? this.getByGrafanaSelector(addAnnotationCTAV2)
: this.ctx.page.getByRole('button', { name: 'New query' });
await newQueryButton.click();
}
Expand Down
8 changes: 4 additions & 4 deletions packages/plugin-e2e/src/models/pages/DashboardPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export class DashboardPage extends GrafanaPage {
* await expect(panel.fieldNames).toContainText(['time', 'temperature']);
*/
getPanelByTitle(title: string): Panel {
let locator = this.getByTestIdOrAriaLabel(this.ctx.selectors.components.Panels.Panel.title(title), {
let locator = this.getByGrafanaSelector(this.ctx.selectors.components.Panels.Panel.title(title), {
startsWith: true,
});
// in older versions, the panel selector is added to a child element, so we need to go up two levels to get the wrapper
Expand Down Expand Up @@ -81,12 +81,12 @@ export class DashboardPage extends GrafanaPage {
async addPanel(): Promise<PanelEditPage> {
const { components, pages } = this.ctx.selectors;
if (semver.gte(this.ctx.grafanaVersion, '10.0.0')) {
await this.getByTestIdOrAriaLabel(
await this.getByGrafanaSelector(
components.PageToolbar.itemButton(components.PageToolbar.itemButtonTitle)
).click();
await this.getByTestIdOrAriaLabel(pages.AddDashboard.itemButton(pages.AddDashboard.itemButtonAddViz)).click();
await this.getByGrafanaSelector(pages.AddDashboard.itemButton(pages.AddDashboard.itemButtonAddViz)).click();
} else {
await this.getByTestIdOrAriaLabel(pages.AddDashboard.addNewPanel).click();
await this.getByGrafanaSelector(pages.AddDashboard.addNewPanel).click();
}

const panelId = await this.ctx.page.evaluate(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export class DataSourceConfigPage extends GrafanaPage {
);
const healthPath = options?.path ?? health(this.datasource.uid, this.datasource.id.toString());
const healthResponsePromise = this.ctx.page.waitForResponse((resp) => resp.url().includes(healthPath));
await this.getByTestIdOrAriaLabel(this.ctx.selectors.pages.DataSource.saveAndTest).click();
await this.getByGrafanaSelector(this.ctx.selectors.pages.DataSource.saveAndTest).click();
return saveResponsePromise.then(() => healthResponsePromise);
}
}
12 changes: 6 additions & 6 deletions packages/plugin-e2e/src/models/pages/ExplorePage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export class ExplorePage extends GrafanaPage {

private getPanelLocators(suffix: string, text: string) {
const page = this.ctx.page;
let locator = this.getByTestIdOrAriaLabel(this.ctx.selectors.components.Panels.Panel.title(suffix), {
let locator = this.getByGrafanaSelector(this.ctx.selectors.components.Panels.Panel.title(suffix), {
startsWith: true,
});

Expand All @@ -57,8 +57,8 @@ export class ExplorePage extends GrafanaPage {
* Returns the locator for the query editor row with the given refId
*/
getQueryEditorRow(refId: string): Locator {
return this.getByTestIdOrAriaLabel(this.ctx.selectors.components.QueryEditorRows.rows).filter({
has: this.getByTestIdOrAriaLabel(this.ctx.selectors.components.QueryEditorRow.title(refId)),
return this.getByGrafanaSelector(this.ctx.selectors.components.QueryEditorRows.rows).filter({
has: this.getByGrafanaSelector(this.ctx.selectors.components.QueryEditorRow.title(refId)),
});
}

Expand All @@ -72,13 +72,13 @@ export class ExplorePage extends GrafanaPage {
options
);
try {
await this.getByTestIdOrAriaLabel(components.RefreshPicker.runButtonV2).click({
await this.getByGrafanaSelector(components.RefreshPicker.runButtonV2).click({
timeout: 1000,
});
} catch (_) {
// handle the case when the run button is hidden behind the "Show more items" button
await this.getByTestIdOrAriaLabel(components.PageToolbar.item(components.PageToolbar.shotMoreItems)).click();
await this.getByTestIdOrAriaLabel(components.RefreshPicker.runButtonV2).last().click();
await this.getByGrafanaSelector(components.PageToolbar.item(components.PageToolbar.shotMoreItems)).click();
await this.getByGrafanaSelector(components.RefreshPicker.runButtonV2).last().click();
}
return responsePromise;
}
Expand Down
9 changes: 4 additions & 5 deletions packages/plugin-e2e/src/models/pages/GrafanaPage.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Locator, Request, Response } from '@playwright/test';
import { GetByTestIdOrAriaLabelOptions, NavigateOptions, PluginTestCtx } from '../../types';
import { getByGrafanaSelectorOptions, NavigateOptions, PluginTestCtx } from '../../types';

/**
* Base class for all Grafana pages.
Expand All @@ -20,11 +20,10 @@ export abstract class GrafanaPage {
}

/**
* Get a locator for a Grafana element by data-testid or aria-label
* @param selector the data-testid or aria-label of the element
* @param root optional root locator to search within. If no locator is provided, the page will be used
* Get a locator based on a Grafana E2E selector. A grafana E2E selector is defined in @grafana/e2e-selectors or in plugin-e2e/src/e2e-selectors.
* An E2E selector is a string that identifies a specific element in the Grafana UI. The element referencing the E2E selector use the data-testid or aria-label attribute.
*/
getByTestIdOrAriaLabel(selector: string, options?: GetByTestIdOrAriaLabelOptions): Locator {
getByGrafanaSelector(selector: string, options?: getByGrafanaSelectorOptions): Locator {
const startsWith = options?.startsWith ? '^' : '';
if (selector.startsWith('data-testid')) {
return (options?.root || this.ctx.page).locator(`[data-testid${startsWith}="${selector}"]`);
Expand Down
Loading

0 comments on commit cc16775

Please sign in to comment.