From 4c8ad8d031b51089b12db7ae5d9daf48c867128c Mon Sep 17 00:00:00 2001 From: Steven Vachon Date: Thu, 11 Jun 2020 16:31:49 -0400 Subject: [PATCH] @grafana/e2e: improvements (#25497) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Minor changes * Use UI for removing dashboards and datasources … with an optional “quick” mode that instead does so via a request * Improved URL helpers * Simplified test teardown * Added support for multiple tests ... instead of being forced to cram everything into a single test because the session was cleared --- .../cypress/plugins/extendConfig.js | 1 + packages/grafana-e2e/src/flows/addPanel.ts | 15 +++-- .../grafana-e2e/src/flows/deleteDashboard.ts | 55 +++++++++++-------- .../grafana-e2e/src/flows/deleteDataSource.ts | 50 ++++++++++------- packages/grafana-e2e/src/flows/index.ts | 2 + packages/grafana-e2e/src/flows/login.ts | 2 +- .../grafana-e2e/src/flows/openDashboard.ts | 1 + .../grafana-e2e/src/flows/revertAllChanges.ts | 8 +++ packages/grafana-e2e/src/support/scenario.ts | 17 +++--- packages/grafana-e2e/src/support/url.ts | 9 +-- 10 files changed, 95 insertions(+), 65 deletions(-) create mode 100644 packages/grafana-e2e/src/flows/revertAllChanges.ts diff --git a/packages/grafana-e2e/cypress/plugins/extendConfig.js b/packages/grafana-e2e/cypress/plugins/extendConfig.js index fe37bb9083c..134c4eafb8a 100644 --- a/packages/grafana-e2e/cypress/plugins/extendConfig.js +++ b/packages/grafana-e2e/cypress/plugins/extendConfig.js @@ -15,6 +15,7 @@ module.exports = async baseConfig => { // @todo: https://github.com/cypress-io/cypress/issues/6406 const jsonReporter = require.resolve('@mochajs/json-file-reporter'); + // @todo `baseUrl: env.CYPRESS_BASEURL` const projectConfig = { fixturesFolder: `${CWD}/cypress/fixtures`, integrationFolder: `${CWD}/cypress/integration`, diff --git a/packages/grafana-e2e/src/flows/addPanel.ts b/packages/grafana-e2e/src/flows/addPanel.ts index 83d7bbf3b74..15cd5bea1b1 100644 --- a/packages/grafana-e2e/src/flows/addPanel.ts +++ b/packages/grafana-e2e/src/flows/addPanel.ts @@ -26,10 +26,18 @@ export const addPanel = (config?: Partial): any => const { dashboardUid, dataSourceName, panelTitle, queriesForm, visualizationName } = fullConfig; - e2e.flows.openDashboard(dashboardUid); + e2e.pages.Dashboard.visit(dashboardUid); e2e.pages.Dashboard.Toolbar.toolbarItems('Add panel').click(); e2e.pages.AddDashboard.addNewPanel().click(); + e2e().server(); + + // @todo alias '/**/*.js*' as '@pluginModule' when possible: https://github.com/cypress-io/cypress/issues/1296 + + e2e() + .route('POST', '/api/ds/query') + .as('chartData'); + e2e() .get('.ds-picker') .click() @@ -58,11 +66,6 @@ export const addPanel = (config?: Partial): any => .click(); closeOptionsGroup('type'); - e2e().server(); - e2e() - .route('POST', '/api/ds/query') - .as('chartData'); - queriesForm(fullConfig); e2e().wait('@chartData'); diff --git a/packages/grafana-e2e/src/flows/deleteDashboard.ts b/packages/grafana-e2e/src/flows/deleteDashboard.ts index 28238a7153c..ea7526c2ddf 100644 --- a/packages/grafana-e2e/src/flows/deleteDashboard.ts +++ b/packages/grafana-e2e/src/flows/deleteDashboard.ts @@ -2,37 +2,21 @@ import { e2e } from '../index'; import { fromBaseUrl } from '../support/url'; export interface DeleteDashboardConfig { + quick?: boolean; title: string; uid: string; } -export const deleteDashboard = ({ title, uid }: DeleteDashboardConfig) => { +export const deleteDashboard = ({ quick = false, title, uid }: DeleteDashboardConfig) => { e2e().logToConsole('Deleting dashboard with uid:', uid); - // Avoid dashboard page errors - e2e.pages.Home.visit(); - e2e().request('DELETE', fromBaseUrl(`/api/dashboards/uid/${uid}`)); + if (quick) { + quickDelete(uid); + } else { + uiDelete(uid, title); + } - /* https://github.com/cypress-io/cypress/issues/2831 - Flows.openDashboard(title); - - Pages.Dashboard.settings().click(); - - Pages.DashboardSettings.deleteDashBoard().click(); - - Pages.ConfirmModal.delete().click(); - - Flows.assertSuccessNotification(); - - Pages.Dashboards.visit(); - Pages.Dashboards.dashboards().each(item => { - const text = item.text(); - Cypress.log({ message: [text] }); - if (text && text.indexOf(title) !== -1) { - expect(false).equals(true, `Dashboard ${title} was found although it was deleted.`); - } - }); - */ + e2e().logToConsole('Deleted dashboard with uid:', uid); e2e.getScenarioContext().then(({ addedDashboards }: any) => { e2e.setScenarioContext({ @@ -42,3 +26,26 @@ export const deleteDashboard = ({ title, uid }: DeleteDashboardConfig) => { }); }); }; + +const quickDelete = (uid: string) => { + e2e().request('DELETE', fromBaseUrl(`/api/dashboards/uid/${uid}`)); +}; + +const uiDelete = (uid: string, title: string) => { + e2e.pages.Dashboard.visit(uid); + e2e.pages.Dashboard.Toolbar.toolbarItems('Dashboard settings').click(); + e2e.pages.Dashboard.Settings.General.deleteDashBoard().click(); + e2e.pages.ConfirmModal.delete().click(); + e2e.flows.assertSuccessNotification(); + + e2e.pages.Dashboards.visit(); + + // @todo replace `e2e.pages.Dashboards.dashboards` with this when argument is empty + e2e() + .get('[aria-label^="Dashboard search item "]') + .each(item => + e2e() + .wrap(item) + .should('not.contain', title) + ); +}; diff --git a/packages/grafana-e2e/src/flows/deleteDataSource.ts b/packages/grafana-e2e/src/flows/deleteDataSource.ts index 2ca447addc5..4e2a6230f55 100644 --- a/packages/grafana-e2e/src/flows/deleteDataSource.ts +++ b/packages/grafana-e2e/src/flows/deleteDataSource.ts @@ -4,31 +4,19 @@ import { fromBaseUrl } from '../support/url'; export interface DeleteDataSourceConfig { id: string; name: string; + quick?: boolean; } -export const deleteDataSource = ({ id, name }: DeleteDataSourceConfig) => { +export const deleteDataSource = ({ id, name, quick = false }: DeleteDataSourceConfig) => { e2e().logToConsole('Deleting data source with name:', name); - // Avoid datasources page errors - e2e.pages.Home.visit(); - e2e().request('DELETE', fromBaseUrl(`/api/datasources/name/${name}`)); + if (quick) { + quickDelete(name); + } else { + uiDelete(name); + } - /* https://github.com/cypress-io/cypress/issues/2831 - Pages.DataSources.visit(); - Pages.DataSources.dataSources(name).click(); - - Pages.DataSource.delete().click(); - - Pages.ConfirmModal.delete().click(); - - Pages.DataSources.visit(); - Pages.DataSources.dataSources().each(item => { - const text = item.text(); - if (text && text.indexOf(name) !== -1) { - expect(false).equals(true, `Data source ${name} was found although it was deleted.`); - } - }); - */ + e2e().logToConsole('Deleted data source with name:', name); e2e.getScenarioContext().then(({ addedDataSources }: any) => { e2e.setScenarioContext({ @@ -38,3 +26,25 @@ export const deleteDataSource = ({ id, name }: DeleteDataSourceConfig) => { }); }); }; + +const quickDelete = (name: string) => { + e2e().request('DELETE', fromBaseUrl(`/api/datasources/name/${name}`)); +}; + +const uiDelete = (name: string) => { + e2e.pages.DataSources.visit(); + e2e.pages.DataSources.dataSources(name).click(); + e2e.pages.DataSource.delete().click(); + e2e.pages.ConfirmModal.delete().click(); + + e2e.pages.DataSources.visit(); + + // @todo replace `e2e.pages.DataSources.dataSources` with this when argument is empty + e2e() + .get('[aria-label^="Data source list item "]') + .each(item => + e2e() + .wrap(item) + .should('not.contain', name) + ); +}; diff --git a/packages/grafana-e2e/src/flows/index.ts b/packages/grafana-e2e/src/flows/index.ts index 976fac4c326..8cc537ca2a6 100644 --- a/packages/grafana-e2e/src/flows/index.ts +++ b/packages/grafana-e2e/src/flows/index.ts @@ -8,6 +8,7 @@ import { login } from './login'; import { openDashboard } from './openDashboard'; import { saveDashboard } from './saveDashboard'; import { openPanelMenuItem, PanelMenuItems } from './openPanelMenuItem'; +import { revertAllChanges } from './revertAllChanges'; export const Flows = { addDashboard, @@ -21,4 +22,5 @@ export const Flows = { saveDashboard, openPanelMenuItem, PanelMenuItems, + revertAllChanges, }; diff --git a/packages/grafana-e2e/src/flows/login.ts b/packages/grafana-e2e/src/flows/login.ts index 168a278276b..81b5b6303e6 100644 --- a/packages/grafana-e2e/src/flows/login.ts +++ b/packages/grafana-e2e/src/flows/login.ts @@ -1,7 +1,7 @@ import { e2e } from '../index'; export const login = (username: string = 'admin', password: string = 'admin') => { - e2e().logToConsole('Trying to login with username:', username); + e2e().logToConsole('Logging in with username:', username); e2e.pages.Login.visit(); e2e.pages.Login.username() .should('be.visible') // prevents flakiness diff --git a/packages/grafana-e2e/src/flows/openDashboard.ts b/packages/grafana-e2e/src/flows/openDashboard.ts index 6c4153c9265..9bf26d36921 100644 --- a/packages/grafana-e2e/src/flows/openDashboard.ts +++ b/packages/grafana-e2e/src/flows/openDashboard.ts @@ -1,5 +1,6 @@ import { e2e } from '../index'; +// @todo remove this, as it's a page change and not a flow export const openDashboard = (dashboardUid: string) => { e2e.pages.Dashboard.visit(dashboardUid); }; diff --git a/packages/grafana-e2e/src/flows/revertAllChanges.ts b/packages/grafana-e2e/src/flows/revertAllChanges.ts new file mode 100644 index 00000000000..8fda82d2c60 --- /dev/null +++ b/packages/grafana-e2e/src/flows/revertAllChanges.ts @@ -0,0 +1,8 @@ +import { e2e } from '../index'; + +export const revertAllChanges = () => { + e2e.getScenarioContext().then(({ addedDashboards, addedDataSources }: any) => { + addedDashboards.forEach((dashboard: any) => e2e.flows.deleteDashboard({ ...dashboard, quick: true })); + addedDataSources.forEach((dataSource: any) => e2e.flows.deleteDataSource({ ...dataSource, quick: true })); + }); +}; diff --git a/packages/grafana-e2e/src/support/scenario.ts b/packages/grafana-e2e/src/support/scenario.ts index 735492ea4f4..e1c8ea9060d 100644 --- a/packages/grafana-e2e/src/support/scenario.ts +++ b/packages/grafana-e2e/src/support/scenario.ts @@ -1,6 +1,5 @@ import { e2e } from '../'; import { Flows } from '../flows'; -import { getScenarioContext } from './scenarioContext'; export interface ScenarioArguments { describeName: string; @@ -23,8 +22,11 @@ export const e2eScenario = ({ if (skipScenario) { it.skip(itName, () => scenario()); } else { + before(() => Flows.login(e2e.env('USERNAME'), e2e.env('PASSWORD'))); + beforeEach(() => { - Flows.login(e2e.env('USERNAME'), e2e.env('PASSWORD')); + Cypress.Cookies.preserveOnce('grafana_session'); + if (addScenarioDataSource) { Flows.addDataSource(); } @@ -33,14 +35,13 @@ export const e2eScenario = ({ } }); - afterEach(() => { - getScenarioContext().then(({ addedDashboards, addedDataSources }: any) => { - addedDashboards.forEach((dashboard: any) => Flows.deleteDashboard(dashboard)); - addedDataSources.forEach((dataSource: any) => Flows.deleteDataSource(dataSource)); - }); - }); + afterEach(() => Flows.revertAllChanges()); + after(() => e2e().clearCookies()); it(itName, () => scenario()); + + // @todo remove when possible: https://github.com/cypress-io/cypress/issues/2831 + it('temporary', () => {}); } }); }; diff --git a/packages/grafana-e2e/src/support/url.ts b/packages/grafana-e2e/src/support/url.ts index d36b8c6bc2a..9d579d75076 100644 --- a/packages/grafana-e2e/src/support/url.ts +++ b/packages/grafana-e2e/src/support/url.ts @@ -2,13 +2,10 @@ import { e2e } from '../index'; const getBaseUrl = () => e2e.env('BASE_URL') || e2e.config().baseUrl || 'http://localhost:3000'; -export const fromBaseUrl = (url = ''): string => { - const strippedUrl = url.replace('^/', ''); - return `${getBaseUrl()}${strippedUrl}`; -}; +export const fromBaseUrl = (url = '') => new URL(url, getBaseUrl()).href; export const getDashboardUid = (url: string): string => { - const matches = url.match(/\/d\/(.*)\//); + const matches = new URL(url).pathname.match(/\/d\/([^/]+)/); if (!matches) { throw new Error(`Couldn't parse uid from ${url}`); } else { @@ -17,7 +14,7 @@ export const getDashboardUid = (url: string): string => { }; export const getDataSourceId = (url: string): string => { - const matches = url.match(/\/edit\/(.*)\//); + const matches = new URL(url).pathname.match(/\/edit\/([^/]+)/); if (!matches) { throw new Error(`Couldn't parse id from ${url}`); } else {