E2E: migrate to playwright (#107241)
* separate playwright into its own folder * better separation * add login scenario, add tags * remove ui option * update CODEOWNERS * add a panels suite test * migrate queries test to playwright * rename + add dashlist test * add panelEdit_base * add geomap map controls test * add geomap-layer-types * add geomap-spatial-operations tests * add frontend-sandbox-panel tests * add smoke-tests-suite * add comment about adding datasource * add dashboard-browse-nested * add dashboard-browse * add dashboard-export-json * add dashboard-keybindings test * remove @wip tag * turn on screenshots and add comment for why this test fails * add dashboard-links-without-slug test * try adding permissions in the test as well * add dashboard-live-streaming * context in the test doesn't work - sad * create dashboard-public-templating * add dashboard-public-create and make live streaming more resilient * add share externally test * add dashboard-share-internally * add share-snapshot-create test * add dashboard-templating * add timepicker tests * add embedded-dashboard test * add general_dashboards test * add import-dashboard test * add load-options-from-url test * add new-constant-variable test * add custom-variable test * add new-datasource-variable test * add new-interval-variable test * add text-box-variable test * add new-query-variable test * add horizontal repeat test * add panel-vertical-repeat test * add empty-row-repeat test * add set-options-from-ui test * add snapshot-create test * add templating test * add textbox-variables test * add cloud-plugins-suite * add storybook verification tests * add playwright storybook verification workflow * add playwright browsers * update CODEOWNERS * test change to trigger storybook verification workflows * try container instead * get the version right... * go back to installing - less chance of forgetting to update * Basic Github Actions Squashed commit of the following: commitf84c650a71Author: joshhunt <josh.hunt@grafana.com> Date: Tue Jul 1 13:23:46 2025 +0100 add arg for sharding, but not using it yet commit7bcf0512c6Author: joshhunt <josh.hunt@grafana.com> Date: Tue Jul 1 12:30:30 2025 +0100 less newline commitb643911882Author: joshhunt <josh.hunt@grafana.com> Date: Tue Jul 1 12:24:31 2025 +0100 less logs commit38f871e9c2Author: joshhunt <josh.hunt@grafana.com> Date: Tue Jul 1 10:00:26 2025 +0100 fix yaml commitdb9a773136Author: joshhunt <josh.hunt@grafana.com> Date: Tue Jul 1 09:57:47 2025 +0100 clean up files commitc0525f41faAuthor: joshhunt <josh.hunt@grafana.com> Date: Tue Jul 1 09:44:56 2025 +0100 gha workflow commit895bea7c52Author: joshhunt <josh.hunt@grafana.com> Date: Mon Jun 30 19:33:08 2025 +0100 working dagger commitcea1f84437Author: joshhunt <josh.hunt@grafana.com> Date: Mon Jun 30 16:17:46 2025 +0100 wip * shard gha * some tidy up * add flags for exporting results, and a gha step to merge runs * fix shard gha * add dashboard-duplicate-panel test * add dashboard-outline test * add dashboards-add-panel * remove some commented out code * add dashboards-title-description test * add dashboards-remove-panel * don't install cypress * gha: check playwright results * add dashboards-edit-adhoc-variables test * fix check-jobs * add dagger cloud token * add dagger cloud token * add edit-datasource-variable test * update CODEOWNERS * add dashboards-edit-group-by-variables (skipped for now) * add dashboards-edit-panel-title-description test * add dashboards-edit-transparent-bg test * add dashboards-edit-query-variables test * run with 8 shards * add dashboards-edit-variables * tidy up gha * add dashboard-group-panels * fix action * try to cache the grafana build * fix missing action becuase no checkout, use builtin continue-on-error instead * fix missing id * cat out.txt * debug build cache * fix debug build cache * add dashboards-panel-layouts test * tidy up * no more debug * fix grafana dir * add dashboards-move-panel test * skip some failing tests * mark up plugins tests with @plugins tag, only run @plugins tests in drone * Hackathon/Playwright Conversion - Various Suite (#107516) * Playwright Migration: Various Suite tests * skipping bad tests * fix some tests that can fail * fix uid * separate user for the verify-i18n test * build test plugins for grafana server * properly blur input fields * login manually * get dashboardPage from goto * ignore a couple of type assertions * remove a couple of timeouts * remove timeouts on dashboard-share-internally * use toBeHidden * make dashboard-share-internally more stable * remove TEMP_DAGGER_TOKEN * clean up visaulization-suggestions * unskip gauge test * unskip trace-view-scrolling * attempt to make create variable utils stable * unskip loki tests * make go linter happy * unskip edit-group-by-variables test * unskip move panel tests * isolate dashboard-timepicker tests with separate user * create data source as part of smoke test * make sure we're awaiting in dashboard-edit-adhoc-variables * make dashboards-edit-variables test more robust * Hackathon Playwright: Dashboards Search (#107580) * Hackathon Playwright: Dashboards Search * Feedback changes * make trace-view-scrolling more stable * add json report and bench step * fix bench version * move fail step to after the playwright report so we can report test failures * fix output file name * fix typo * try wrap in expect.poll * stability * bit more tidy up * fix dashboard-new-layouts tests * move test-plugins to e2e-playwright * fix go code for drone e2e run * move loki plugin-e2e test * make v2 dashboards work again --------- Co-authored-by: joshhunt <josh.hunt@grafana.com> Co-authored-by: Josh Hunt <joshhunt@users.noreply.github.com> Co-authored-by: Collin Fingar <collin.fingar@grafana.com> Co-authored-by: Jeff Levin <jeff@levinology.com>
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
# @grafana/plugin-e2e API tests
|
||||
|
||||
The purpose of the E2E tests in this directory is not to test the plugins per se - it's to verify that the fixtures, models and expect matchers provided by the [`@grafana/plugin-e2e`](https://github.com/grafana/plugin-tools/tree/main/packages/plugin-e2e) package are compatible with the latest version of Grafana. If you find that any of these tests are failing, it's likely because you have made changes in the Grafana UI that breaks the end-to-end testing APIs in plugin-e2e. For information on how to address this, follow the instructions in the [contributing guidelines](https://github.com/grafana/plugin-tools/blob/main/packages/plugin-e2e/CONTRIBUTING.md#how-to-fix-broken-test-scenarios-after-changes-in-grafana) for the @grafana/plugin-e2e package in the plugin-tools repository.
|
||||
@@ -0,0 +1,31 @@
|
||||
import * as e2e from '@grafana/e2e-selectors';
|
||||
import { expect, test } from '@grafana/plugin-e2e';
|
||||
|
||||
// let's disable the feature toggles for now, otherwise the getAlertRuleQueryRow fails and I don't see any other way to get the query row
|
||||
test.use({ featureToggles: { alertingQueryAndExpressionsStepMode: false, alertingNotificationsStepMode: false } });
|
||||
|
||||
test.describe('plugin-e2e-api-tests admin', { tag: ['@plugins'] }, () => {
|
||||
test('should evaluate to false if entire request returns 200 but partial query result is invalid', async ({
|
||||
page,
|
||||
alertRuleEditPage,
|
||||
}) => {
|
||||
await alertRuleEditPage.alertRuleNameField.fill('Test Alert Rule');
|
||||
|
||||
//add working query
|
||||
const queryA = alertRuleEditPage.getAlertRuleQueryRow('A');
|
||||
await queryA.datasource.set('gdev-prometheus');
|
||||
await queryA.locator.getByLabel('Code').click();
|
||||
await page.waitForFunction(() => window.monaco);
|
||||
await queryA.getByGrafanaSelector(e2e.selectors.components.QueryField.container).click();
|
||||
await page.keyboard.insertText('topk(5, max(scrape_duration_seconds) by (job))');
|
||||
|
||||
//add broken query
|
||||
const newQuery = await alertRuleEditPage.clickAddQueryRow();
|
||||
await newQuery.datasource.set('gdev-prometheus');
|
||||
await newQuery.locator.getByLabel('Code').click();
|
||||
await newQuery.getByGrafanaSelector(e2e.selectors.components.QueryField.container).click();
|
||||
await page.keyboard.insertText('topk(5,');
|
||||
|
||||
await expect(alertRuleEditPage.evaluate()).not.toBeOK();
|
||||
});
|
||||
});
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
import { expect, test } from '@grafana/plugin-e2e';
|
||||
import { AlertVariant } from '@grafana/ui';
|
||||
|
||||
import {
|
||||
successfulAnnotationQueryWithData,
|
||||
failedAnnotationQueryWithMultipleErrors,
|
||||
successfulAnnotationQueryWithoutData,
|
||||
failedAnnotationQuery,
|
||||
} from '../mocks/queries';
|
||||
|
||||
interface Scenario {
|
||||
name: string;
|
||||
mock: object;
|
||||
text: string;
|
||||
severity: AlertVariant;
|
||||
status: number;
|
||||
}
|
||||
|
||||
const scenarios: Scenario[] = [
|
||||
{ name: 'error', severity: 'error', mock: failedAnnotationQuery, text: 'Google API Error 400', status: 400 },
|
||||
{
|
||||
name: 'multiple errors',
|
||||
severity: 'error',
|
||||
mock: failedAnnotationQueryWithMultipleErrors,
|
||||
text: 'Google API Error 400Google API Error 401',
|
||||
status: 400,
|
||||
},
|
||||
{
|
||||
name: 'data',
|
||||
severity: 'success',
|
||||
mock: successfulAnnotationQueryWithData,
|
||||
text: '2 events (from 2 fields)',
|
||||
status: 200,
|
||||
},
|
||||
{
|
||||
name: 'empty result',
|
||||
severity: 'warning',
|
||||
mock: successfulAnnotationQueryWithoutData,
|
||||
text: 'No events found',
|
||||
status: 200,
|
||||
},
|
||||
];
|
||||
|
||||
test.describe('plugin-e2e-api-tests admin', { tag: ['@plugins'] }, () => {
|
||||
for (const scenario of scenarios) {
|
||||
test(`annotation query data with ${scenario.name}`, async ({ annotationEditPage, page }) => {
|
||||
annotationEditPage.mockQueryDataResponse(scenario.mock, scenario.status);
|
||||
await annotationEditPage.datasource.set('gdev-testdata');
|
||||
await page.getByLabel('Scenario').last().fill('CSV Content');
|
||||
await page.keyboard.press('Tab');
|
||||
await annotationEditPage.runQuery();
|
||||
await expect(annotationEditPage).toHaveAlert(scenario.severity, { hasText: scenario.text });
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,23 @@
|
||||
import { expect, test } from '@grafana/plugin-e2e';
|
||||
|
||||
const REACT_TABLE_DASHBOARD = { uid: 'U_bZIMRMk' };
|
||||
|
||||
test.describe(
|
||||
'plugin-e2e-api-tests admin',
|
||||
{
|
||||
tag: ['@plugins'],
|
||||
},
|
||||
() => {
|
||||
test('add panel in already existing dashboard', async ({ gotoDashboardPage, page }) => {
|
||||
const dashboardPage = await gotoDashboardPage(REACT_TABLE_DASHBOARD);
|
||||
await dashboardPage.addPanel();
|
||||
await expect(page.url()).toContain('editPanel');
|
||||
});
|
||||
|
||||
test('add panel in new dashboard', async ({ dashboardPage, page }) => {
|
||||
const panelEditPage = await dashboardPage.addPanel();
|
||||
await expect(panelEditPage.panel.locator).toBeVisible();
|
||||
await expect(page.url()).toContain('editPanel');
|
||||
});
|
||||
}
|
||||
);
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
import { expect, test } from '@grafana/plugin-e2e';
|
||||
|
||||
import { formatExpectError } from '../errors';
|
||||
|
||||
test.describe(
|
||||
'plugin-e2e-api-tests admin',
|
||||
{
|
||||
tag: ['@plugins'],
|
||||
},
|
||||
() => {
|
||||
test.describe('test createDataSourceConfigPage fixture, saveAndTest and toBeOK matcher', () => {
|
||||
test('invalid credentials should return an error', async ({ createDataSourceConfigPage, page }) => {
|
||||
const configPage = await createDataSourceConfigPage({ type: 'prometheus' });
|
||||
await page.getByPlaceholder('http://localhost:9090').fill('http://localhost:9090');
|
||||
await expect(
|
||||
configPage.saveAndTest(),
|
||||
formatExpectError('Expected save data source config to fail when Prometheus server is not running')
|
||||
).not.toBeOK();
|
||||
});
|
||||
|
||||
test('valid credentials should return a 200 status code', async ({ createDataSourceConfigPage, page }) => {
|
||||
const configPage = await createDataSourceConfigPage({ type: 'prometheus' });
|
||||
configPage.mockHealthCheckResponse({ status: 200 });
|
||||
await page.getByPlaceholder('http://localhost:9090').fill('http://localhost:9090');
|
||||
await expect(
|
||||
configPage.saveAndTest(),
|
||||
formatExpectError('Expected data source config to be successfully saved')
|
||||
).toBeOK();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('test data source with frontend only health check', () => {
|
||||
test('valid credentials should display a success alert on the page', async ({
|
||||
createDataSourceConfigPage,
|
||||
page,
|
||||
}) => {
|
||||
const configPage = await createDataSourceConfigPage({ type: 'zipkin' });
|
||||
configPage.mockHealthCheckResponse({ message: 'Data source is working', status: 'OK' }, 200);
|
||||
await page.getByPlaceholder('http://localhost:9411').fill('http://localhost:9411');
|
||||
await expect(configPage.saveAndTest()).toBeOK();
|
||||
await expect(
|
||||
configPage,
|
||||
formatExpectError('Expected data source config to display success alert after save')
|
||||
).toHaveAlert('success', { hasNotText: 'Datasource updated' });
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,19 @@
|
||||
import { expect, test } from '@grafana/plugin-e2e';
|
||||
|
||||
import { formatExpectError } from '../errors';
|
||||
|
||||
test.describe(
|
||||
'plugin-e2e-api-tests admin',
|
||||
{
|
||||
tag: ['@plugins'],
|
||||
},
|
||||
() => {
|
||||
test('query data response should be OK when query is valid', async ({ explorePage }) => {
|
||||
await explorePage.datasource.set('gdev-testdata');
|
||||
await expect(
|
||||
explorePage.runQuery(),
|
||||
formatExpectError('Expected Explore query to execute successfully')
|
||||
).toBeOK();
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,25 @@
|
||||
import { expect, test } from '@grafana/plugin-e2e';
|
||||
|
||||
const TRUTHY_CUSTOM_TOGGLE = 'custom_toggle1';
|
||||
const FALSY_CUSTOM_TOGGLE = 'custom_toggle2';
|
||||
|
||||
// override the feature toggles defined in playwright.config.ts only for tests in this file
|
||||
test.use({
|
||||
featureToggles: {
|
||||
[TRUTHY_CUSTOM_TOGGLE]: true,
|
||||
[FALSY_CUSTOM_TOGGLE]: false,
|
||||
},
|
||||
});
|
||||
|
||||
test.describe(
|
||||
'plugin-e2e-api-tests admin',
|
||||
{
|
||||
tag: ['@plugins'],
|
||||
},
|
||||
() => {
|
||||
test('should set and check feature toggles correctly', async ({ isFeatureToggleEnabled }) => {
|
||||
expect(await isFeatureToggleEnabled(TRUTHY_CUSTOM_TOGGLE)).toBeTruthy();
|
||||
expect(await isFeatureToggleEnabled(FALSY_CUSTOM_TOGGLE)).toBeFalsy();
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,95 @@
|
||||
import * as e2e from '@grafana/e2e-selectors';
|
||||
import { expect, test } from '@grafana/plugin-e2e';
|
||||
|
||||
test.describe(
|
||||
'plugin-e2e-api-tests admin',
|
||||
{
|
||||
tag: ['@plugins'],
|
||||
},
|
||||
() => {
|
||||
test.describe('Loki editor', () => {
|
||||
test('Autocomplete features should work as expected.', async ({ page }) => {
|
||||
// Go to loki datasource in explore
|
||||
await page.goto(
|
||||
'/explore?schemaVersion=1&panes=%7B%22iap%22:%7B%22datasource%22:%22gdev-loki%22,%22queries%22:%5B%7B%22refId%22:%22A%22,%22expr%22:%22%22,%22queryType%22:%22range%22,%22datasource%22:%7B%22type%22:%22loki%22,%22uid%22:%22gdev-loki%22%7D,%22editorMode%22:%22builder%22%7D%5D,%22range%22:%7B%22from%22:%22now-1h%22,%22to%22:%22now%22%7D%7D%7D&orgId=1'
|
||||
);
|
||||
|
||||
const queryEditor = page.getByTestId(e2e.selectors.components.QueryField.container);
|
||||
const queryEditorRows = page.getByTestId('query-editor-rows');
|
||||
|
||||
async function assertQueryEditorEmpty() {
|
||||
const queryEditorEmptyText = /^Enter to Rename.+/;
|
||||
await expect(queryEditor).toHaveText(queryEditorEmptyText);
|
||||
}
|
||||
|
||||
async function clearInput() {
|
||||
// Clear focused input
|
||||
// Monaco appears to need some time to init keybindings after a change, adding this timeout to prevent flake
|
||||
await page.waitForTimeout(100);
|
||||
await page.keyboard.press('ControlOrMeta+A');
|
||||
await page.keyboard.press('Backspace');
|
||||
}
|
||||
|
||||
// assert that the query builder is shown by default
|
||||
await expect(page.getByText('Label filters')).toHaveCount(1);
|
||||
|
||||
// switch to code editor
|
||||
await page.getByLabel('Code').click();
|
||||
|
||||
await page.waitForFunction(() => window.monaco);
|
||||
await expect(queryEditor).toHaveCount(1);
|
||||
await assertQueryEditorEmpty();
|
||||
|
||||
// assert editor automatically adds close paren
|
||||
await queryEditor.click();
|
||||
await page.keyboard.type('time(');
|
||||
await expect(queryEditor).toContainText('time()');
|
||||
|
||||
// removes closing brace when opening brace is removed
|
||||
await clearInput();
|
||||
await assertQueryEditorEmpty();
|
||||
await page.keyboard.type('avg_over_time(');
|
||||
await expect(queryEditor).toContainText('avg_over_time()');
|
||||
await page.keyboard.press('Backspace');
|
||||
await expect(queryEditor).not.toContainText('avg_over_time()');
|
||||
await expect(queryEditor).toContainText('avg_over_time');
|
||||
|
||||
// keeps closing brace when opening brace is removed and inner values exist
|
||||
await clearInput();
|
||||
await assertQueryEditorEmpty();
|
||||
await page.keyboard.type('time(test');
|
||||
await page.keyboard.press('ArrowLeft');
|
||||
await page.keyboard.press('ArrowLeft');
|
||||
await page.keyboard.press('ArrowLeft');
|
||||
await page.keyboard.press('ArrowLeft');
|
||||
await page.keyboard.press('Backspace');
|
||||
await expect(queryEditor).toContainText('timetest');
|
||||
|
||||
// overrides an automatically inserted paren
|
||||
await clearInput();
|
||||
await assertQueryEditorEmpty();
|
||||
await page.keyboard.type('time()');
|
||||
await expect(queryEditor).toContainText('time()');
|
||||
|
||||
// does not override manually inserted braces
|
||||
await clearInput();
|
||||
await assertQueryEditorEmpty();
|
||||
await page.keyboard.type('))');
|
||||
await expect(queryEditor).toContainText('))');
|
||||
|
||||
// Should execute the query when enter with shift is pressed
|
||||
await clearInput();
|
||||
await assertQueryEditorEmpty();
|
||||
await page.keyboard.press('Shift+Enter');
|
||||
await expect(page.getByTestId('explore-no-data')).toHaveCount(1);
|
||||
|
||||
// Suggestions plugin
|
||||
await clearInput();
|
||||
await assertQueryEditorEmpty();
|
||||
await page.keyboard.type('av');
|
||||
await expect(queryEditorRows.getByLabel(/avg, docs:/)).toHaveCount(1);
|
||||
await expect(queryEditorRows.getByLabel(/avg_over_time, docs:/)).toHaveCount(1);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
import { expect, test } from '@grafana/plugin-e2e';
|
||||
|
||||
import { formatExpectError } from '../errors';
|
||||
import { successfulDataQuery } from '../mocks/queries';
|
||||
|
||||
const REACT_TABLE_DASHBOARD = { uid: 'U_bZIMRMk' };
|
||||
|
||||
test.describe(
|
||||
'plugin-e2e-api-tests admin',
|
||||
{
|
||||
tag: ['@plugins'],
|
||||
},
|
||||
() => {
|
||||
test.describe('panel edit page', () => {
|
||||
test('table panel data assertions with provisioned dashboard', async ({ gotoPanelEditPage }) => {
|
||||
const panelEditPage = await gotoPanelEditPage({ dashboard: REACT_TABLE_DASHBOARD, id: '4' });
|
||||
await expect(
|
||||
panelEditPage.panel.locator,
|
||||
formatExpectError('Could not locate panel in panel edit page')
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
panelEditPage.panel.fieldNames,
|
||||
formatExpectError('Could not locate header elements in table panel')
|
||||
).toContainText(['Field', 'Max', 'Mean', 'Last']);
|
||||
});
|
||||
|
||||
test('table panel data assertions', async ({ panelEditPage }) => {
|
||||
await panelEditPage.mockQueryDataResponse(successfulDataQuery, 200);
|
||||
await panelEditPage.datasource.set('gdev-testdata');
|
||||
await panelEditPage.setVisualization('Table');
|
||||
await panelEditPage.refreshPanel();
|
||||
await expect(
|
||||
panelEditPage.panel.locator,
|
||||
formatExpectError('Could not locate panel in panel edit page')
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
panelEditPage.panel.fieldNames,
|
||||
formatExpectError('Could not locate header elements in table panel')
|
||||
).toContainText(['col1', 'col2']);
|
||||
await expect(
|
||||
panelEditPage.panel.data,
|
||||
formatExpectError('Could not locate headers in table panel')
|
||||
).toContainText(['val1', 'val2', 'val3', 'val4']);
|
||||
});
|
||||
|
||||
test('timeseries panel - table view assertions', async ({ panelEditPage }) => {
|
||||
await panelEditPage.mockQueryDataResponse(successfulDataQuery, 200);
|
||||
await panelEditPage.datasource.set('gdev-testdata');
|
||||
await panelEditPage.setVisualization('Time series');
|
||||
await panelEditPage.refreshPanel();
|
||||
await panelEditPage.toggleTableView();
|
||||
await expect(
|
||||
panelEditPage.panel.locator,
|
||||
formatExpectError('Could not locate panel in panel edit page')
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
panelEditPage.panel.fieldNames,
|
||||
formatExpectError('Could not locate header elements in table panel')
|
||||
).toContainText(['col1', 'col2']);
|
||||
await expect(
|
||||
panelEditPage.panel.data,
|
||||
formatExpectError('Could not locate data elements in table panel')
|
||||
).toContainText(['val1', 'val2', 'val3', 'val4']);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('dashboard page', () => {
|
||||
test('getting panel by title', async ({ gotoDashboardPage }) => {
|
||||
const dashboardPage = await gotoDashboardPage(REACT_TABLE_DASHBOARD);
|
||||
await dashboardPage.goto();
|
||||
const panel = await dashboardPage.getPanelByTitle('Colored background');
|
||||
await expect(panel.fieldNames).toContainText(['Field', 'Max', 'Mean', 'Last']);
|
||||
});
|
||||
|
||||
test('getting panel by id', async ({ gotoDashboardPage }) => {
|
||||
const dashboardPage = await gotoDashboardPage(REACT_TABLE_DASHBOARD);
|
||||
await dashboardPage.goto();
|
||||
const panel = await dashboardPage.getPanelByTitle('Colored background');
|
||||
await expect(
|
||||
panel.fieldNames,
|
||||
formatExpectError('Could not locate header elements in table panel')
|
||||
).toContainText(['Field', 'Max', 'Mean', 'Last']);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('explore page', () => {
|
||||
test('table panel', async ({ explorePage }) => {
|
||||
const url =
|
||||
'left=%7B"datasource":"grafana","queries":%5B%7B"queryType":"randomWalk","refId":"A","datasource":%7B"type":"datasource","uid":"grafana"%7D%7D%5D,"range":%7B"from":"1547161200000","to":"1576364400000"%7D%7D&orgId=1';
|
||||
await explorePage.goto({
|
||||
queryParams: new URLSearchParams(url),
|
||||
});
|
||||
await expect(
|
||||
explorePage.timeSeriesPanel.locator,
|
||||
formatExpectError('Could not locate time series panel in explore page')
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
explorePage.tablePanel.locator,
|
||||
formatExpectError('Could not locate table panel in explore page')
|
||||
).toBeVisible();
|
||||
await expect(explorePage.tablePanel.fieldNames).toContainText(['time', 'A-series']);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,214 @@
|
||||
import { expect, test } from '@grafana/plugin-e2e';
|
||||
|
||||
import { formatExpectError } from '../errors';
|
||||
import { successfulDataQuery } from '../mocks/queries';
|
||||
import { scenarios } from '../mocks/resources';
|
||||
|
||||
const PANEL_TITLE = 'Table panel E2E test';
|
||||
const TABLE_VIZ_NAME = 'Table';
|
||||
const TIME_SERIES_VIZ_NAME = 'Time series';
|
||||
const STANDARD_OTIONS_CATEGORY = 'Standard options';
|
||||
const DISPLAY_NAME_LABEL = 'Display name';
|
||||
const REACT_TABLE_DASHBOARD = { uid: 'U_bZIMRMk' };
|
||||
|
||||
test.describe(
|
||||
'plugin-e2e-api-tests admin',
|
||||
{
|
||||
tag: ['@plugins'],
|
||||
},
|
||||
() => {
|
||||
test.describe('query editor query data', () => {
|
||||
test('query data response should be OK when query is valid', async ({ panelEditPage }) => {
|
||||
await panelEditPage.datasource.set('gdev-testdata');
|
||||
await expect(
|
||||
panelEditPage.refreshPanel(),
|
||||
formatExpectError('Expected panel query to execute successfully')
|
||||
).toBeOK();
|
||||
});
|
||||
|
||||
test('query data response should not be OK and panel error should be displayed when query is invalid', async ({
|
||||
panelEditPage,
|
||||
}) => {
|
||||
await panelEditPage.datasource.set('gdev-testdata');
|
||||
const queryEditorRow = await panelEditPage.getQueryEditorRow('A');
|
||||
await queryEditorRow.getByLabel('Labels').fill('invalid-label-format');
|
||||
await expect(panelEditPage.refreshPanel(), formatExpectError('Expected panel query to fail')).not.toBeOK();
|
||||
await expect(
|
||||
panelEditPage.panel.getErrorIcon(),
|
||||
formatExpectError('Expected panel error to be displayed after query execution')
|
||||
).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('query editor with mocked responses', () => {
|
||||
test('and resource `scenarios` is mocked', async ({ selectors, dashboardPage }) => {
|
||||
await dashboardPage.mockResourceResponse('scenarios', scenarios);
|
||||
const panelEditPage = await dashboardPage.addPanel();
|
||||
await panelEditPage.datasource.set('gdev-testdata');
|
||||
const queryEditorRow = await panelEditPage.getQueryEditorRow('A');
|
||||
await queryEditorRow.getByLabel('Scenario').last().click();
|
||||
await expect(
|
||||
panelEditPage.getByGrafanaSelector(selectors.components.Select.option),
|
||||
formatExpectError('Expected certain select options to be displayed after clicking on the select input')
|
||||
).toHaveText(scenarios.map((s) => s.name));
|
||||
});
|
||||
|
||||
test('mocked query data response', async ({ panelEditPage, selectors }) => {
|
||||
await panelEditPage.mockQueryDataResponse(successfulDataQuery, 200);
|
||||
await panelEditPage.datasource.set('gdev-testdata');
|
||||
await panelEditPage.setVisualization(TABLE_VIZ_NAME);
|
||||
await panelEditPage.refreshPanel();
|
||||
await expect(
|
||||
panelEditPage.panel.getErrorIcon(),
|
||||
formatExpectError('Did not expect panel error to be displayed after query execution')
|
||||
).toBeHidden();
|
||||
await expect(
|
||||
panelEditPage.getByGrafanaSelector(selectors.components.Panels.Visualization.Table.body),
|
||||
formatExpectError('Expected certain select options to be displayed after clicking on the select input')
|
||||
).toHaveText('val1val2val3val4');
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('edit panel plugin settings', () => {
|
||||
test('change viz to table panel, set panel title and collapse section', async ({
|
||||
panelEditPage,
|
||||
selectors,
|
||||
page,
|
||||
}) => {
|
||||
await panelEditPage.setVisualization(TABLE_VIZ_NAME);
|
||||
await expect(
|
||||
panelEditPage.getByGrafanaSelector(selectors.components.PanelEditor.toggleVizPicker),
|
||||
formatExpectError('Expected panel visualization to be set to table')
|
||||
).toHaveText(TABLE_VIZ_NAME);
|
||||
await panelEditPage.setPanelTitle(PANEL_TITLE);
|
||||
await expect(
|
||||
panelEditPage.getByGrafanaSelector(selectors.components.Panels.Panel.title(PANEL_TITLE)),
|
||||
formatExpectError('Expected panel title to be updated')
|
||||
).toBeVisible();
|
||||
await panelEditPage.collapseSection(STANDARD_OTIONS_CATEGORY);
|
||||
await expect(
|
||||
page.getByText(DISPLAY_NAME_LABEL),
|
||||
formatExpectError('Expected section to be collapsed')
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test('Select time zone in timezone picker', async ({ panelEditPage }) => {
|
||||
await panelEditPage.setVisualization(TIME_SERIES_VIZ_NAME);
|
||||
const axisOptions = await panelEditPage.getCustomOptions('Axis');
|
||||
const timeZonePicker = axisOptions.getSelect('Time zone');
|
||||
|
||||
await timeZonePicker.selectOption('Europe/Stockholm');
|
||||
await expect(timeZonePicker).toHaveSelected('Europe/Stockholm');
|
||||
});
|
||||
|
||||
test('select unit in unit picker', async ({ panelEditPage }) => {
|
||||
await panelEditPage.setVisualization(TIME_SERIES_VIZ_NAME);
|
||||
const standardOptions = panelEditPage.getStandardOptions();
|
||||
const unitPicker = standardOptions.getUnitPicker('Unit');
|
||||
|
||||
await unitPicker.selectOption('Misc > Pixels');
|
||||
|
||||
await expect(unitPicker).toHaveSelected('Pixels');
|
||||
});
|
||||
|
||||
test('enter value in number input', async ({ panelEditPage }) => {
|
||||
await panelEditPage.setVisualization(TIME_SERIES_VIZ_NAME);
|
||||
const axisOptions = panelEditPage.getCustomOptions('Axis');
|
||||
const lineWith = axisOptions.getNumberInput('Soft min');
|
||||
|
||||
await lineWith.fill('10');
|
||||
|
||||
await expect(lineWith).toHaveValue('10');
|
||||
});
|
||||
|
||||
test('enter value in slider', async ({ panelEditPage }) => {
|
||||
await panelEditPage.setVisualization(TIME_SERIES_VIZ_NAME);
|
||||
const graphOptions = panelEditPage.getCustomOptions('Graph styles');
|
||||
const lineWidth = graphOptions.getSliderInput('Line width');
|
||||
|
||||
await lineWidth.fill('10');
|
||||
|
||||
await expect(lineWidth).toHaveValue('10');
|
||||
});
|
||||
|
||||
test('select value in single value select', async ({ panelEditPage }) => {
|
||||
await panelEditPage.setVisualization(TIME_SERIES_VIZ_NAME);
|
||||
const standardOptions = panelEditPage.getStandardOptions();
|
||||
const colorSchemeSelect = standardOptions.getSelect('Color scheme');
|
||||
|
||||
await colorSchemeSelect.selectOption('Classic palette');
|
||||
await expect(colorSchemeSelect).toHaveSelected('Classic palette');
|
||||
});
|
||||
|
||||
test('clear input', async ({ panelEditPage }) => {
|
||||
await panelEditPage.setVisualization(TIME_SERIES_VIZ_NAME);
|
||||
const panelOptions = panelEditPage.getPanelOptions();
|
||||
const title = panelOptions.getTextInput('Title');
|
||||
|
||||
await expect(title).toHaveValue('New panel');
|
||||
await title.clear();
|
||||
await expect(title).toHaveValue('');
|
||||
});
|
||||
|
||||
test('enter value in input', async ({ panelEditPage }) => {
|
||||
await panelEditPage.setVisualization(TIME_SERIES_VIZ_NAME);
|
||||
const panelOptions = panelEditPage.getPanelOptions();
|
||||
const description = panelOptions.getTextInput('Description');
|
||||
|
||||
await expect(description).toHaveValue('');
|
||||
await description.fill('This is a panel');
|
||||
await expect(description).toHaveValue('This is a panel');
|
||||
});
|
||||
|
||||
test('unchecking switch', async ({ panelEditPage }) => {
|
||||
await panelEditPage.setVisualization(TIME_SERIES_VIZ_NAME);
|
||||
const axisOptions = panelEditPage.getCustomOptions('Axis');
|
||||
const showBorder = axisOptions.getSwitch('Show border');
|
||||
|
||||
await expect(showBorder).toBeChecked({ checked: false });
|
||||
await showBorder.check();
|
||||
await expect(showBorder).toBeChecked();
|
||||
|
||||
await showBorder.uncheck();
|
||||
await expect(showBorder).toBeChecked({ checked: false });
|
||||
});
|
||||
|
||||
test('checking switch', async ({ panelEditPage }) => {
|
||||
await panelEditPage.setVisualization(TIME_SERIES_VIZ_NAME);
|
||||
const axisOptions = panelEditPage.getCustomOptions('Axis');
|
||||
const showBorder = axisOptions.getSwitch('Show border');
|
||||
|
||||
await expect(showBorder).toBeChecked({ checked: false });
|
||||
await showBorder.check();
|
||||
await expect(showBorder).toBeChecked();
|
||||
});
|
||||
|
||||
test('re-selecting value in radio button group', async ({ panelEditPage }) => {
|
||||
await panelEditPage.setVisualization(TIME_SERIES_VIZ_NAME);
|
||||
const axisOptions = panelEditPage.getCustomOptions('Axis');
|
||||
const placement = axisOptions.getRadioGroup('Placement');
|
||||
|
||||
await placement.check('Right');
|
||||
await expect(placement).toHaveChecked('Right');
|
||||
|
||||
await placement.check('Auto');
|
||||
await expect(placement).toHaveChecked('Auto');
|
||||
});
|
||||
|
||||
test('selecting value in radio button group', async ({ panelEditPage }) => {
|
||||
await panelEditPage.setVisualization(TIME_SERIES_VIZ_NAME);
|
||||
const axisOptions = panelEditPage.getCustomOptions('Axis');
|
||||
const placement = axisOptions.getRadioGroup('Placement');
|
||||
|
||||
await placement.check('Right');
|
||||
await expect(placement).toHaveChecked('Right');
|
||||
});
|
||||
});
|
||||
|
||||
test('backToDashboard method should navigate to dashboard page', async ({ gotoPanelEditPage, page }) => {
|
||||
const panelEditPage = await gotoPanelEditPage({ dashboard: REACT_TABLE_DASHBOARD, id: '4' });
|
||||
await panelEditPage.backToDashboard();
|
||||
await expect(page.url()).not.toContain('editPanel');
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,25 @@
|
||||
import { expect, test } from '@grafana/plugin-e2e';
|
||||
|
||||
import { formatExpectError } from '../errors';
|
||||
import { prometheusLabels } from '../mocks/resources';
|
||||
|
||||
test.describe(
|
||||
'plugin-e2e-api-tests admin',
|
||||
{
|
||||
tag: ['@plugins'],
|
||||
},
|
||||
() => {
|
||||
test('variable query with mocked response', async ({ variableEditPage, page }) => {
|
||||
variableEditPage.mockResourceResponse('api/v1/labels?*', prometheusLabels);
|
||||
variableEditPage.mockResourceResponse('suggestions*', prometheusLabels);
|
||||
await variableEditPage.datasource.set('gdev-prometheus');
|
||||
await variableEditPage.getByGrafanaSelector('Query type').fill('Label names');
|
||||
await page.keyboard.press('Tab');
|
||||
await variableEditPage.runQuery();
|
||||
await expect(
|
||||
variableEditPage,
|
||||
formatExpectError('Expected variable edit page to display certain label names after query execution')
|
||||
).toDisplayPreviews(prometheusLabels.data);
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,24 @@
|
||||
import { expect, test } from '@grafana/plugin-e2e';
|
||||
|
||||
test.describe(
|
||||
'plugin-e2e-api-test viewer',
|
||||
{
|
||||
tag: ['@plugins'],
|
||||
},
|
||||
() => {
|
||||
test('should redirect to start page when permissions to navigate to page is missing', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
const homePageURL = new URL(page.url());
|
||||
await page.goto('/datasources', { waitUntil: 'networkidle' });
|
||||
const redirectedPageURL = new URL(page.url());
|
||||
expect(homePageURL.pathname).toEqual(redirectedPageURL.pathname);
|
||||
});
|
||||
|
||||
test('current user should have viewer role', async ({ page, request }) => {
|
||||
await page.goto('/');
|
||||
const response = await request.get('/api/user/orgs');
|
||||
await expect(response).toBeOK();
|
||||
await expect(await response.json()).toContainEqual(expect.objectContaining({ role: 'Viewer' }));
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,4 @@
|
||||
export const formatExpectError = (message: string) => {
|
||||
return `Error while verifying @grafana/plugin-e2e scenarios: ${message}.
|
||||
See https://github.com/grafana/grafana/blob/main/plugin-e2e/plugin-e2e-api-tests/README.md for more information.`;
|
||||
};
|
||||
@@ -0,0 +1,138 @@
|
||||
export const successfulDataQuery = {
|
||||
results: {
|
||||
A: {
|
||||
status: 200,
|
||||
frames: [
|
||||
{
|
||||
schema: {
|
||||
refId: 'A',
|
||||
fields: [
|
||||
{
|
||||
name: 'col1',
|
||||
type: 'string',
|
||||
typeInfo: {
|
||||
frame: 'string',
|
||||
nullable: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'col2',
|
||||
type: 'string',
|
||||
typeInfo: {
|
||||
frame: 'string',
|
||||
nullable: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
data: {
|
||||
values: [
|
||||
['val1', 'val3'],
|
||||
['val2', 'val4'],
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const failedAnnotationQuery: object = {
|
||||
results: {
|
||||
Anno: {
|
||||
error: 'Google API Error 400',
|
||||
errorSource: '',
|
||||
status: 500,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const failedAnnotationQueryWithMultipleErrors: object = {
|
||||
results: {
|
||||
Anno1: {
|
||||
error: 'Google API Error 400',
|
||||
errorSource: '',
|
||||
status: 400,
|
||||
},
|
||||
Anno2: {
|
||||
error: 'Google API Error 401',
|
||||
errorSource: '',
|
||||
status: 401,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const successfulAnnotationQueryWithData: object = {
|
||||
results: {
|
||||
Anno: {
|
||||
status: 200,
|
||||
frames: [
|
||||
{
|
||||
schema: {
|
||||
refId: 'Anno',
|
||||
fields: [
|
||||
{
|
||||
name: 'time',
|
||||
type: 'time',
|
||||
typeInfo: {
|
||||
frame: 'time.Time',
|
||||
nullable: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'col2',
|
||||
type: 'string',
|
||||
typeInfo: {
|
||||
frame: 'string',
|
||||
nullable: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
data: {
|
||||
values: [
|
||||
[1702973084093, 1702973084099],
|
||||
['val1', 'val2'],
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const successfulAnnotationQueryWithoutData: object = {
|
||||
results: {
|
||||
Anno: {
|
||||
status: 200,
|
||||
frames: [
|
||||
{
|
||||
schema: {
|
||||
refId: 'Anno',
|
||||
fields: [
|
||||
{
|
||||
name: 'time',
|
||||
type: 'time',
|
||||
typeInfo: {
|
||||
frame: 'time.Time',
|
||||
nullable: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'col2',
|
||||
type: 'string',
|
||||
typeInfo: {
|
||||
frame: 'string',
|
||||
nullable: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
data: {
|
||||
values: [],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
export const scenarios = [
|
||||
{
|
||||
description: '',
|
||||
id: 'annotations',
|
||||
name: 'Annotations',
|
||||
stringInput: '',
|
||||
},
|
||||
{
|
||||
description: '',
|
||||
id: 'arrow',
|
||||
name: 'Load Apache Arrow Data',
|
||||
stringInput: '',
|
||||
},
|
||||
];
|
||||
|
||||
export const prometheusLabels = {
|
||||
status: 'success',
|
||||
data: ['__name__', 'action', 'active', 'address'],
|
||||
};
|
||||
Reference in New Issue
Block a user