mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
e2e: adds inspect drawer tests (#23823)
* Explore: Create basic E2E test * Feature: adds e2e tests for panel inspector * Refactor: adds ts-ignore because of type checking errors * Refactor: changes after PR comments and updates snapshot * Refactor: adds typings back for IScope * Refactor: changes after PR comments Co-authored-by: Andreas Opferkuch <andreas.opferkuch@gmail.com>
This commit is contained in:
19
e2e/suite1/specs/explore.spec.ts
Normal file
19
e2e/suite1/specs/explore.spec.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { e2e } from '@grafana/e2e';
|
||||
|
||||
e2e.scenario({
|
||||
describeName: 'Explore',
|
||||
itName: 'Basic path through Explore.',
|
||||
addScenarioDataSource: true,
|
||||
addScenarioDashBoard: false,
|
||||
skipScenario: false,
|
||||
scenario: () => {
|
||||
e2e.pages.Explore.visit();
|
||||
e2e.pages.Explore.General.container().should('have.length', 1);
|
||||
e2e.pages.Explore.General.runButton().should('have.length', 1);
|
||||
|
||||
const canvases = e2e().get('canvas');
|
||||
canvases.should('have.length', 2);
|
||||
|
||||
e2e.components.DataSource.TestData.QueryTab.noise().should('have.length', 1);
|
||||
},
|
||||
});
|
||||
134
e2e/suite1/specs/inspect-drawer.spec.ts
Normal file
134
e2e/suite1/specs/inspect-drawer.spec.ts
Normal file
@@ -0,0 +1,134 @@
|
||||
import { e2e } from '@grafana/e2e';
|
||||
|
||||
const PANEL_UNDER_TEST = '2 yaxis and axis labels';
|
||||
|
||||
e2e.scenario({
|
||||
describeName: 'Inspect drawer tests',
|
||||
itName: 'Testes various Inpect Drawer scenarios',
|
||||
addScenarioDataSource: false,
|
||||
addScenarioDashBoard: false,
|
||||
skipScenario: false,
|
||||
scenario: () => {
|
||||
const viewPortWidth = e2e.config().viewportWidth;
|
||||
e2e.flows.openDashboard('5SdHCadmz');
|
||||
|
||||
// testing opening inspect drawer directly by clicking on Inspect in header menu
|
||||
e2e.flows.openPanelMenuItem(e2e.flows.PanelMenuItems.Inspect, PANEL_UNDER_TEST);
|
||||
|
||||
expectDrawerTabsAndContent();
|
||||
|
||||
expectDrawerExpandAndContract(viewPortWidth);
|
||||
|
||||
expectDrawerClose();
|
||||
|
||||
expectSubMenuScenario('Data');
|
||||
expectSubMenuScenario('Query');
|
||||
expectSubMenuScenario('Panel JSON', 'JSON');
|
||||
|
||||
e2e.flows.openPanelMenuItem(e2e.flows.PanelMenuItems.Edit, PANEL_UNDER_TEST);
|
||||
|
||||
e2e.components.QueryEditorToolbarItem.button('Query inspector')
|
||||
.should('be.visible')
|
||||
.click();
|
||||
|
||||
e2e.components.Drawer.General.title(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(PANEL_UNDER_TEST)
|
||||
.should('be.visible')
|
||||
.within(() => {
|
||||
e2e.components.Tab.title('Data').should('be.visible');
|
||||
// data should be the active tab
|
||||
e2e.components.Tab.active().within((li: JQuery<HTMLLIElement>) => {
|
||||
expect(li.text()).equals('Data');
|
||||
});
|
||||
e2e.components.PanelInspector.Data.content().should('be.visible');
|
||||
e2e.components.PanelInspector.Stats.content().should('not.be.visible');
|
||||
e2e.components.PanelInspector.Json.content().should('not.be.visible');
|
||||
e2e.components.PanelInspector.Query.content().should('not.be.visible');
|
||||
|
||||
// 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.be.visible');
|
||||
e2e.components.PanelInspector.Json.content().should('not.be.visible');
|
||||
e2e.components.PanelInspector.Query.content().should('not.be.visible');
|
||||
|
||||
e2e.components.Tab.title('JSON')
|
||||
.should('be.visible')
|
||||
.click();
|
||||
e2e.components.PanelInspector.Json.content().should('be.visible');
|
||||
e2e.components.PanelInspector.Data.content().should('not.be.visible');
|
||||
e2e.components.PanelInspector.Stats.content().should('not.be.visible');
|
||||
e2e.components.PanelInspector.Query.content().should('not.be.visible');
|
||||
|
||||
e2e.components.Tab.title('Query')
|
||||
.should('be.visible')
|
||||
.click();
|
||||
e2e.components.PanelInspector.Query.content().should('be.visible');
|
||||
e2e.components.PanelInspector.Data.content().should('not.be.visible');
|
||||
e2e.components.PanelInspector.Stats.content().should('not.be.visible');
|
||||
e2e.components.PanelInspector.Json.content().should('not.be.visible');
|
||||
});
|
||||
};
|
||||
|
||||
const expectDrawerClose = () => {
|
||||
// close using close button
|
||||
e2e.components.Drawer.General.close().click();
|
||||
e2e.components.Drawer.General.title(PANEL_UNDER_TEST).should('not.be.visible');
|
||||
};
|
||||
|
||||
const expectDrawerExpandAndContract = (viewPortWidth: number) => {
|
||||
// try expand button
|
||||
// drawer should take up half the screen
|
||||
e2e.components.Drawer.General.rcContentWrapper()
|
||||
.should('be.visible')
|
||||
.should('have.css', 'width', `${viewPortWidth / 2}px`);
|
||||
|
||||
e2e.components.Drawer.General.expand().click();
|
||||
e2e.components.Drawer.General.contract().should('be.visible');
|
||||
|
||||
// drawer should take up the whole screen
|
||||
e2e.components.Drawer.General.rcContentWrapper()
|
||||
.should('be.visible')
|
||||
.should('have.css', 'width', `${viewPortWidth}px`);
|
||||
|
||||
// try contract button
|
||||
e2e.components.Drawer.General.contract().click();
|
||||
e2e.components.Drawer.General.expand().should('be.visible');
|
||||
|
||||
e2e.components.Drawer.General.rcContentWrapper()
|
||||
.should('be.visible')
|
||||
.should('have.css', 'width', `${viewPortWidth / 2}px`);
|
||||
};
|
||||
|
||||
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')
|
||||
.click();
|
||||
|
||||
// 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
|
||||
e2e.components.Panels.Panel.headerItems(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();
|
||||
};
|
||||
@@ -1,5 +1,74 @@
|
||||
import { e2e } from '@grafana/e2e';
|
||||
|
||||
// This test should really be broken into several smaller tests
|
||||
e2e.scenario({
|
||||
describeName: 'Variables',
|
||||
itName: 'Query Variables CRUD',
|
||||
addScenarioDataSource: true,
|
||||
addScenarioDashBoard: true,
|
||||
skipScenario: false,
|
||||
scenario: () => {
|
||||
// @todo remove `@ts-ignore` when possible
|
||||
// @ts-ignore
|
||||
e2e.getScenarioContext().then(({ lastAddedDashboardUid }) => {
|
||||
e2e.flows.openDashboard(lastAddedDashboardUid);
|
||||
});
|
||||
e2e.pages.Dashboard.Toolbar.toolbarItems('Dashboard settings').click();
|
||||
e2e.pages.Dashboard.Settings.General.sectionItems('Variables').click();
|
||||
e2e.pages.Dashboard.Settings.Variables.List.addVariableCTA().click();
|
||||
|
||||
assertDefaultsForNewVariable();
|
||||
|
||||
e2e.pages.Dashboard.Settings.General.sectionItems('General').click();
|
||||
e2e.pages.Dashboard.Settings.General.sectionItems('Variables').click();
|
||||
e2e.pages.Dashboard.Settings.Variables.List.addVariableCTA().click();
|
||||
|
||||
let queryVariables: QueryVariableData[] = [
|
||||
{
|
||||
name: 'query1',
|
||||
query: '*',
|
||||
label: 'query1-label',
|
||||
options: ['All', 'A', 'B', 'C'],
|
||||
selectedOption: 'A',
|
||||
},
|
||||
{
|
||||
name: 'query2',
|
||||
query: '$query1.*',
|
||||
label: 'query2-label',
|
||||
options: ['All', 'AA', 'AB', 'AC'],
|
||||
selectedOption: 'AA',
|
||||
},
|
||||
{
|
||||
name: 'query3',
|
||||
query: '$query1.$query2.*',
|
||||
label: 'query3-label',
|
||||
options: ['All', 'AAA', 'AAB', 'AAC'],
|
||||
selectedOption: 'AAA',
|
||||
},
|
||||
];
|
||||
|
||||
assertAdding3dependantQueryVariablesScenario(queryVariables);
|
||||
|
||||
// assert select updates
|
||||
assertSelects(queryVariables);
|
||||
|
||||
// assert that duplicate works
|
||||
queryVariables = assertDuplicateItem(queryVariables);
|
||||
|
||||
// assert that delete works
|
||||
queryVariables = assertDeleteItem(queryVariables);
|
||||
|
||||
// assert that update works
|
||||
queryVariables = assertUpdateItem(queryVariables);
|
||||
|
||||
// assert that move down works
|
||||
queryVariables = assertMoveDownItem(queryVariables);
|
||||
|
||||
// assert that move up works
|
||||
assertMoveUpItem(queryVariables);
|
||||
},
|
||||
});
|
||||
|
||||
const assertDefaultsForNewVariable = () => {
|
||||
logSection('Asserting defaults for new variable');
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalNameInput().within(input => {
|
||||
@@ -550,72 +619,3 @@ const assertMoveUpItem = (data: QueryVariableData[]) => {
|
||||
|
||||
return queryVariables;
|
||||
};
|
||||
|
||||
// This test should really be broken into several smaller tests
|
||||
e2e.scenario({
|
||||
describeName: 'Variables',
|
||||
itName: 'Query Variables CRUD',
|
||||
addScenarioDataSource: true,
|
||||
addScenarioDashBoard: true,
|
||||
skipScenario: false,
|
||||
scenario: () => {
|
||||
// @todo remove `@ts-ignore` when possible
|
||||
// @ts-ignore
|
||||
e2e.getScenarioContext().then(({ lastAddedDashboardUid }) => {
|
||||
e2e.flows.openDashboard(lastAddedDashboardUid);
|
||||
});
|
||||
e2e.pages.Dashboard.Toolbar.toolbarItems('Dashboard settings').click();
|
||||
e2e.pages.Dashboard.Settings.General.sectionItems('Variables').click();
|
||||
e2e.pages.Dashboard.Settings.Variables.List.addVariableCTA().click();
|
||||
|
||||
assertDefaultsForNewVariable();
|
||||
|
||||
e2e.pages.Dashboard.Settings.General.sectionItems('General').click();
|
||||
e2e.pages.Dashboard.Settings.General.sectionItems('Variables').click();
|
||||
e2e.pages.Dashboard.Settings.Variables.List.addVariableCTA().click();
|
||||
|
||||
let queryVariables: QueryVariableData[] = [
|
||||
{
|
||||
name: 'query1',
|
||||
query: '*',
|
||||
label: 'query1-label',
|
||||
options: ['All', 'A', 'B', 'C'],
|
||||
selectedOption: 'A',
|
||||
},
|
||||
{
|
||||
name: 'query2',
|
||||
query: '$query1.*',
|
||||
label: 'query2-label',
|
||||
options: ['All', 'AA', 'AB', 'AC'],
|
||||
selectedOption: 'AA',
|
||||
},
|
||||
{
|
||||
name: 'query3',
|
||||
query: '$query1.$query2.*',
|
||||
label: 'query3-label',
|
||||
options: ['All', 'AAA', 'AAB', 'AAC'],
|
||||
selectedOption: 'AAA',
|
||||
},
|
||||
];
|
||||
|
||||
assertAdding3dependantQueryVariablesScenario(queryVariables);
|
||||
|
||||
// assert select updates
|
||||
assertSelects(queryVariables);
|
||||
|
||||
// assert that duplicate works
|
||||
queryVariables = assertDuplicateItem(queryVariables);
|
||||
|
||||
// assert that delete works
|
||||
queryVariables = assertDeleteItem(queryVariables);
|
||||
|
||||
// assert that update works
|
||||
queryVariables = assertUpdateItem(queryVariables);
|
||||
|
||||
// assert that move down works
|
||||
queryVariables = assertMoveDownItem(queryVariables);
|
||||
|
||||
// assert that move up works
|
||||
assertMoveUpItem(queryVariables);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -23,3 +23,10 @@ if (Cypress.env('SLOWMO')) {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// uncomment below to prevent Cypress from failing tests when unhandled errors are thrown
|
||||
// Cypress.on('uncaught:exception', (err, runnable) => {
|
||||
// // returning false here prevents Cypress from
|
||||
// // failing the test
|
||||
// return false;
|
||||
// });
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
module.exports = require('./index.production.js');
|
||||
|
||||
67
packages/grafana-e2e/src/components/index.ts
Normal file
67
packages/grafana-e2e/src/components/index.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import { TestData } from '../pages/testdata';
|
||||
import { Panel } from '../pages/panel';
|
||||
import { EditPanel } from '../pages/editPanel';
|
||||
import { Graph } from '../pages/graph';
|
||||
import { componentFactory } from '../support';
|
||||
|
||||
export const Components = {
|
||||
DataSource: {
|
||||
TestData,
|
||||
},
|
||||
Panels: {
|
||||
Panel,
|
||||
EditPanel,
|
||||
Visualization: {
|
||||
Graph,
|
||||
},
|
||||
},
|
||||
Drawer: {
|
||||
General: componentFactory({
|
||||
selectors: {
|
||||
title: (title: string) => `Drawer title ${title}`,
|
||||
expand: 'Drawer expand',
|
||||
contract: 'Drawer contract',
|
||||
close: 'Drawer close',
|
||||
rcContentWrapper: () => '.drawer-content-wrapper',
|
||||
},
|
||||
}),
|
||||
},
|
||||
PanelInspector: {
|
||||
Data: componentFactory({
|
||||
selectors: {
|
||||
content: 'Panel inspector Data content',
|
||||
},
|
||||
}),
|
||||
Stats: componentFactory({
|
||||
selectors: {
|
||||
content: 'Panel inspector Stats content',
|
||||
},
|
||||
}),
|
||||
Json: componentFactory({
|
||||
selectors: {
|
||||
content: 'Panel inspector Json content',
|
||||
},
|
||||
}),
|
||||
Query: componentFactory({
|
||||
selectors: {
|
||||
content: 'Panel inspector Query content',
|
||||
},
|
||||
}),
|
||||
},
|
||||
Tab: componentFactory({
|
||||
selectors: {
|
||||
title: (title: string) => `Tab ${title}`,
|
||||
active: () => '[class*="-activeTabStyle"]',
|
||||
},
|
||||
}),
|
||||
QueryEditorToolbarItem: componentFactory({
|
||||
selectors: {
|
||||
button: (title: string) => `QueryEditor toolbar item button ${title}`,
|
||||
},
|
||||
}),
|
||||
BackButton: componentFactory({
|
||||
selectors: {
|
||||
backArrow: 'Go Back button',
|
||||
},
|
||||
}),
|
||||
};
|
||||
@@ -8,6 +8,7 @@ import { login } from './login';
|
||||
import { openDashboard } from './openDashboard';
|
||||
import { saveDashboard } from './saveDashboard';
|
||||
import { saveNewDashboard } from './saveNewDashboard';
|
||||
import { openPanelMenuItem, PanelMenuItems } from './openPanelMenuItem';
|
||||
|
||||
export const Flows = {
|
||||
addDashboard,
|
||||
@@ -20,4 +21,6 @@ export const Flows = {
|
||||
openDashboard,
|
||||
saveDashboard,
|
||||
saveNewDashboard,
|
||||
openPanelMenuItem,
|
||||
PanelMenuItems,
|
||||
};
|
||||
|
||||
16
packages/grafana-e2e/src/flows/openPanelMenuItem.ts
Normal file
16
packages/grafana-e2e/src/flows/openPanelMenuItem.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { e2e } from '../noTypeCheck';
|
||||
|
||||
export enum PanelMenuItems {
|
||||
Edit = 'Edit',
|
||||
Inspect = 'Inspect',
|
||||
}
|
||||
|
||||
export const openPanelMenuItem = (menu: PanelMenuItems, panelTitle = 'Panel Title') => {
|
||||
e2e.components.Panels.Panel.title(panelTitle)
|
||||
.should('be.visible')
|
||||
.click();
|
||||
|
||||
e2e.components.Panels.Panel.headerItems(menu)
|
||||
.should('be.visible')
|
||||
.click();
|
||||
};
|
||||
@@ -4,15 +4,13 @@
|
||||
// toBe, toEqual and so forth. That's why this file is not type checked and will be so until we
|
||||
// can solve the above mentioned issue with Cypress/Jest.
|
||||
import { e2eScenario, ScenarioArguments } from './support/scenario';
|
||||
import { Pages, Components } from './pages';
|
||||
import { Pages } from './pages';
|
||||
import { Components } from './components';
|
||||
import { Flows } from './flows';
|
||||
import { getScenarioContext, setScenarioContext } from './support/scenarioContext';
|
||||
|
||||
export type SelectorFunction = (text?: string) => Cypress.Chainable<JQuery<HTMLElement>>;
|
||||
export type SelectorObject<S> = {
|
||||
visit: (args?: string) => Cypress.Chainable<Window>;
|
||||
selectors: S;
|
||||
};
|
||||
export type VisitFunction = (args?: string) => Cypress.Chainable<Window>;
|
||||
|
||||
const e2eObject = {
|
||||
env: (args: string) => Cypress.env(args),
|
||||
|
||||
9
packages/grafana-e2e/src/pages/explore.ts
Normal file
9
packages/grafana-e2e/src/pages/explore.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { pageFactory } from '../support';
|
||||
|
||||
export const Explore = pageFactory({
|
||||
url: '/explore',
|
||||
selectors: {
|
||||
container: 'Explore',
|
||||
runButton: 'Run button',
|
||||
},
|
||||
});
|
||||
@@ -8,14 +8,10 @@ import { Dashboard } from './dashboard';
|
||||
import { SaveDashboardAsModal } from './saveDashboardAsModal';
|
||||
import { Dashboards } from './dashboards';
|
||||
import { DashboardSettings } from './dashboardSettings';
|
||||
import { EditPanel } from './editPanel';
|
||||
import { TestData } from './testdata';
|
||||
import { Graph } from './graph';
|
||||
import { Explore } from './explore';
|
||||
import { SaveDashboardModal } from './saveDashboardModal';
|
||||
import { Panel } from './panel';
|
||||
import { SharePanelModal } from './sharePanelModal';
|
||||
import { ConstantVariable, QueryVariable, VariableGeneral, Variables, VariablesSubMenu } from './variables';
|
||||
import { pageFactory } from '../support';
|
||||
|
||||
export const Pages = {
|
||||
Login,
|
||||
@@ -44,22 +40,8 @@ export const Pages = {
|
||||
SaveDashboardAsModal,
|
||||
SaveDashboardModal,
|
||||
SharePanelModal,
|
||||
};
|
||||
|
||||
export const Components = {
|
||||
DataSource: {
|
||||
TestData,
|
||||
Explore: {
|
||||
visit: () => Explore.visit(),
|
||||
General: Explore,
|
||||
},
|
||||
Panels: {
|
||||
Panel,
|
||||
EditPanel,
|
||||
Visualization: {
|
||||
Graph,
|
||||
},
|
||||
},
|
||||
BackButton: pageFactory({
|
||||
selectors: {
|
||||
backArrow: 'Go Back button',
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
import { pageFactory } from '../../support';
|
||||
import { componentFactory } from '../../support';
|
||||
|
||||
export const QueryTab = pageFactory({
|
||||
url: '',
|
||||
export const QueryTab = componentFactory({
|
||||
selectors: {
|
||||
scenarioSelect: 'Test Data Query scenario select',
|
||||
max: 'TestData max',
|
||||
min: 'TestData min',
|
||||
noise: 'TestData noise',
|
||||
seriesCount: 'TestData series count',
|
||||
spread: 'TestData spread',
|
||||
startValue: 'TestData start value',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { e2e } from '../index';
|
||||
import { Flows } from '../flows';
|
||||
import { getScenarioContext } from './scenarioContext';
|
||||
|
||||
export interface ScenarioArguments {
|
||||
describeName: string;
|
||||
@@ -17,34 +18,48 @@ export const e2eScenario = ({
|
||||
addScenarioDataSource = false,
|
||||
addScenarioDashBoard = false,
|
||||
}: ScenarioArguments) => {
|
||||
// when we started to use import { e2e } from '@grafana/e2e'; in grafana/ui components
|
||||
// then type checking @grafana/run-time started to fail with
|
||||
// Cannot find name 'describe'. Do you need to install type definitions for a test runner? Try `npm i @types/jest` or `npm i @types/mocha`.
|
||||
// Haven't investigated deeper why this happens yet so adding ts-ignore as temporary solution
|
||||
// @todo remove `@ts-ignore` when possible
|
||||
// @ts-ignore
|
||||
describe(describeName, () => {
|
||||
if (skipScenario) {
|
||||
// @todo remove `@ts-ignore` when possible
|
||||
// @ts-ignore
|
||||
it.skip(itName, () => scenario());
|
||||
} else {
|
||||
// @todo remove `@ts-ignore` when possible
|
||||
// @ts-ignore
|
||||
beforeEach(() => {
|
||||
e2e.flows.login('admin', 'admin');
|
||||
Flows.login('admin', 'admin');
|
||||
if (addScenarioDataSource) {
|
||||
e2e.flows.addDataSource();
|
||||
Flows.addDataSource();
|
||||
}
|
||||
if (addScenarioDashBoard) {
|
||||
e2e.flows.addDashboard();
|
||||
Flows.addDashboard();
|
||||
}
|
||||
});
|
||||
|
||||
// @todo remove `@ts-ignore` when possible
|
||||
// @ts-ignore
|
||||
afterEach(() => {
|
||||
// @todo remove `@ts-ignore` when possible
|
||||
// @ts-ignore
|
||||
e2e.getScenarioContext().then(({ lastAddedDashboardUid, lastAddedDataSource }) => {
|
||||
getScenarioContext().then(({ lastAddedDashboardUid, lastAddedDataSource }) => {
|
||||
if (lastAddedDataSource) {
|
||||
e2e.flows.deleteDataSource(lastAddedDataSource);
|
||||
Flows.deleteDataSource(lastAddedDataSource);
|
||||
}
|
||||
|
||||
if (lastAddedDashboardUid) {
|
||||
e2e.flows.deleteDashboard(lastAddedDashboardUid);
|
||||
Flows.deleteDashboard(lastAddedDashboardUid);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// @todo remove `@ts-ignore` when possible
|
||||
// @ts-ignore
|
||||
it(itName, () => scenario());
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
import { Selector } from './selector';
|
||||
import { fromBaseUrl } from './url';
|
||||
import { e2e } from '../index';
|
||||
import { SelectorFunction, SelectorObject } from '../noTypeCheck';
|
||||
import { SelectorFunction, VisitFunction } from '../noTypeCheck';
|
||||
|
||||
export type Selectors = Record<string, string | Function>;
|
||||
export type PageObjects<S> = { [P in keyof S]: SelectorFunction };
|
||||
export type PageFactory<S> = PageObjects<S> & SelectorObject<S>;
|
||||
export interface PageFactoryArgs<S extends Selectors> {
|
||||
url?: string | Function;
|
||||
export type SelectorFunctions<S> = { [P in keyof S]: SelectorFunction };
|
||||
|
||||
export type Page<S> = SelectorFunctions<S> & {
|
||||
selectors: S;
|
||||
visit: VisitFunction;
|
||||
};
|
||||
export interface PageFactoryArgs<S> {
|
||||
selectors: S;
|
||||
url?: string | Function;
|
||||
}
|
||||
|
||||
export const pageFactory = <S extends Selectors>({ url, selectors }: PageFactoryArgs<S>): PageFactory<S> => {
|
||||
export const pageFactory = <S extends Selectors>({ url, selectors }: PageFactoryArgs<S>): Page<S> => {
|
||||
const visit = (args?: string) => {
|
||||
if (!url) {
|
||||
return e2e().visit('');
|
||||
@@ -29,7 +33,7 @@ export const pageFactory = <S extends Selectors>({ url, selectors }: PageFactory
|
||||
e2e().logToConsole('Visiting', parsedUrl);
|
||||
return e2e().visit(parsedUrl);
|
||||
};
|
||||
const pageObjects: PageObjects<S> = {} as PageObjects<S>;
|
||||
const pageObjects: SelectorFunctions<S> = {} as SelectorFunctions<S>;
|
||||
const keys = Object.keys(selectors);
|
||||
|
||||
keys.forEach(key => {
|
||||
@@ -62,3 +66,11 @@ export const pageFactory = <S extends Selectors>({ url, selectors }: PageFactory
|
||||
selectors,
|
||||
};
|
||||
};
|
||||
|
||||
type Component<S> = Omit<Page<S>, 'visit'>;
|
||||
type ComponentFactoryArgs<S> = Omit<PageFactoryArgs<S>, 'url'>;
|
||||
|
||||
export const componentFactory = <S extends Selectors>(args: ComponentFactoryArgs<S>): Component<S> => {
|
||||
const { visit, ...rest } = pageFactory(args);
|
||||
return rest;
|
||||
};
|
||||
|
||||
@@ -5,6 +5,7 @@ import { css } from 'emotion';
|
||||
import CustomScrollbar from '../CustomScrollbar/CustomScrollbar';
|
||||
import { IconButton } from '../IconButton/IconButton';
|
||||
import { stylesFactory, useTheme } from '../../themes';
|
||||
import { e2e } from '@grafana/e2e';
|
||||
|
||||
export interface Props {
|
||||
children: ReactNode;
|
||||
@@ -93,17 +94,40 @@ export const Drawer: FC<Props> = ({
|
||||
getContainer={inline ? false : 'body'}
|
||||
style={{ position: `${inline && 'absolute'}` } as CSSProperties}
|
||||
className={drawerStyles.drawer}
|
||||
aria-label={
|
||||
typeof title === 'string'
|
||||
? e2e.components.Drawer.General.selectors.title(title)
|
||||
: e2e.components.Drawer.General.selectors.title('no title')
|
||||
}
|
||||
>
|
||||
{typeof title === 'string' && (
|
||||
<div className={drawerStyles.header}>
|
||||
<div className={drawerStyles.actions}>
|
||||
{expandable && !isExpanded && (
|
||||
<IconButton name="angle-left" size="xl" onClick={() => setIsExpanded(true)} surface="header" />
|
||||
<IconButton
|
||||
name="angle-left"
|
||||
size="xl"
|
||||
onClick={() => setIsExpanded(true)}
|
||||
surface="header"
|
||||
aria-label={e2e.components.Drawer.General.selectors.expand}
|
||||
/>
|
||||
)}
|
||||
{expandable && isExpanded && (
|
||||
<IconButton name="angle-right" size="xl" onClick={() => setIsExpanded(false)} surface="header" />
|
||||
<IconButton
|
||||
name="angle-right"
|
||||
size="xl"
|
||||
onClick={() => setIsExpanded(false)}
|
||||
surface="header"
|
||||
aria-label={e2e.components.Drawer.General.selectors.contract}
|
||||
/>
|
||||
)}
|
||||
<IconButton name="times" size="xl" onClick={onClose} surface="header" />
|
||||
<IconButton
|
||||
name="times"
|
||||
size="xl"
|
||||
onClick={onClose}
|
||||
surface="header"
|
||||
aria-label={e2e.components.Drawer.General.selectors.close}
|
||||
/>
|
||||
</div>
|
||||
<div className={drawerStyles.titleWrapper}>
|
||||
<h3>{title}</h3>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Icon } from '../Icon/Icon';
|
||||
import { IconName } from '../../types';
|
||||
import { stylesFactory, useTheme } from '../../themes';
|
||||
import { Counter } from './Counter';
|
||||
import { e2e } from '@grafana/e2e';
|
||||
|
||||
export interface TabProps {
|
||||
label: string;
|
||||
@@ -40,6 +41,7 @@ const getTabStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||
}
|
||||
`,
|
||||
activeStyle: css`
|
||||
label: activeTabStyle;
|
||||
border-color: ${theme.palette.orange} ${colors.pageHeaderBorder} transparent;
|
||||
background: ${colors.bodyBg};
|
||||
color: ${colors.link};
|
||||
@@ -64,7 +66,11 @@ export const Tab: FC<TabProps> = ({ label, active, icon, onChangeTab, counter })
|
||||
const tabsStyles = getTabStyles(theme);
|
||||
|
||||
return (
|
||||
<li className={cx(tabsStyles.tabItem, active && tabsStyles.activeStyle)} onClick={onChangeTab}>
|
||||
<li
|
||||
className={cx(tabsStyles.tabItem, active && tabsStyles.activeStyle)}
|
||||
onClick={onChangeTab}
|
||||
aria-label={e2e.components.Tab.selectors.title(label)}
|
||||
>
|
||||
{icon && <Icon name={icon} />}
|
||||
{label}
|
||||
{typeof counter === 'number' && <Counter value={counter} />}
|
||||
|
||||
@@ -13,6 +13,7 @@ import { config } from 'app/core/config';
|
||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
import { saveAs } from 'file-saver';
|
||||
import { cx } from 'emotion';
|
||||
import { e2e } from '@grafana/e2e';
|
||||
|
||||
interface Props {
|
||||
data: DataFrame[];
|
||||
@@ -108,7 +109,7 @@ export class InspectDataTab extends PureComponent<Props, State> {
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={styles.dataTabContent}>
|
||||
<div className={styles.dataTabContent} aria-label={e2e.components.PanelInspector.Data.selectors.content}>
|
||||
<div className={styles.toolbar}>
|
||||
<Field label="Transformer" className="flex-grow-1">
|
||||
<Select options={transformationOptions} value={transformId} onChange={this.onTransformationChange} />
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { chain } from 'lodash';
|
||||
import { PanelData, SelectableValue, AppEvents } from '@grafana/data';
|
||||
import { TextArea, Button, Select, ClipboardButton, JSONFormatter, Field } from '@grafana/ui';
|
||||
import { AppEvents, PanelData, SelectableValue } from '@grafana/data';
|
||||
import { Button, ClipboardButton, Field, JSONFormatter, Select, TextArea } from '@grafana/ui';
|
||||
import { appEvents } from 'app/core/core';
|
||||
import { PanelModel, DashboardModel } from '../../state';
|
||||
import { DashboardModel, PanelModel } from '../../state';
|
||||
import { getPanelInspectorStyles } from './styles';
|
||||
import { e2e } from '@grafana/e2e';
|
||||
|
||||
enum ShowContent {
|
||||
PanelJSON = 'panel',
|
||||
@@ -141,7 +142,7 @@ export class InspectJSONTab extends PureComponent<Props, State> {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={styles.toolbar}>
|
||||
<div className={styles.toolbar} aria-label={e2e.components.PanelInspector.Json.selectors.content}>
|
||||
<Field label="Select source" className="flex-grow-1">
|
||||
<Select options={options} value={selected} onChange={this.onSelectChanged} />
|
||||
</Field>
|
||||
|
||||
@@ -6,25 +6,26 @@ import { InspectJSONTab } from './InspectJSONTab';
|
||||
import { QueryInspector } from './QueryInspector';
|
||||
|
||||
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
|
||||
import { JSONFormatter, Drawer, TabContent, CustomScrollbar } from '@grafana/ui';
|
||||
import { getLocationSrv, getDataSourceSrv } from '@grafana/runtime';
|
||||
import { CustomScrollbar, Drawer, JSONFormatter, TabContent } from '@grafana/ui';
|
||||
import { getDataSourceSrv, getLocationSrv } from '@grafana/runtime';
|
||||
import {
|
||||
DataFrame,
|
||||
DataSourceApi,
|
||||
SelectableValue,
|
||||
getDisplayProcessor,
|
||||
DataQueryError,
|
||||
PanelData,
|
||||
DataSourceApi,
|
||||
FieldType,
|
||||
formattedValueToString,
|
||||
QueryResultMetaStat,
|
||||
getDisplayProcessor,
|
||||
LoadingState,
|
||||
PanelData,
|
||||
PanelPlugin,
|
||||
QueryResultMetaStat,
|
||||
SelectableValue,
|
||||
} from '@grafana/data';
|
||||
import { config } from 'app/core/config';
|
||||
import { getPanelInspectorStyles } from './styles';
|
||||
import { StoreState } from 'app/types';
|
||||
import { InspectDataTab } from './InspectDataTab';
|
||||
import { e2e } from '@grafana/e2e';
|
||||
|
||||
interface OwnProps {
|
||||
dashboard: DashboardModel;
|
||||
@@ -222,10 +223,10 @@ export class PanelInspectorUnconnected extends PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div aria-label={e2e.components.PanelInspector.Stats.selectors.content}>
|
||||
{this.renderStatsTable('Stats', stats)}
|
||||
{this.renderStatsTable('Data source stats', dataStats)}
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import appEvents from 'app/core/app_events';
|
||||
import { CopyToClipboard } from 'app/core/components/CopyToClipboard/CopyToClipboard';
|
||||
import { JSONFormatter, LoadingPlaceholder, Button } from '@grafana/ui';
|
||||
import { Button, JSONFormatter, LoadingPlaceholder } from '@grafana/ui';
|
||||
import { CoreEvents } from 'app/types';
|
||||
import { AppEvents, PanelEvents } from '@grafana/data';
|
||||
import { PanelModel } from 'app/features/dashboard/state';
|
||||
import { getPanelInspectorStyles } from './styles';
|
||||
import { e2e } from '@grafana/e2e';
|
||||
|
||||
interface DsQuery {
|
||||
isLoading: boolean;
|
||||
@@ -188,7 +189,7 @@ export class QueryInspector extends PureComponent<Props, State> {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<div aria-label={e2e.components.PanelInspector.Query.selectors.content}>
|
||||
<h3 className="section-heading">Query inspector</h3>
|
||||
<p className="small muted">
|
||||
Query inspector allows you to view raw request and response. To collect this data Grafana needs to issue a
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// Libraries
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
// Components
|
||||
import { CustomScrollbar, PanelOptionsGroup, Icon, IconName } from '@grafana/ui';
|
||||
import { CustomScrollbar, Icon, IconName, PanelOptionsGroup } from '@grafana/ui';
|
||||
import { FadeIn } from 'app/core/components/Animations/FadeIn';
|
||||
import { e2e } from '@grafana/e2e';
|
||||
|
||||
interface Props {
|
||||
children: JSX.Element;
|
||||
@@ -89,7 +89,12 @@ export class EditorTabBody extends PureComponent<Props, State> {
|
||||
|
||||
return (
|
||||
<div className="nav-buttons" key={view.title + view.icon}>
|
||||
<button className="btn navbar-button" onClick={onClick} disabled={view.disabled}>
|
||||
<button
|
||||
className="btn navbar-button"
|
||||
onClick={onClick}
|
||||
disabled={view.disabled}
|
||||
aria-label={e2e.components.QueryEditorToolbarItem.selectors.button(view.title)}
|
||||
>
|
||||
{view.icon && <Icon name={view.icon as IconName} />} {view.title}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -63,6 +63,7 @@ import { ExploreGraphPanel } from './ExploreGraphPanel';
|
||||
import { TraceView } from './TraceView/TraceView';
|
||||
import { SecondaryActions } from './SecondaryActions';
|
||||
import { compose } from 'redux';
|
||||
import { e2e } from '@grafana/e2e';
|
||||
|
||||
const getStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||
return {
|
||||
@@ -314,7 +315,7 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
||||
const queryError = getFirstNonQueryRowSpecificError(queryErrors);
|
||||
|
||||
return (
|
||||
<div className={exploreClass} ref={this.getRef}>
|
||||
<div className={exploreClass} ref={this.getRef} aria-label={e2e.pages.Explore.General.selectors.container}>
|
||||
<ExploreToolbar exploreId={exploreId} onChangeTime={this.onChangeTime} />
|
||||
{datasourceMissing ? this.renderEmptyState() : null}
|
||||
{datasourceInstance && (
|
||||
|
||||
@@ -3,6 +3,7 @@ import { RefreshPicker } from '@grafana/ui';
|
||||
import memoizeOne from 'memoize-one';
|
||||
import { css } from 'emotion';
|
||||
import classNames from 'classnames';
|
||||
import { e2e } from '@grafana/e2e';
|
||||
|
||||
import { ResponsiveButton } from './ResponsiveButton';
|
||||
|
||||
@@ -42,6 +43,7 @@ export function RunButton(props: Props) {
|
||||
})}
|
||||
icon={loading ? 'fa fa-spinner' : 'sync'}
|
||||
iconClassName={loading && ' fa-spin run-icon'}
|
||||
aria-label={e2e.pages.Explore.General.selectors.runButton}
|
||||
/>
|
||||
);
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
exports[`Explore should render component 1`] = `
|
||||
<div
|
||||
aria-label="Explore"
|
||||
className="explore"
|
||||
>
|
||||
<Connect(UnConnectedExploreToolbar)
|
||||
|
||||
@@ -57,7 +57,9 @@
|
||||
ng-model="ctrl.target.seriesCount"
|
||||
min="1"
|
||||
step="1"
|
||||
ng-change="ctrl.refresh()" />
|
||||
ng-change="ctrl.refresh()"
|
||||
aria-label="{{::ctrl.selectors.seriesCount}}"
|
||||
/>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label query-keyword width-7">Start value</label>
|
||||
@@ -66,7 +68,9 @@
|
||||
placeholder="auto"
|
||||
ng-model="ctrl.target.startValue"
|
||||
step="1"
|
||||
ng-change="ctrl.refresh()" />
|
||||
ng-change="ctrl.refresh()"
|
||||
aria-label="{{::ctrl.selectors.startValue}}"
|
||||
/>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label query-keyword width-7">Spread</label>
|
||||
@@ -76,7 +80,9 @@
|
||||
ng-model="ctrl.target.spread"
|
||||
min="0.5"
|
||||
step="0.1"
|
||||
ng-change="ctrl.refresh()" />
|
||||
ng-change="ctrl.refresh()"
|
||||
aria-label="{{::ctrl.selectors.spread}}"
|
||||
/>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label query-keyword width-7">Noise</label>
|
||||
@@ -86,7 +92,9 @@
|
||||
ng-model="ctrl.target.noise"
|
||||
min="0"
|
||||
step="0.1"
|
||||
ng-change="ctrl.refresh()" />
|
||||
ng-change="ctrl.refresh()"
|
||||
aria-label="{{::ctrl.selectors.noise}}"
|
||||
/>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label query-keyword width-7">Min</label>
|
||||
@@ -95,7 +103,9 @@
|
||||
placeholder="none"
|
||||
ng-model="ctrl.target.min"
|
||||
step="0.1"
|
||||
ng-change="ctrl.refresh()" />
|
||||
ng-change="ctrl.refresh()"
|
||||
aria-label="{{::ctrl.selectors.min}}"
|
||||
/>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label query-keyword width-7">Max</label>
|
||||
@@ -104,7 +114,9 @@
|
||||
placeholder="none"
|
||||
ng-model="ctrl.target.max"
|
||||
step="0.1"
|
||||
ng-change="ctrl.refresh()" />
|
||||
ng-change="ctrl.refresh()"
|
||||
aria-label="{{::ctrl.selectors.max}}"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gf-form-inline" ng-if="ctrl.scenario.id === 'streaming_client'">
|
||||
@@ -127,7 +139,8 @@
|
||||
ng-model="ctrl.target.stream.speed"
|
||||
min="10"
|
||||
step="10"
|
||||
ng-change="ctrl.streamChanged()" />
|
||||
ng-change="ctrl.streamChanged()"
|
||||
/>
|
||||
</div>
|
||||
<div class="gf-form" ng-if="ctrl.target.stream.type === 'signal'">
|
||||
<label class="gf-form-label query-keyword">Spread</label>
|
||||
@@ -188,7 +201,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="gf-form-inline" ng-if="ctrl.scenario.id === 'grafana_api'">
|
||||
<div class="gf-form gf-form">
|
||||
<label class="gf-form-label query-keyword width-7">Endpoint</label>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import _ from 'lodash';
|
||||
import { IScope } from 'angular';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { dateMath, dateTime } from '@grafana/data';
|
||||
import { e2e } from '@grafana/e2e';
|
||||
|
||||
import { QueryCtrl } from 'app/plugins/sdk';
|
||||
import { defaultQuery } from './runStreams';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { promiseToDigest } from 'app/core/utils/promiseToDigest';
|
||||
import { IScope } from 'angular';
|
||||
|
||||
export const defaultPulse: any = {
|
||||
timeStep: 60,
|
||||
|
||||
Reference in New Issue
Block a user