From 8d0e437287553d9aaab83999c7ebb9d22cbac61a Mon Sep 17 00:00:00 2001 From: Sergej-Vlasov <37613182+Sergej-Vlasov@users.noreply.github.com> Date: Wed, 12 Jun 2024 11:36:11 +0200 Subject: [PATCH] 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 --- e2e/scenes/various-suite/bar-gauge.spec.ts | 20 ++ e2e/scenes/various-suite/exemplars.spec.ts | 76 ++++++ e2e/scenes/various-suite/explore.spec.ts | 22 ++ .../various-suite/filter-annotations.spec.ts | 72 ++++++ .../frontend-sandbox-app.spec.ts | 71 ++++++ .../frontend-sandbox-datasource.spec.ts | 145 +++++++++++ e2e/scenes/various-suite/gauge.spec.ts | 18 ++ .../various-suite/graph-auto-migrate.spec.ts | 81 +++++++ .../helpers/prometheus-helpers.ts | 62 +++++ .../various-suite/inspect-drawer.spec.ts | 123 ++++++++++ e2e/scenes/various-suite/keybinds.spec.ts | 79 ++++++ e2e/scenes/various-suite/loki-editor.spec.ts | 88 +++++++ .../various-suite/loki-query-builder.spec.ts | 106 ++++++++ .../loki-table-explore-to-dash.spec.ts | 226 ++++++++++++++++++ e2e/scenes/various-suite/navigation.spec.ts | 43 ++++ e2e/scenes/various-suite/pie-chart.spec.ts | 18 ++ .../prometheus-annotations.spec.ts | 76 ++++++ .../various-suite/prometheus-config.spec.ts | 111 +++++++++ .../various-suite/prometheus-editor.spec.ts | 182 ++++++++++++++ .../prometheus-variable-editor.spec.ts | 123 ++++++++++ e2e/scenes/various-suite/query-editor.spec.ts | 32 +++ .../various-suite/return-to-previous.spec.ts | 71 ++++++ e2e/scenes/various-suite/select-focus.spec.ts | 34 +++ e2e/scenes/various-suite/solo-route.spec.ts | 44 ++++ .../trace-view-scrolling.spec.ts | 43 ++++ .../visualization-suggestions.spec.ts | 27 +++ 26 files changed, 1993 insertions(+) create mode 100644 e2e/scenes/various-suite/bar-gauge.spec.ts create mode 100644 e2e/scenes/various-suite/exemplars.spec.ts create mode 100644 e2e/scenes/various-suite/explore.spec.ts create mode 100644 e2e/scenes/various-suite/filter-annotations.spec.ts create mode 100644 e2e/scenes/various-suite/frontend-sandbox-app.spec.ts create mode 100644 e2e/scenes/various-suite/frontend-sandbox-datasource.spec.ts create mode 100644 e2e/scenes/various-suite/gauge.spec.ts create mode 100644 e2e/scenes/various-suite/graph-auto-migrate.spec.ts create mode 100644 e2e/scenes/various-suite/helpers/prometheus-helpers.ts create mode 100644 e2e/scenes/various-suite/inspect-drawer.spec.ts create mode 100644 e2e/scenes/various-suite/keybinds.spec.ts create mode 100644 e2e/scenes/various-suite/loki-editor.spec.ts create mode 100644 e2e/scenes/various-suite/loki-query-builder.spec.ts create mode 100644 e2e/scenes/various-suite/loki-table-explore-to-dash.spec.ts create mode 100644 e2e/scenes/various-suite/navigation.spec.ts create mode 100644 e2e/scenes/various-suite/pie-chart.spec.ts create mode 100644 e2e/scenes/various-suite/prometheus-annotations.spec.ts create mode 100644 e2e/scenes/various-suite/prometheus-config.spec.ts create mode 100644 e2e/scenes/various-suite/prometheus-editor.spec.ts create mode 100644 e2e/scenes/various-suite/prometheus-variable-editor.spec.ts create mode 100644 e2e/scenes/various-suite/query-editor.spec.ts create mode 100644 e2e/scenes/various-suite/return-to-previous.spec.ts create mode 100644 e2e/scenes/various-suite/select-focus.spec.ts create mode 100644 e2e/scenes/various-suite/solo-route.spec.ts create mode 100644 e2e/scenes/various-suite/trace-view-scrolling.spec.ts create mode 100644 e2e/scenes/various-suite/visualization-suggestions.spec.ts diff --git a/e2e/scenes/various-suite/bar-gauge.spec.ts b/e2e/scenes/various-suite/bar-gauge.spec.ts new file mode 100644 index 00000000000..7c28c48a146 --- /dev/null +++ b/e2e/scenes/various-suite/bar-gauge.spec.ts @@ -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'); + }); +}); diff --git a/e2e/scenes/various-suite/exemplars.spec.ts b/e2e/scenes/various-suite/exemplars.spec.ts new file mode 100644 index 00000000000..da642ba3f2d --- /dev/null +++ b/e2e/scenes/various-suite/exemplars.spec.ts @@ -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); + }); +}); diff --git a/e2e/scenes/various-suite/explore.spec.ts b/e2e/scenes/various-suite/explore.spec.ts new file mode 100644 index 00000000000..69d74c29e98 --- /dev/null +++ b/e2e/scenes/various-suite/explore.spec.ts @@ -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(); + }); +}); diff --git a/e2e/scenes/various-suite/filter-annotations.spec.ts b/e2e/scenes/various-suite/filter-annotations.spec.ts new file mode 100644 index 00000000000..883b705e8d1 --- /dev/null +++ b/e2e/scenes/various-suite/filter-annotations.spec.ts @@ -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); + }); + }); +}); diff --git a/e2e/scenes/various-suite/frontend-sandbox-app.spec.ts b/e2e/scenes/various-suite/frontend-sandbox-app.spec.ts new file mode 100644 index 00000000000..b3e17ece6a9 --- /dev/null +++ b/e2e/scenes/various-suite/frontend-sandbox-app.spec.ts @@ -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(); + }); +}); diff --git a/e2e/scenes/various-suite/frontend-sandbox-datasource.spec.ts b/e2e/scenes/various-suite/frontend-sandbox-datasource.spec.ts new file mode 100644 index 00000000000..2881e7dfe5d --- /dev/null +++ b/e2e/scenes/various-suite/frontend-sandbox-datasource.spec.ts @@ -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(); + }); +}); diff --git a/e2e/scenes/various-suite/gauge.spec.ts b/e2e/scenes/various-suite/gauge.spec.ts new file mode 100644 index 00000000000..64e4ce14b61 --- /dev/null +++ b/e2e/scenes/various-suite/gauge.spec.ts @@ -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'); + }); +}); diff --git a/e2e/scenes/various-suite/graph-auto-migrate.spec.ts b/e2e/scenes/various-suite/graph-auto-migrate.spec.ts new file mode 100644 index 00000000000..eedb7a043e6 --- /dev/null +++ b/e2e/scenes/various-suite/graph-auto-migrate.spec.ts @@ -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'); + }); + }); +}); diff --git a/e2e/scenes/various-suite/helpers/prometheus-helpers.ts b/e2e/scenes/various-suite/helpers/prometheus-helpers.ts new file mode 100644 index 00000000000..265095ecd65 --- /dev/null +++ b/e2e/scenes/various-suite/helpers/prometheus-helpers.ts @@ -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'], +}; diff --git a/e2e/scenes/various-suite/inspect-drawer.spec.ts b/e2e/scenes/various-suite/inspect-drawer.spec.ts new file mode 100644 index 00000000000..2faf952ba08 --- /dev/null +++ b/e2e/scenes/various-suite/inspect-drawer.spec.ts @@ -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) => { + 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(); +}; diff --git a/e2e/scenes/various-suite/keybinds.spec.ts b/e2e/scenes/various-suite/keybinds.spec.ts new file mode 100644 index 00000000000..d054edad0b7 --- /dev/null +++ b/e2e/scenes/various-suite/keybinds.spec.ts @@ -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); + }); +}); diff --git a/e2e/scenes/various-suite/loki-editor.spec.ts b/e2e/scenes/various-suite/loki-editor.spec.ts new file mode 100644 index 00000000000..42bf063c137 --- /dev/null +++ b/e2e/scenes/various-suite/loki-editor.spec.ts @@ -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'); + }); +}); diff --git a/e2e/scenes/various-suite/loki-query-builder.spec.ts b/e2e/scenes/various-suite/loki-query-builder.spec.ts new file mode 100644 index 00000000000..45b527fca5b --- /dev/null +++ b/e2e/scenes/various-suite/loki-query-builder.spec.ts @@ -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'); + }); +}); diff --git a/e2e/scenes/various-suite/loki-table-explore-to-dash.spec.ts b/e2e/scenes/various-suite/loki-table-explore-to-dash.spec.ts new file mode 100644 index 00000000000..c56aa16b578 --- /dev/null +++ b/e2e/scenes/various-suite/loki-table-explore-to-dash.spec.ts @@ -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'); + }); +}); diff --git a/e2e/scenes/various-suite/navigation.spec.ts b/e2e/scenes/various-suite/navigation.spec.ts new file mode 100644 index 00000000000..099a0c875f4 --- /dev/null +++ b/e2e/scenes/various-suite/navigation.spec.ts @@ -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'); + }); +}); diff --git a/e2e/scenes/various-suite/pie-chart.spec.ts b/e2e/scenes/various-suite/pie-chart.spec.ts new file mode 100644 index 00000000000..c6d537a40a2 --- /dev/null +++ b/e2e/scenes/various-suite/pie-chart.spec.ts @@ -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); + }); +}); diff --git a/e2e/scenes/various-suite/prometheus-annotations.spec.ts b/e2e/scenes/various-suite/prometheus-annotations.spec.ts new file mode 100644 index 00000000000..91392a145a1 --- /dev/null +++ b/e2e/scenes/various-suite/prometheus-annotations.spec.ts @@ -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); + }); +}); diff --git a/e2e/scenes/various-suite/prometheus-config.spec.ts b/e2e/scenes/various-suite/prometheus-config.spec.ts new file mode 100644 index 00000000000..641cad7b22b --- /dev/null +++ b/e2e/scenes/various-suite/prometheus-config.spec.ts @@ -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(); +} diff --git a/e2e/scenes/various-suite/prometheus-editor.spec.ts b/e2e/scenes/various-suite/prometheus-editor.spec.ts new file mode 100644 index 00000000000..a35ca282e50 --- /dev/null +++ b/e2e/scenes/various-suite/prometheus-editor.spec.ts @@ -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(); +} diff --git a/e2e/scenes/various-suite/prometheus-variable-editor.spec.ts b/e2e/scenes/various-suite/prometheus-variable-editor.spec.ts new file mode 100644 index 00000000000..d5e90f2c31c --- /dev/null +++ b/e2e/scenes/various-suite/prometheus-variable-editor.spec.ts @@ -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(); +} diff --git a/e2e/scenes/various-suite/query-editor.spec.ts b/e2e/scenes/various-suite/query-editor.spec.ts new file mode 100644 index 00000000000..f9149f7b280 --- /dev/null +++ b/e2e/scenes/various-suite/query-editor.spec.ts @@ -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'); + }); +}); diff --git a/e2e/scenes/various-suite/return-to-previous.spec.ts b/e2e/scenes/various-suite/return-to-previous.spec.ts new file mode 100644 index 00000000000..395c233d76a --- /dev/null +++ b/e2e/scenes/various-suite/return-to-previous.spec.ts @@ -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); + }); + }); +}); diff --git a/e2e/scenes/various-suite/select-focus.spec.ts b/e2e/scenes/various-suite/select-focus.spec.ts new file mode 100644 index 00000000000..5ff66306b35 --- /dev/null +++ b/e2e/scenes/various-suite/select-focus.spec.ts @@ -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'); + }); + }); +}); diff --git a/e2e/scenes/various-suite/solo-route.spec.ts b/e2e/scenes/various-suite/solo-route.spec.ts new file mode 100644 index 00000000000..cfea359cff2 --- /dev/null +++ b/e2e/scenes/various-suite/solo-route.spec.ts @@ -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'); + }); +}); diff --git a/e2e/scenes/various-suite/trace-view-scrolling.spec.ts b/e2e/scenes/various-suite/trace-view-scrolling.spec.ts new file mode 100644 index 00000000000..be24aa79e20 --- /dev/null +++ b/e2e/scenes/various-suite/trace-view-scrolling.spec.ts @@ -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); + }); + }); + }); +}); diff --git a/e2e/scenes/various-suite/visualization-suggestions.spec.ts b/e2e/scenes/various-suite/visualization-suggestions.spec.ts new file mode 100644 index 00000000000..aef309ea452 --- /dev/null +++ b/e2e/scenes/various-suite/visualization-suggestions.spec.ts @@ -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'); + }); +});