e2e: adds e2e for panel edit (#23849)

* 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

* e2e: adds e2e for panel edit

Co-authored-by: Andreas Opferkuch <andreas.opferkuch@gmail.com>
This commit is contained in:
Hugo Häggmark 2020-04-24 12:51:38 +02:00 committed by GitHub
parent cf77c3a50b
commit 80ab18f43d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 254 additions and 38 deletions

View File

@ -0,0 +1,143 @@
import { e2e } from '@grafana/e2e';
const PANEL_UNDER_TEST = 'Random walk series';
e2e.scenario({
describeName: 'Panel edit tests',
itName: 'Testes various Panel edit scenarios',
addScenarioDataSource: false,
addScenarioDashBoard: false,
skipScenario: false,
scenario: () => {
e2e.flows.openDashboard('5SdHCadmz');
e2e.flows.openPanelMenuItem(e2e.flows.PanelMenuItems.Edit, PANEL_UNDER_TEST);
// New panel editor opens when navigating from Panel menu
e2e.components.PanelEditor.General.content().should('be.visible');
// Queries tab is rendered and open by default
e2e.components.PanelEditor.DataPane.content()
.should('be.visible')
.within(() => {
e2e.components.Tab.title('Query').should('be.visible');
// data should be the active tab
e2e.components.Tab.active().within((li: JQuery<HTMLLIElement>) => {
expect(li.text()).equals('Query1'); // there's already a query so therefore Query + 1
});
e2e.components.QueryTab.content().should('be.visible');
e2e.components.TransformTab.content().should('not.be.visible');
e2e.components.AlertTab.content().should('not.be.visible');
// Bottom pane tabs
// Can change to Transform tab
e2e.components.Tab.title('Transform')
.should('be.visible')
.click();
e2e.components.Tab.active().within((li: JQuery<HTMLLIElement>) => {
expect(li.text()).equals('Transform0'); // there's no transform so therefore Transform + 0
});
e2e.components.TransformTab.content().should('be.visible');
e2e.components.QueryTab.content().should('not.be.visible');
e2e.components.AlertTab.content().should('not.be.visible');
// Can change to Alerts tab (graph panel is the default vis so the alerts tab should be rendered)
e2e.components.Tab.title('Alert')
.should('be.visible')
.click();
e2e.components.Tab.active().within((li: JQuery<HTMLLIElement>) => {
expect(li.text()).equals('Alert0'); // there's no alert so therefore Alert + 0
});
e2e.components.AlertTab.content().should('be.visible');
e2e.components.QueryTab.content().should('not.be.visible');
e2e.components.TransformTab.content().should('not.be.visible');
e2e.components.Tab.title('Query')
.should('be.visible')
.click();
});
// Panel sidebar is rendered open by default
e2e.components.PanelEditor.OptionsPane.content().should('be.visible');
// Can toggle on/off sidebar
e2e.components.PanelEditor.OptionsPane.close().should('be.visible');
e2e.components.PanelEditor.OptionsPane.open().should('not.be.visible');
// close options pane
e2e.components.PanelEditor.OptionsPane.close().click();
e2e.components.PanelEditor.OptionsPane.open().should('be.visible');
e2e.components.PanelEditor.OptionsPane.close().should('not.be.visible');
e2e.components.PanelEditor.OptionsPane.content().should('not.be.visible');
// open options pane
e2e.components.PanelEditor.OptionsPane.open().click();
e2e.components.PanelEditor.OptionsPane.close().should('be.visible');
e2e.components.PanelEditor.OptionsPane.open().should('not.be.visible');
e2e.components.PanelEditor.OptionsPane.content().should('be.visible');
// Can change visualisation type
e2e.components.OptionsGroup.toggle('Panel type')
.should('be.visible')
.click();
// Check that Graph is chosen
e2e.components.PluginVisualization.item('Graph').should('be.visible');
e2e.components.PluginVisualization.current().within((div: JQuery<HTMLDivElement>) => {
expect(div.text()).equals('Graph');
});
// Change to Text panel
e2e.components.PluginVisualization.item('Text')
.scrollIntoView()
.should('be.visible')
.click();
e2e.components.PluginVisualization.current().within((div: JQuery<HTMLDivElement>) => {
expect(div.text()).equals('Text');
});
// Data pane should not be rendered
e2e.components.PanelEditor.DataPane.content().should('not.be.visible');
// Change to Table panel
e2e.components.PluginVisualization.item('Table')
.scrollIntoView()
.should('be.visible')
.click();
e2e.components.PluginVisualization.current().within((div: JQuery<HTMLDivElement>) => {
expect(div.text()).equals('Table');
});
// Data pane should be rendered
e2e.components.PanelEditor.DataPane.content().should('be.visible');
// Field & Overrides tabs (need to switch to React based vis, i.e. Table)
e2e.components.PanelEditor.OptionsPane.select()
.should('be.visible')
.click()
.within(() => {
// Can change to Fields tab
e2e.components.Select.option()
.should('be.visible')
.eq(1)
.click();
});
e2e.components.FieldConfigEditor.content().should('be.visible');
e2e.components.OverridesConfigEditor.content().should('not.be.visible');
e2e.components.PanelEditor.OptionsPane.select()
.should('be.visible')
.click()
.within(() => {
// Can change to Overrides tab
e2e.components.Select.option()
.should('be.visible')
.eq(2)
.click();
});
e2e.components.OverridesConfigEditor.content().should('be.visible');
e2e.components.FieldConfigEditor.content().should('not.be.visible');
},
});

View File

@ -1,8 +1,8 @@
import { TestData } from '../pages/testdata';
import { Panel } from '../pages/panel';
import { EditPanel } from '../pages/editPanel';
import { Graph } from '../pages/graph';
import { componentFactory } from '../support';
import { Dashboard } from '../pages/dashboard';
export const Components = {
DataSource: {
@ -10,7 +10,6 @@ export const Components = {
},
Panels: {
Panel,
EditPanel,
Visualization: {
Graph,
},
@ -26,6 +25,27 @@ export const Components = {
},
}),
},
PanelEditor: {
General: componentFactory({
selectors: {
content: 'Panel editor content',
},
}),
OptionsPane: componentFactory({
selectors: {
content: 'Panel editor option pane content',
close: Dashboard.selectors.toolbarItems('Close options pane'),
open: Dashboard.selectors.toolbarItems('Open options pane'),
select: 'Panel editor option pane select',
},
}),
// not sure about the naming *DataPane*
DataPane: componentFactory({
selectors: {
content: 'Panel editor data pane content',
},
}),
},
PanelInspector: {
Data: componentFactory({
selectors: {
@ -54,6 +74,21 @@ export const Components = {
active: () => '[class*="-activeTabStyle"]',
},
}),
QueryTab: componentFactory({
selectors: {
content: 'Query editor tab content',
},
}),
AlertTab: componentFactory({
selectors: {
content: 'Alert editor tab content',
},
}),
TransformTab: componentFactory({
selectors: {
content: 'Transform editor tab content',
},
}),
QueryEditorToolbarItem: componentFactory({
selectors: {
button: (title: string) => `QueryEditor toolbar item button ${title}`,
@ -64,4 +99,30 @@ export const Components = {
backArrow: 'Go Back button',
},
}),
OptionsGroup: componentFactory({
selectors: {
toggle: (title: string) => `Options group ${title}`,
},
}),
PluginVisualization: componentFactory({
selectors: {
item: (title: string) => `Plugin visualization item ${title}`,
current: () => '[class*="-currentVisualizationItem"]',
},
}),
Select: componentFactory({
selectors: {
option: 'Select option',
},
}),
FieldConfigEditor: componentFactory({
selectors: {
content: 'Field config editor content',
},
}),
OverridesConfigEditor: componentFactory({
selectors: {
content: 'Field overrides editor content',
},
}),
};

View File

@ -1,8 +0,0 @@
import { pageFactory } from '../support';
export const EditPanel = pageFactory({
url: '',
selectors: {
tabItems: (text: string) => `Edit panel tab item ${text}`,
},
});

View File

@ -1,5 +1,5 @@
import React, { PureComponent } from 'react';
import { connect, MapStateToProps, MapDispatchToProps } from 'react-redux';
import { connect, MapDispatchToProps, MapStateToProps } from 'react-redux';
import { css } from 'emotion';
import { Alert, Button, IconName } from '@grafana/ui';
@ -15,10 +15,10 @@ import 'app/features/alerting/AlertTabCtrl';
import { DashboardModel } from '../dashboard/state/DashboardModel';
import { PanelModel } from '../dashboard/state/PanelModel';
import { TestRuleResult } from './TestRuleResult';
import { AppNotificationSeverity, StoreState } from 'app/types';
import { CoreEvents } from 'app/types';
import { AppNotificationSeverity, CoreEvents, StoreState } from 'app/types';
import { updateLocation } from 'app/core/actions';
import { PanelEditorTabId } from '../dashboard/components/PanelEditor/types';
import { e2e } from '@grafana/e2e';
interface OwnProps {
dashboard: DashboardModel;
@ -206,7 +206,7 @@ class UnConnectedAlertTab extends PureComponent<Props, State> {
return (
<EditorTabBody heading="Alert" toolbarItems={toolbarItems}>
<>
<div aria-label={e2e.components.AlertTab.selectors.content}>
{alert && hasTransformations && (
<Alert
severity={AppNotificationSeverity.Error}
@ -216,7 +216,7 @@ class UnConnectedAlertTab extends PureComponent<Props, State> {
<div ref={element => (this.element = element)} />
{!alert && !validatonMessage && <EmptyListCTA {...model} />}
</>
</div>
</EditorTabBody>
);
}

View File

@ -13,6 +13,7 @@ import { getDataLinksVariableSuggestions } from '../../../panel/panellinks/link_
import { OverrideEditor } from './OverrideEditor';
import groupBy from 'lodash/groupBy';
import { OptionsGroup } from './OptionsGroup';
import { e2e } from '@grafana/e2e';
interface Props {
plugin: PanelPlugin;
@ -102,7 +103,7 @@ export const OverrideFieldConfigEditor: React.FC<Props> = props => {
};
return (
<div>
<div aria-label={e2e.components.OverridesConfigEditor.selectors.content}>
{renderOverrides()}
{renderAddOverride()}
</div>
@ -182,7 +183,7 @@ export const DefaultFieldConfigEditor: React.FC<Props> = ({ data, onChange, conf
const groupedConfigs = groupBy(plugin.fieldConfigRegistry.list(), i => i.category && i.category[0]);
return (
<>
<div aria-label={e2e.components.FieldConfigEditor.selectors.content}>
{Object.keys(groupedConfigs).map((k, i) => {
const groupItemsCounter = countGroupItems(groupedConfigs[k], config);
@ -204,7 +205,7 @@ export const DefaultFieldConfigEditor: React.FC<Props> = ({ data, onChange, conf
</OptionsGroup>
);
})}
</>
</div>
);
};

View File

@ -4,6 +4,7 @@ import { GrafanaTheme } from '@grafana/data';
import { Icon, stylesFactory, useTheme } from '@grafana/ui';
import { PANEL_EDITOR_UI_STATE_STORAGE_KEY } from './state/reducers';
import { useLocalStorage } from 'react-use';
import { e2e } from '@grafana/e2e';
export interface OptionsGroupProps {
id: string;
@ -46,6 +47,7 @@ export const OptionsGroup: FC<OptionsGroupProps> = ({
return (
<CollapsibleSection
id={id}
defaultToClosed={defaultToClosed}
className={className}
nested={nested}
@ -75,7 +77,8 @@ const CollapsibleSectionWithPersistence: FC<OptionsGroupProps> = memo(props => {
return <CollapsibleSection {...props} defaultToClosed={value.defaultToClosed} onToggle={onToggle} />;
});
const CollapsibleSection: FC<Omit<OptionsGroupProps, 'id' | 'persistMe'>> = ({
const CollapsibleSection: FC<Omit<OptionsGroupProps, 'persistMe'>> = ({
id,
title,
children,
defaultToClosed,
@ -95,7 +98,11 @@ const CollapsibleSection: FC<Omit<OptionsGroupProps, 'id' | 'persistMe'>> = ({
return (
<div className={cx(styles.box, className, 'options-group')}>
<div className={styles.header} onClick={() => toggleExpand(!isExpanded)}>
<div
className={styles.header}
onClick={() => toggleExpand(!isExpanded)}
aria-label={e2e.components.OptionsGroup.selectors.toggle(id)}
>
<div className={cx(styles.toggle, 'editor-options-group-toggle')}>
<Icon name={isExpanded ? 'angle-down' : 'angle-right'} />
</div>

View File

@ -1,13 +1,14 @@
import React, { useCallback, useState, CSSProperties } from 'react';
import React, { CSSProperties, useCallback, useState } from 'react';
import Transition from 'react-transition-group/Transition';
import { FieldConfigSource, GrafanaTheme, PanelPlugin, SelectableValue } from '@grafana/data';
import { DashboardModel, PanelModel } from '../../state';
import { CustomScrollbar, stylesFactory, Tab, TabContent, TabsBar, Select, useTheme, Icon, Input } from '@grafana/ui';
import { CustomScrollbar, Icon, Input, Select, stylesFactory, Tab, TabContent, TabsBar, useTheme } from '@grafana/ui';
import { DefaultFieldConfigEditor, OverrideFieldConfigEditor } from './FieldConfigEditor';
import { css } from 'emotion';
import { PanelOptionsTab } from './PanelOptionsTab';
import { DashNavButton } from 'app/features/dashboard/components/DashNav/DashNavButton';
import { usePanelLatestData } from './usePanelLatestData';
import { e2e } from '@grafana/e2e';
interface Props {
plugin: PanelPlugin;
@ -80,7 +81,7 @@ export const OptionsPaneContent: React.FC<Props> = ({
const showMainTab = activeTab === 'options' || plugin.meta.skipDataQuery;
return (
<div className={styles.panelOptionsPane}>
<div className={styles.panelOptionsPane} aria-label={e2e.components.PanelEditor.OptionsPane.selectors.content}>
{plugin && (
<div className={styles.wrapper}>
<TabsBar className={styles.tabsBar}>
@ -184,7 +185,7 @@ export const TabsBarContent: React.FC<{
return (
<>
{width < 352 ? (
<div className="flex-grow-1">
<div className="flex-grow-1" aria-label={e2e.components.PanelEditor.OptionsPane.selectors.select}>
<Select
options={tabs}
value={active}

View File

@ -1,6 +1,6 @@
import React, { PureComponent } from 'react';
import { FieldConfigSource, GrafanaTheme, PanelData, PanelPlugin } from '@grafana/data';
import { Button, stylesFactory, Icon, RadioButtonGroup } from '@grafana/ui';
import { Button, Icon, RadioButtonGroup, stylesFactory } from '@grafana/ui';
import { css, cx } from 'emotion';
import config from 'app/core/config';
import AutoSizer from 'react-virtualized-auto-sizer';
@ -17,7 +17,7 @@ import { Unsubscribable } from 'rxjs';
import { DisplayMode, displayModes, PanelEditorTab } from './types';
import { PanelEditorTabs } from './PanelEditorTabs';
import { DashNavTimeControls } from '../DashNav/DashNavTimeControls';
import { LocationState, CoreEvents } from 'app/types';
import { CoreEvents, LocationState } from 'app/types';
import { calculatePanelSize } from './utils';
import { initPanelEditor, panelEditorCleanUp, updatePanelEditorUIState } from './state/actions';
import { PanelEditorUIState, setDiscardChanges } from './state/reducers';
@ -31,6 +31,7 @@ import { SubMenuItems } from 'app/features/dashboard/components/SubMenu/SubMenuI
import { BackButton } from 'app/core/components/BackButton/BackButton';
import { appEvents } from 'app/core/core';
import { SaveDashboardModalProxy } from '../SaveDashboard/SaveDashboardModalProxy';
import { e2e } from '@grafana/e2e';
interface OwnProps {
dashboard: DashboardModel;
@ -196,7 +197,7 @@ export class PanelEditorUnconnected extends PureComponent<Props> {
onDragFinished={size => this.onDragFinished(Pane.Top, size)}
>
{this.renderPanel(styles)}
<div className={styles.tabsWrapper}>
<div className={styles.tabsWrapper} aria-label={e2e.components.PanelEditor.DataPane.selectors.content}>
<PanelEditorTabs panel={panel} dashboard={dashboard} tabs={tabs} onChangeTab={this.onChangeTab} data={data} />
</div>
</SplitPane>
@ -331,7 +332,7 @@ export class PanelEditorUnconnected extends PureComponent<Props> {
}
return (
<div className={styles.wrapper}>
<div className={styles.wrapper} aria-label={e2e.components.PanelEditor.General.selectors.content}>
{this.editorToolbar(styles)}
<div className={styles.verticalSplitPanesWrapper}>
{uiState.isPanelOptionsVisible ? this.renderWithOptionsPane(styles) : this.renderHorizontalSplit(styles)}

View File

@ -1,5 +1,5 @@
import React from 'react';
import { Container, CustomScrollbar, ValuePicker, Button, useTheme, VerticalGroup, stylesFactory } from '@grafana/ui';
import { Button, Container, CustomScrollbar, stylesFactory, useTheme, ValuePicker, VerticalGroup } from '@grafana/ui';
import {
DataFrame,
DataTransformerConfig,
@ -11,6 +11,7 @@ import {
import { TransformationOperationRow } from './TransformationOperationRow';
import { Card, CardProps } from '../../../../core/components/Card/Card';
import { css } from 'emotion';
import { e2e } from '@grafana/e2e';
interface Props {
onChange: (transformations: DataTransformerConfig[]) => void;
@ -143,9 +144,11 @@ export class TransformationsEditor extends React.PureComponent<Props> {
return (
<CustomScrollbar autoHeightMin="100%">
<Container padding="md">
{!hasTransformationsConfigured && this.renderNoAddedTransformsState()}
{hasTransformationsConfigured && this.renderTransformationEditors()}
{hasTransformationsConfigured && this.renderTransformationSelector()}
<div aria-label={e2e.components.TransformTab.selectors.content}>
{!hasTransformationsConfigured && this.renderNoAddedTransformsState()}
{hasTransformationsConfigured && this.renderTransformationEditors()}
{hasTransformationsConfigured && this.renderTransformationSelector()}
</div>
</Container>
</CustomScrollbar>
);

View File

@ -1,6 +1,5 @@
// Libraries
import React, { PureComponent } from 'react';
import _ from 'lodash';
// Components
import { EditorTabBody, EditorToolbarView } from './EditorTabBody';
import { DataSourcePicker } from 'app/core/components/Select/DataSourcePicker';
@ -15,12 +14,13 @@ import config from 'app/core/config';
// Types
import { PanelModel } from '../state/PanelModel';
import { DashboardModel } from '../state/DashboardModel';
import { LoadingState, DefaultTimeRange, DataSourceSelectItem, DataQuery, PanelData } from '@grafana/data';
import { DataQuery, DataSourceSelectItem, DefaultTimeRange, LoadingState, PanelData } from '@grafana/data';
import { PluginHelp } from 'app/core/components/PluginHelp/PluginHelp';
import { addQuery } from 'app/core/utils/query';
import { Unsubscribable } from 'rxjs';
import { isSharedDashboardQuery, DashboardQueryEditor } from 'app/plugins/datasource/dashboard';
import { DashboardQueryEditor, isSharedDashboardQuery } from 'app/plugins/datasource/dashboard';
import { expressionDatasource, ExpressionDatasourceID } from 'app/features/expressions/ExpressionDatasource';
import { e2e } from '@grafana/e2e';
interface Props {
panel: PanelModel;
@ -227,7 +227,7 @@ export class QueriesTab extends PureComponent<Props, State> {
}
return (
<>
<div aria-label={e2e.components.QueryTab.selectors.content}>
<QueryEditorRows
queries={panel.targets}
datasource={currentDS}
@ -240,7 +240,7 @@ export class QueriesTab extends PureComponent<Props, State> {
<PanelOptionsGroup>
<QueryOptions panel={panel} datasource={currentDS} />
</PanelOptionsGroup>
</>
</div>
);
};

View File

@ -2,6 +2,7 @@ import React from 'react';
import { GrafanaTheme, PanelPluginMeta } from '@grafana/data';
import { stylesFactory, useTheme } from '@grafana/ui';
import { css, cx } from 'emotion';
import { e2e } from '@grafana/e2e';
interface Props {
isCurrent: boolean;
@ -20,7 +21,12 @@ const VizTypePickerPlugin: React.FC<Props> = ({ isCurrent, plugin, onClick, disa
});
return (
<div className={cssClass} onClick={disabled ? () => {} : onClick} title={plugin.name}>
<div
className={cssClass}
onClick={disabled ? () => {} : onClick}
title={plugin.name}
aria-label={e2e.components.PluginVisualization.selectors.item(plugin.name)}
>
<div className={styles.name}>{plugin.name}</div>
<img className={styles.img} src={plugin.info.logos.small} />
</div>
@ -52,6 +58,7 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => {
}
`,
current: css`
label: currentVisualizationItem;
box-shadow: 0 0 6px ${theme.palette.orange} !important;
border: 1px solid ${theme.palette.orange} !important;
`,