mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
@grafana/e2e: improvements (#25497)
* 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
This commit is contained in:
parent
034abaa73a
commit
4c8ad8d031
@ -15,6 +15,7 @@ module.exports = async baseConfig => {
|
|||||||
// @todo: https://github.com/cypress-io/cypress/issues/6406
|
// @todo: https://github.com/cypress-io/cypress/issues/6406
|
||||||
const jsonReporter = require.resolve('@mochajs/json-file-reporter');
|
const jsonReporter = require.resolve('@mochajs/json-file-reporter');
|
||||||
|
|
||||||
|
// @todo `baseUrl: env.CYPRESS_BASEURL`
|
||||||
const projectConfig = {
|
const projectConfig = {
|
||||||
fixturesFolder: `${CWD}/cypress/fixtures`,
|
fixturesFolder: `${CWD}/cypress/fixtures`,
|
||||||
integrationFolder: `${CWD}/cypress/integration`,
|
integrationFolder: `${CWD}/cypress/integration`,
|
||||||
|
@ -26,10 +26,18 @@ export const addPanel = (config?: Partial<AddPanelConfig>): any =>
|
|||||||
|
|
||||||
const { dashboardUid, dataSourceName, panelTitle, queriesForm, visualizationName } = fullConfig;
|
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.Dashboard.Toolbar.toolbarItems('Add panel').click();
|
||||||
e2e.pages.AddDashboard.addNewPanel().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()
|
e2e()
|
||||||
.get('.ds-picker')
|
.get('.ds-picker')
|
||||||
.click()
|
.click()
|
||||||
@ -58,11 +66,6 @@ export const addPanel = (config?: Partial<AddPanelConfig>): any =>
|
|||||||
.click();
|
.click();
|
||||||
closeOptionsGroup('type');
|
closeOptionsGroup('type');
|
||||||
|
|
||||||
e2e().server();
|
|
||||||
e2e()
|
|
||||||
.route('POST', '/api/ds/query')
|
|
||||||
.as('chartData');
|
|
||||||
|
|
||||||
queriesForm(fullConfig);
|
queriesForm(fullConfig);
|
||||||
|
|
||||||
e2e().wait('@chartData');
|
e2e().wait('@chartData');
|
||||||
|
@ -2,37 +2,21 @@ import { e2e } from '../index';
|
|||||||
import { fromBaseUrl } from '../support/url';
|
import { fromBaseUrl } from '../support/url';
|
||||||
|
|
||||||
export interface DeleteDashboardConfig {
|
export interface DeleteDashboardConfig {
|
||||||
|
quick?: boolean;
|
||||||
title: string;
|
title: string;
|
||||||
uid: string;
|
uid: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const deleteDashboard = ({ title, uid }: DeleteDashboardConfig) => {
|
export const deleteDashboard = ({ quick = false, title, uid }: DeleteDashboardConfig) => {
|
||||||
e2e().logToConsole('Deleting dashboard with uid:', uid);
|
e2e().logToConsole('Deleting dashboard with uid:', uid);
|
||||||
|
|
||||||
// Avoid dashboard page errors
|
if (quick) {
|
||||||
e2e.pages.Home.visit();
|
quickDelete(uid);
|
||||||
e2e().request('DELETE', fromBaseUrl(`/api/dashboards/uid/${uid}`));
|
} else {
|
||||||
|
uiDelete(uid, title);
|
||||||
|
}
|
||||||
|
|
||||||
/* https://github.com/cypress-io/cypress/issues/2831
|
e2e().logToConsole('Deleted dashboard with uid:', uid);
|
||||||
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.getScenarioContext().then(({ addedDashboards }: any) => {
|
e2e.getScenarioContext().then(({ addedDashboards }: any) => {
|
||||||
e2e.setScenarioContext({
|
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)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
@ -4,31 +4,19 @@ import { fromBaseUrl } from '../support/url';
|
|||||||
export interface DeleteDataSourceConfig {
|
export interface DeleteDataSourceConfig {
|
||||||
id: string;
|
id: string;
|
||||||
name: 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);
|
e2e().logToConsole('Deleting data source with name:', name);
|
||||||
|
|
||||||
// Avoid datasources page errors
|
if (quick) {
|
||||||
e2e.pages.Home.visit();
|
quickDelete(name);
|
||||||
e2e().request('DELETE', fromBaseUrl(`/api/datasources/name/${name}`));
|
} else {
|
||||||
|
uiDelete(name);
|
||||||
|
}
|
||||||
|
|
||||||
/* https://github.com/cypress-io/cypress/issues/2831
|
e2e().logToConsole('Deleted data source with name:', name);
|
||||||
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.getScenarioContext().then(({ addedDataSources }: any) => {
|
e2e.getScenarioContext().then(({ addedDataSources }: any) => {
|
||||||
e2e.setScenarioContext({
|
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)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
@ -8,6 +8,7 @@ import { login } from './login';
|
|||||||
import { openDashboard } from './openDashboard';
|
import { openDashboard } from './openDashboard';
|
||||||
import { saveDashboard } from './saveDashboard';
|
import { saveDashboard } from './saveDashboard';
|
||||||
import { openPanelMenuItem, PanelMenuItems } from './openPanelMenuItem';
|
import { openPanelMenuItem, PanelMenuItems } from './openPanelMenuItem';
|
||||||
|
import { revertAllChanges } from './revertAllChanges';
|
||||||
|
|
||||||
export const Flows = {
|
export const Flows = {
|
||||||
addDashboard,
|
addDashboard,
|
||||||
@ -21,4 +22,5 @@ export const Flows = {
|
|||||||
saveDashboard,
|
saveDashboard,
|
||||||
openPanelMenuItem,
|
openPanelMenuItem,
|
||||||
PanelMenuItems,
|
PanelMenuItems,
|
||||||
|
revertAllChanges,
|
||||||
};
|
};
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { e2e } from '../index';
|
import { e2e } from '../index';
|
||||||
|
|
||||||
export const login = (username: string = 'admin', password: string = 'admin') => {
|
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.visit();
|
||||||
e2e.pages.Login.username()
|
e2e.pages.Login.username()
|
||||||
.should('be.visible') // prevents flakiness
|
.should('be.visible') // prevents flakiness
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { e2e } from '../index';
|
import { e2e } from '../index';
|
||||||
|
|
||||||
|
// @todo remove this, as it's a page change and not a flow
|
||||||
export const openDashboard = (dashboardUid: string) => {
|
export const openDashboard = (dashboardUid: string) => {
|
||||||
e2e.pages.Dashboard.visit(dashboardUid);
|
e2e.pages.Dashboard.visit(dashboardUid);
|
||||||
};
|
};
|
||||||
|
8
packages/grafana-e2e/src/flows/revertAllChanges.ts
Normal file
8
packages/grafana-e2e/src/flows/revertAllChanges.ts
Normal file
@ -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 }));
|
||||||
|
});
|
||||||
|
};
|
@ -1,6 +1,5 @@
|
|||||||
import { e2e } from '../';
|
import { e2e } from '../';
|
||||||
import { Flows } from '../flows';
|
import { Flows } from '../flows';
|
||||||
import { getScenarioContext } from './scenarioContext';
|
|
||||||
|
|
||||||
export interface ScenarioArguments {
|
export interface ScenarioArguments {
|
||||||
describeName: string;
|
describeName: string;
|
||||||
@ -23,8 +22,11 @@ export const e2eScenario = ({
|
|||||||
if (skipScenario) {
|
if (skipScenario) {
|
||||||
it.skip(itName, () => scenario());
|
it.skip(itName, () => scenario());
|
||||||
} else {
|
} else {
|
||||||
|
before(() => Flows.login(e2e.env('USERNAME'), e2e.env('PASSWORD')));
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
Flows.login(e2e.env('USERNAME'), e2e.env('PASSWORD'));
|
Cypress.Cookies.preserveOnce('grafana_session');
|
||||||
|
|
||||||
if (addScenarioDataSource) {
|
if (addScenarioDataSource) {
|
||||||
Flows.addDataSource();
|
Flows.addDataSource();
|
||||||
}
|
}
|
||||||
@ -33,14 +35,13 @@ export const e2eScenario = ({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => Flows.revertAllChanges());
|
||||||
getScenarioContext().then(({ addedDashboards, addedDataSources }: any) => {
|
after(() => e2e().clearCookies());
|
||||||
addedDashboards.forEach((dashboard: any) => Flows.deleteDashboard(dashboard));
|
|
||||||
addedDataSources.forEach((dataSource: any) => Flows.deleteDataSource(dataSource));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it(itName, () => scenario());
|
it(itName, () => scenario());
|
||||||
|
|
||||||
|
// @todo remove when possible: https://github.com/cypress-io/cypress/issues/2831
|
||||||
|
it('temporary', () => {});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -2,13 +2,10 @@ import { e2e } from '../index';
|
|||||||
|
|
||||||
const getBaseUrl = () => e2e.env('BASE_URL') || e2e.config().baseUrl || 'http://localhost:3000';
|
const getBaseUrl = () => e2e.env('BASE_URL') || e2e.config().baseUrl || 'http://localhost:3000';
|
||||||
|
|
||||||
export const fromBaseUrl = (url = ''): string => {
|
export const fromBaseUrl = (url = '') => new URL(url, getBaseUrl()).href;
|
||||||
const strippedUrl = url.replace('^/', '');
|
|
||||||
return `${getBaseUrl()}${strippedUrl}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getDashboardUid = (url: string): string => {
|
export const getDashboardUid = (url: string): string => {
|
||||||
const matches = url.match(/\/d\/(.*)\//);
|
const matches = new URL(url).pathname.match(/\/d\/([^/]+)/);
|
||||||
if (!matches) {
|
if (!matches) {
|
||||||
throw new Error(`Couldn't parse uid from ${url}`);
|
throw new Error(`Couldn't parse uid from ${url}`);
|
||||||
} else {
|
} else {
|
||||||
@ -17,7 +14,7 @@ export const getDashboardUid = (url: string): string => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const getDataSourceId = (url: string): string => {
|
export const getDataSourceId = (url: string): string => {
|
||||||
const matches = url.match(/\/edit\/(.*)\//);
|
const matches = new URL(url).pathname.match(/\/edit\/([^/]+)/);
|
||||||
if (!matches) {
|
if (!matches) {
|
||||||
throw new Error(`Couldn't parse id from ${url}`);
|
throw new Error(`Couldn't parse id from ${url}`);
|
||||||
} else {
|
} else {
|
||||||
|
Loading…
Reference in New Issue
Block a user