DashboardScene - Add various-suite to scenes e2e (#88911)

* duplicate various-suite in scenes e2e

* restore original various-suite

* adjust various-suite e2e tests for scenes

* adjust workflow for test run

* adjust flaky exemplars test

* remove old dash script and restore workflow

* duplicate e2e/various-suite/keybinds.spec.ts into e2e/scenes

* restore original e2e/various-suite/keybinds.spec.ts

* add new keybinds e2e test

* restore workflow file after test run
This commit is contained in:
Sergej-Vlasov
2024-06-12 11:36:11 +02:00
committed by GitHub
parent dc03798c34
commit 8d0e437287
26 changed files with 1993 additions and 0 deletions
@@ -0,0 +1,20 @@
import { selectors } from '@grafana/e2e-selectors';
import { e2e } from '../utils';
describe('Bar Gauge Panel', () => {
beforeEach(() => {
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'));
});
it('Bar Gauge rendering e2e tests', () => {
// open Panel Tests - Bar Gauge
e2e.flows.openDashboard({ uid: 'O6f11TZWk' });
cy.get(
`[data-viz-panel-key="panel-6"] [data-testid^="${selectors.components.Panels.Visualization.BarGauge.valueV2}"]`
)
.should('have.css', 'color', 'rgb(242, 73, 92)')
.contains('100');
});
});
@@ -0,0 +1,76 @@
import { e2e } from '../utils';
import { waitForMonacoToLoad } from '../utils/support/monaco';
const dataSourceName = 'PromExemplar';
const addDataSource = () => {
e2e.flows.addDataSource({
type: 'Prometheus',
expectedAlertMessage: 'Prometheus',
name: dataSourceName,
form: () => {
e2e.components.DataSource.Prometheus.configPage.exemplarsAddButton().click();
e2e.components.DataSource.Prometheus.configPage.internalLinkSwitch().check({ force: true });
e2e.components.DataSource.Prometheus.configPage.connectionSettings().type('http://prom-url:9090');
cy.get('[data-testid="data-testid Data source picker select container"]').click();
cy.contains('gdev-tempo').scrollIntoView().should('be.visible').click();
},
});
};
describe('Exemplars', () => {
beforeEach(() => {
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'));
cy.request({
url: `${Cypress.env('BASE_URL')}/api/datasources/name/${dataSourceName}`,
failOnStatusCode: false,
}).then((response) => {
if (response.isOkStatusCode) {
return;
}
addDataSource();
});
});
it('should be able to navigate to configured data source', () => {
cy.intercept(
{
pathname: '/api/ds/query',
},
(req) => {
const datasourceType = req.body.queries[0].datasource.type;
if (datasourceType === 'prometheus') {
req.reply({ fixture: 'exemplars-query-response.json' });
} else if (datasourceType === 'tempo') {
req.reply({ fixture: 'tempo-response.json' });
} else {
req.reply({});
}
}
);
e2e.pages.Explore.visit();
e2e.components.DataSourcePicker.container().should('be.visible').click();
cy.contains(dataSourceName).scrollIntoView().should('be.visible').click();
// Switch to code editor
e2e.components.RadioButton.container().filter(':contains("Code")').click();
// Wait for lazy loading Monaco
waitForMonacoToLoad();
e2e.components.TimePicker.openButton().click();
e2e.components.TimePicker.fromField().clear().type('2021-07-10 17:10:00');
e2e.components.TimePicker.toField().clear().type('2021-07-10 17:30:00');
e2e.components.TimePicker.applyTimeRange().click();
e2e.components.QueryField.container().should('be.visible').type('exemplar-query_bucket{shift}{enter}');
cy.get(`[data-testid="time-series-zoom-to-data"]`).click();
e2e.components.DataSource.Prometheus.exemplarMarker().first().trigger('mousemove', { force: true });
cy.contains('Query with gdev-tempo').click();
e2e.components.TraceViewer.spanBar().should('have.length', 11);
});
});
+22
View File
@@ -0,0 +1,22 @@
import { e2e } from '../utils';
describe('Explore', () => {
beforeEach(() => {
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'));
});
it('Basic path through Explore.', () => {
e2e.pages.Explore.visit();
e2e.pages.Explore.General.container().should('have.length', 1);
e2e.components.RefreshPicker.runButtonV2().should('have.length', 1);
e2e.components.DataSource.TestData.QueryTab.scenarioSelectContainer()
.scrollIntoView()
.should('be.visible')
.within(() => {
cy.get('input[id*="test-data-scenario-select-"]').should('be.visible').click();
});
cy.contains('CSV Metric Values').scrollIntoView().should('be.visible').click();
});
});
@@ -0,0 +1,72 @@
import { e2e } from '../utils';
const DASHBOARD_ID = 'ed155665';
describe('Annotations filtering', () => {
beforeEach(() => {
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'));
});
it('Tests switching filter type updates the UI accordingly', () => {
e2e.flows.openDashboard({ uid: DASHBOARD_ID });
e2e.components.NavToolbar.editDashboard.editButton().should('be.visible').click();
e2e.components.NavToolbar.editDashboard.settingsButton().should('be.visible').click();
e2e.components.Tab.title('Annotations').click();
cy.contains('New query').click();
e2e.pages.Dashboard.Settings.Annotations.Settings.name().clear().type('Red - Panel two');
e2e.pages.Dashboard.Settings.Annotations.NewAnnotation.showInLabel()
.should('be.visible')
.within(() => {
// All panels
e2e.components.Annotations.annotationsTypeInput().find('input').type('All panels{enter}', { force: true });
e2e.components.Annotations.annotationsChoosePanelInput().should('not.exist');
// All panels except
e2e.components.Annotations.annotationsTypeInput()
.find('input')
.type('All panels except{enter}', { force: true });
e2e.components.Annotations.annotationsChoosePanelInput().should('be.visible');
// Selected panels
e2e.components.Annotations.annotationsTypeInput().find('input').type('Selected panels{enter}', { force: true });
e2e.components.Annotations.annotationsChoosePanelInput()
.should('be.visible')
.find('input')
.type('Panel two{enter}', { force: true });
});
cy.get('body').click();
e2e.components.NavToolbar.editDashboard.backToDashboardButton().should('be.visible').click();
e2e.pages.Dashboard.Controls()
.should('be.visible')
.within(() => {
e2e.pages.Dashboard.SubMenu.submenuItemLabels('Red - Panel two')
.should('be.visible')
.parent()
.within((el) => {
cy.get('input')
.should('be.checked')
.uncheck({ force: true })
.should('not.be.checked')
.check({ force: true });
});
e2e.pages.Dashboard.SubMenu.submenuItemLabels('Red, only panel 1')
.should('be.visible')
.parent()
.within((el) => {
cy.get('input').should('be.checked');
});
});
e2e.components.Panels.Panel.title('Panel one')
.should('exist')
.within(() => {
e2e.pages.Dashboard.Annotations.marker().should('exist').should('have.length', 4);
});
});
});
@@ -0,0 +1,71 @@
import { e2e } from '../utils';
const APP_ID = 'sandbox-app-test';
describe('Datasource sandbox', () => {
before(() => {
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'), true);
cy.request({
url: `${Cypress.env('BASE_URL')}/api/plugins/${APP_ID}/settings`,
method: 'POST',
body: {
enabled: true,
},
});
});
beforeEach(() => {
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'), true);
});
describe('App Page', () => {
describe('Sandbox disabled', () => {
beforeEach(() => {
cy.window().then((win) => {
win.localStorage.setItem('grafana.featureToggles', 'pluginsFrontendSandbox=0');
});
});
it('Loads the app page without the sandbox div wrapper', () => {
cy.visit(`/a/${APP_ID}`);
cy.wait(200); // wait to prevent false positives because cypress checks too fast
cy.get('div[data-plugin-sandbox="sandbox-app-test"]').should('not.exist');
cy.get('div[data-testid="sandbox-app-test-page-one"]').should('exist');
});
it('Loads the app configuration without the sandbox div wrapper', () => {
cy.visit(`/plugins/${APP_ID}`);
cy.wait(200); // wait to prevent false positives because cypress checks too fast
cy.get('div[data-plugin-sandbox="sandbox-app-test"]').should('not.exist');
cy.get('div[data-testid="sandbox-app-test-config-page"]').should('exist');
});
});
describe('Sandbox enabled', () => {
beforeEach(() => {
cy.window().then((win) => {
win.localStorage.setItem('grafana.featureToggles', 'pluginsFrontendSandbox=1');
});
});
it('Loads the app page with the sandbox div wrapper', () => {
cy.visit(`/a/${APP_ID}`);
cy.get('div[data-plugin-sandbox="sandbox-app-test"]').should('exist');
cy.get('div[data-testid="sandbox-app-test-page-one"]').should('exist');
});
it('Loads the app configuration with the sandbox div wrapper', () => {
cy.visit(`/plugins/${APP_ID}`);
cy.get('div[data-plugin-sandbox="sandbox-app-test"]').should('exist');
cy.get('div[data-testid="sandbox-app-test-config-page"]').should('exist');
});
});
});
afterEach(() => {
e2e.flows.revertAllChanges();
});
after(() => {
cy.clearCookies();
});
});
@@ -0,0 +1,145 @@
import { random } from 'lodash';
import { e2e } from '../utils';
const DATASOURCE_ID = 'sandbox-test-datasource';
let DATASOURCE_CONNECTION_ID = '';
const DATASOURCE_TYPED_NAME = 'SandboxDatasourceInstance';
describe('Datasource sandbox', () => {
before(() => {
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'), true);
e2e.pages.AddDataSource.visit();
e2e.pages.AddDataSource.dataSourcePluginsV2('Sandbox datasource test plugin')
.scrollIntoView()
.should('be.visible') // prevents flakiness
.click();
e2e.pages.DataSource.name().clear();
e2e.pages.DataSource.name().type(DATASOURCE_TYPED_NAME);
e2e.pages.DataSource.saveAndTest().click();
cy.url().then((url) => {
const split = url.split('/');
DATASOURCE_CONNECTION_ID = split[split.length - 1];
});
});
beforeEach(() => {
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'), true);
});
describe('Config Editor', () => {
describe('Sandbox disabled', () => {
beforeEach(() => {
cy.window().then((win) => {
win.localStorage.setItem('grafana.featureToggles', 'pluginsFrontendSandbox=0');
});
});
it('Should not render a sandbox wrapper around the datasource config editor', () => {
e2e.pages.EditDataSource.visit(DATASOURCE_CONNECTION_ID);
cy.wait(300); // wait to prevent false positives because cypress checks too fast
cy.get(`div[data-plugin-sandbox="${DATASOURCE_ID}"]`).should('not.exist');
});
});
describe('Sandbox enabled', () => {
beforeEach(() => {
cy.window().then((win) => {
win.localStorage.setItem('grafana.featureToggles', 'pluginsFrontendSandbox=1');
});
});
it('Should render a sandbox wrapper around the datasource config editor', () => {
e2e.pages.EditDataSource.visit(DATASOURCE_CONNECTION_ID);
cy.get(`div[data-plugin-sandbox="${DATASOURCE_ID}"]`).should('exist');
});
it('Should store values in jsonData and secureJsonData correctly', () => {
e2e.pages.EditDataSource.visit(DATASOURCE_CONNECTION_ID);
const valueToStore = 'test' + random(100);
cy.get('[data-testid="sandbox-config-editor-query-input"]').should('not.be.disabled');
cy.get('[data-testid="sandbox-config-editor-query-input"]').type(valueToStore);
cy.get('[data-testid="sandbox-config-editor-query-input"]').should('have.value', valueToStore);
e2e.pages.DataSource.saveAndTest().click();
e2e.pages.DataSource.alert().should('exist').contains('Sandbox Success', {});
// validate the value was stored
e2e.pages.EditDataSource.visit(DATASOURCE_CONNECTION_ID);
cy.get('[data-testid="sandbox-config-editor-query-input"]').should('not.be.disabled');
cy.get('[data-testid="sandbox-config-editor-query-input"]').should('have.value', valueToStore);
});
});
});
describe('Explore Page', () => {
describe('Sandbox disabled', () => {
beforeEach(() => {
cy.window().then((win) => {
win.localStorage.setItem('grafana.featureToggles', 'pluginsFrontendSandbox=0');
});
});
it('Should not wrap the query editor in a sandbox wrapper', () => {
e2e.pages.Explore.visit();
e2e.components.DataSourcePicker.container().should('be.visible').click();
cy.contains(DATASOURCE_TYPED_NAME).scrollIntoView().should('be.visible').click();
cy.wait(300); // wait to prevent false positives because cypress checks too fast
cy.get(`div[data-plugin-sandbox="${DATASOURCE_ID}"]`).should('not.exist');
});
it('Should accept values when typed', () => {
e2e.pages.Explore.visit();
e2e.components.DataSourcePicker.container().should('be.visible').click();
cy.contains(DATASOURCE_TYPED_NAME).scrollIntoView().should('be.visible').click();
const valueToType = 'test' + random(100);
cy.get('[data-testid="sandbox-query-editor-query-input"]').should('not.be.disabled');
cy.get('[data-testid="sandbox-query-editor-query-input"]').type(valueToType);
cy.get('[data-testid="sandbox-query-editor-query-input"]').should('have.value', valueToType);
});
});
describe('Sandbox enabled', () => {
beforeEach(() => {
cy.window().then((win) => {
win.localStorage.setItem('grafana.featureToggles', 'pluginsFrontendSandbox=1');
});
});
it('Should wrap the query editor in a sandbox wrapper', () => {
e2e.pages.Explore.visit();
e2e.components.DataSourcePicker.container().should('be.visible').click();
cy.contains(DATASOURCE_TYPED_NAME).scrollIntoView().should('be.visible').click();
cy.get(`div[data-plugin-sandbox="${DATASOURCE_ID}"]`).should('exist');
});
it('Should accept values when typed', () => {
e2e.pages.Explore.visit();
e2e.components.DataSourcePicker.container().should('be.visible').click();
cy.contains(DATASOURCE_TYPED_NAME).scrollIntoView().should('be.visible').click();
const valueToType = 'test' + random(100);
cy.get('[data-testid="sandbox-query-editor-query-input"]').should('not.be.disabled');
cy.get('[data-testid="sandbox-query-editor-query-input"]').type(valueToType);
cy.get('[data-testid="sandbox-query-editor-query-input"]').should('have.value', valueToType);
// typing the query editor should reflect in the url
cy.url().should('include', valueToType);
});
});
});
afterEach(() => {
e2e.flows.revertAllChanges();
});
after(() => {
cy.clearCookies();
});
});
+18
View File
@@ -0,0 +1,18 @@
import { e2e } from '../utils';
describe('Gauge Panel', () => {
beforeEach(() => {
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'));
});
it('Gauge rendering e2e tests', () => {
// open Panel Tests - Gauge
e2e.flows.openDashboard({ uid: '_5rDmaQiz' });
// check that gauges are rendered
cy.get('body').find(`.flot-base`).should('have.length', 16);
// check that no panel errors exist
e2e.components.Panels.Panel.headerCornerInfo('error').should('not.exist');
});
});
@@ -0,0 +1,81 @@
import { e2e } from '../utils';
const DASHBOARD_ID = 'XMjIZPmik';
const DASHBOARD_NAME = 'Panel Tests - Graph Time Regions';
const UPLOT_MAIN_DIV_SELECTOR = '[data-testid="uplot-main-div"]';
describe('Auto-migrate graph panel', () => {
beforeEach(() => {
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'));
});
it('Graph panel is migrated with `autoMigrateOldPanels` feature toggle', () => {
e2e.flows.openDashboard({ uid: DASHBOARD_ID });
cy.contains(DASHBOARD_NAME).should('be.visible');
cy.get(UPLOT_MAIN_DIV_SELECTOR).should('not.exist');
e2e.flows.openDashboard({ uid: DASHBOARD_ID, queryParams: { '__feature.autoMigrateOldPanels': true } });
cy.get(UPLOT_MAIN_DIV_SELECTOR).should('exist');
});
it('Graph panel is migrated with config `disableAngular` feature toggle', () => {
e2e.flows.openDashboard({ uid: DASHBOARD_ID });
cy.contains(DASHBOARD_NAME).should('be.visible');
cy.get(UPLOT_MAIN_DIV_SELECTOR).should('not.exist');
e2e.flows.openDashboard({ uid: DASHBOARD_ID, queryParams: { '__feature.disableAngular': true } });
cy.get(UPLOT_MAIN_DIV_SELECTOR).should('exist');
});
it('Graph panel is migrated with `autoMigrateGraphPanel` feature toggle', () => {
e2e.flows.openDashboard({ uid: DASHBOARD_ID });
cy.contains(DASHBOARD_NAME).should('be.visible');
cy.get(UPLOT_MAIN_DIV_SELECTOR).should('not.exist');
e2e.flows.openDashboard({ uid: DASHBOARD_ID, queryParams: { '__feature.autoMigrateGraphPanel': true } });
cy.get(UPLOT_MAIN_DIV_SELECTOR).should('exist');
});
it('Annotation markers exist for time regions', () => {
e2e.flows.openDashboard({ uid: DASHBOARD_ID });
cy.contains(DASHBOARD_NAME).should('be.visible');
cy.get(UPLOT_MAIN_DIV_SELECTOR).should('not.exist');
e2e.flows.openDashboard({ uid: DASHBOARD_ID, queryParams: { '__feature.autoMigrateGraphPanel': true } });
e2e.components.Panels.Panel.title('Business Hours')
.should('exist')
.within(() => {
e2e.pages.Dashboard.Annotations.marker().should('exist');
});
e2e.components.Panels.Panel.title("Sunday's 20-23")
.should('exist')
.within(() => {
e2e.pages.Dashboard.Annotations.marker().should('exist');
});
e2e.components.Panels.Panel.title('Each day of week')
.should('exist')
.within(() => {
e2e.pages.Dashboard.Annotations.marker().should('exist');
});
cy.get('#pageContent .scrollbar-view').first().scrollTo('bottom');
e2e.components.Panels.Panel.title('05:00')
.should('exist')
.within(() => {
e2e.pages.Dashboard.Annotations.marker().should('exist');
});
e2e.components.Panels.Panel.title('From 22:00 to 00:30 (crossing midnight)')
.should('exist')
.within(() => {
e2e.pages.Dashboard.Annotations.marker().should('exist');
});
});
});
@@ -0,0 +1,62 @@
import { e2e } from '../../utils';
/**
* Create a Prom data source
*/
export function createPromDS(dataSourceID: string, name: string): void {
// login
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'), true);
// select the prometheus DS
e2e.pages.AddDataSource.visit();
e2e.pages.AddDataSource.dataSourcePluginsV2(dataSourceID)
.scrollIntoView()
.should('be.visible') // prevents flakiness
.click();
// add url for DS to save without error
e2e.components.DataSource.Prometheus.configPage.connectionSettings().type('http://prom-url:9090');
// name the DS
e2e.pages.DataSource.name().clear();
e2e.pages.DataSource.name().type(name);
e2e.pages.DataSource.saveAndTest().click();
}
export function getResources() {
cy.intercept(/__name__/g, metricResponse);
cy.intercept(/metadata/g, metadataResponse);
cy.intercept(/labels/g, labelsResponse);
}
const metricResponse = {
status: 'success',
data: ['metric1', 'metric2'],
};
const metadataResponse = {
status: 'success',
data: {
metric1: [
{
type: 'counter',
help: 'metric1 help',
unit: '',
},
],
metric2: [
{
type: 'counter',
help: 'metric2 help',
unit: '',
},
],
},
};
const labelsResponse = {
status: 'success',
data: ['__name__', 'action', 'active', 'backend'],
};
@@ -0,0 +1,123 @@
import { e2e } from '../utils';
const PANEL_UNDER_TEST = 'Value reducers 1';
describe('Inspect drawer tests', () => {
beforeEach(() => {
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'));
});
it('Tests various Inspect Drawer scenarios', () => {
// @ts-ignore some typing issue
cy.on('uncaught:exception', (err) => {
if (err.stack?.indexOf("TypeError: Cannot read property 'getText' of null") !== -1) {
// On occasion monaco editor will not have the time to be properly unloaded when we change the tab
// and then the e2e test fails with the uncaught:exception:
// TypeError: Cannot read property 'getText' of null
// at Object.ai [as getFoldingRanges] (http://localhost:3001/public/build/monaco-json.worker.js:2:215257)
// at e.getFoldingRanges (http://localhost:3001/public/build/monaco-json.worker.js:2:221188)
// at e.fmr (http://localhost:3001/public/build/monaco-json.worker.js:2:116605)
// at e._handleMessage (http://localhost:3001/public/build/monaco-json.worker.js:2:7414)
// at Object.handleMessage (http://localhost:3001/public/build/monaco-json.worker.js:2:7018)
// at e._handleMessage (http://localhost:3001/public/build/monaco-json.worker.js:2:5038)
// at e.handleMessage (http://localhost:3001/public/build/monaco-json.worker.js:2:4606)
// at e.onmessage (http://localhost:3001/public/build/monaco-json.worker.js:2:7097)
// at Tt.self.onmessage (http://localhost:3001/public/build/monaco-json.worker.js:2:117109)
// return false to prevent the error from
// failing this test
return false;
}
return true;
});
e2e.flows.openDashboard({ uid: 'wfTJJL5Wz' });
// testing opening inspect drawer directly by clicking on Inspect in header menu
e2e.flows.openPanelMenuItem(e2e.flows.PanelMenuItems.Inspect, PANEL_UNDER_TEST);
expectDrawerTabsAndContent();
expectDrawerClose();
expectSubMenuScenario('Data');
expectSubMenuScenario('Query');
expectSubMenuScenario('Panel JSON', 'JSON');
e2e.flows.openPanelMenuItem(e2e.flows.PanelMenuItems.Edit, PANEL_UNDER_TEST);
e2e.components.QueryTab.queryInspectorButton().should('be.visible').click();
e2e.components.Drawer.General.title(`Inspect: ${PANEL_UNDER_TEST}`)
.should('be.visible')
.within(() => {
e2e.components.Tab.title('Query').should('be.visible');
// query should be the active tab
e2e.components.Tab.active().should('have.text', 'Query');
});
e2e.components.PanelInspector.Query.content().should('be.visible');
});
});
const expectDrawerTabsAndContent = () => {
e2e.components.Drawer.General.title(`Inspect: ${PANEL_UNDER_TEST}`)
.should('be.visible')
.within(() => {
e2e.components.Tab.title('Data').should('be.visible');
// data should be the active tab
e2e.components.Tab.active().within((li: JQuery<HTMLLIElement>) => {
expect(li.text()).equals('Data');
});
e2e.components.PanelInspector.Data.content().should('be.visible');
e2e.components.PanelInspector.Stats.content().should('not.exist');
e2e.components.PanelInspector.Json.content().should('not.exist');
e2e.components.PanelInspector.Query.content().should('not.exist');
// other tabs should also be visible, click on each to see if we get any console errors
e2e.components.Tab.title('Stats').should('be.visible').click();
e2e.components.PanelInspector.Stats.content().should('be.visible');
e2e.components.PanelInspector.Data.content().should('not.exist');
e2e.components.PanelInspector.Json.content().should('not.exist');
e2e.components.PanelInspector.Query.content().should('not.exist');
e2e.components.Tab.title('JSON').should('be.visible').click();
e2e.components.PanelInspector.Json.content().should('be.visible');
e2e.components.PanelInspector.Data.content().should('not.exist');
e2e.components.PanelInspector.Stats.content().should('not.exist');
e2e.components.PanelInspector.Query.content().should('not.exist');
e2e.components.Tab.title('Query').should('be.visible').click();
e2e.components.PanelInspector.Query.content().should('be.visible');
e2e.components.PanelInspector.Data.content().should('not.exist');
e2e.components.PanelInspector.Stats.content().should('not.exist');
e2e.components.PanelInspector.Json.content().should('not.exist');
});
};
const expectDrawerClose = () => {
// close using close button
e2e.components.Drawer.General.close().click();
e2e.components.Drawer.General.title(`Inspect: ${PANEL_UNDER_TEST}`).should('not.exist');
};
const expectSubMenuScenario = (subMenu: string, tabTitle?: string) => {
tabTitle = tabTitle ?? subMenu;
// testing opening inspect drawer from sub menus under Inspect in header menu
e2e.components.Panels.Panel.title(PANEL_UNDER_TEST).scrollIntoView().should('be.visible');
e2e.components.Panels.Panel.menu(PANEL_UNDER_TEST).click({ force: true }); // force click because menu is hidden and show on hover
// sub menus are in the DOM but not visible and because there is no hover support in Cypress force click
// https://github.com/cypress-io/cypress-example-recipes/blob/master/examples/testing-dom__hover-hidden-elements/cypress/integration/hover-hidden-elements-spec.js
// simulate hover on Inspector menu item to display sub menus
e2e.components.Panels.Panel.menuItems('Inspect').trigger('mouseover', { force: true });
e2e.components.Panels.Panel.menuItems(subMenu).click({ force: true });
// data should be the default tab
e2e.components.Tab.title(tabTitle).should('be.visible');
e2e.components.Tab.active().should('have.text', tabTitle);
expectDrawerClose();
};
+79
View File
@@ -0,0 +1,79 @@
import { e2e } from '../utils';
import { fromBaseUrl } from '../utils/support/url';
describe('Keyboard shortcuts', () => {
beforeEach(() => {
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'));
cy.visit(fromBaseUrl('/'));
// wait for the page to load
e2e.components.Panels.Panel.title('Latest from the blog').should('be.visible');
});
it('sequence shortcuts should work', () => {
cy.get('body').type('ge');
e2e.pages.Explore.General.container().should('be.visible');
cy.get('body').type('gp');
e2e.components.UserProfile.preferencesSaveButton().should('be.visible');
cy.get('body').type('gh');
e2e.components.Panels.Panel.title('Latest from the blog').should('be.visible');
});
it('ctrl+z should zoom out the time range', () => {
cy.get('body').type('ge');
e2e.pages.Explore.General.container().should('be.visible');
// Time range is 1 minute, so each shortcut press should jump back or forward by 1 minute
e2e.flows.setTimeRange({
from: '2024-06-05 10:05:00',
to: '2024-06-05 10:06:00',
zone: 'Browser',
});
e2e.components.RefreshPicker.runButtonV2().should('have.text', 'Run query');
cy.get('body').type('{ctrl}z');
e2e.components.RefreshPicker.runButtonV2().should('have.text', 'Run query');
let expectedRange = `Time range selected: 2024-06-05 10:03:30 to 2024-06-05 10:07:30`;
e2e.components.TimePicker.openButton().should('have.attr', 'aria-label', expectedRange);
});
it('multiple time range shortcuts should work', () => {
cy.get('body').type('ge');
e2e.pages.Explore.General.container().should('be.visible');
// Time range is 1 minute, so each shortcut press should jump back or forward by 1 minute
e2e.flows.setTimeRange({
from: '2024-06-05 10:05:00',
to: '2024-06-05 10:06:00',
zone: 'Browser',
});
e2e.components.RefreshPicker.runButtonV2().should('have.text', 'Run query');
cy.log('Trying one shift-left');
cy.get('body').type('t{leftarrow}');
e2e.components.RefreshPicker.runButtonV2().should('have.text', 'Run query');
let expectedRange = `Time range selected: 2024-06-05 10:04:00 to 2024-06-05 10:05:00`; // 1 min back
e2e.components.TimePicker.openButton().should('have.attr', 'aria-label', expectedRange);
cy.log('Trying two shift-lefts');
cy.get('body').type('t{leftarrow}');
e2e.components.RefreshPicker.runButtonV2().should('have.text', 'Run query');
cy.get('body').type('t{leftarrow}');
e2e.components.RefreshPicker.runButtonV2().should('have.text', 'Run query');
expectedRange = `Time range selected: 2024-06-05 10:02:00 to 2024-06-05 10:03:00`; // 2 mins back
e2e.components.TimePicker.openButton().should('have.attr', 'aria-label', expectedRange);
cy.log('Trying two shift-lefts and a shift-right');
cy.get('body').type('t{leftarrow}');
e2e.components.RefreshPicker.runButtonV2().should('have.text', 'Run query');
cy.get('body').type('t{leftarrow}');
e2e.components.RefreshPicker.runButtonV2().should('have.text', 'Run query');
cy.get('body').type('t{rightarrow}');
e2e.components.RefreshPicker.runButtonV2().should('have.text', 'Run query');
expectedRange = `Time range selected: 2024-06-05 10:01:00 to 2024-06-05 10:02:00`; // 2 mins back, 1 min forward (1 min back total)
e2e.components.TimePicker.openButton().should('have.attr', 'aria-label', expectedRange);
});
});
@@ -0,0 +1,88 @@
import { e2e } from '../utils';
import { waitForMonacoToLoad } from '../utils/support/monaco';
const dataSourceName = 'LokiEditor';
const addDataSource = () => {
e2e.flows.addDataSource({
type: 'Loki',
expectedAlertMessage: 'Unable to connect with Loki. Please check the server logs for more details.',
name: dataSourceName,
form: () => {
cy.get('#connection-url').type('http://loki-url:3100');
},
});
};
describe('Loki Query Editor', () => {
beforeEach(() => {
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'));
});
afterEach(() => {
e2e.flows.revertAllChanges();
});
it('Autocomplete features should work as expected.', () => {
addDataSource();
cy.intercept(/labels?/, (req) => {
req.reply({ status: 'success', data: ['instance', 'job', 'source'] });
});
cy.intercept(/series?/, (req) => {
req.reply({ status: 'success', data: [{ instance: 'instance1' }] });
});
// Go to Explore and choose Loki data source
e2e.pages.Explore.visit();
e2e.components.DataSourcePicker.container().should('be.visible').click();
cy.contains(dataSourceName).scrollIntoView().should('be.visible').click();
e2e.components.RadioButton.container().filter(':contains("Code")').click();
waitForMonacoToLoad();
// adds closing braces around empty value
e2e.components.QueryField.container().type('time(');
cy.get('.monaco-editor textarea:first').should(($el) => {
expect($el.val()).to.eq('time()');
});
// removes closing brace when opening brace is removed
e2e.components.QueryField.container().type('{selectall}{backspace}avg_over_time({backspace}');
cy.get('.monaco-editor textarea:first').should(($el) => {
expect($el.val()).to.eq('avg_over_time');
});
// keeps closing brace when opening brace is removed and inner values exist
e2e.components.QueryField.container().type(
'{selectall}{backspace}time(test{leftArrow}{leftArrow}{leftArrow}{leftArrow}{backspace}'
);
cy.get('.monaco-editor textarea:first').should(($el) => {
expect($el.val()).to.eq('timetest)');
});
// overrides an automatically inserted brace
e2e.components.QueryField.container().type('{selectall}{backspace}time()');
cy.get('.monaco-editor textarea:first').should(($el) => {
expect($el.val()).to.eq('time()');
});
// does not override manually inserted braces
e2e.components.QueryField.container().type('{selectall}{backspace}))');
cy.get('.monaco-editor textarea:first').should(($el) => {
expect($el.val()).to.eq('))');
});
/** Runner plugin */
// Should execute the query when enter with shift is pressed
e2e.components.QueryField.container().type('{selectall}{backspace}{shift+enter}');
cy.get('[data-testid="explore-no-data"]').should('be.visible');
/** Suggestions plugin */
e2e.components.QueryField.container().type('{selectall}av');
cy.contains('avg').should('be.visible');
cy.contains('avg_over_time').should('be.visible');
});
});
@@ -0,0 +1,106 @@
import { e2e } from '../utils';
const MISSING_LABEL_FILTER_ERROR_MESSAGE = 'Select at least 1 label filter (label and value)';
const dataSourceName = 'LokiBuilder';
const addDataSource = () => {
e2e.flows.addDataSource({
type: 'Loki',
expectedAlertMessage: 'Unable to connect with Loki. Please check the server logs for more details.',
name: dataSourceName,
form: () => {
cy.get('#connection-url').type('http://loki-url:3100');
},
});
};
const finalQuery = 'rate({instance=~"instance1|instance2"} | logfmt | __error__=`` [$__auto]';
describe('Loki query builder', () => {
beforeEach(() => {
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'));
cy.request({
url: `${Cypress.env('BASE_URL')}/api/datasources/name/${dataSourceName}`,
failOnStatusCode: false,
}).then((response) => {
if (response.isOkStatusCode) {
return;
}
addDataSource();
});
});
it('should be able to use all modes', () => {
cy.intercept(/labels\?/, (req) => {
req.reply({ status: 'success', data: ['instance', 'job', 'source'] });
}).as('labelsRequest');
cy.intercept(/series?/, (req) => {
req.reply({ status: 'success', data: [{ instance: 'instance1' }] });
});
cy.intercept(/values/, (req) => {
req.reply({ status: 'success', data: ['instance1', 'instance2'] });
}).as('valuesRequest');
cy.intercept(/index\/stats/, (req) => {
req.reply({ streams: 2, chunks: 2660, bytes: 2721792, entries: 14408 });
});
// Go to Explore and choose Loki data source
e2e.pages.Explore.visit();
e2e.components.DataSourcePicker.container().should('be.visible').click();
cy.contains(dataSourceName).scrollIntoView().should('be.visible').click();
// Start in builder mode, click and choose query pattern
e2e.components.QueryBuilder.queryPatterns().click();
cy.contains('Log query starters').click();
cy.contains('Use this query').click();
cy.contains('No pipeline errors').should('be.visible');
cy.contains('Logfmt').should('be.visible');
cy.contains('{} | logfmt | __error__=``').should('be.visible');
// Add operation
cy.contains('Operations').should('be.visible').click();
cy.contains('Range functions').should('be.visible').click();
cy.contains('Rate').should('be.visible').click();
cy.contains('rate({} | logfmt | __error__=`` [$__auto]').should('be.visible');
// Check for expected error
cy.contains(MISSING_LABEL_FILTER_ERROR_MESSAGE).should('be.visible');
// Add labels to remove error
e2e.components.QueryBuilder.labelSelect().should('be.visible').click();
// wait until labels are loaded and set on the component before starting to type
e2e.components.QueryBuilder.inputSelect().type('i');
cy.wait('@labelsRequest');
e2e.components.QueryBuilder.inputSelect().type('nstance{enter}');
e2e.components.QueryBuilder.matchOperatorSelect()
.should('be.visible')
.click({ force: true })
.children('div')
.children('input')
.type('=~{enter}', { force: true });
e2e.components.QueryBuilder.valueSelect().should('be.visible').click();
e2e.components.QueryBuilder.valueSelect().children('div').children('input').type('instance1{enter}');
cy.wait('@valuesRequest');
e2e.components.QueryBuilder.valueSelect().children('div').children('input').type('instance2{enter}');
cy.contains(MISSING_LABEL_FILTER_ERROR_MESSAGE).should('not.exist');
cy.contains(finalQuery).should('be.visible');
// Change to code editor
e2e.components.RadioButton.container().filter(':contains("Code")').click();
// We need to test this manually because the final query is split into separate DOM elements using cy.contains(finalQuery).should('be.visible'); does not detect the query.
cy.contains('rate').should('be.visible');
cy.contains('instance1|instance2').should('be.visible');
cy.contains('logfmt').should('be.visible');
cy.contains('__error__').should('be.visible');
cy.contains('$__auto').should('be.visible');
// Checks the explain mode toggle
cy.contains('label', 'Explain').click();
cy.contains('Fetch all log lines matching label filters.').should('be.visible');
});
});
@@ -0,0 +1,226 @@
import { e2e } from '../utils';
const dataSourceName = 'LokiEditor';
const addDataSource = () => {
e2e.flows.addDataSource({
type: 'Loki',
expectedAlertMessage: 'Unable to connect with Loki. Please check the server logs for more details.',
name: dataSourceName,
form: () => {
cy.get('#connection-url').type('http://loki-url:3100');
},
});
};
const lokiQueryResult = {
status: 'success',
results: {
A: {
status: 200,
frames: [
{
schema: {
refId: 'A',
meta: {
typeVersion: [0, 0],
custom: {
frameType: 'LabeledTimeValues',
},
stats: [
{
displayName: 'Summary: bytes processed per second',
unit: 'Bps',
value: 223921,
},
{
displayName: 'Summary: total bytes processed',
unit: 'decbytes',
value: 4156,
},
{
displayName: 'Summary: exec time',
unit: 's',
value: 0.01856,
},
],
executedQueryString: 'Expr: {targetLabelName="targetLabelValue"}',
},
fields: [
{
name: 'labels',
type: 'other',
typeInfo: {
frame: 'json.RawMessage',
},
},
{
name: 'Time',
type: 'time',
typeInfo: {
frame: 'time.Time',
},
},
{
name: 'Line',
type: 'string',
typeInfo: {
frame: 'string',
},
},
{
name: 'tsNs',
type: 'string',
typeInfo: {
frame: 'string',
},
},
{
name: 'id',
type: 'string',
typeInfo: {
frame: 'string',
},
},
],
},
data: {
values: [
[
{
targetLabelName: 'targetLabelValue',
instance: 'server\\1',
job: '"grafana/data"',
nonIndexed: 'value',
place: 'moon',
re: 'one.two$three^four',
source: 'data',
},
],
[1700077283237],
[
'{"_entry":"log text with ANSI \\u001b[31mpart of the text\\u001b[0m [149702545]","counter":"22292","float":"NaN","wave":-0.5877852522916832,"label":"val3","level":"info"}',
],
['1700077283237000000'],
['1700077283237000000_9b025d35'],
],
},
},
],
},
},
};
describe('Loki Query Editor', () => {
beforeEach(() => {
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'));
});
afterEach(() => {
e2e.flows.revertAllChanges();
});
beforeEach(() => {
cy.window().then((win) => {
win.localStorage.setItem('grafana.featureToggles', 'logsExploreTableVisualisation=1');
});
});
it('Should be able to add explore table to dashboard', () => {
addDataSource();
cy.intercept(/labels?/, (req) => {
req.reply({ status: 'success', data: ['instance', 'job', 'source'] });
});
cy.intercept(/series?/, (req) => {
req.reply({ status: 'success', data: [{ instance: 'instance1' }] });
});
cy.intercept(/\/api\/ds\/query\?ds_type=loki?/, (req) => {
req.reply(lokiQueryResult);
});
// Go to Explore and choose Loki data source
e2e.pages.Explore.visit();
e2e.components.DataSourcePicker.container().should('be.visible').click();
cy.contains(dataSourceName).scrollIntoView().should('be.visible').click();
cy.contains('Code').click({ force: true });
// Wait for lazy loading
// const monacoLoadingText = 'Loading...';
// e2e.components.QueryField.container().should('be.visible').should('have.text', monacoLoadingText);
e2e.components.QueryField.container()
.find('.view-overlays[role="presentation"]')
.get('.cdr')
.then(($el) => {
const win = $el[0].ownerDocument.defaultView;
const after = win.getComputedStyle($el[0], '::after');
const content = after.getPropertyValue('content');
expect(content).to.eq('"Enter a Loki query (run with Shift+Enter)"');
});
// Write a simple query
e2e.components.QueryField.container().type('query').type('{instance="instance1"');
cy.get('.monaco-editor textarea:first').should(($el) => {
expect($el.val()).to.eq('query{instance="instance1"}');
});
// Submit the query
e2e.components.QueryField.container().type('{shift+enter}');
// Assert the no-data message is not visible
cy.get('[data-testid="explore-no-data"]').should('not.exist');
// Click on the table toggle
cy.contains('Table').click({ force: true });
// One row with two cells
cy.get('[role="cell"]').should('have.length', 2);
cy.contains('label', 'targetLabelName').should('be.visible');
cy.contains('label', 'targetLabelName').click();
cy.contains('label', 'targetLabelName').within(() => {
cy.get('input[type="checkbox"]').check({ force: true });
});
cy.contains('label', 'targetLabelName').within(() => {
cy.get('input[type="checkbox"]').should('be.checked');
});
const exploreCells = cy.get('[role="cell"]');
// Now we should have a row with 3 columns
exploreCells.should('have.length', 3);
// And a value of "targetLabelValue"
exploreCells.should('contain', 'targetLabelValue');
const addToButton = cy.get('[aria-label="Add"]');
addToButton.should('be.visible');
addToButton.click();
const addToDashboardButton = cy.get('[aria-label="Add to dashboard"]');
// Now let's add this to a dashboard
addToDashboardButton.should('be.visible');
addToDashboardButton.click();
const addPanelToDashboardButton = cy.contains('Add panel to dashboard');
addPanelToDashboardButton.should('be.visible');
const openDashboardButton = cy.contains('Open dashboard');
openDashboardButton.should('be.visible');
openDashboardButton.click();
const panel = cy.get('[data-panelid="1"]');
panel.should('be.visible');
const cells = panel.find('[role="table"] [role="cell"]');
// Should have 3 columns
cells.should('have.length', 3);
// Cells contain strings found in log line
cells.contains('"wave":-0.5877852522916832');
// column has correct value of "targetLabelValue", need to requery the DOM because of the .contains call above
cy.get('[data-panelid="1"]').find('[role="table"] [role="cell"]').contains('targetLabelValue');
});
});
@@ -0,0 +1,43 @@
import { e2e } from '../utils';
import { fromBaseUrl } from '../utils/support/url';
describe('Docked Navigation', () => {
beforeEach(() => {
cy.viewport(1280, 800);
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'));
cy.visit(fromBaseUrl('/'));
});
it('should remain docked when reloading the page', () => {
// Expand, then dock the mega menu
cy.get('[aria-label="Open menu"]').click();
cy.get('[aria-label="Dock menu"]').click();
e2e.components.NavMenu.Menu().should('be.visible');
cy.reload();
e2e.components.NavMenu.Menu().should('be.visible');
});
it('should remain docked when navigating to another page', () => {
// Expand, then dock the mega menu
cy.get('[aria-label="Open menu"]').click();
cy.get('[aria-label="Dock menu"]').click();
cy.contains('a', 'Administration').click();
e2e.components.NavMenu.Menu().should('be.visible');
cy.contains('a', 'Users').click();
e2e.components.NavMenu.Menu().should('be.visible');
});
it('should become docked at larger viewport sizes', () => {
e2e.components.NavMenu.Menu().should('not.exist');
cy.viewport(1920, 1080);
cy.reload();
e2e.components.NavMenu.Menu().should('be.visible');
});
});
@@ -0,0 +1,18 @@
import { selectors } from '@grafana/e2e-selectors';
import { e2e } from '../utils';
describe('Pie Chart Panel', () => {
beforeEach(() => {
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'));
});
it('Pie Chart rendering e2e tests', () => {
// open Panel Tests - Pie Chart
e2e.flows.openDashboard({ uid: 'lVE-2YFMz' });
cy.get(
`[data-viz-panel-key="panel-11"] [data-testid^="${selectors.components.Panels.Visualization.PieChart.svgSlice}"]`
).should('have.length', 5);
});
});
@@ -0,0 +1,76 @@
import { selectors } from '@grafana/e2e-selectors';
import { e2e } from '../utils';
import { addDashboard } from '../utils/flows';
import { createPromDS, getResources } from './helpers/prometheus-helpers';
const DATASOURCE_ID = 'Prometheus';
const DATASOURCE_NAME = 'aprometheusAnnotationDS';
/**
* Click dashboard settings and then the variables tab
*
*/
function navigateToAnnotations() {
e2e.components.NavToolbar.editDashboard.editButton().should('be.visible').click();
e2e.components.NavToolbar.editDashboard.settingsButton().should('be.visible').click();
e2e.components.Tab.title('Annotations').click();
}
function addPrometheusAnnotation(annotationName: string) {
e2e.pages.Dashboard.Settings.Annotations.List.addAnnotationCTAV2().click();
getResources();
e2e.pages.Dashboard.Settings.Annotations.Settings.name().clear().type(annotationName);
e2e.components.DataSourcePicker.container().should('be.visible').click();
cy.contains(DATASOURCE_NAME).scrollIntoView().should('be.visible').click();
}
describe('Prometheus annotations', () => {
beforeEach(() => {
createPromDS(DATASOURCE_ID, DATASOURCE_NAME);
});
it('should navigate to variable query editor', () => {
const annotationName = 'promAnnotation';
addDashboard();
navigateToAnnotations();
addPrometheusAnnotation(annotationName);
e2e.components.DataSource.Prometheus.queryEditor.code.metricsBrowser
.openButton()
.contains('Metrics browser')
.click();
e2e.components.DataSource.Prometheus.queryEditor.code.metricsBrowser.selectMetric().should('exist').type('met');
e2e.components.DataSource.Prometheus.queryEditor.code.metricsBrowser
.metricList()
.should('exist')
.contains('metric1')
.click();
e2e.components.DataSource.Prometheus.queryEditor.code.metricsBrowser.useQuery().should('exist').click();
e2e.components.DataSource.Prometheus.queryEditor.code.queryField().should('exist').contains('metric1');
// check for other parts of the annotations
// min step
cy.get(`#${selectors.components.DataSource.Prometheus.annotations.minStep}`);
// title
e2e.components.DataSource.Prometheus.annotations.title().scrollIntoView().should('exist');
// tags
e2e.components.DataSource.Prometheus.annotations.tags().scrollIntoView().should('exist');
// text
e2e.components.DataSource.Prometheus.annotations.text().scrollIntoView().should('exist');
// series value as timestamp
e2e.components.DataSource.Prometheus.annotations.seriesValueAsTimestamp().scrollIntoView().should('exist');
e2e.components.NavToolbar.editDashboard.backToDashboardButton().should('be.visible').click();
// check that annotation exists
cy.get('body').contains(annotationName);
});
});
@@ -0,0 +1,111 @@
import { selectors } from '@grafana/e2e-selectors';
import { e2e } from '../utils';
const DATASOURCE_ID = 'Prometheus';
const DATASOURCE_TYPED_NAME = 'PrometheusDatasourceInstance';
describe('Prometheus config', () => {
beforeEach(() => {
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'), true);
e2e.pages.AddDataSource.visit();
e2e.pages.AddDataSource.dataSourcePluginsV2(DATASOURCE_ID)
.scrollIntoView()
.should('be.visible') // prevents flakiness
.click({ force: true });
});
it(`should have the following components:
connection settings
managed alerts
scrape interval
query timeout
default editor
disable metric lookup
prometheus type
cache level
incremental querying
disable recording rules
custom query parameters
http method
`, () => {
// connection settings
e2e.components.DataSource.Prometheus.configPage.connectionSettings().should('be.visible');
// managed alerts
cy.get(`#${selectors.components.DataSource.Prometheus.configPage.manageAlerts}`).scrollIntoView().should('exist');
// scrape interval
e2e.components.DataSource.Prometheus.configPage.scrapeInterval().scrollIntoView().should('exist');
// query timeout
e2e.components.DataSource.Prometheus.configPage.queryTimeout().scrollIntoView().should('exist');
// default editor
e2e.components.DataSource.Prometheus.configPage.defaultEditor().scrollIntoView().should('exist');
// disable metric lookup
cy.get(`#${selectors.components.DataSource.Prometheus.configPage.disableMetricLookup}`)
.scrollIntoView()
.should('exist');
// prometheus type
e2e.components.DataSource.Prometheus.configPage.prometheusType().scrollIntoView().should('exist');
// cache level
e2e.components.DataSource.Prometheus.configPage.cacheLevel().scrollIntoView().should('exist');
// incremental querying
cy.get(`#${selectors.components.DataSource.Prometheus.configPage.incrementalQuerying}`)
.scrollIntoView()
.should('exist');
// disable recording rules
cy.get(`#${selectors.components.DataSource.Prometheus.configPage.disableRecordingRules}`)
.scrollIntoView()
.should('exist');
// custom query parameters
e2e.components.DataSource.Prometheus.configPage.customQueryParameters().scrollIntoView().should('exist');
// http method
e2e.components.DataSource.Prometheus.configPage.httpMethod().scrollIntoView().should('exist');
});
it('should save the default editor when navigating to explore', () => {
e2e.components.DataSource.Prometheus.configPage.defaultEditor().scrollIntoView().should('exist').click();
selectOption('Builder');
e2e.components.DataSource.Prometheus.configPage.connectionSettings().type('http://prom-url:9090');
e2e.pages.DataSource.name().clear();
e2e.pages.DataSource.name().type(DATASOURCE_TYPED_NAME);
e2e.pages.DataSource.saveAndTest().click();
e2e.pages.Explore.visit();
e2e.components.DataSourcePicker.container().should('be.visible').click();
e2e.components.DataSourcePicker.container().type(`${DATASOURCE_TYPED_NAME}{enter}`);
e2e.components.DataSource.Prometheus.queryEditor.builder.metricSelect().should('exist');
});
it('should allow a user to add the version when the Prom type is selected', () => {
e2e.components.DataSource.Prometheus.configPage.prometheusType().scrollIntoView().should('exist').click();
selectOption('Prometheus');
e2e.components.DataSource.Prometheus.configPage.prometheusVersion().scrollIntoView().should('exist');
});
it('should have a cache level component', () => {
e2e.components.DataSource.Prometheus.configPage.cacheLevel().scrollIntoView().should('exist');
});
it('should allow a user to select a query overlap window when incremental querying is selected', () => {
cy.get(`#${selectors.components.DataSource.Prometheus.configPage.incrementalQuerying}`)
.scrollIntoView()
.should('exist')
.check({ force: true });
e2e.components.DataSource.Prometheus.configPage.queryOverlapWindow().scrollIntoView().should('exist');
});
// exemplars tested in exemplar.spec
});
export function selectOption(option: string) {
e2e.components.Select.option().contains(option).should('be.visible').click();
}
@@ -0,0 +1,182 @@
import { selectors } from '@grafana/e2e-selectors';
import { e2e } from '../utils';
import { getResources } from './helpers/prometheus-helpers';
const DATASOURCE_ID = 'Prometheus';
type editorType = 'Code' | 'Builder';
/**
* Login, create and save a Prometheus data source, navigate to code or builder
*
* @param editorType 'Code' or 'Builder'
*/
function navigateToEditor(editorType: editorType, name: string): void {
// login
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'), true);
// select the prometheus DS
e2e.pages.AddDataSource.visit();
e2e.pages.AddDataSource.dataSourcePluginsV2(DATASOURCE_ID)
.scrollIntoView()
.should('be.visible') // prevents flakiness
.click();
// choose default editor
e2e.components.DataSource.Prometheus.configPage.defaultEditor().scrollIntoView().should('exist').click();
selectOption(editorType);
// add url for DS to save without error
e2e.components.DataSource.Prometheus.configPage.connectionSettings().type('http://prom-url:9090');
// name the DS
e2e.pages.DataSource.name().clear();
e2e.pages.DataSource.name().type(name);
e2e.pages.DataSource.saveAndTest().click();
// visit explore
e2e.pages.Explore.visit();
// choose the right DS
e2e.components.DataSourcePicker.container().should('be.visible').click();
cy.contains(name).scrollIntoView().should('be.visible').click();
}
describe('Prometheus query editor', () => {
it('should have a kickstart component', () => {
navigateToEditor('Code', 'prometheus');
e2e.components.QueryBuilder.queryPatterns().scrollIntoView().should('exist');
});
it('should have an explain component', () => {
navigateToEditor('Code', 'prometheus');
e2e.components.DataSource.Prometheus.queryEditor.explain().scrollIntoView().should('exist');
});
it('should have an editor toggle component', () => {
navigateToEditor('Code', 'prometheus');
e2e.components.DataSource.Prometheus.queryEditor.editorToggle().scrollIntoView().should('exist');
});
it('should have an options component with legend, format, step, type and exemplars', () => {
navigateToEditor('Code', 'prometheus');
// open options
e2e.components.DataSource.Prometheus.queryEditor.options().scrollIntoView().should('exist').click();
// check options
e2e.components.DataSource.Prometheus.queryEditor.legend().scrollIntoView().should('exist');
e2e.components.DataSource.Prometheus.queryEditor.format().scrollIntoView().should('exist');
cy.get(`#${selectors.components.DataSource.Prometheus.queryEditor.step}`).scrollIntoView().should('exist');
e2e.components.DataSource.Prometheus.queryEditor.type().scrollIntoView().should('exist');
cy.get(`#${selectors.components.DataSource.Prometheus.queryEditor.exemplars}`).scrollIntoView().should('exist');
});
describe('Code editor', () => {
it('navigates to the code editor with editor type as code', () => {
navigateToEditor('Code', 'prometheusCode');
});
it('navigates to the code editor and opens the metrics browser with metric search, labels, label values, and all components', () => {
navigateToEditor('Code', 'prometheusCode');
getResources();
e2e.components.DataSource.Prometheus.queryEditor.code.queryField().should('exist');
e2e.components.DataSource.Prometheus.queryEditor.code.metricsBrowser
.openButton()
.contains('Metrics browser')
.click();
e2e.components.DataSource.Prometheus.queryEditor.code.metricsBrowser.selectMetric().should('exist');
e2e.components.DataSource.Prometheus.queryEditor.code.metricsBrowser.labelNamesFilter().should('exist');
e2e.components.DataSource.Prometheus.queryEditor.code.metricsBrowser.labelValuesFilter().should('exist');
e2e.components.DataSource.Prometheus.queryEditor.code.metricsBrowser.useQuery().should('exist');
e2e.components.DataSource.Prometheus.queryEditor.code.metricsBrowser.useAsRateQuery().should('exist');
e2e.components.DataSource.Prometheus.queryEditor.code.metricsBrowser.validateSelector().should('exist');
e2e.components.DataSource.Prometheus.queryEditor.code.metricsBrowser.clear().should('exist');
});
it('selects a metric in the metrics browser and uses the query', () => {
navigateToEditor('Code', 'prometheusCode');
getResources();
e2e.components.DataSource.Prometheus.queryEditor.code.metricsBrowser
.openButton()
.contains('Metrics browser')
.click();
e2e.components.DataSource.Prometheus.queryEditor.code.metricsBrowser.selectMetric().should('exist').type('met');
e2e.components.DataSource.Prometheus.queryEditor.code.metricsBrowser
.metricList()
.should('exist')
.contains('metric1')
.click();
e2e.components.DataSource.Prometheus.queryEditor.code.metricsBrowser.useQuery().should('exist').click();
e2e.components.DataSource.Prometheus.queryEditor.code.queryField().should('exist').contains('metric1');
});
});
describe('Query builder', () => {
it('navigates to the query builder with editor type as code', () => {
navigateToEditor('Builder', 'prometheusBuilder');
});
it('the query builder contains metric select, label filters and operations', () => {
navigateToEditor('Builder', 'prometheusBuilder');
getResources();
e2e.components.DataSource.Prometheus.queryEditor.builder.metricSelect().should('exist');
e2e.components.QueryBuilder.labelSelect().should('exist');
e2e.components.QueryBuilder.matchOperatorSelect().should('exist');
e2e.components.QueryBuilder.valueSelect().should('exist');
});
it('can select a metric and provide a hint', () => {
navigateToEditor('Builder', 'prometheusBuilder');
getResources();
e2e.components.DataSource.Prometheus.queryEditor.builder.metricSelect().should('exist').click();
selectOption('metric1');
e2e.components.DataSource.Prometheus.queryEditor.builder.hints().contains('hint: add rate');
});
it('should have the metrics explorer opened via the metric select', () => {
navigateToEditor('Builder', 'prometheusBuilder');
getResources();
e2e.components.DataSource.Prometheus.queryEditor.builder.metricSelect().should('exist').click();
selectOption('Metrics explorer');
e2e.components.DataSource.Prometheus.queryEditor.builder.metricsExplorer().should('exist');
});
// NEED TO COMPLETE QUEY ADVISOR WORK OR FIGURE OUT HOW TO ENABLE EXPERIMENTAL FEATURE TOGGLES
// it('should have a query advisor when enabled with feature toggle', () => {
// cy.window().then((win) => {
// win.localStorage.setItem('grafana.featureToggles', 'prometheusPromQAIL=0');
// navigateToEditor('Builder', 'prometheusBuilder');
// getResources();
// e2e.components.DataSource.Prometheus.queryEditor.builder.queryAdvisor().should('exist');
// });
// });
});
});
function selectOption(option: string) {
e2e.components.Select.option().contains(option).should('be.visible').click();
}
@@ -0,0 +1,123 @@
import { e2e } from '../utils';
import { addDashboard } from '../utils/flows';
import { createPromDS, getResources } from './helpers/prometheus-helpers';
const DATASOURCE_ID = 'Prometheus';
const DATASOURCE_NAME = 'prometheusVariableDS';
/**
* Click dashboard settings and then the variables tab
*/
function navigateToVariables() {
e2e.components.NavToolbar.editDashboard.editButton().should('be.visible').click();
e2e.components.NavToolbar.editDashboard.settingsButton().should('be.visible').click();
e2e.components.Tab.title('Variables').click();
}
/**
* Begin the process of adding a query type variable for a Prometheus data source
*
* @param variableName the name of the variable as a label of the variable dropdown
*/
function addPrometheusQueryVariable(variableName: string) {
e2e.pages.Dashboard.Settings.Variables.List.addVariableCTAV2().click();
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalNameInputV2().clear().type(variableName);
e2e.components.DataSourcePicker.container().should('be.visible').click();
cy.contains(DATASOURCE_NAME).scrollIntoView().should('be.visible').click();
getResources();
}
/**
* Create a Prometheus variable and navigate to the query editor to check that it is available to use.
*
* @param variableName name the variable
* @param queryType query type of 'Label names', 'Label values', 'Metrics', 'Query result', 'Series query' or 'Classic query'. These types should be imported from the Prometheus library eventually but not now because we are in the process of decoupling the DS from core grafana.
*/
function variableFlowToQueryEditor(variableName: string, queryType: string) {
addDashboard();
navigateToVariables();
addPrometheusQueryVariable(variableName);
// select query type
e2e.components.DataSource.Prometheus.variableQueryEditor.queryType().click();
selectOption(queryType);
// apply the variable
e2e.pages.Dashboard.Settings.Variables.Edit.General.applyButton().click();
// close to return to dashboard
e2e.components.NavToolbar.editDashboard.backToDashboardButton().should('be.visible').click();
// add visualization
e2e.pages.AddDashboard.itemButton('Create new panel button').should('be.visible').click();
// close the data source picker modal
cy.get('[aria-label="Close"]').click();
// select prom data source from the data source list with the useful data-testid
e2e.components.DataSourcePicker.inputV2().click({ force: true }).type(`${DATASOURCE_NAME}{enter}`);
// confirm the variable exists in the correct input
// use the variable query type from the library in the future
switch (queryType) {
case 'Label names':
e2e.components.QueryBuilder.labelSelect().should('exist').click({ force: true });
selectOption(`${variableName}`);
case 'Label values':
e2e.components.QueryBuilder.valueSelect().should('exist').click({ force: true });
selectOption(`${variableName}`);
case 'Metrics':
e2e.components.DataSource.Prometheus.queryEditor.builder.metricSelect().should('exist').click({ force: true });
selectOption(`${variableName}`);
default:
// do nothing
}
}
describe('Prometheus variable query editor', () => {
beforeEach(() => {
createPromDS(DATASOURCE_ID, DATASOURCE_NAME);
});
it('should navigate to variable query editor', () => {
addDashboard();
navigateToVariables();
});
it('should select a query type for a Prometheus variable query', () => {
addDashboard();
navigateToVariables();
addPrometheusQueryVariable('labelsVariable');
// select query type
e2e.components.DataSource.Prometheus.variableQueryEditor.queryType().click();
selectOption('Label names');
});
it('should create a label names variable that is selectable in the label select in query builder', () => {
addDashboard();
navigateToVariables();
variableFlowToQueryEditor('labelnames', 'Label names');
});
it('should create a label values variable that is selectable in the label values select in query builder', () => {
addDashboard();
navigateToVariables();
variableFlowToQueryEditor('labelvalues', 'Label values');
});
it('should create a metric names variable that is selectable in the metric select in query builder', () => {
addDashboard();
navigateToVariables();
variableFlowToQueryEditor('metrics', 'Metrics');
});
});
function selectOption(option: string) {
e2e.components.Select.option().contains(option).should('be.visible').click();
}
@@ -0,0 +1,32 @@
import { e2e } from '../utils';
import { waitForMonacoToLoad } from '../utils/support/monaco';
describe('Query editor', () => {
beforeEach(() => {
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'));
});
// x-ing to bypass this flaky test.
// Will rewrite in plugin-e2e with this issue
xit('Undo should work in query editor for prometheus -- test CI.', () => {
e2e.pages.Explore.visit();
e2e.components.DataSourcePicker.container().should('be.visible').click();
cy.contains('gdev-prometheus').scrollIntoView().should('be.visible').click();
const queryText = `rate(http_requests_total{job="grafana"}[5m])`;
e2e.components.RadioButton.container().filter(':contains("Code")').should('be.visible').click();
waitForMonacoToLoad();
e2e.components.QueryField.container().type(queryText, { parseSpecialCharSequences: false }).type('{backspace}');
cy.contains(queryText.slice(0, -1)).should('be.visible');
e2e.components.QueryField.container().type(e2e.typings.undo());
cy.contains(queryText).should('be.visible');
e2e.components.Alert.alertV2('error').should('not.be.visible');
});
});
@@ -0,0 +1,71 @@
import { e2e } from '../utils';
describe('ReturnToPrevious button', () => {
beforeEach(() => {
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'));
cy.visit('/alerting/list');
e2e.components.AlertRules.groupToggle().first().click();
e2e.components.AlertRules.toggle().click();
cy.get('a[title="View"]').click();
cy.url().as('alertRuleUrl');
cy.get('a').contains('View panel').click();
});
it('should appear when changing context and go back to alert rule when clicking "Back"', () => {
// check whether all elements of RTP are available
e2e.components.ReturnToPrevious.buttonGroup().should('be.visible');
e2e.components.ReturnToPrevious.dismissButton().should('be.visible');
e2e.components.ReturnToPrevious.backButton()
.find('span')
.contains('Back to e2e-ReturnToPrevious-test')
.should('be.visible')
.click();
// check whether the RTP button leads back to alert rule
cy.get('@alertRuleUrl').then((alertRuleUrl) => {
cy.url().should('eq', alertRuleUrl);
});
});
it('should disappear when clicking "Dismiss"', () => {
e2e.components.ReturnToPrevious.dismissButton().should('be.visible').click();
e2e.components.ReturnToPrevious.buttonGroup().should('not.exist');
});
it('should not persist when going back to the alert rule details view', () => {
e2e.components.ReturnToPrevious.buttonGroup().should('be.visible');
cy.visit('/alerting/list');
e2e.components.AlertRules.groupToggle().first().click();
cy.get('a[title="View"]').click();
e2e.components.ReturnToPrevious.buttonGroup().should('not.exist');
});
it('should override the button label and change the href when user changes alert rules', () => {
e2e.components.ReturnToPrevious.backButton()
.find('span')
.contains('Back to e2e-ReturnToPrevious-test')
.should('be.visible');
cy.visit('/alerting/list');
e2e.components.AlertRules.groupToggle().last().click();
cy.get('a[title="View"]').click();
cy.url().as('alertRule2Url');
cy.get('a').contains('View panel').click();
e2e.components.ReturnToPrevious.backButton()
.find('span')
.contains('Back to e2e-ReturnToPrevious-test-2')
.should('be.visible')
.click();
e2e.components.ReturnToPrevious.buttonGroup().should('not.exist');
// check whether the RTP button leads back to alert rule
cy.get('@alertRule2Url').then((alertRule2Url) => {
cy.url().should('eq', alertRule2Url);
});
});
});
@@ -0,0 +1,34 @@
import { e2e } from '../utils';
describe('Select focus/unfocus tests', () => {
beforeEach(() => {
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'));
});
it.skip('Tests select focus/unfocus scenarios', () => {
e2e.flows.openDashboard({ uid: '5SdHCadmz' });
e2e.components.PageToolbar.item('Dashboard settings').click();
e2e.components.FolderPicker.containerV2()
.should('be.visible')
.within(() => {
cy.get('#dashboard-folder-input').should('be.visible').click();
});
e2e.components.Select.option().should('be.visible').first().click();
e2e.components.FolderPicker.containerV2()
.should('be.visible')
.within(() => {
cy.get('#dashboard-folder-input').should('exist').should('have.focus');
});
e2e.pages.Dashboard.Settings.General.title().click();
e2e.components.FolderPicker.containerV2()
.should('be.visible')
.within(() => {
cy.get('#dashboard-folder-input').should('exist').should('not.have.focus');
});
});
});
@@ -0,0 +1,44 @@
import { e2e } from '../utils';
describe('Solo Route', () => {
beforeEach(() => {
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'));
});
it('Can view panels with shared queries in fullsceen', () => {
// open Panel Tests - Bar Gauge
e2e.pages.SoloPanel.visit('ZqZnVvFZz/datasource-tests-shared-queries?orgId=1&panelId=4');
cy.get('canvas').should('have.length', 6);
});
it('Can view solo panel in scenes', () => {
// open Panel Tests - Graph NG
e2e.pages.SoloPanel.visit(
'TkZXxlNG3/panel-tests-graph-ng?orgId=1&from=1699954597665&to=1699956397665&panelId=54&__feature.dashboardSceneSolo=true'
);
e2e.components.Panels.Panel.title('Interpolation: Step before').should('exist');
cy.contains('uplot-main-div').should('not.exist');
});
it('Can view solo repeated panel in scenes', () => {
// open Panel Tests - Graph NG
e2e.pages.SoloPanel.visit(
'templating-repeating-panels/templating-repeating-panels?orgId=1&from=1699934989607&to=1699956589607&panelId=panel-2-clone-1&__feature.dashboardSceneSolo=true'
);
e2e.components.Panels.Panel.title('server=B').should('exist');
cy.contains('uplot-main-div').should('not.exist');
});
it('Can view solo in repeaterd row and panel in scenes', () => {
// open Panel Tests - Graph NG
e2e.pages.SoloPanel.visit(
'Repeating-rows-uid/repeating-rows?orgId=1&var-server=A&var-server=B&var-server=D&var-pod=1&var-pod=2&var-pod=3&panelId=panel-2-clone-D-clone-2&__feature.dashboardSceneSolo=true'
);
e2e.components.Panels.Panel.title('server = D, pod = Sod').should('exist');
cy.contains('uplot-main-div').should('not.exist');
});
});
@@ -0,0 +1,43 @@
import { e2e } from '../utils';
describe('Trace view', () => {
beforeEach(() => {
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'));
});
it('Can lazy load big traces', () => {
cy.intercept('GET', '**/api/traces/trace', {
fixture: 'long-trace-response.json',
}).as('longTrace');
e2e.pages.Explore.visit();
e2e.components.DataSourcePicker.container().should('be.visible').type('gdev-jaeger{enter}');
// Wait for the query editor to be set correctly
e2e.components.QueryEditorRows.rows().within(() => {
cy.contains('gdev-jaeger').should('be.visible');
});
// type this with 0 delay to prevent flaky tests due to cursor position changing between rerenders
e2e.components.QueryField.container().should('be.visible').type('trace', {
delay: 0,
});
// Use shift+enter to execute the query as it's more stable than clicking the execute button
e2e.components.QueryField.container().type('{shift+enter}');
cy.wait('@longTrace');
e2e.components.TraceViewer.spanBar().should('be.visible');
e2e.components.TraceViewer.spanBar()
.its('length')
.then((oldLength) => {
e2e.pages.Explore.General.scrollView().children('.scrollbar-view').scrollTo('center');
// After scrolling we should load more spans
e2e.components.TraceViewer.spanBar().should(($span) => {
expect($span.length).to.be.gt(oldLength);
});
});
});
});
@@ -0,0 +1,27 @@
import { e2e } from '../utils';
describe('Visualization suggestions', () => {
beforeEach(() => {
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'));
});
it('Should be shown and clickable', () => {
e2e.flows.openDashboard({ uid: 'aBXrJ0R7z', queryParams: { editPanel: 9 } });
// Try visualization suggestions
e2e.components.PanelEditor.toggleVizPicker().click();
e2e.components.RadioButton.container().filter(':contains("Suggestions")').click();
// Verify we see suggestions
e2e.components.VisualizationPreview.card('Line chart').should('be.visible');
// Verify search works
cy.get('[placeholder="Search for..."]').type('Table');
// Should no longer see line chart
e2e.components.VisualizationPreview.card('Line chart').should('not.exist');
// Select a visualisation
e2e.components.VisualizationPreview.card('Table').click();
e2e.components.Panels.Visualization.Table.header().should('be.visible');
});
});