Skip to content
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

Plugin E2E: Rename getByTestIdOrAriaLabel to getByGrafanaSelector #832

Merged
merged 5 commits into from
Apr 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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();
}
}
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
Loading