Commit e4d492fd by Hugo Häggmark Committed by GitHub

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>
parent db3f2b90
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);
},
});
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();
};
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;
// });
'use strict'
'use strict';
if (process.env.NODE_ENV === 'production') {
module.exports = require('./index.production.js');
......
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,
};
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),
......
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',
},
}),
};
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',
},
});
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());
}
});
......
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} />
......
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>
);
}
......
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
......
// 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>
......
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,
......
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