Commit 85c4311c by Hugo Häggmark Committed by GitHub

e2eTests: Adds cleanup of created datasource and dashboard (#20244)

* e2eTests: Adds cleanup of created datasource and dashboard

* Chore: Fixes Prettier error

* Tests: Updates snapshots
parent f2415a31
...@@ -63,7 +63,7 @@ ...@@ -63,7 +63,7 @@
</div> </div>
</div> </div>
<div class="gf-form-button-row"> <div class="gf-form-button-row">
<button class="btn btn-danger" ng-click="ctrl.deleteDashboard()" ng-show="ctrl.canDelete"> <button class="btn btn-danger" ng-click="ctrl.deleteDashboard()" ng-show="ctrl.canDelete" aria-label="Dashboard settings page delete dashboard button">
Delete Dashboard Delete Dashboard
</button> </button>
</div> </div>
......
...@@ -19,7 +19,7 @@ export class DataSourcesListItem extends PureComponent<Props> { ...@@ -19,7 +19,7 @@ export class DataSourcesListItem extends PureComponent<Props> {
<img src={dataSource.typeLogoUrl} alt={dataSource.name} /> <img src={dataSource.typeLogoUrl} alt={dataSource.name} />
</figure> </figure>
<div className="card-item-details"> <div className="card-item-details">
<div className="card-item-name"> <div className="card-item-name" aria-label={`Data source list item for ${dataSource.name}`}>
{dataSource.name} {dataSource.name}
{dataSource.isDefault && <span className="btn btn-secondary btn-small card-item-label">default</span>} {dataSource.isDefault && <span className="btn btn-secondary btn-small card-item-label">default</span>}
</div> </div>
......
...@@ -32,6 +32,7 @@ exports[`Render should render component 1`] = ` ...@@ -32,6 +32,7 @@ exports[`Render should render component 1`] = `
className="card-item-details" className="card-item-details"
> >
<div <div
aria-label="Data source list item for gdev-cloudwatch"
className="card-item-name" className="card-item-name"
> >
gdev-cloudwatch gdev-cloudwatch
......
...@@ -10,7 +10,7 @@ export interface Props { ...@@ -10,7 +10,7 @@ export interface Props {
const BasicSettings: FC<Props> = ({ dataSourceName, isDefault, onDefaultChange, onNameChange }) => { const BasicSettings: FC<Props> = ({ dataSourceName, isDefault, onDefaultChange, onNameChange }) => {
return ( return (
<div className="gf-form-group"> <div className="gf-form-group" aria-label="Datasource settings page basic settings">
<div className="gf-form-inline"> <div className="gf-form-inline">
<div className="gf-form max-width-30" style={{ marginRight: '3px' }}> <div className="gf-form max-width-30" style={{ marginRight: '3px' }}>
<FormLabel <FormLabel
...@@ -28,11 +28,17 @@ const BasicSettings: FC<Props> = ({ dataSourceName, isDefault, onDefaultChange, ...@@ -28,11 +28,17 @@ const BasicSettings: FC<Props> = ({ dataSourceName, isDefault, onDefaultChange,
placeholder="Name" placeholder="Name"
onChange={event => onNameChange(event.target.value)} onChange={event => onNameChange(event.target.value)}
required required
aria-label="Datasource settings page name input field"
/> />
</div> </div>
{/* <Switch
//@ts-ignore */} label="Default"
<Switch label="Default" checked={isDefault} onChange={event => onDefaultChange(event.target.checked)} /> checked={isDefault}
onChange={event => {
// @ts-ignore
onDefaultChange(event.target.checked);
}}
/>
</div> </div>
</div> </div>
); );
......
...@@ -27,7 +27,13 @@ const ButtonRow: FC<Props> = ({ isReadOnly, onDelete, onSubmit, onTest }) => { ...@@ -27,7 +27,13 @@ const ButtonRow: FC<Props> = ({ isReadOnly, onDelete, onSubmit, onTest }) => {
Test Test
</button> </button>
)} )}
<button type="submit" className="btn btn-danger" disabled={isReadOnly} onClick={onDelete}> <button
type="submit"
className="btn btn-danger"
disabled={isReadOnly}
onClick={onDelete}
aria-label="Delete button"
>
Delete Delete
</button> </button>
<a className="btn btn-inverse" href={`${config.appSubUrl}/datasources`}> <a className="btn btn-inverse" href={`${config.appSubUrl}/datasources`}>
......
...@@ -3,36 +3,31 @@ import React, { PureComponent } from 'react'; ...@@ -3,36 +3,31 @@ import React, { PureComponent } from 'react';
import { hot } from 'react-hot-loader'; import { hot } from 'react-hot-loader';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import isString from 'lodash/isString'; import isString from 'lodash/isString';
// Components // Components
import Page from 'app/core/components/Page/Page'; import Page from 'app/core/components/Page/Page';
import { PluginSettings, GenericDataSourcePlugin } from './PluginSettings'; import { GenericDataSourcePlugin, PluginSettings } from './PluginSettings';
import BasicSettings from './BasicSettings'; import BasicSettings from './BasicSettings';
import ButtonRow from './ButtonRow'; import ButtonRow from './ButtonRow';
// Services & Utils // Services & Utils
import appEvents from 'app/core/app_events'; import appEvents from 'app/core/app_events';
import { getBackendSrv } from 'app/core/services/backend_srv'; import { getBackendSrv } from 'app/core/services/backend_srv';
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
// Actions & selectors // Actions & selectors
import { getDataSource, getDataSourceMeta } from '../state/selectors'; import { getDataSource, getDataSourceMeta } from '../state/selectors';
import { import {
dataSourceLoaded,
deleteDataSource, deleteDataSource,
loadDataSource, loadDataSource,
setDataSourceName, setDataSourceName,
setIsDefault, setIsDefault,
updateDataSource, updateDataSource,
dataSourceLoaded,
} from '../state/actions'; } from '../state/actions';
import { getNavModel } from 'app/core/selectors/navModel'; import { getNavModel } from 'app/core/selectors/navModel';
import { getRouteParamsId } from 'app/core/selectors/location'; import { getRouteParamsId } from 'app/core/selectors/location';
// Types // Types
import { StoreState, CoreEvents } from 'app/types/'; import { CoreEvents, StoreState } from 'app/types/';
import { UrlQueryMap } from '@grafana/runtime'; import { UrlQueryMap } from '@grafana/runtime';
import { DataSourceSettings, DataSourcePluginMeta } from '@grafana/data'; import { DataSourcePluginMeta, DataSourceSettings, NavModel } from '@grafana/data';
import { NavModel } from '@grafana/data';
import { getDataSourceLoadingNav } from '../state/navModel'; import { getDataSourceLoadingNav } from '../state/navModel';
import PluginStateinfo from 'app/features/plugins/PluginStateInfo'; import PluginStateinfo from 'app/features/plugins/PluginStateInfo';
import { importDataSourcePlugin } from 'app/features/plugins/plugin_loader'; import { importDataSourcePlugin } from 'app/features/plugins/plugin_loader';
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
exports[`Render should render component 1`] = ` exports[`Render should render component 1`] = `
<div <div
aria-label="Datasource settings page basic settings"
className="gf-form-group" className="gf-form-group"
> >
<div <div
...@@ -21,6 +22,7 @@ exports[`Render should render component 1`] = ` ...@@ -21,6 +22,7 @@ exports[`Render should render component 1`] = `
Name Name
</Component> </Component>
<Input <Input
aria-label="Datasource settings page name input field"
className="gf-form-input max-width-23" className="gf-form-input max-width-23"
onChange={[Function]} onChange={[Function]}
placeholder="Name" placeholder="Name"
......
...@@ -12,6 +12,7 @@ exports[`Render should render component 1`] = ` ...@@ -12,6 +12,7 @@ exports[`Render should render component 1`] = `
Test Test
</button> </button>
<button <button
aria-label="Delete button"
className="btn btn-danger" className="btn btn-danger"
disabled={true} disabled={true}
onClick={[MockFunction]} onClick={[MockFunction]}
...@@ -42,6 +43,7 @@ exports[`Render should render with buttons enabled 1`] = ` ...@@ -42,6 +43,7 @@ exports[`Render should render with buttons enabled 1`] = `
Save & Test Save & Test
</button> </button>
<button <button
aria-label="Delete button"
className="btn btn-danger" className="btn btn-danger"
disabled={false} disabled={false}
onClick={[MockFunction]} onClick={[MockFunction]}
......
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
<div class="confirm-modal-buttons"> <div class="confirm-modal-buttons">
<button ng-show="onAltAction" type="button" class="btn btn-primary" ng-click="dismiss();onAltAction();">{{altActionText}}</button> <button ng-show="onAltAction" type="button" class="btn btn-primary" ng-click="dismiss();onAltAction();">{{altActionText}}</button>
<button ng-show="onConfirm" type="button" class="btn btn-danger" ng-click="onConfirm();dismiss();" ng-disabled="!confirmTextValid" give-focus="true">{{yesText}}</button> <button ng-show="onConfirm" type="button" class="btn btn-danger" ng-click="onConfirm();dismiss();" ng-disabled="!confirmTextValid" give-focus="true" aria-label="Confirm Modal Danger Button">{{yesText}}</button>
<button type="button" class="btn btn-inverse" ng-click="dismiss()">{{noText}}</button> <button type="button" class="btn btn-inverse" ng-click="dismiss()">{{noText}}</button>
</div> </div>
</div> </div>
......
import { ClickablePageObject, ClickablePageObjectType, Selector, TestPage } from '@grafana/toolkit/src/e2e';
export interface DashboardPage {
settings: ClickablePageObjectType;
}
export const dashboardPage = new TestPage<DashboardPage>({
pageObjects: {
settings: new ClickablePageObject(Selector.fromAriaLabel('Dashboard settings navbar button')),
},
});
import { ClickablePageObject, ClickablePageObjectType, Selector, TestPage } from '@grafana/toolkit/src/e2e';
export interface DashboardSettingsPage {
deleteDashBoard: ClickablePageObjectType;
}
export const dashboardSettingsPage = new TestPage<DashboardSettingsPage>({
pageObjects: {
deleteDashBoard: new ClickablePageObject(Selector.fromAriaLabel('Dashboard settings page delete dashboard button')),
},
});
import { TestPage } from '@grafana/toolkit/src/e2e'; import { ClickablePageObject, ClickablePageObjectType, Selector, TestPage } from '@grafana/toolkit/src/e2e';
export interface DataSourcesPage {} export interface DataSourcesPage {
testData: ClickablePageObjectType;
}
export const dataSourcesPage = new TestPage<DataSourcesPage>({ export const dataSourcesPageFactory = (testDataSourceName: string) =>
url: '/datasources', new TestPage<DataSourcesPage>({
pageObjects: {}, url: '/datasources',
}); pageObjects: {
testData: new ClickablePageObject(Selector.fromAriaLabel(`Data source list item for ${testDataSourceName}`)),
},
});
import { import {
TestPage,
ClickablePageObjectType,
PageObjectType,
ClickablePageObject, ClickablePageObject,
ClickablePageObjectType,
InputPageObject,
InputPageObjectType,
PageObject, PageObject,
PageObjectType,
Selector, Selector,
TestPage,
} from '@grafana/toolkit/src/e2e'; } from '@grafana/toolkit/src/e2e';
export interface EditDataSourcePage { export interface EditDataSourcePage {
name: InputPageObjectType;
default: ClickablePageObjectType;
delete: ClickablePageObjectType;
saveAndTest: ClickablePageObjectType; saveAndTest: ClickablePageObjectType;
alert: PageObjectType; alert: PageObjectType;
alertMessage: PageObjectType; alertMessage: PageObjectType;
...@@ -15,6 +20,11 @@ export interface EditDataSourcePage { ...@@ -15,6 +20,11 @@ export interface EditDataSourcePage {
export const editDataSourcePage = new TestPage<EditDataSourcePage>({ export const editDataSourcePage = new TestPage<EditDataSourcePage>({
pageObjects: { pageObjects: {
name: new InputPageObject(Selector.fromAriaLabel('Datasource settings page name input field')),
default: new ClickablePageObject(
Selector.fromSelector('[aria-label="Datasource settings page basic settings"] .gf-form-switch')
),
delete: new ClickablePageObject(Selector.fromAriaLabel('Delete button')),
saveAndTest: new ClickablePageObject(Selector.fromAriaLabel('Save and Test button')), saveAndTest: new ClickablePageObject(Selector.fromAriaLabel('Save and Test button')),
alert: new PageObject(Selector.fromAriaLabel('Datasource settings page Alert')), alert: new PageObject(Selector.fromAriaLabel('Datasource settings page Alert')),
alertMessage: new PageObject(Selector.fromAriaLabel('Datasource settings page Alert message')), alertMessage: new PageObject(Selector.fromAriaLabel('Datasource settings page Alert message')),
......
import { ClickablePageObject, ClickablePageObjectType, PageObject, Selector, TestPage } from '@grafana/toolkit/src/e2e';
export interface ConfirmModal {
delete: ClickablePageObjectType;
success: PageObject;
}
export const confirmModal = new TestPage<ConfirmModal>({
pageObjects: {
delete: new ClickablePageObject(Selector.fromAriaLabel('Confirm Modal Danger Button')),
success: new PageObject(Selector.fromSelector('.alert-success')),
},
});
import { Browser, Page, Target } from 'puppeteer-core'; import { Browser, Page, Target } from 'puppeteer-core';
import { e2eScenario, constants, takeScreenShot, compareScreenShots } from '@grafana/toolkit/src/e2e'; import { compareScreenShots, constants, e2eScenario, takeScreenShot } from '@grafana/toolkit/src/e2e';
import { addDataSourcePage } from 'e2e-test/pages/datasources/addDataSourcePage'; import { addDataSourcePage } from 'e2e-test/pages/datasources/addDataSourcePage';
import { editDataSourcePage } from 'e2e-test/pages/datasources/editDataSourcePage'; import { editDataSourcePage } from 'e2e-test/pages/datasources/editDataSourcePage';
import { dataSourcesPage } from 'e2e-test/pages/datasources/dataSources'; import { dataSourcesPageFactory } from 'e2e-test/pages/datasources/dataSources';
import { createDashboardPage } from 'e2e-test/pages/dashboards/createDashboardPage'; import { createDashboardPage } from 'e2e-test/pages/dashboards/createDashboardPage';
import { saveDashboardModal } from 'e2e-test/pages/dashboards/saveDashboardModal'; import { saveDashboardModal } from 'e2e-test/pages/dashboards/saveDashboardModal';
import { dashboardsPageFactory } from 'e2e-test/pages/dashboards/dashboardsPage'; import { dashboardsPageFactory } from 'e2e-test/pages/dashboards/dashboardsPage';
import { panel } from 'e2e-test/pages/panels/panel'; import { panel } from 'e2e-test/pages/panels/panel';
import { editPanelPage } from 'e2e-test/pages/panels/editPanel'; import { editPanelPage } from 'e2e-test/pages/panels/editPanel';
import { sharePanelModal } from 'e2e-test/pages/panels/sharePanelModal'; import { sharePanelModal } from 'e2e-test/pages/panels/sharePanelModal';
import { confirmModal } from '../pages/modals/confirmModal';
import { dashboardPage } from '../pages/dashboards/dashboardPage';
import { dashboardSettingsPage } from '../pages/dashboards/dashboardSettingsPage';
e2eScenario( const addTestDataSourceAndVerify = async (page: Page) => {
'Login scenario, create test data source, dashboard, panel, and export scenario', // Add TestData DB
'should pass', const testDataSourceName = `TestData-${new Date().getTime()}`;
async (browser: Browser, page: Page) => { await addDataSourcePage.init(page);
// Add TestData DB await addDataSourcePage.navigateTo();
await addDataSourcePage.init(page); await addDataSourcePage.pageObjects.testDataDB.exists();
await addDataSourcePage.navigateTo(); await addDataSourcePage.pageObjects.testDataDB.click();
await addDataSourcePage.pageObjects.testDataDB.exists();
await addDataSourcePage.pageObjects.testDataDB.click(); await editDataSourcePage.init(page);
await editDataSourcePage.waitForNavigation();
await editDataSourcePage.init(page); await editDataSourcePage.pageObjects.name.enter(testDataSourceName);
await editDataSourcePage.waitForNavigation(); await editDataSourcePage.pageObjects.default.click();
await editDataSourcePage.pageObjects.saveAndTest.click(); await editDataSourcePage.pageObjects.saveAndTest.click();
await editDataSourcePage.pageObjects.alert.exists(); await editDataSourcePage.pageObjects.alert.exists();
await editDataSourcePage.pageObjects.alertMessage.containsText('Data source is working'); await editDataSourcePage.pageObjects.alertMessage.containsText('Data source is working');
// Verify that data source is listed // Verify that data source is listed
const url = await editDataSourcePage.getUrlWithoutBaseUrl(); const url = await editDataSourcePage.getUrlWithoutBaseUrl();
const expectedUrl = url.substring(1, url.length - 1); const expectedUrl = url.substring(1, url.length - 1);
const selector = `a[href="${expectedUrl}"]`; const selector = `a[href="${expectedUrl}"]`;
await dataSourcesPage.init(page); const dataSourcesPage = dataSourcesPageFactory(testDataSourceName);
await dataSourcesPage.navigateTo(); await dataSourcesPage.init(page);
await dataSourcesPage.expectSelector({ selector }); await dataSourcesPage.navigateTo();
await dataSourcesPage.expectSelector({ selector });
// Create a new Dashboard
await createDashboardPage.init(page); return testDataSourceName;
await createDashboardPage.navigateTo(); };
await createDashboardPage.pageObjects.addQuery.click();
const addDashboardAndSetupTestDataGraph = async (page: Page) => {
await editPanelPage.init(page); // Create a new Dashboard
await editPanelPage.waitForNavigation(); const dashboardTitle = `Dashboard-${new Date().getTime()}`;
await editPanelPage.pageObjects.queriesTab.click(); await createDashboardPage.init(page);
await editPanelPage.pageObjects.scenarioSelect.select('string:csv_metric_values'); await createDashboardPage.navigateTo();
await editPanelPage.pageObjects.visualizationTab.click(); await createDashboardPage.pageObjects.addQuery.click();
await editPanelPage.pageObjects.showXAxis.click();
await editPanelPage.pageObjects.saveDashboard.click(); await editPanelPage.init(page);
await editPanelPage.waitForNavigation();
// Confirm save modal await editPanelPage.pageObjects.queriesTab.click();
await saveDashboardModal.init(page); await editPanelPage.pageObjects.scenarioSelect.select('string:csv_metric_values');
await saveDashboardModal.expectSelector({ selector: 'save-dashboard-as-modal' }); await editPanelPage.pageObjects.visualizationTab.click();
const dashboardTitle = new Date().toISOString(); await editPanelPage.pageObjects.showXAxis.click();
await saveDashboardModal.pageObjects.name.enter(dashboardTitle); await editPanelPage.pageObjects.saveDashboard.click();
await saveDashboardModal.pageObjects.save.click();
await saveDashboardModal.pageObjects.success.exists(); // Confirm save modal
await saveDashboardModal.init(page);
// Share the dashboard await saveDashboardModal.expectSelector({ selector: 'save-dashboard-as-modal' });
const dashboardsPage = dashboardsPageFactory(dashboardTitle); await saveDashboardModal.pageObjects.name.enter(dashboardTitle);
await dashboardsPage.init(page); await saveDashboardModal.pageObjects.save.click();
await dashboardsPage.navigateTo(); await saveDashboardModal.pageObjects.success.exists();
await dashboardsPage.pageObjects.dashboard.exists();
await dashboardsPage.pageObjects.dashboard.click(); return dashboardTitle;
};
await panel.init(page);
await panel.pageObjects.panelTitle.click(); const clickOnSharePanelImageLinkAndCompareImages = async (browser: Browser, page: Page, dashboardTitle: string) => {
await panel.pageObjects.share.click(); // Share the dashboard
const dashboardsPage = dashboardsPageFactory(dashboardTitle);
// Verify that a new tab is opened await dashboardsPage.init(page);
const targetPromise = new Promise(resolve => browser.once('targetcreated', resolve)); await dashboardsPage.navigateTo();
await sharePanelModal.init(page); await dashboardsPage.pageObjects.dashboard.exists();
await sharePanelModal.pageObjects.directLinkRenderedImage.click(); await dashboardsPage.pageObjects.dashboard.click();
const newTarget: Target = (await targetPromise) as Target;
expect(newTarget.url()).toContain(`${constants.baseUrl}/render/d-solo`); await panel.init(page);
await panel.pageObjects.panelTitle.click();
// Take snapshot of page await panel.pageObjects.share.click();
// Verify that a new tab is opened
const targetPromise = new Promise(resolve => browser.once('targetcreated', resolve));
await sharePanelModal.init(page);
await sharePanelModal.pageObjects.directLinkRenderedImage.click();
const newTarget: Target = (await targetPromise) as Target;
expect(newTarget.url()).toContain(`${constants.baseUrl}/render/d-solo`);
// Take snapshot of page only when running on CircleCI
if (process.env.CIRCLE_SHA1) {
const newPage = await newTarget.page(); const newPage = await newTarget.page();
const fileName = 'smoke-test-scenario'; const fileName = 'smoke-test-scenario';
await takeScreenShot(newPage, fileName); await takeScreenShot(newPage, fileName);
await compareScreenShots(fileName); await compareScreenShots(fileName);
} }
};
const cleanUpTestDataSource = async (page: Page, testDataSourceName: string) => {
const dataSourcesPage = dataSourcesPageFactory(testDataSourceName);
await dataSourcesPage.init(page);
await dataSourcesPage.navigateTo();
await dataSourcesPage.pageObjects.testData.click();
await editDataSourcePage.init(page);
await editDataSourcePage.pageObjects.delete.exists();
await editDataSourcePage.pageObjects.delete.click();
await confirmModal.init(page);
await confirmModal.pageObjects.delete.click();
await confirmModal.pageObjects.success.exists();
};
const cleanDashboard = async (page: Page, dashboardTitle: string) => {
const dashboardsPage = dashboardsPageFactory(dashboardTitle);
await dashboardsPage.init(page);
await dashboardsPage.navigateTo();
await dashboardsPage.pageObjects.dashboard.exists();
await dashboardsPage.pageObjects.dashboard.click();
await dashboardPage.init(page);
await dashboardPage.pageObjects.settings.click();
await dashboardSettingsPage.init(page);
await dashboardSettingsPage.pageObjects.deleteDashBoard.click();
await confirmModal.init(page);
await confirmModal.pageObjects.delete.click();
await confirmModal.pageObjects.success.exists();
};
e2eScenario(
'Login scenario, create test data source, dashboard, panel, and export scenario',
'should pass',
async (browser: Browser, page: Page) => {
const testDataSourceName = await addTestDataSourceAndVerify(page);
const dashboardTitle = await addDashboardAndSetupTestDataGraph(page);
await clickOnSharePanelImageLinkAndCompareImages(browser, page, dashboardTitle);
await cleanUpTestDataSource(page, testDataSourceName);
await cleanDashboard(page, dashboardTitle);
}
); );
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment