From a12c8ff42cd7eb392e165f2af4edaf129cc213a8 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Mon, 20 Nov 2023 17:03:43 +0100 Subject: [PATCH 01/25] add setup proj that adds data source --- packages/plugin-e2e/playwright.config.ts | 13 +++++++++++-- .../tests/datasource/datasource.setup.ts | 10 ++++++++++ .../plugin-e2e/tests/datasource/datasource.ts | 16 ++++++++++++++++ 3 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 packages/plugin-e2e/tests/datasource/datasource.setup.ts create mode 100644 packages/plugin-e2e/tests/datasource/datasource.ts diff --git a/packages/plugin-e2e/playwright.config.ts b/packages/plugin-e2e/playwright.config.ts index f2153ea07..4e9f42fc0 100644 --- a/packages/plugin-e2e/playwright.config.ts +++ b/packages/plugin-e2e/playwright.config.ts @@ -38,14 +38,23 @@ export default defineConfig({ name: 'authenticate', testMatch: [/.*auth\.setup\.ts/], }, - // 2. Run all tests in parallel with Chrome. + // 2. Create a datasource that can be used across tests (should use provsiioning DS instead, but currently not working in CI) + { + name: 'setupDatasource', + use: { + storageState: 'playwright/.auth/user.json', + }, + testMatch: [/.*datasource\.setup\.ts/], + dependencies: ['authenticate'], + }, + // 3. Run all tests in parallel using Chrome. { name: 'chromium', use: { ...devices['Desktop Chrome'], storageState: 'playwright/.auth/user.json', }, - dependencies: ['authenticate'], + dependencies: ['authenticate', 'setupDatasource'], }, ], }); diff --git a/packages/plugin-e2e/tests/datasource/datasource.setup.ts b/packages/plugin-e2e/tests/datasource/datasource.setup.ts new file mode 100644 index 000000000..0d5472f1b --- /dev/null +++ b/packages/plugin-e2e/tests/datasource/datasource.setup.ts @@ -0,0 +1,10 @@ +import { test as setup } from '../../src'; +import { sheetsDataSource } from './datasource'; + +setup('setupDataSource', async ({ createDataSource }) => { + try { + await createDataSource({ datasource: sheetsDataSource }); + } catch (error) { + console.error(error); + } +}); diff --git a/packages/plugin-e2e/tests/datasource/datasource.ts b/packages/plugin-e2e/tests/datasource/datasource.ts new file mode 100644 index 000000000..b635336e7 --- /dev/null +++ b/packages/plugin-e2e/tests/datasource/datasource.ts @@ -0,0 +1,16 @@ +import { DataSource } from '../../src/types'; + +export const sheetsDataSource: DataSource = { + type: 'grafana-googlesheets-datasource', + name: 'GoogleSheets_E2E', + uid: 'P7DC3E4760CFAC4AHHGGAA', + access: 'proxy', + editable: true, + isDefault: false, + jsonData: { + authType: 'jwt', + }, + secureJsonData: { + jwt: process.env.GOOGLE_JWT_FILE ?? ''.replace(/\\n/g, '\n'), + }, +}; From 03da826fdef481cb2bc569295805014a6e8f6fbf Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Mon, 20 Nov 2023 17:04:21 +0100 Subject: [PATCH 02/25] add custom selector engine --- packages/plugin-e2e/src/api.ts | 11 +++++++++++ packages/plugin-e2e/src/selectorEngine.ts | 22 ++++++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 packages/plugin-e2e/src/selectorEngine.ts diff --git a/packages/plugin-e2e/src/api.ts b/packages/plugin-e2e/src/api.ts index 3eab5ba92..c77b99058 100644 --- a/packages/plugin-e2e/src/api.ts +++ b/packages/plugin-e2e/src/api.ts @@ -63,4 +63,15 @@ export const test = base.extend(fixtures); export const expect = baseExpect.extend(matchers); +/** Register a custom selector engine that resolves locators for Grafana E2E selectors + * + * The same functionality is available in the {@link GrafanaPage.getByTestIdOrAriaLabel} method. However, + * by registering the selector engine, one can resolve locators by Grafana E2E selectors also within a locator. + * + * Example: + * const queryEditorRow = await panelEditPage.getQueryEditorRow('A'); // returns a locator + * queryEditorRow.locator(`selector=${selectors.components.TimePicker.openButton}`).click(); + * */ +selectors.register('selector', grafanaE2ESelectorEngine); + export { selectors } from '@playwright/test'; diff --git a/packages/plugin-e2e/src/selectorEngine.ts b/packages/plugin-e2e/src/selectorEngine.ts new file mode 100644 index 000000000..79fbd2e4f --- /dev/null +++ b/packages/plugin-e2e/src/selectorEngine.ts @@ -0,0 +1,22 @@ +/** + * Returns a selector engine that resolves selectors by data-testid or aria-label + */ +export const grafanaE2ESelectorEngine = () => ({ + // Returns the first element matching given selector in the root's subtree. + query(root: Element, selector: string) { + if (selector.startsWith('data-testid')) { + return root.querySelector(`[data-testid="${selector}"]`); + } + + return root.querySelector(`[aria-label="${selector}"]`); + }, + + // Returns all elements matching given selector in the root's subtree. + queryAll(root: Element, selector: string) { + if (selector.startsWith('data-testid')) { + return root.querySelectorAll(`[data-testid="${selector}"]`); + } + + return root.querySelectorAll(`[aria-label="${selector}"]`); + }, +}); From 3ae22825e7b525b6100b85726dc9e830e984dff4 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Mon, 20 Nov 2023 17:19:12 +0100 Subject: [PATCH 03/25] add new fixtures and models --- packages/plugin-e2e/src/api.ts | 35 +++++++++- .../plugin-e2e/src/e2e-selectors/types.ts | 2 +- .../commands/createDataSourceConfigPage.ts | 2 +- .../src/fixtures/emptyDashboardPage.ts | 17 +++++ packages/plugin-e2e/src/fixtures/index.ts | 6 ++ .../plugin-e2e/src/fixtures/panelEditPage.ts | 13 ++++ .../plugin-e2e/src/models/DashboardPage.ts | 64 +++++++++++++++++ .../plugin-e2e/src/models/DataSourcePicker.ts | 21 ++++++ .../src/models/EmptyDashboardPage.ts | 15 ++++ .../plugin-e2e/src/models/PanelEditPage.ts | 62 ++++++++++++++++ packages/plugin-e2e/src/models/index.ts | 4 ++ packages/plugin-e2e/src/types.ts | 70 ++++++++++++++++++- .../tests/datasource/mocks/resource.ts | 6 ++ 13 files changed, 311 insertions(+), 6 deletions(-) create mode 100644 packages/plugin-e2e/src/fixtures/emptyDashboardPage.ts create mode 100644 packages/plugin-e2e/src/fixtures/panelEditPage.ts create mode 100644 packages/plugin-e2e/src/models/DashboardPage.ts create mode 100644 packages/plugin-e2e/src/models/DataSourcePicker.ts create mode 100644 packages/plugin-e2e/src/models/EmptyDashboardPage.ts create mode 100644 packages/plugin-e2e/src/models/PanelEditPage.ts create mode 100644 packages/plugin-e2e/tests/datasource/mocks/resource.ts diff --git a/packages/plugin-e2e/src/api.ts b/packages/plugin-e2e/src/api.ts index c77b99058..40d0f046a 100644 --- a/packages/plugin-e2e/src/api.ts +++ b/packages/plugin-e2e/src/api.ts @@ -1,9 +1,10 @@ -import { test as base, expect as baseExpect } from '@playwright/test'; +import { test as base, expect as baseExpect, selectors } from '@playwright/test'; import { E2ESelectors } from './e2e-selectors/types'; import fixtures from './fixtures'; -import { DataSourceConfigPage } from './models'; import matchers from './matchers'; -import { CreateDataSourcePageArgs } from './types'; +import { CreateDataSourceArgs, CreateDataSourcePageArgs, DataSource } from './types'; +import { PanelEditPage, GrafanaPage, DataSourceConfigPage, EmptyDashboardPage } from './models'; +import { grafanaE2ESelectorEngine } from './selectorEngine'; export type PluginOptions = { selectorRegistration: void; @@ -23,6 +24,25 @@ export type PluginFixture = { */ selectors: E2ESelectors; + /** + * Isolated {@link EmptyDashboardPage} instance for each test. + * + * Navigates to a new dashboard page. + */ + emptyDashboardPage: EmptyDashboardPage; + + /** + * Isolated {@link PanelEditPage} instance for each test. + * + * Navigates to a new dashboard page and adds a new panel. + * + * Use {@link PanelEditPage.setVisualization} to change the visualization + * Use {@link PanelEditPage.datasource.set} to change the + * Use {@link ExplorePage.getQueryEditorEditorRow} to retrieve the query + * editor row locator for a given query refId + */ + panelEditPage: PanelEditPage; + /** * Fixture command that will create an isolated DataSourceConfigPage instance for a given data source type. * @@ -31,6 +51,15 @@ export type PluginFixture = { */ createDataSourceConfigPage: (args: CreateDataSourcePageArgs) => Promise; + /** + * Fixture command that create a data source via the Grafana API. + * + * If you have tests that depend on the the existance of a data source, + * you may use this command in a setup project. Read more about setup projects + * here: https://playwright.dev/docs/auth#basic-shared-account-in-all-tests + */ + createDataSource: (args: CreateDataSourceArgs) => Promise; + /** * Fixture command that login to Grafana using the Grafana API. * If the same credentials should be used in every test, diff --git a/packages/plugin-e2e/src/e2e-selectors/types.ts b/packages/plugin-e2e/src/e2e-selectors/types.ts index 808e2bf2f..2c22301e0 100644 --- a/packages/plugin-e2e/src/e2e-selectors/types.ts +++ b/packages/plugin-e2e/src/e2e-selectors/types.ts @@ -6,7 +6,7 @@ export type E2ESelectors = { export type APIs = { DataSource: { - getResource: string; + resource: string; healthCheck: string; }; }; diff --git a/packages/plugin-e2e/src/fixtures/commands/createDataSourceConfigPage.ts b/packages/plugin-e2e/src/fixtures/commands/createDataSourceConfigPage.ts index a9ac7b4ce..b0d7c1a18 100644 --- a/packages/plugin-e2e/src/fixtures/commands/createDataSourceConfigPage.ts +++ b/packages/plugin-e2e/src/fixtures/commands/createDataSourceConfigPage.ts @@ -27,7 +27,7 @@ const createDataSourceConfigPage: CreateDataSourceConfigPageFixture = async ( await datasourceConfigPage.goto(); return datasourceConfigPage; }); - deleteDataSource && datasourceConfigPage?.deleteDataSource(); + deleteDataSource && (await datasourceConfigPage?.deleteDataSource()); }; export default createDataSourceConfigPage; diff --git a/packages/plugin-e2e/src/fixtures/emptyDashboardPage.ts b/packages/plugin-e2e/src/fixtures/emptyDashboardPage.ts new file mode 100644 index 000000000..97f7e8297 --- /dev/null +++ b/packages/plugin-e2e/src/fixtures/emptyDashboardPage.ts @@ -0,0 +1,17 @@ +import { TestFixture, expect } from '@playwright/test'; +import { PluginFixture, PluginOptions } from '../api'; +import { EmptyDashboardPage } from '../models'; +import { PlaywrightCombinedArgs } from './types'; + +type EmptyDashboardPageFixture = TestFixture< + EmptyDashboardPage, + PluginFixture & PluginOptions & PlaywrightCombinedArgs +>; + +const emptyDashboardPage: EmptyDashboardPageFixture = async ({ page, request, selectors, grafanaVersion }, use) => { + const emptyDashboardPage = new EmptyDashboardPage({ page, selectors, grafanaVersion, request }, expect); + await emptyDashboardPage.goto(); + await use(emptyDashboardPage); +}; + +export default emptyDashboardPage; diff --git a/packages/plugin-e2e/src/fixtures/index.ts b/packages/plugin-e2e/src/fixtures/index.ts index 30b62f938..5b04efce9 100644 --- a/packages/plugin-e2e/src/fixtures/index.ts +++ b/packages/plugin-e2e/src/fixtures/index.ts @@ -2,10 +2,16 @@ import grafanaVersion from './grafanaVersion'; import selectors from './selectors'; import login from './commands/login'; import createDataSourceConfigPage from './commands/createDataSourceConfigPage'; +import panelEditPage from './panelEditPage'; +import createDataSource from './commands/createDataSource'; +import emptyDashboardPage from './emptyDashboardPage'; export default { selectors, grafanaVersion, login, createDataSourceConfigPage, + panelEditPage, + createDataSource, + emptyDashboardPage, }; diff --git a/packages/plugin-e2e/src/fixtures/panelEditPage.ts b/packages/plugin-e2e/src/fixtures/panelEditPage.ts new file mode 100644 index 000000000..147d8c906 --- /dev/null +++ b/packages/plugin-e2e/src/fixtures/panelEditPage.ts @@ -0,0 +1,13 @@ +import { TestFixture } from '@playwright/test'; +import { PluginFixture, PluginOptions } from '../api'; +import { PanelEditPage } from '../models'; +import { PlaywrightCombinedArgs } from './types'; + +type PanelEditPageFixture = TestFixture; + +const panelEditPage: PanelEditPageFixture = async ({ emptyDashboardPage }, use) => { + const panelEditPage = await emptyDashboardPage.addPanel(); + await use(panelEditPage); +}; + +export default panelEditPage; diff --git a/packages/plugin-e2e/src/models/DashboardPage.ts b/packages/plugin-e2e/src/models/DashboardPage.ts new file mode 100644 index 000000000..6c359d6a3 --- /dev/null +++ b/packages/plugin-e2e/src/models/DashboardPage.ts @@ -0,0 +1,64 @@ +const gte = require('semver/functions/gte'); + +import { GotoDashboardArgs, PluginTestCtx } from '../types'; + +import { Expect } from '@playwright/test'; +import { DataSourcePicker } from './DataSourcePicker'; +import { GrafanaPage } from './GrafanaPage'; +import { PanelEditPage } from './PanelEditPage'; +import { TimeRange } from './TimeRange'; + +export class DashboardPage extends GrafanaPage { + dataSourcePicker: any; + timeRange: TimeRange; + + constructor(ctx: PluginTestCtx, expect: Expect, protected readonly dashboardUid?: string) { + super(ctx, expect); + this.dataSourcePicker = new DataSourcePicker(ctx, expect); + this.timeRange = new TimeRange(ctx, this.expect); + } + + async goto(opts?: GotoDashboardArgs) { + let url = this.ctx.selectors.pages.Dashboard.url(opts?.uid ?? this.dashboardUid ?? ''); + if (opts?.queryParams) { + url += `?${opts.queryParams.toString()}`; + } + await this.ctx.page.goto(url, { + waitUntil: 'networkidle', + }); + if (opts?.timeRange) { + await this.timeRange.set(opts.timeRange); + } + } + + async gotoPanelEditPage(panelId: string) { + const url = this.ctx.selectors.pages.Dashboard.url(this.dashboardUid ?? ''); + await this.ctx.page.goto(`${url}?editPanel=${panelId}`, { + waitUntil: 'networkidle', + }); + return new PanelEditPage(this.ctx, this.expect); + } + + async addPanel(): Promise { + if (gte(this.ctx.grafanaVersion, '10.0.0')) { + //TODO: add new selector and use it in grafana/ui + const title = gte(this.ctx.grafanaVersion, '10.1.0') ? 'Add button' : 'Add panel button'; + await this.getByTestIdOrAriaLabel(this.ctx.selectors.components.PageToolbar.itemButton(title)).click(); + await this.getByTestIdOrAriaLabel( + this.ctx.selectors.pages.AddDashboard.itemButton('Add new visualization menu item') + ).click(); + } else { + await this.getByTestIdOrAriaLabel(this.ctx.selectors.pages.AddDashboard.addNewPanel).click(); + } + + return new PanelEditPage(this.ctx, this.expect); + } + + async deleteDashboard() { + await this.ctx.request.delete(`/api/datasources/uid/${this.dashboardUid}`); + } + + async refreshDashboard() { + await this.ctx.page.getByTestId(this.ctx.selectors.components.RefreshPicker.runButtonV2).click(); + } +} diff --git a/packages/plugin-e2e/src/models/DataSourcePicker.ts b/packages/plugin-e2e/src/models/DataSourcePicker.ts new file mode 100644 index 000000000..63246d8e7 --- /dev/null +++ b/packages/plugin-e2e/src/models/DataSourcePicker.ts @@ -0,0 +1,21 @@ +import { Expect } from '@playwright/test'; +import { PluginTestCtx } from '../types'; +import { GrafanaPage } from './GrafanaPage'; + +export class DataSourcePicker extends GrafanaPage { + constructor(ctx: PluginTestCtx, expect: Expect) { + super(ctx, expect); + } + + async set(name: string) { + await this.getByTestIdOrAriaLabel(this.ctx.selectors.components.DataSourcePicker.container) + .locator('input') + .fill(name); + + // this is a hack to get the selection to work in 10.ish versions of Grafana. + // TODO: investigate if the select component can somehow be refactored so that its easier to interact with + await this.ctx.page.keyboard.press('ArrowDown'); + await this.ctx.page.keyboard.press('ArrowUp'); + await this.ctx.page.keyboard.press('Enter'); + } +} diff --git a/packages/plugin-e2e/src/models/EmptyDashboardPage.ts b/packages/plugin-e2e/src/models/EmptyDashboardPage.ts new file mode 100644 index 000000000..640c5a4f0 --- /dev/null +++ b/packages/plugin-e2e/src/models/EmptyDashboardPage.ts @@ -0,0 +1,15 @@ +import { Expect } from '@playwright/test'; +import { PluginTestCtx } from '../types'; +import { DashboardPage } from './DashboardPage'; + +export class EmptyDashboardPage extends DashboardPage { + constructor(ctx: PluginTestCtx, expect: Expect) { + super(ctx, expect); + } + + async goto() { + await this.ctx.page.goto(this.ctx.selectors.pages.AddDashboard.url, { + waitUntil: 'networkidle', + }); + } +} diff --git a/packages/plugin-e2e/src/models/PanelEditPage.ts b/packages/plugin-e2e/src/models/PanelEditPage.ts new file mode 100644 index 000000000..fb251ad57 --- /dev/null +++ b/packages/plugin-e2e/src/models/PanelEditPage.ts @@ -0,0 +1,62 @@ +import * as semver from 'semver'; +import { Expect, Locator } from '@playwright/test'; + +import { PanelError, PluginTestCtx, RequestOptions, Visualization } from '../types'; +import { DataSourcePicker } from './DataSourcePicker'; +import { GrafanaPage } from './GrafanaPage'; +import { TimeRange } from './TimeRange'; + +export class PanelEditPage extends GrafanaPage implements PanelError { + datasource: DataSourcePicker; + timeRange: TimeRange; + + constructor(ctx: PluginTestCtx, expect: Expect) { + super(ctx, expect); + this.datasource = new DataSourcePicker(ctx, expect); + this.timeRange = new TimeRange(ctx, this.expect); + } + + async setVisualization(visualization: Visualization) { + await this.getByTestIdOrAriaLabel(this.ctx.selectors.components.PanelEditor.toggleVizPicker).click(); + await this.getByTestIdOrAriaLabel(this.ctx.selectors.components.PluginVisualization.item(visualization)).click(); + await this.expect( + this.getByTestIdOrAriaLabel(this.ctx.selectors.components.PanelEditor.toggleVizPicker), + `Could not set visualization to ${visualization}. Ensure the panel is installed.` + ).toHaveText(visualization); + } + + async apply() { + await this.ctx.page.getByTestId(this.ctx.selectors.components.PanelEditor.applyButton).click(); + } + + getQueryEditorRow(refId: string): Locator { + //TODO: add new selector and use it in grafana/ui + const locator = this.ctx.page.locator('[aria-label="Query editor row"]').filter({ + has: this.ctx.page.locator(`[aria-label="Query editor row title ${refId}"]`), + }); + this.expect(locator).toBeVisible(); + return locator; + } + + getPanelError() { + // the selector (not the selector value) used to identify a panel error changed in 9.4.3 and in 10.1.5 + let selector = this.ctx.selectors.components.Panels.Panel.status('error'); + if (semver.lte(this.ctx.grafanaVersion, '9.4.3')) { + selector = this.ctx.selectors.components.Panels.Panel.headerCornerInfo('error'); + } else if (semver.lte(this.ctx.grafanaVersion, '10.1.5')) { + selector = 'Panel status'; + } + + return this.getByTestIdOrAriaLabel(selector); + } + + async refreshPanel(options?: RequestOptions) { + const responsePromise = this.ctx.page.waitForResponse((resp) => resp.url().includes('/query'), options); + // in older versions of grafana, the refresh button is rendered twice. this is a workaround to click the correct one + await this.getByTestIdOrAriaLabel(this.ctx.selectors.components.PanelEditor.General.content) + .locator(`selector=${this.ctx.selectors.components.RefreshPicker.runButtonV2}`) + .click(); + + return responsePromise; + } +} diff --git a/packages/plugin-e2e/src/models/index.ts b/packages/plugin-e2e/src/models/index.ts index 7cea80c36..7aff76777 100644 --- a/packages/plugin-e2e/src/models/index.ts +++ b/packages/plugin-e2e/src/models/index.ts @@ -1 +1,5 @@ export { DataSourceConfigPage } from './DataSourceConfigPage'; +export { PanelEditPage } from './PanelEditPage'; +export { DashboardPage } from './DashboardPage'; +export { EmptyDashboardPage } from './EmptyDashboardPage'; +export { GrafanaPage } from './GrafanaPage'; diff --git a/packages/plugin-e2e/src/types.ts b/packages/plugin-e2e/src/types.ts index 842295d37..a3d1d7304 100644 --- a/packages/plugin-e2e/src/types.ts +++ b/packages/plugin-e2e/src/types.ts @@ -1,4 +1,4 @@ -import { PlaywrightTestArgs } from '@playwright/test'; +import { Locator, PlaywrightTestArgs } from '@playwright/test'; import { E2ESelectors } from './e2e-selectors/types'; @@ -50,3 +50,71 @@ export type CreateDataSourcePageArgs = { */ deleteDataSourceAfterTest?: boolean; }; + +export type RequestOptions = { + /** + * Maximum wait time in milliseconds, defaults to 30 seconds, pass `0` to disable the timeout. The default value can + * be changed by using the + * [browserContext.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-browsercontext#browser-context-set-default-timeout) + * or [page.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-page#page-set-default-timeout) methods. + */ + timeout?: number; +}; + +export interface TimeRangeArgs { + /** + * The from time + * @example 'now-6h' + * @example '2020-01-01 00:00:00' + */ + from: string; + /** + * The to time + * @example 'now' + * @example '2020-01-01 00:00:00' + */ + to: string; + /** + * The time zone + * @example 'utc' + * @example 'browser' + */ + zone?: string; +} + +export type GotoDashboardArgs = { + /** + * The uid of the dashboard to go to + */ + uid?: string; + /** + * The time range to set + */ + timeRange?: TimeRangeArgs; + /** + * Query parameters to add to the url + */ + queryParams?: URLSearchParams; +}; + +/** + * Panel visualization types + */ +export type Visualization = + | 'Alert list' + | 'Bar gauge' + | 'Clock' + | 'Dashboard list' + | 'Gauge' + | 'Graph' + | 'Heatmap' + | 'Logs' + | 'News' + | 'Pie Chart' + | 'Plugin list' + | 'Polystat' + | 'Stat' + | 'Table' + | 'Text' + | 'Time series' + | 'Worldmap Panel'; diff --git a/packages/plugin-e2e/tests/datasource/mocks/resource.ts b/packages/plugin-e2e/tests/datasource/mocks/resource.ts new file mode 100644 index 000000000..c720173a5 --- /dev/null +++ b/packages/plugin-e2e/tests/datasource/mocks/resource.ts @@ -0,0 +1,6 @@ +export const GOOGLE_SHEETS_SPREADSHEETS = { + spreadsheets: { + sheet1: 'Datasource test spreadsheet', + sheet2: 'Google Sheets Datasource - Average Temperature', + }, +}; From f19ab91dfc53985a48ebf069848683229c250175 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Tue, 21 Nov 2023 08:37:25 +0100 Subject: [PATCH 04/25] internalize certain types --- packages/plugin-e2e/src/types.ts | 11 +++++++++++ packages/plugin-e2e/tsconfig.json | 3 ++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/plugin-e2e/src/types.ts b/packages/plugin-e2e/src/types.ts index a3d1d7304..f3557e175 100644 --- a/packages/plugin-e2e/src/types.ts +++ b/packages/plugin-e2e/src/types.ts @@ -118,3 +118,14 @@ export type Visualization = | 'Text' | 'Time series' | 'Worldmap Panel'; + +/** + * Implement this interface in a POM in case you want to enable the `toHavePanelError` matcher for the page. + * Only applicable to pages that have one panel only, such as the explore page or panel edit page. + * + * @internal + */ +export interface PanelError { + ctx: PluginTestCtx; + getPanelError: () => Locator; +} diff --git a/packages/plugin-e2e/tsconfig.json b/packages/plugin-e2e/tsconfig.json index 261fe8b17..6aae42c27 100644 --- a/packages/plugin-e2e/tsconfig.json +++ b/packages/plugin-e2e/tsconfig.json @@ -1,7 +1,8 @@ { "compilerOptions": { "outDir": "./dist", - "declaration": true + "declaration": true, + "stripInternal": true }, "exclude": ["node_modules"], "extends": "../../tsconfig.base.json", From 8ec62370db489e60b9a938e5475289aa2941d0ad Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Tue, 21 Nov 2023 08:37:38 +0100 Subject: [PATCH 05/25] add custom matcher for panel error --- packages/plugin-e2e/src/matchers/index.ts | 2 ++ .../src/matchers/toHavePanelError.ts | 29 +++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 packages/plugin-e2e/src/matchers/toHavePanelError.ts diff --git a/packages/plugin-e2e/src/matchers/index.ts b/packages/plugin-e2e/src/matchers/index.ts index c596330d1..43c3553ca 100644 --- a/packages/plugin-e2e/src/matchers/index.ts +++ b/packages/plugin-e2e/src/matchers/index.ts @@ -1,5 +1,7 @@ import toBeOK from './toBeOK'; +import { toHavePanelError } from './toHavePanelError'; export default { toBeOK, + toHavePanelError, }; diff --git a/packages/plugin-e2e/src/matchers/toHavePanelError.ts b/packages/plugin-e2e/src/matchers/toHavePanelError.ts new file mode 100644 index 000000000..d48b89de1 --- /dev/null +++ b/packages/plugin-e2e/src/matchers/toHavePanelError.ts @@ -0,0 +1,29 @@ +import { expect } from '@playwright/test'; +import { PanelError } from '../types'; +import { getMessage } from './utils'; + +/** + * Hello + */ +export const toHavePanelError = async (panelError: PanelError, options?: { timeout?: number }) => { + let pass = true; + let actual; + let message: any = 'A panel error to be displayed'; + + try { + const numberOfErrors = await panelError.getPanelError().count(); + await expect(numberOfErrors).toBe(1); + } catch (_) { + message = getMessage(message, 'No panel error was found on the page'); + actual = await panelError.getPanelError().count(); + pass = false; + } + + return { + message: () => message, + pass, + actual, + }; +}; + +// export default toHavePanelError; From e575dcfabb880adaf2dff53455215d4e34893d12 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Tue, 21 Nov 2023 08:37:55 +0100 Subject: [PATCH 06/25] cleanup --- packages/plugin-e2e/src/models/GrafanaPage.ts | 17 ++++++- packages/plugin-e2e/src/models/TimeRange.ts | 47 +++++++++++++++++++ 2 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 packages/plugin-e2e/src/models/TimeRange.ts diff --git a/packages/plugin-e2e/src/models/GrafanaPage.ts b/packages/plugin-e2e/src/models/GrafanaPage.ts index ac1505b8a..74465e27f 100644 --- a/packages/plugin-e2e/src/models/GrafanaPage.ts +++ b/packages/plugin-e2e/src/models/GrafanaPage.ts @@ -1,4 +1,4 @@ -import { Expect, Locator } from '@playwright/test'; +import { Expect, Locator, Request } from '@playwright/test'; import { PluginTestCtx } from '../types'; /** @@ -40,8 +40,21 @@ export abstract class GrafanaPage { * @param status the HTTP status code to return. Defaults to 200 */ async mockResourceResponse(path: string, json: T, status = 200) { - await this.ctx.page.route(`${this.ctx.selectors.apis.DataSource.getResource}/${path}`, async (route) => { + await this.ctx.page.route(`${this.ctx.selectors.apis.DataSource.resource}/${path}`, async (route) => { await route.fulfill({ json, status }); }); } + + /** + * Waits for a data source query data request to be made. + * + * @param cb optional callback to filter the request. Use this to filter by request body or other request properties + */ + async waitForQueryDataRequest(cb?: (request: Request) => boolean | Promise) { + return this.ctx.page.waitForRequest((request) => { + if (request.url().includes('api/ds/query') && request.method() === 'POST') { + return cb ? cb(request) : true; + } + }); + } } diff --git a/packages/plugin-e2e/src/models/TimeRange.ts b/packages/plugin-e2e/src/models/TimeRange.ts new file mode 100644 index 000000000..95d2f0b8a --- /dev/null +++ b/packages/plugin-e2e/src/models/TimeRange.ts @@ -0,0 +1,47 @@ +import { Expect } from '@playwright/test'; +import { PluginTestCtx, TimeRangeArgs } from '../types'; +import { GrafanaPage } from './GrafanaPage'; + +export class TimeRange extends GrafanaPage { + constructor(ctx: PluginTestCtx, expect: Expect) { + super(ctx, expect); + } + + async set({ from, to, zone }: TimeRangeArgs) { + try { + await this.ctx.page + .getByLabel(this.ctx.selectors.components.PanelEditor.General.content) + .locator(`selector=${this.ctx.selectors.components.TimePicker.openButton}`) + .click({ force: true, timeout: 2000 }); + } 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) { + await this.ctx.page.getByRole('button', { name: 'Change time settings' }).click(); + await this.getByTestIdOrAriaLabel(this.ctx.selectors.components.TimeZonePicker.containerV2).fill(zone); + } + await this.getByTestIdOrAriaLabel(this.ctx.selectors.components.TimePicker.absoluteTimeRangeTitle).click(); + const fromField = await this.getByTestIdOrAriaLabel(this.ctx.selectors.components.TimePicker.fromField); + await fromField.clear(); + await fromField.fill(from); + const toField = await this.getByTestIdOrAriaLabel(this.ctx.selectors.components.TimePicker.toField); + await toField.clear(); + await toField.fill(to); + await this.getByTestIdOrAriaLabel(this.ctx.selectors.components.TimePicker.applyTimeRange).click(); + + await this.expect + .soft( + this.ctx.page.getByLabel(this.ctx.selectors.components.PanelEditor.General.content).getByText(from), + 'Could not set "from" in dashboard time range picker' + ) + .toBeVisible(); + await this.expect + .soft( + this.ctx.page.getByLabel(this.ctx.selectors.components.PanelEditor.General.content).getByText(to), + 'Could not set "to" in dashboard time range picker' + ) + .toBeVisible(); + } +} From 87af085309cba17ee081db1474f7af070f76144e Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Tue, 21 Nov 2023 08:38:04 +0100 Subject: [PATCH 07/25] add tests --- .../queryEditor.integration.spec.ts | 31 +++++++++++++++++++ .../tests/datasource/queryEditor.spec.ts | 28 +++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 packages/plugin-e2e/tests/datasource/queryEditor.integration.spec.ts create mode 100644 packages/plugin-e2e/tests/datasource/queryEditor.spec.ts diff --git a/packages/plugin-e2e/tests/datasource/queryEditor.integration.spec.ts b/packages/plugin-e2e/tests/datasource/queryEditor.integration.spec.ts new file mode 100644 index 000000000..ab2724b97 --- /dev/null +++ b/packages/plugin-e2e/tests/datasource/queryEditor.integration.spec.ts @@ -0,0 +1,31 @@ +import { expect, test } from '../../src'; +import { sheetsDataSource } from './datasource'; + +test('should return data and not display panel error when a valid query is provided', async ({ + panelEditPage, + page, +}) => { + await panelEditPage.datasource.set(sheetsDataSource.name!); + await panelEditPage.timeRange.set({ from: '2019-01-11', to: '2019-12-15' }); + const queryEditorRow = await panelEditPage.getQueryEditorRow('A'); + await queryEditorRow.getByText('Enter SpreadsheetID').click(); + await page.keyboard.insertText('1TZlZX67Y0s4CvRro_3pCYqRCKuXer81oFp_xcsjPpe8'); + await page.keyboard.press('Enter'); + await expect(panelEditPage.refreshPanel()).toBeOK(); + await expect(panelEditPage).not.toHavePanelError(); +}); + +test('should return an error and display panel error when an invalid query is provided', async ({ + panelEditPage, + page, +}) => { + await panelEditPage.datasource.set(sheetsDataSource.name!); + await panelEditPage.timeRange.set({ from: '2019-01-11', to: '2019-12-15' }); + const queryEditorRow = await panelEditPage.getQueryEditorRow('A'); + await queryEditorRow.getByText('Enter SpreadsheetID').click(); + await page.keyboard.insertText('1TZlZX67Y0s4CvRro_3pCYqRCKuXer81oFp_xcsjPpe8'); + await page.keyboard.press('Enter'); + await page.getByPlaceholder('ie: Class Data!A2:E').fill('invalid range'); + await expect(panelEditPage.refreshPanel()).not.toBeOK(); + await expect(panelEditPage).toHavePanelError(); +}); diff --git a/packages/plugin-e2e/tests/datasource/queryEditor.spec.ts b/packages/plugin-e2e/tests/datasource/queryEditor.spec.ts new file mode 100644 index 000000000..3823874bf --- /dev/null +++ b/packages/plugin-e2e/tests/datasource/queryEditor.spec.ts @@ -0,0 +1,28 @@ +import { expect, test } from '../../src'; +import { sheetsDataSource } from './datasource'; +import { GOOGLE_SHEETS_SPREADSHEETS } from './mocks/resource'; + +test('should list spreadsheets when clicking on spreadsheet segment', async ({ panelEditPage, page }) => { + await panelEditPage.datasource.set(sheetsDataSource.name!); + const queryEditorRow = await panelEditPage.getQueryEditorRow('A'); + await panelEditPage.mockResourceResponse('spreadsheets', GOOGLE_SHEETS_SPREADSHEETS); + await queryEditorRow.getByText('Enter SpreadsheetID').click(); + await expect(page.getByText(GOOGLE_SHEETS_SPREADSHEETS.spreadsheets.sheet1, { exact: true })).toHaveCount(1); + await expect(page.getByText(GOOGLE_SHEETS_SPREADSHEETS.spreadsheets.sheet2, { exact: true })).toHaveCount(1); +}); + +test('should set correct cache time on query passed to the backend', async ({ panelEditPage, page }) => { + await panelEditPage.datasource.set(sheetsDataSource.name!); + const queryEditorRow = await panelEditPage.getQueryEditorRow('A'); + await panelEditPage.mockResourceResponse('spreadsheets', GOOGLE_SHEETS_SPREADSHEETS); + await queryEditorRow.getByRole('button', { name: '5m' }).click(); + await page.keyboard.insertText('1h'); + await page.keyboard.press('Enter'); + + const queryReq = panelEditPage.waitForQueryDataRequest((request) => + (request.postData() ?? '').includes('"cacheDurationSeconds":3600') + ); + + await panelEditPage.refreshPanel(); + await expect(await queryReq).toBeTruthy(); +}); From 2934f4540b4ae3f29ef3ec862eb8d746dc53a09f Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Tue, 21 Nov 2023 08:43:08 +0100 Subject: [PATCH 08/25] fix request --- packages/plugin-e2e/src/models/GrafanaPage.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/plugin-e2e/src/models/GrafanaPage.ts b/packages/plugin-e2e/src/models/GrafanaPage.ts index 74465e27f..5a21bf9a8 100644 --- a/packages/plugin-e2e/src/models/GrafanaPage.ts +++ b/packages/plugin-e2e/src/models/GrafanaPage.ts @@ -55,6 +55,7 @@ export abstract class GrafanaPage { if (request.url().includes('api/ds/query') && request.method() === 'POST') { return cb ? cb(request) : true; } + return false; }); } } From 50752f34716247a177b0029b48a8766771354504 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Tue, 21 Nov 2023 09:23:30 +0100 Subject: [PATCH 09/25] include ds version in multi dimensional matrix --- .github/workflows/playwright.yml | 6 ++++-- packages/plugin-e2e/docker-compose.yaml | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index d42fee048..98ebe26df 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -11,6 +11,8 @@ jobs: fail-fast: false matrix: GRAFANA_VERSION: ['latest', '10.0.5', '9.5.5', '9.2.5'] + GOOGLE_SHEETS_VERSION: ['1.2.4', '1.2.0'] + name: E2E Tests - Grafana${{ matrix.GRAFANA_VERSION }} GoogleSheets${{ matrix.GOOGLE_SHEETS_VERSION }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 @@ -31,7 +33,7 @@ jobs: run: npx playwright install --with-deps chromium - name: Start Grafana - run: docker run --rm -d -p 3000:3000 --name=grafana --env GF_INSTALL_PLUGINS=grafana-googlesheets-datasource grafana/grafana:${{ matrix.GRAFANA_VERSION }}; sleep 30 + run: docker run --rm -d -p 3000:3000 --name=grafana --env "GF_INSTALL_PLUGINS=grafana-googlesheets-datasource ${{matrix.GOOGLE_SHEETS_VERSION}}" grafana/grafana:${{ matrix.GRAFANA_VERSION }}; sleep 30 - name: Run Playwright tests run: npm run playwright:test --w @grafana/plugin-e2e @@ -41,6 +43,6 @@ jobs: - uses: actions/upload-artifact@v3 if: always() with: - name: playwright-report-${{ matrix.GRAFANA_VERSION }} + name: playwright-report-Grafana${{ matrix.GRAFANA_VERSION }}-GoogleSheets${{ matrix.GOOGLE_SHEETS_VERSION }} path: packages/plugin-e2e/playwright-report/ retention-days: 30 diff --git a/packages/plugin-e2e/docker-compose.yaml b/packages/plugin-e2e/docker-compose.yaml index 43f5787f9..df6f40dbe 100644 --- a/packages/plugin-e2e/docker-compose.yaml +++ b/packages/plugin-e2e/docker-compose.yaml @@ -4,7 +4,7 @@ services: grafana: image: grafana/${GRAFANA_IMAGE:-grafana-enterprise}:${GRAFANA_VERSION:-main} environment: - - GF_INSTALL_PLUGINS=grafana-clock-panel,grafana-googlesheets-datasource + - GF_INSTALL_PLUGINS=grafana-clock-panel,grafana-googlesheets-datasource 1.2.4 - GF_AUTH_ANONYMOUS_ENABLED=true - GF_AUTH_ANONYMOUS_ORG_ROLE=Admin - GF_AUTH_ANONYMOUS_ORG_NAME=Main Org. From 1f717b062366f65b4e3597cac837757d13a8687d Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Tue, 21 Nov 2023 09:23:44 +0100 Subject: [PATCH 10/25] use props ds name --- packages/plugin-e2e/src/fixtures/commands/createDataSource.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/plugin-e2e/src/fixtures/commands/createDataSource.ts b/packages/plugin-e2e/src/fixtures/commands/createDataSource.ts index e707f46a7..3402b6048 100644 --- a/packages/plugin-e2e/src/fixtures/commands/createDataSource.ts +++ b/packages/plugin-e2e/src/fixtures/commands/createDataSource.ts @@ -33,7 +33,7 @@ export const createDataSourceViaAPI = async ( const text = await createDsReq.text(); const status = await createDsReq.status(); if (status === 200) { - console.log('Data source created: ', name); + console.log('Data source created: ', dsName); return createDsReq.json().then((r) => r.datasource); } From 51be95bb7f7b381db7c38c5bdc356490fb56db36 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Tue, 21 Nov 2023 09:24:08 +0100 Subject: [PATCH 11/25] remove example test --- packages/plugin-e2e/tests/selectors.spec.ts | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 packages/plugin-e2e/tests/selectors.spec.ts diff --git a/packages/plugin-e2e/tests/selectors.spec.ts b/packages/plugin-e2e/tests/selectors.spec.ts deleted file mode 100644 index e804adbc5..000000000 --- a/packages/plugin-e2e/tests/selectors.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import * as semver from 'semver'; -import { test, expect } from '../src'; - -test('should resolve ds picker selector with test id for grafana 10 and later', async ({ - grafanaVersion, - selectors, -}, testInfo) => { - testInfo.skip(semver.lt(grafanaVersion, '10.0.0')); - expect(selectors.components.DataSourcePicker.container).toBe('data-testid Data source picker select container'); -}); - -test('should resolve ds picker selector without test id for grafana 10 and later', async ({ - grafanaVersion, - selectors, -}, testInfo) => { - testInfo.skip(semver.gte(grafanaVersion, '10.0.0')); - expect(selectors.components.DataSourcePicker.container).toBe('Data source picker select container'); -}); From f55f80b3743a65adacfe2b8557b8fbb62a681620 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Tue, 21 Nov 2023 09:24:17 +0100 Subject: [PATCH 12/25] use better selector --- packages/plugin-e2e/tests/datasource/queryEditor.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/plugin-e2e/tests/datasource/queryEditor.spec.ts b/packages/plugin-e2e/tests/datasource/queryEditor.spec.ts index 3823874bf..d2075dfbb 100644 --- a/packages/plugin-e2e/tests/datasource/queryEditor.spec.ts +++ b/packages/plugin-e2e/tests/datasource/queryEditor.spec.ts @@ -15,7 +15,7 @@ test('should set correct cache time on query passed to the backend', async ({ pa await panelEditPage.datasource.set(sheetsDataSource.name!); const queryEditorRow = await panelEditPage.getQueryEditorRow('A'); await panelEditPage.mockResourceResponse('spreadsheets', GOOGLE_SHEETS_SPREADSHEETS); - await queryEditorRow.getByRole('button', { name: '5m' }).click(); + await queryEditorRow.getByText('5m', { exact: true }).click(); await page.keyboard.insertText('1h'); await page.keyboard.press('Enter'); From 8f4c157ff946e745e0b011b6fa40d99b07a70216 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Tue, 21 Nov 2023 09:30:21 +0100 Subject: [PATCH 13/25] improve name --- .github/workflows/playwright.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 98ebe26df..52a0f482f 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -12,7 +12,7 @@ jobs: matrix: GRAFANA_VERSION: ['latest', '10.0.5', '9.5.5', '9.2.5'] GOOGLE_SHEETS_VERSION: ['1.2.4', '1.2.0'] - name: E2E Tests - Grafana${{ matrix.GRAFANA_VERSION }} GoogleSheets${{ matrix.GOOGLE_SHEETS_VERSION }} + name: E2E Tests - Grafana@${{ matrix.GRAFANA_VERSION }} GoogleSheets@${{ matrix.GOOGLE_SHEETS_VERSION }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 From 0c02c26198234964d57a75794260a8ff1411b204 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Tue, 21 Nov 2023 09:43:07 +0100 Subject: [PATCH 14/25] tidy up --- packages/plugin-e2e/src/api.ts | 2 +- packages/plugin-e2e/src/matchers/index.ts | 2 +- packages/plugin-e2e/src/matchers/toHavePanelError.ts | 7 ++----- packages/plugin-e2e/src/models/DataSourcePicker.ts | 2 +- packages/plugin-e2e/src/models/PanelEditPage.ts | 3 +-- 5 files changed, 6 insertions(+), 10 deletions(-) diff --git a/packages/plugin-e2e/src/api.ts b/packages/plugin-e2e/src/api.ts index 40d0f046a..d88bcb5c3 100644 --- a/packages/plugin-e2e/src/api.ts +++ b/packages/plugin-e2e/src/api.ts @@ -52,7 +52,7 @@ export type PluginFixture = { createDataSourceConfigPage: (args: CreateDataSourcePageArgs) => Promise; /** - * Fixture command that create a data source via the Grafana API. + * Fixture command that creates a data source via the Grafana API. * * If you have tests that depend on the the existance of a data source, * you may use this command in a setup project. Read more about setup projects diff --git a/packages/plugin-e2e/src/matchers/index.ts b/packages/plugin-e2e/src/matchers/index.ts index 43c3553ca..c947739b9 100644 --- a/packages/plugin-e2e/src/matchers/index.ts +++ b/packages/plugin-e2e/src/matchers/index.ts @@ -1,5 +1,5 @@ import toBeOK from './toBeOK'; -import { toHavePanelError } from './toHavePanelError'; +import toHavePanelError from './toHavePanelError'; export default { toBeOK, diff --git a/packages/plugin-e2e/src/matchers/toHavePanelError.ts b/packages/plugin-e2e/src/matchers/toHavePanelError.ts index d48b89de1..ee22eab12 100644 --- a/packages/plugin-e2e/src/matchers/toHavePanelError.ts +++ b/packages/plugin-e2e/src/matchers/toHavePanelError.ts @@ -2,10 +2,7 @@ import { expect } from '@playwright/test'; import { PanelError } from '../types'; import { getMessage } from './utils'; -/** - * Hello - */ -export const toHavePanelError = async (panelError: PanelError, options?: { timeout?: number }) => { +const toHavePanelError = async (panelError: PanelError, options?: { timeout?: number }) => { let pass = true; let actual; let message: any = 'A panel error to be displayed'; @@ -26,4 +23,4 @@ export const toHavePanelError = async (panelError: PanelError, options?: { timeo }; }; -// export default toHavePanelError; +export default toHavePanelError; diff --git a/packages/plugin-e2e/src/models/DataSourcePicker.ts b/packages/plugin-e2e/src/models/DataSourcePicker.ts index 63246d8e7..a5e941e9c 100644 --- a/packages/plugin-e2e/src/models/DataSourcePicker.ts +++ b/packages/plugin-e2e/src/models/DataSourcePicker.ts @@ -13,7 +13,7 @@ export class DataSourcePicker extends GrafanaPage { .fill(name); // this is a hack to get the selection to work in 10.ish versions of Grafana. - // TODO: investigate if the select component can somehow be refactored so that its easier to interact with + // TODO: investigate if the select component can somehow be refactored so that its easier to test with playwright await this.ctx.page.keyboard.press('ArrowDown'); await this.ctx.page.keyboard.press('ArrowUp'); await this.ctx.page.keyboard.press('Enter'); diff --git a/packages/plugin-e2e/src/models/PanelEditPage.ts b/packages/plugin-e2e/src/models/PanelEditPage.ts index fb251ad57..cddf27aaf 100644 --- a/packages/plugin-e2e/src/models/PanelEditPage.ts +++ b/packages/plugin-e2e/src/models/PanelEditPage.ts @@ -1,6 +1,5 @@ -import * as semver from 'semver'; import { Expect, Locator } from '@playwright/test'; - +import * as semver from 'semver'; import { PanelError, PluginTestCtx, RequestOptions, Visualization } from '../types'; import { DataSourcePicker } from './DataSourcePicker'; import { GrafanaPage } from './GrafanaPage'; From da2f1e04d791333c39668d4f3d8bc482d16db255 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Tue, 21 Nov 2023 15:48:04 +0100 Subject: [PATCH 15/25] provision ds instead of using setup proj --- .github/workflows/playwright.yml | 2 +- packages/plugin-e2e/docker-compose.yaml | 1 + packages/plugin-e2e/playwright.config.ts | 13 ++--------- .../google-sheets-datasource-jwt.yaml | 17 ++++++++++++++ packages/plugin-e2e/src/api.ts | 8 ++++++- .../src/fixtures/commands/readProvision.ts | 22 +++++++++++++++++++ packages/plugin-e2e/src/fixtures/index.ts | 2 ++ packages/plugin-e2e/src/types.ts | 20 ++++++++++++++--- .../plugin-e2e/tests/datasource/datasource.ts | 16 -------------- .../queryEditor.integration.spec.ts | 14 +++++++++++- .../tests/datasource/queryEditor.spec.ts | 16 +++++++++++--- 11 files changed, 95 insertions(+), 36 deletions(-) create mode 100644 packages/plugin-e2e/provisioning/datasources/google-sheets-datasource-jwt.yaml create mode 100644 packages/plugin-e2e/src/fixtures/commands/readProvision.ts delete mode 100644 packages/plugin-e2e/tests/datasource/datasource.ts diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 52a0f482f..a98417fa6 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -33,7 +33,7 @@ jobs: run: npx playwright install --with-deps chromium - name: Start Grafana - run: docker run --rm -d -p 3000:3000 --name=grafana --env "GF_INSTALL_PLUGINS=grafana-googlesheets-datasource ${{matrix.GOOGLE_SHEETS_VERSION}}" grafana/grafana:${{ matrix.GRAFANA_VERSION }}; sleep 30 + run: docker run --rm -d -p 3000:3000 --name=grafana --env JWT_FILE=${{secrets.GOOGLE_JWT_FILE}} --env "GF_INSTALL_PLUGINS=grafana-googlesheets-datasource ${{matrix.GOOGLE_SHEETS_VERSION}}" grafana/grafana:${{ matrix.GRAFANA_VERSION }}; sleep 30 - name: Run Playwright tests run: npm run playwright:test --w @grafana/plugin-e2e diff --git a/packages/plugin-e2e/docker-compose.yaml b/packages/plugin-e2e/docker-compose.yaml index df6f40dbe..5cf51a379 100644 --- a/packages/plugin-e2e/docker-compose.yaml +++ b/packages/plugin-e2e/docker-compose.yaml @@ -9,6 +9,7 @@ services: - GF_AUTH_ANONYMOUS_ORG_ROLE=Admin - GF_AUTH_ANONYMOUS_ORG_NAME=Main Org. - GF_AUTH_ANONYMOUS_ORG_ID=1 + - GOOGLE_JWT_FILE=${GOOGLE_JWT_FILE} ports: - 3000:3000/tcp volumes: diff --git a/packages/plugin-e2e/playwright.config.ts b/packages/plugin-e2e/playwright.config.ts index 4e9f42fc0..280babaf6 100644 --- a/packages/plugin-e2e/playwright.config.ts +++ b/packages/plugin-e2e/playwright.config.ts @@ -38,23 +38,14 @@ export default defineConfig({ name: 'authenticate', testMatch: [/.*auth\.setup\.ts/], }, - // 2. Create a datasource that can be used across tests (should use provsiioning DS instead, but currently not working in CI) - { - name: 'setupDatasource', - use: { - storageState: 'playwright/.auth/user.json', - }, - testMatch: [/.*datasource\.setup\.ts/], - dependencies: ['authenticate'], - }, - // 3. Run all tests in parallel using Chrome. + // 2. Run all tests in parallel using Chrome. { name: 'chromium', use: { ...devices['Desktop Chrome'], storageState: 'playwright/.auth/user.json', }, - dependencies: ['authenticate', 'setupDatasource'], + dependencies: ['authenticate'], }, ], }); diff --git a/packages/plugin-e2e/provisioning/datasources/google-sheets-datasource-jwt.yaml b/packages/plugin-e2e/provisioning/datasources/google-sheets-datasource-jwt.yaml new file mode 100644 index 000000000..64b250bf3 --- /dev/null +++ b/packages/plugin-e2e/provisioning/datasources/google-sheets-datasource-jwt.yaml @@ -0,0 +1,17 @@ +# config file version +apiVersion: 1 + +deleteDatasources: + - name: Google Sheets Service Account + orgId: 1 + +datasources: + - editable: true + enabled: true + jsonData: + authType: jwt + name: Google Sheets Service Account + secureJsonData: + jwt: ${GOOGLE_JWT_FILE} + type: grafana-googlesheets-datasource + version: 1 diff --git a/packages/plugin-e2e/src/api.ts b/packages/plugin-e2e/src/api.ts index d88bcb5c3..226b45817 100644 --- a/packages/plugin-e2e/src/api.ts +++ b/packages/plugin-e2e/src/api.ts @@ -2,7 +2,7 @@ import { test as base, expect as baseExpect, selectors } from '@playwright/test' import { E2ESelectors } from './e2e-selectors/types'; import fixtures from './fixtures'; import matchers from './matchers'; -import { CreateDataSourceArgs, CreateDataSourcePageArgs, DataSource } from './types'; +import { CreateDataSourceArgs, CreateDataSourcePageArgs, DataSource, ReadProvisionArgs } from './types'; import { PanelEditPage, GrafanaPage, DataSourceConfigPage, EmptyDashboardPage } from './models'; import { grafanaE2ESelectorEngine } from './selectorEngine'; @@ -85,6 +85,12 @@ export type PluginFixture = { * test.use({ storageState: { cookies: [], origins: [] } }); */ login: () => Promise; + + /** + * Fixture command that reads a the yaml file for a provisioned dashboard + * or data source and returns it as json. + */ + readProvision(args: ReadProvisionArgs): Promise; }; // extend Playwright with Grafana plugin specific fixtures diff --git a/packages/plugin-e2e/src/fixtures/commands/readProvision.ts b/packages/plugin-e2e/src/fixtures/commands/readProvision.ts new file mode 100644 index 000000000..45b481739 --- /dev/null +++ b/packages/plugin-e2e/src/fixtures/commands/readProvision.ts @@ -0,0 +1,22 @@ +import { TestFixture } from '@playwright/test'; +import { promises } from 'fs'; +import { resolve as resolvePath } from 'path'; +import { parse as parseYml } from 'yaml'; +import { PluginFixture, PluginOptions } from '../../api'; +import { ReadProvisionArgs } from '../../types'; +import { PlaywrightCombinedArgs } from '../types'; + +type ReadProvisionFixture = TestFixture< + (args: ReadProvisionArgs) => Promise, + PluginFixture & PluginOptions & PlaywrightCombinedArgs +>; + +const readProvision: ReadProvisionFixture = async ({}, use) => { + await use(async ({ filePath }) => { + const resolvedPath = resolvePath(process.cwd(), 'provisioning', filePath); + const contents = await promises.readFile(resolvedPath, 'utf8'); + return parseYml(contents); + }); +}; + +export default readProvision; diff --git a/packages/plugin-e2e/src/fixtures/index.ts b/packages/plugin-e2e/src/fixtures/index.ts index 5b04efce9..dd0995daf 100644 --- a/packages/plugin-e2e/src/fixtures/index.ts +++ b/packages/plugin-e2e/src/fixtures/index.ts @@ -5,6 +5,7 @@ import createDataSourceConfigPage from './commands/createDataSourceConfigPage'; import panelEditPage from './panelEditPage'; import createDataSource from './commands/createDataSource'; import emptyDashboardPage from './emptyDashboardPage'; +import readProvision from './commands/readProvision'; export default { selectors, @@ -14,4 +15,5 @@ export default { panelEditPage, createDataSource, emptyDashboardPage, + readProvision, }; diff --git a/packages/plugin-e2e/src/types.ts b/packages/plugin-e2e/src/types.ts index f3557e175..470d72bbe 100644 --- a/packages/plugin-e2e/src/types.ts +++ b/packages/plugin-e2e/src/types.ts @@ -13,7 +13,7 @@ export type PluginTestCtx = { grafanaVersion: string; selectors: E2ESelectors } /** * The data source object */ -export interface DataSource { +export interface DataSource { id?: number; editable?: boolean; uid?: string; @@ -24,10 +24,17 @@ export interface DataSource { url?: string; database?: string; isDefault?: boolean; - jsonData?: any; - secureJsonData?: any; + jsonData?: T; + secureJsonData?: T; } +/** + * The YAML provision file parsed to a javascript object + */ +export type ProvisionFile = { + datasources: Array>; +}; + export type CreateDataSourceArgs = { /** * The data source to create @@ -97,6 +104,13 @@ export type GotoDashboardArgs = { queryParams?: URLSearchParams; }; +export type ReadProvisionArgs = { + /** + * The path, relative to the provisioning folder, to the dashboard json file + */ + filePath: string; +}; + /** * Panel visualization types */ diff --git a/packages/plugin-e2e/tests/datasource/datasource.ts b/packages/plugin-e2e/tests/datasource/datasource.ts deleted file mode 100644 index b635336e7..000000000 --- a/packages/plugin-e2e/tests/datasource/datasource.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { DataSource } from '../../src/types'; - -export const sheetsDataSource: DataSource = { - type: 'grafana-googlesheets-datasource', - name: 'GoogleSheets_E2E', - uid: 'P7DC3E4760CFAC4AHHGGAA', - access: 'proxy', - editable: true, - isDefault: false, - jsonData: { - authType: 'jwt', - }, - secureJsonData: { - jwt: process.env.GOOGLE_JWT_FILE ?? ''.replace(/\\n/g, '\n'), - }, -}; diff --git a/packages/plugin-e2e/tests/datasource/queryEditor.integration.spec.ts b/packages/plugin-e2e/tests/datasource/queryEditor.integration.spec.ts index ab2724b97..50258df6b 100644 --- a/packages/plugin-e2e/tests/datasource/queryEditor.integration.spec.ts +++ b/packages/plugin-e2e/tests/datasource/queryEditor.integration.spec.ts @@ -1,10 +1,18 @@ import { expect, test } from '../../src'; -import { sheetsDataSource } from './datasource'; +import { ProvisionFile } from '../../src/types'; + +export type RedshiftDatasourceConfig = { + name: string; +}; test('should return data and not display panel error when a valid query is provided', async ({ panelEditPage, page, + readProvision, }) => { + const sheetsDataSource = await readProvision({ + filePath: 'datasources/google-sheets-datasource-jwt.yaml', + }).then((provision) => provision.datasources[0]); await panelEditPage.datasource.set(sheetsDataSource.name!); await panelEditPage.timeRange.set({ from: '2019-01-11', to: '2019-12-15' }); const queryEditorRow = await panelEditPage.getQueryEditorRow('A'); @@ -18,7 +26,11 @@ test('should return data and not display panel error when a valid query is provi test('should return an error and display panel error when an invalid query is provided', async ({ panelEditPage, page, + readProvision, }) => { + const sheetsDataSource = await readProvision({ + filePath: 'datasources/google-sheets-datasource-jwt.yaml', + }).then((provision) => provision.datasources[0]); await panelEditPage.datasource.set(sheetsDataSource.name!); await panelEditPage.timeRange.set({ from: '2019-01-11', to: '2019-12-15' }); const queryEditorRow = await panelEditPage.getQueryEditorRow('A'); diff --git a/packages/plugin-e2e/tests/datasource/queryEditor.spec.ts b/packages/plugin-e2e/tests/datasource/queryEditor.spec.ts index d2075dfbb..bcd159fdc 100644 --- a/packages/plugin-e2e/tests/datasource/queryEditor.spec.ts +++ b/packages/plugin-e2e/tests/datasource/queryEditor.spec.ts @@ -1,8 +1,15 @@ import { expect, test } from '../../src'; -import { sheetsDataSource } from './datasource'; +import { ProvisionFile } from '../../src/types'; import { GOOGLE_SHEETS_SPREADSHEETS } from './mocks/resource'; -test('should list spreadsheets when clicking on spreadsheet segment', async ({ panelEditPage, page }) => { +test('should list spreadsheets when clicking on spreadsheet segment', async ({ + panelEditPage, + page, + readProvision, +}) => { + const sheetsDataSource = await readProvision({ + filePath: 'datasources/google-sheets-datasource-jwt.yaml', + }).then((provision) => provision.datasources[0]); await panelEditPage.datasource.set(sheetsDataSource.name!); const queryEditorRow = await panelEditPage.getQueryEditorRow('A'); await panelEditPage.mockResourceResponse('spreadsheets', GOOGLE_SHEETS_SPREADSHEETS); @@ -11,7 +18,10 @@ test('should list spreadsheets when clicking on spreadsheet segment', async ({ p await expect(page.getByText(GOOGLE_SHEETS_SPREADSHEETS.spreadsheets.sheet2, { exact: true })).toHaveCount(1); }); -test('should set correct cache time on query passed to the backend', async ({ panelEditPage, page }) => { +test('should set correct cache time on query passed to the backend', async ({ panelEditPage, page, readProvision }) => { + const sheetsDataSource = await readProvision({ + filePath: 'datasources/google-sheets-datasource-jwt.yaml', + }).then((provision) => provision.datasources[0]); await panelEditPage.datasource.set(sheetsDataSource.name!); const queryEditorRow = await panelEditPage.getQueryEditorRow('A'); await panelEditPage.mockResourceResponse('spreadsheets', GOOGLE_SHEETS_SPREADSHEETS); From b749741b2c299f4f3550b1de742b544711bb71c6 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Tue, 21 Nov 2023 15:53:15 +0100 Subject: [PATCH 16/25] fix env variable name --- .github/workflows/playwright.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index a98417fa6..46f5b33ad 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -33,7 +33,7 @@ jobs: run: npx playwright install --with-deps chromium - name: Start Grafana - run: docker run --rm -d -p 3000:3000 --name=grafana --env JWT_FILE=${{secrets.GOOGLE_JWT_FILE}} --env "GF_INSTALL_PLUGINS=grafana-googlesheets-datasource ${{matrix.GOOGLE_SHEETS_VERSION}}" grafana/grafana:${{ matrix.GRAFANA_VERSION }}; sleep 30 + run: docker run --rm -d -p 3000:3000 --name=grafana --env GOOGLE_JWT_FILE=${{secrets.GOOGLE_JWT_FILE}} --env "GF_INSTALL_PLUGINS=grafana-googlesheets-datasource ${{matrix.GOOGLE_SHEETS_VERSION}}" grafana/grafana:${{ matrix.GRAFANA_VERSION }}; sleep 30 - name: Run Playwright tests run: npm run playwright:test --w @grafana/plugin-e2e From cdd4f1444d250018335deae64bcc9e83c0d84a3f Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Tue, 21 Nov 2023 15:55:40 +0100 Subject: [PATCH 17/25] mount provisioning folder as volume --- .github/workflows/playwright.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 46f5b33ad..ef7a5e095 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -33,7 +33,7 @@ jobs: run: npx playwright install --with-deps chromium - name: Start Grafana - run: docker run --rm -d -p 3000:3000 --name=grafana --env GOOGLE_JWT_FILE=${{secrets.GOOGLE_JWT_FILE}} --env "GF_INSTALL_PLUGINS=grafana-googlesheets-datasource ${{matrix.GOOGLE_SHEETS_VERSION}}" grafana/grafana:${{ matrix.GRAFANA_VERSION }}; sleep 30 + run: docker run --rm -d -p 3000:3000 --name=grafana --volume ./packages/plugin-e2e/provisioning:/etc/grafana/provisioning --env GOOGLE_JWT_FILE=${{secrets.GOOGLE_JWT_FILE}} --env "GF_INSTALL_PLUGINS=grafana-googlesheets-datasource ${{matrix.GOOGLE_SHEETS_VERSION}}" grafana/grafana:${{ matrix.GRAFANA_VERSION }}; sleep 30 - name: Run Playwright tests run: npm run playwright:test --w @grafana/plugin-e2e From 2634e648dbbb0bb89e06e2a3341fd618f0ef9c12 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Tue, 21 Nov 2023 16:25:26 +0100 Subject: [PATCH 18/25] remove not used file --- .../src/fixtures/commands/createDataSource.ts | 2 +- .../plugin-e2e/tests/datasource/configEditor.spec.ts | 2 +- .../plugin-e2e/tests/datasource/datasource.setup.ts | 10 ---------- 3 files changed, 2 insertions(+), 12 deletions(-) delete mode 100644 packages/plugin-e2e/tests/datasource/datasource.setup.ts diff --git a/packages/plugin-e2e/src/fixtures/commands/createDataSource.ts b/packages/plugin-e2e/src/fixtures/commands/createDataSource.ts index 3402b6048..e707f46a7 100644 --- a/packages/plugin-e2e/src/fixtures/commands/createDataSource.ts +++ b/packages/plugin-e2e/src/fixtures/commands/createDataSource.ts @@ -33,7 +33,7 @@ export const createDataSourceViaAPI = async ( const text = await createDsReq.text(); const status = await createDsReq.status(); if (status === 200) { - console.log('Data source created: ', dsName); + console.log('Data source created: ', name); return createDsReq.json().then((r) => r.datasource); } diff --git a/packages/plugin-e2e/tests/datasource/configEditor.spec.ts b/packages/plugin-e2e/tests/datasource/configEditor.spec.ts index 4f35e8dee..f4e29464c 100644 --- a/packages/plugin-e2e/tests/datasource/configEditor.spec.ts +++ b/packages/plugin-e2e/tests/datasource/configEditor.spec.ts @@ -11,6 +11,6 @@ test('valid credentials should return a 200 status code', async ({ createDataSou const configPage = await createDataSourceConfigPage({ type: 'grafana-googlesheets-datasource' }); await page.getByText('Google JWT File', { exact: true }).click(); await page.getByTestId('Paste JWT button').click(); - await page.getByTestId('Configuration text area').fill(process.env.GOOGLE_JWT_FILE!); + await page.getByTestId('Configuration text area').fill(process.env.GOOGLE_JWT_FILE!.replace(/'/g, '')); await expect(configPage.saveAndTest()).toBeOK(); }); diff --git a/packages/plugin-e2e/tests/datasource/datasource.setup.ts b/packages/plugin-e2e/tests/datasource/datasource.setup.ts deleted file mode 100644 index 0d5472f1b..000000000 --- a/packages/plugin-e2e/tests/datasource/datasource.setup.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { test as setup } from '../../src'; -import { sheetsDataSource } from './datasource'; - -setup('setupDataSource', async ({ createDataSource }) => { - try { - await createDataSource({ datasource: sheetsDataSource }); - } catch (error) { - console.error(error); - } -}); From dc459ea2a321ce1fb7f755fd8498a29c9de15537 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Fri, 24 Nov 2023 14:42:33 +0100 Subject: [PATCH 19/25] remove empty dashboard page --- packages/plugin-e2e/src/api.ts | 13 +++++++++---- .../src/fixtures/emptyDashboardPage.ts | 17 ----------------- packages/plugin-e2e/src/fixtures/index.ts | 4 ++-- .../plugin-e2e/src/fixtures/newDashboardPage.ts | 14 ++++++++++++++ .../plugin-e2e/src/fixtures/panelEditPage.ts | 4 ++-- packages/plugin-e2e/src/models/DashboardPage.ts | 3 ++- .../plugin-e2e/src/models/EmptyDashboardPage.ts | 15 --------------- packages/plugin-e2e/src/models/index.ts | 1 - .../datasource/queryEditor.integration.spec.ts | 2 +- 9 files changed, 30 insertions(+), 43 deletions(-) delete mode 100644 packages/plugin-e2e/src/fixtures/emptyDashboardPage.ts create mode 100644 packages/plugin-e2e/src/fixtures/newDashboardPage.ts delete mode 100644 packages/plugin-e2e/src/models/EmptyDashboardPage.ts diff --git a/packages/plugin-e2e/src/api.ts b/packages/plugin-e2e/src/api.ts index 226b45817..bb1d532cc 100644 --- a/packages/plugin-e2e/src/api.ts +++ b/packages/plugin-e2e/src/api.ts @@ -3,7 +3,7 @@ import { E2ESelectors } from './e2e-selectors/types'; import fixtures from './fixtures'; import matchers from './matchers'; import { CreateDataSourceArgs, CreateDataSourcePageArgs, DataSource, ReadProvisionArgs } from './types'; -import { PanelEditPage, GrafanaPage, DataSourceConfigPage, EmptyDashboardPage } from './models'; +import { PanelEditPage, GrafanaPage, DataSourceConfigPage } from './models'; import { grafanaE2ESelectorEngine } from './selectorEngine'; export type PluginOptions = { @@ -25,11 +25,16 @@ export type PluginFixture = { selectors: E2ESelectors; /** - * Isolated {@link EmptyDashboardPage} instance for each test. + * Isolated {@link DashboardPage} instance for each test. * - * Navigates to a new dashboard page. + * Navigates to a new dashboard page and adds a new panel. + * + * Use {@link PanelEditPage.setVisualization} to change the visualization + * Use {@link PanelEditPage.datasource.set} to change the + * Use {@link PanelEditPage.getQueryEditorEditorRow} to retrieve the query + * editor row locator for a given query refId */ - emptyDashboardPage: EmptyDashboardPage; + newDashboardPage: GrafanaPage; /** * Isolated {@link PanelEditPage} instance for each test. diff --git a/packages/plugin-e2e/src/fixtures/emptyDashboardPage.ts b/packages/plugin-e2e/src/fixtures/emptyDashboardPage.ts deleted file mode 100644 index 97f7e8297..000000000 --- a/packages/plugin-e2e/src/fixtures/emptyDashboardPage.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { TestFixture, expect } from '@playwright/test'; -import { PluginFixture, PluginOptions } from '../api'; -import { EmptyDashboardPage } from '../models'; -import { PlaywrightCombinedArgs } from './types'; - -type EmptyDashboardPageFixture = TestFixture< - EmptyDashboardPage, - PluginFixture & PluginOptions & PlaywrightCombinedArgs ->; - -const emptyDashboardPage: EmptyDashboardPageFixture = async ({ page, request, selectors, grafanaVersion }, use) => { - const emptyDashboardPage = new EmptyDashboardPage({ page, selectors, grafanaVersion, request }, expect); - await emptyDashboardPage.goto(); - await use(emptyDashboardPage); -}; - -export default emptyDashboardPage; diff --git a/packages/plugin-e2e/src/fixtures/index.ts b/packages/plugin-e2e/src/fixtures/index.ts index dd0995daf..c299b18a9 100644 --- a/packages/plugin-e2e/src/fixtures/index.ts +++ b/packages/plugin-e2e/src/fixtures/index.ts @@ -4,16 +4,16 @@ import login from './commands/login'; import createDataSourceConfigPage from './commands/createDataSourceConfigPage'; import panelEditPage from './panelEditPage'; import createDataSource from './commands/createDataSource'; -import emptyDashboardPage from './emptyDashboardPage'; import readProvision from './commands/readProvision'; +import newDashboardPage from './newDashboardPage'; export default { selectors, grafanaVersion, login, createDataSourceConfigPage, + newDashboardPage, panelEditPage, createDataSource, - emptyDashboardPage, readProvision, }; diff --git a/packages/plugin-e2e/src/fixtures/newDashboardPage.ts b/packages/plugin-e2e/src/fixtures/newDashboardPage.ts new file mode 100644 index 000000000..a00eacee8 --- /dev/null +++ b/packages/plugin-e2e/src/fixtures/newDashboardPage.ts @@ -0,0 +1,14 @@ +import { TestFixture, expect } from '@playwright/test'; +import { PluginFixture, PluginOptions } from '../api'; +import { DashboardPage } from '../models'; +import { PlaywrightCombinedArgs } from './types'; + +type NewDashboardPageFixture = TestFixture; + +const newDashboardPage: NewDashboardPageFixture = async ({ page, request, selectors, grafanaVersion }, use) => { + const newDashboardPage = new DashboardPage({ page, selectors, grafanaVersion, request }, expect); + await newDashboardPage.goto(); + await use(newDashboardPage); +}; + +export default newDashboardPage; diff --git a/packages/plugin-e2e/src/fixtures/panelEditPage.ts b/packages/plugin-e2e/src/fixtures/panelEditPage.ts index 147d8c906..40315abec 100644 --- a/packages/plugin-e2e/src/fixtures/panelEditPage.ts +++ b/packages/plugin-e2e/src/fixtures/panelEditPage.ts @@ -5,8 +5,8 @@ import { PlaywrightCombinedArgs } from './types'; type PanelEditPageFixture = TestFixture; -const panelEditPage: PanelEditPageFixture = async ({ emptyDashboardPage }, use) => { - const panelEditPage = await emptyDashboardPage.addPanel(); +const panelEditPage: PanelEditPageFixture = async ({ newDashboardPage }, use) => { + const panelEditPage = await newDashboardPage.addPanel(); await use(panelEditPage); }; diff --git a/packages/plugin-e2e/src/models/DashboardPage.ts b/packages/plugin-e2e/src/models/DashboardPage.ts index 6c359d6a3..affca3af4 100644 --- a/packages/plugin-e2e/src/models/DashboardPage.ts +++ b/packages/plugin-e2e/src/models/DashboardPage.ts @@ -19,7 +19,8 @@ export class DashboardPage extends GrafanaPage { } async goto(opts?: GotoDashboardArgs) { - let url = this.ctx.selectors.pages.Dashboard.url(opts?.uid ?? this.dashboardUid ?? ''); + const uid = opts?.uid || this.dashboardUid; + let url = uid ? this.ctx.selectors.pages.Dashboard.url(uid) : this.ctx.selectors.pages.AddDashboard.url; if (opts?.queryParams) { url += `?${opts.queryParams.toString()}`; } diff --git a/packages/plugin-e2e/src/models/EmptyDashboardPage.ts b/packages/plugin-e2e/src/models/EmptyDashboardPage.ts deleted file mode 100644 index 640c5a4f0..000000000 --- a/packages/plugin-e2e/src/models/EmptyDashboardPage.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Expect } from '@playwright/test'; -import { PluginTestCtx } from '../types'; -import { DashboardPage } from './DashboardPage'; - -export class EmptyDashboardPage extends DashboardPage { - constructor(ctx: PluginTestCtx, expect: Expect) { - super(ctx, expect); - } - - async goto() { - await this.ctx.page.goto(this.ctx.selectors.pages.AddDashboard.url, { - waitUntil: 'networkidle', - }); - } -} diff --git a/packages/plugin-e2e/src/models/index.ts b/packages/plugin-e2e/src/models/index.ts index 7aff76777..25b60f411 100644 --- a/packages/plugin-e2e/src/models/index.ts +++ b/packages/plugin-e2e/src/models/index.ts @@ -1,5 +1,4 @@ export { DataSourceConfigPage } from './DataSourceConfigPage'; export { PanelEditPage } from './PanelEditPage'; export { DashboardPage } from './DashboardPage'; -export { EmptyDashboardPage } from './EmptyDashboardPage'; export { GrafanaPage } from './GrafanaPage'; diff --git a/packages/plugin-e2e/tests/datasource/queryEditor.integration.spec.ts b/packages/plugin-e2e/tests/datasource/queryEditor.integration.spec.ts index 50258df6b..5ec79dbfa 100644 --- a/packages/plugin-e2e/tests/datasource/queryEditor.integration.spec.ts +++ b/packages/plugin-e2e/tests/datasource/queryEditor.integration.spec.ts @@ -37,7 +37,7 @@ test('should return an error and display panel error when an invalid query is pr await queryEditorRow.getByText('Enter SpreadsheetID').click(); await page.keyboard.insertText('1TZlZX67Y0s4CvRro_3pCYqRCKuXer81oFp_xcsjPpe8'); await page.keyboard.press('Enter'); - await page.getByPlaceholder('ie: Class Data!A2:E').fill('invalid range'); + await page.getByPlaceholder('Class Data!A2:E').fill('invalid range'); await expect(panelEditPage.refreshPanel()).not.toBeOK(); await expect(panelEditPage).toHavePanelError(); }); From c71f37ea9036650ca1218c05a4a1c4989f469bd6 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Fri, 24 Nov 2023 14:45:34 +0100 Subject: [PATCH 20/25] use correct type --- packages/plugin-e2e/src/api.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/plugin-e2e/src/api.ts b/packages/plugin-e2e/src/api.ts index bb1d532cc..b7121726b 100644 --- a/packages/plugin-e2e/src/api.ts +++ b/packages/plugin-e2e/src/api.ts @@ -3,7 +3,7 @@ import { E2ESelectors } from './e2e-selectors/types'; import fixtures from './fixtures'; import matchers from './matchers'; import { CreateDataSourceArgs, CreateDataSourcePageArgs, DataSource, ReadProvisionArgs } from './types'; -import { PanelEditPage, GrafanaPage, DataSourceConfigPage } from './models'; +import { PanelEditPage, GrafanaPage, DataSourceConfigPage, DashboardPage } from './models'; import { grafanaE2ESelectorEngine } from './selectorEngine'; export type PluginOptions = { @@ -34,7 +34,7 @@ export type PluginFixture = { * Use {@link PanelEditPage.getQueryEditorEditorRow} to retrieve the query * editor row locator for a given query refId */ - newDashboardPage: GrafanaPage; + newDashboardPage: DashboardPage; /** * Isolated {@link PanelEditPage} instance for each test. From 522856a2d8fb8aa4d6d3d6fe96b9f5b58fbefedc Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Fri, 24 Nov 2023 15:40:41 +0100 Subject: [PATCH 21/25] Update packages/plugin-e2e/src/api.ts Co-authored-by: Levente Balogh --- packages/plugin-e2e/src/api.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/plugin-e2e/src/api.ts b/packages/plugin-e2e/src/api.ts index b7121726b..445fc81e7 100644 --- a/packages/plugin-e2e/src/api.ts +++ b/packages/plugin-e2e/src/api.ts @@ -30,7 +30,7 @@ export type PluginFixture = { * Navigates to a new dashboard page and adds a new panel. * * Use {@link PanelEditPage.setVisualization} to change the visualization - * Use {@link PanelEditPage.datasource.set} to change the + * Use {@link PanelEditPage.datasource.set} to change the datasource * Use {@link PanelEditPage.getQueryEditorEditorRow} to retrieve the query * editor row locator for a given query refId */ From be1805b647e35753f3ee5b623d9873e527af1439 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Fri, 24 Nov 2023 15:40:49 +0100 Subject: [PATCH 22/25] Update packages/plugin-e2e/src/api.ts Co-authored-by: Levente Balogh --- packages/plugin-e2e/src/api.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/plugin-e2e/src/api.ts b/packages/plugin-e2e/src/api.ts index 445fc81e7..c003e6e5d 100644 --- a/packages/plugin-e2e/src/api.ts +++ b/packages/plugin-e2e/src/api.ts @@ -42,7 +42,7 @@ export type PluginFixture = { * Navigates to a new dashboard page and adds a new panel. * * Use {@link PanelEditPage.setVisualization} to change the visualization - * Use {@link PanelEditPage.datasource.set} to change the + * Use {@link PanelEditPage.datasource.set} to change the datasource * Use {@link ExplorePage.getQueryEditorEditorRow} to retrieve the query * editor row locator for a given query refId */ From 1f88ca8eac65f461dfe29bc33e9fc987bcc7b2d0 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Fri, 24 Nov 2023 15:41:05 +0100 Subject: [PATCH 23/25] Update packages/plugin-e2e/src/api.ts Co-authored-by: Levente Balogh --- packages/plugin-e2e/src/api.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/plugin-e2e/src/api.ts b/packages/plugin-e2e/src/api.ts index c003e6e5d..19845b77e 100644 --- a/packages/plugin-e2e/src/api.ts +++ b/packages/plugin-e2e/src/api.ts @@ -39,7 +39,7 @@ export type PluginFixture = { /** * Isolated {@link PanelEditPage} instance for each test. * - * Navigates to a new dashboard page and adds a new panel. + * Navigates to a new dashboard page, adds a new panel and moves to the panel edit page. * * Use {@link PanelEditPage.setVisualization} to change the visualization * Use {@link PanelEditPage.datasource.set} to change the datasource From 63f28c25ba0a4f7fe6f006c8f15a1e5fa8e8e5b9 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Fri, 24 Nov 2023 15:57:58 +0100 Subject: [PATCH 24/25] pr feedback --- packages/plugin-e2e/src/models/TimeRange.ts | 3 ++- .../tests/datasource/queryEditor.integration.spec.ts | 4 ---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/plugin-e2e/src/models/TimeRange.ts b/packages/plugin-e2e/src/models/TimeRange.ts index 95d2f0b8a..b5bfd4aca 100644 --- a/packages/plugin-e2e/src/models/TimeRange.ts +++ b/packages/plugin-e2e/src/models/TimeRange.ts @@ -12,8 +12,9 @@ export class TimeRange extends GrafanaPage { await this.ctx.page .getByLabel(this.ctx.selectors.components.PanelEditor.General.content) .locator(`selector=${this.ctx.selectors.components.TimePicker.openButton}`) - .click({ force: true, timeout: 2000 }); + .click({ force: true }); } catch (e) { + //TODO: investigate this properly and find a better solution // seems like in older versions of Grafana the time picker markup is rendered twice await this.ctx.page.locator('[aria-controls="TimePickerContent"]').last().click(); } diff --git a/packages/plugin-e2e/tests/datasource/queryEditor.integration.spec.ts b/packages/plugin-e2e/tests/datasource/queryEditor.integration.spec.ts index 5ec79dbfa..375ae791f 100644 --- a/packages/plugin-e2e/tests/datasource/queryEditor.integration.spec.ts +++ b/packages/plugin-e2e/tests/datasource/queryEditor.integration.spec.ts @@ -1,10 +1,6 @@ import { expect, test } from '../../src'; import { ProvisionFile } from '../../src/types'; -export type RedshiftDatasourceConfig = { - name: string; -}; - test('should return data and not display panel error when a valid query is provided', async ({ panelEditPage, page, From 49b6b9b259497d68bc34e104234069baa5202627 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Fri, 24 Nov 2023 16:31:52 +0100 Subject: [PATCH 25/25] fix broken selector --- .../plugin-e2e/src/e2e-selectors/versioned/components.ts | 2 +- packages/plugin-e2e/src/models/TimeRange.ts | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/plugin-e2e/src/e2e-selectors/versioned/components.ts b/packages/plugin-e2e/src/e2e-selectors/versioned/components.ts index 329c8cb61..b75d3cfce 100644 --- a/packages/plugin-e2e/src/e2e-selectors/versioned/components.ts +++ b/packages/plugin-e2e/src/e2e-selectors/versioned/components.ts @@ -9,7 +9,7 @@ export const versionedComponents = { }, TimePicker: { openButton: { - '8.1.0': 'data-testid TimePicker open button', + '8.1.0': 'data-testid TimePicker Open Button', [MIN_GRAFANA_VERSION]: 'TimePicker open button', }, fromField: 'Time Range from field', diff --git a/packages/plugin-e2e/src/models/TimeRange.ts b/packages/plugin-e2e/src/models/TimeRange.ts index b5bfd4aca..016e7eeb4 100644 --- a/packages/plugin-e2e/src/models/TimeRange.ts +++ b/packages/plugin-e2e/src/models/TimeRange.ts @@ -9,12 +9,8 @@ export class TimeRange extends GrafanaPage { async set({ from, to, zone }: TimeRangeArgs) { try { - await this.ctx.page - .getByLabel(this.ctx.selectors.components.PanelEditor.General.content) - .locator(`selector=${this.ctx.selectors.components.TimePicker.openButton}`) - .click({ force: true }); + await this.getByTestIdOrAriaLabel(this.ctx.selectors.components.TimePicker.openButton).click(); } catch (e) { - //TODO: investigate this properly and find a better solution // seems like in older versions of Grafana the time picker markup is rendered twice await this.ctx.page.locator('[aria-controls="TimePickerContent"]').last().click(); }