Dashboard edit panel e2e (#104775)

setup suite for edit panel;  start adding test for custom variable edit
This commit is contained in:
Scott Lepper
2025-05-01 15:14:36 -04:00
committed by GitHub
parent 1114d33936
commit d30b39f350
9 changed files with 148 additions and 7 deletions

View File

@ -0,0 +1,60 @@
import { e2e } from '../utils';
const PAGE_UNDER_TEST = 'kVi2Gex7z/test-variable-output';
const DASHBOARD_NAME = 'Test variable output';
describe('Dashboard edit variables', () => {
beforeEach(() => {
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'));
});
it('can add a new custom variable', () => {
e2e.pages.Dashboards.visit();
e2e.flows.openDashboard({ uid: `${PAGE_UNDER_TEST}?orgId=1` });
cy.contains(DASHBOARD_NAME).should('be.visible');
const variable: Variable = {
type: 'custom',
name: 'foo',
label: 'Foo',
value: 'one,two,three',
};
// common steps to add a new variable
flows.newEditPaneVariableClick();
flows.newEditPanelCommonVariableInputs(variable);
// set the custom variable value
e2e.pages.Dashboard.Settings.Variables.Edit.CustomVariable.customValueInput().clear().type(variable.value).blur();
// assert the dropdown for the variable is visible and has the correct values
e2e.pages.Dashboard.SubMenu.submenuItemLabels(variable.label).should('be.visible').contains(variable.label);
const values = variable.value.split(',');
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownValueLinkTexts(values[0]).should('be.visible');
});
});
// Common flows for adding/editing variables
// TODO: maybe move to e2e flows
const flows = {
newEditPaneVariableClick() {
e2e.components.NavToolbar.editDashboard.editButton().should('be.visible').click();
e2e.components.PanelEditor.Outline.section().should('be.visible').click();
e2e.components.PanelEditor.Outline.item('Variables').should('be.visible').click();
e2e.components.PanelEditor.ElementEditPane.addVariableButton().should('be.visible').click();
},
newEditPanelCommonVariableInputs(variable: Variable) {
e2e.components.PanelEditor.ElementEditPane.variableType(variable.type).should('be.visible').click();
e2e.components.PanelEditor.ElementEditPane.variableNameInput().clear().type(variable.name).blur();
e2e.components.PanelEditor.ElementEditPane.variableLabelInput().clear().type(variable.label).blur();
},
};
type Variable = {
type: string;
name: string;
label: string;
description?: string;
value: string;
};

View File

@ -30,6 +30,7 @@ rootForEnterpriseSuite="./e2e/extensions-suite"
rootForOldArch="./e2e/old-arch" rootForOldArch="./e2e/old-arch"
rootForKubernetesDashboards="./e2e/dashboards-suite" rootForKubernetesDashboards="./e2e/dashboards-suite"
rootForSearchDashboards="./e2e/dashboards-search-suite" rootForSearchDashboards="./e2e/dashboards-search-suite"
rootForEditDashboards="./e2e/dashboards-edit-v2-suite"
declare -A cypressConfig=( declare -A cypressConfig=(
[screenshotsFolder]=./e2e/"${args[0]}"/screenshots [screenshotsFolder]=./e2e/"${args[0]}"/screenshots
@ -149,6 +150,25 @@ case "$1" in
;; ;;
esac esac
;; ;;
"dashboards-edit-v2")
env[kubernetesDashboards]=true
env[dashboardNewLayouts]=true
cypressConfig[specPattern]=$rootForEditDashboards/$testFilesForSingleSuite
cypressConfig[video]=false
case "$2" in
"debug")
echo -e "Debug mode"
env[SLOWMO]=1
PARAMS="--no-exit"
enterpriseSuite=$(basename "${args[2]}")
;;
"dev")
echo "Dev mode"
CMD="cypress open"
enterpriseSuite=$(basename "${args[2]}")
;;
esac
;;
"enterprise-smtp") "enterprise-smtp")
env[SMTP_PLUGIN_ENABLED]=true env[SMTP_PLUGIN_ENABLED]=true
cypressConfig[specPattern]=./e2e/extensions/enterprise/smtp-suite/$testFilesForSingleSuite cypressConfig[specPattern]=./e2e/extensions/enterprise/smtp-suite/$testFilesForSingleSuite

View File

@ -14,6 +14,8 @@
"e2e:old-arch": "./e2e/start-and-run-suite old-arch", "e2e:old-arch": "./e2e/start-and-run-suite old-arch",
"e2e:schema-v2": "./e2e/start-and-run-suite dashboards-schema-v2", "e2e:schema-v2": "./e2e/start-and-run-suite dashboards-schema-v2",
"e2e:dashboards-search": "./e2e/start-and-run-suite dashboards-search", "e2e:dashboards-search": "./e2e/start-and-run-suite dashboards-search",
"e2e:dashboards-edit-v2": "./e2e/start-and-run-suite dashboards-edit-v2",
"e2e:dashboards-edit-v2:dev": "./e2e/start-and-run-suite dashboards-edit-v2 dev",
"e2e:debug": "./e2e/start-and-run-suite debug", "e2e:debug": "./e2e/start-and-run-suite debug",
"e2e:dev": "./e2e/start-and-run-suite dev", "e2e:dev": "./e2e/start-and-run-suite dev",
"e2e:benchmark:live": "./e2e/start-and-run-suite benchmark live", "e2e:benchmark:live": "./e2e/start-and-run-suite benchmark live",

View File

@ -532,6 +532,33 @@ export const versionedComponents = {
measureButton: { measureButton: {
'9.2.0': 'show measure tools', '9.2.0': 'show measure tools',
}, },
Outline: {
section: {
'12.0.0': 'data-testid Outline section',
},
node: {
'12.0.0': (type: string) => `data-testid outline node ${type}`,
},
item: {
'12.0.0': (type: string) => `data-testid outline item ${type}`,
},
},
ElementEditPane: {
variableType: {
'12.0.0': (type?: string) => `data-testid variable type ${type}`,
},
addVariableButton: {
'12.0.0': 'data-testid add variable button',
},
variableNameInput: {
'12.0.0': 'data-testid variable name input',
},
variableLabelInput: {
'12.0.0': 'data-testid variable label input',
},
},
}, },
PanelInspector: { PanelInspector: {
Data: { Data: {

View File

@ -3,6 +3,7 @@ import { Resizable } from 're-resizable';
import { useLocalStorage } from 'react-use'; import { useLocalStorage } from 'react-use';
import { GrafanaTheme2 } from '@grafana/data'; import { GrafanaTheme2 } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { SceneObjectState, SceneObjectBase, SceneObject, sceneGraph, useSceneObjectState } from '@grafana/scenes'; import { SceneObjectState, SceneObjectBase, SceneObject, sceneGraph, useSceneObjectState } from '@grafana/scenes';
import { import {
ElementSelectionContextItem, ElementSelectionContextItem,
@ -272,11 +273,12 @@ export function DashboardEditPaneRenderer({ editPane, isCollapsed, onToggleColla
role="button" role="button"
onClick={() => setOutlineCollapsed(!outlineCollapsed)} onClick={() => setOutlineCollapsed(!outlineCollapsed)}
className={styles.outlineCollapseButton} className={styles.outlineCollapseButton}
data-testid={selectors.components.PanelEditor.Outline.section}
> >
<Text weight="medium"> <Text weight="medium">
<Trans i18nKey="dashboard-scene.dashboard-edit-pane-renderer.outline">Outline</Trans> <Trans i18nKey="dashboard-scene.dashboard-edit-pane-renderer.outline">Outline</Trans>
</Text> </Text>
<Icon name="angle-up" /> <Icon name={outlineCollapsed ? 'angle-up' : 'angle-down'} />
</div> </div>
{!outlineCollapsed && ( {!outlineCollapsed && (
<div className={styles.outlineContainer}> <div className={styles.outlineContainer}>

View File

@ -3,6 +3,7 @@ import { sortBy } from 'lodash';
import React, { useEffect, useMemo, useState } from 'react'; import React, { useEffect, useMemo, useState } from 'react';
import { GrafanaTheme2 } from '@grafana/data'; import { GrafanaTheme2 } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { SceneObject } from '@grafana/scenes'; import { SceneObject } from '@grafana/scenes';
import { Box, Icon, Text, useElementSelection, useStyles2, useTheme2 } from '@grafana/ui'; import { Box, Icon, Text, useElementSelection, useStyles2, useTheme2 } from '@grafana/ui';
import { t, Trans } from 'app/core/internationalization'; import { t, Trans } from 'app/core/internationalization';
@ -88,7 +89,12 @@ function DashboardOutlineNode({
onPointerDown={onNodeClicked} onPointerDown={onNodeClicked}
> >
{elementInfo.isContainer && ( {elementInfo.isContainer && (
<button role="treeitem" className={styles.angleButton} onPointerDown={onToggleCollapse}> <button
role="treeitem"
className={styles.angleButton}
onPointerDown={onToggleCollapse}
data-testid={selectors.components.PanelEditor.Outline.node(instanceName)}
>
<Icon name={!isCollapsed ? 'angle-down' : 'angle-right'} /> <Icon name={!isCollapsed ? 'angle-down' : 'angle-right'} />
</button> </button>
)} )}
@ -96,6 +102,7 @@ function DashboardOutlineNode({
role="button" role="button"
className={cx(styles.nodeName, isCloned && styles.nodeNameClone)} className={cx(styles.nodeName, isCloned && styles.nodeNameClone)}
onDoubleClick={outlineRename.onNameDoubleClicked} onDoubleClick={outlineRename.onNameDoubleClicked}
data-testid={selectors.components.PanelEditor.Outline.item(instanceName)}
> >
<Icon size="sm" name={elementInfo.icon} /> <Icon size="sm" name={elementInfo.icon} />
{outlineRename.isRenaming ? ( {outlineRename.isRenaming ? (

View File

@ -11,7 +11,7 @@ interface Props {
checkedIcon?: IconName; checkedIcon?: IconName;
checkedLabel?: string; checkedLabel?: string;
disabled?: boolean; disabled?: boolean;
'data-testId'?: string; 'data-testid'?: string;
onClick: (evt: MouseEvent<HTMLDivElement>) => void; onClick: (evt: MouseEvent<HTMLDivElement>) => void;
} }
@ -23,7 +23,7 @@ export const ToolbarSwitch = ({
checkedLabel, checkedLabel,
disabled, disabled,
onClick, onClick,
'data-testId': dataTestId, 'data-testid': dataTestId,
}: Props) => { }: Props) => {
const styles = useStyles2(getStyles); const styles = useStyles2(getStyles);

View File

@ -1,6 +1,7 @@
import { FormEvent, useMemo, useState } from 'react'; import { FormEvent, useMemo, useState } from 'react';
import { VariableHide } from '@grafana/data'; import { VariableHide } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { locationService } from '@grafana/runtime'; import { locationService } from '@grafana/runtime';
import { LocalValueVariable, MultiValueVariable, SceneVariable, SceneVariableSet } from '@grafana/scenes'; import { LocalValueVariable, MultiValueVariable, SceneVariable, SceneVariableSet } from '@grafana/scenes';
import { Input, TextArea, Button, Field, Box, Stack } from '@grafana/ui'; import { Input, TextArea, Button, Field, Box, Stack } from '@grafana/ui';
@ -143,14 +144,27 @@ function VariableNameInput({ variable, isNewElement }: { variable: SceneVariable
return ( return (
<Field label={t('dashboard.edit-pane.variable.name', 'Name')} invalid={!!nameError} error={nameError}> <Field label={t('dashboard.edit-pane.variable.name', 'Name')} invalid={!!nameError} error={nameError}>
<Input ref={ref} value={name} onChange={onChange} required onBlur={onBlur} /> <Input
ref={ref}
value={name}
onChange={onChange}
required
onBlur={onBlur}
data-testid={selectors.components.PanelEditor.ElementEditPane.variableNameInput}
/>
</Field> </Field>
); );
} }
function VariableLabelInput({ variable }: VariableInputProps) { function VariableLabelInput({ variable }: VariableInputProps) {
const { label } = variable.useState(); const { label } = variable.useState();
return <Input value={label} onChange={(e) => variable.setState({ label: e.currentTarget.value })} />; return (
<Input
value={label}
onChange={(e) => variable.setState({ label: e.currentTarget.value })}
data-testid={selectors.components.PanelEditor.ElementEditPane.variableLabelInput}
/>
);
} }
function VariableDescriptionTextArea({ variable }: VariableInputProps) { function VariableDescriptionTextArea({ variable }: VariableInputProps) {

View File

@ -3,6 +3,7 @@ import { useMemo } from 'react';
import { useToggle } from 'react-use'; import { useToggle } from 'react-use';
import { GrafanaTheme2 } from '@grafana/data'; import { GrafanaTheme2 } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { SceneVariable, SceneVariableSet } from '@grafana/scenes'; import { SceneVariable, SceneVariableSet } from '@grafana/scenes';
import { Stack, Button, useStyles2, Text, Box, Card } from '@grafana/ui'; import { Stack, Button, useStyles2, Text, Box, Card } from '@grafana/ui';
import { t, Trans } from 'app/core/internationalization'; import { t, Trans } from 'app/core/internationalization';
@ -87,7 +88,14 @@ function VariableList({ set }: { set: SceneVariableSet }) {
))} ))}
{canAdd && ( {canAdd && (
<Box paddingBottom={1} display={'flex'}> <Box paddingBottom={1} display={'flex'}>
<Button fullWidth icon="plus" size="sm" variant="secondary" onClick={setIsAdding}> <Button
fullWidth
icon="plus"
size="sm"
variant="secondary"
onClick={setIsAdding}
data-testid={selectors.components.PanelEditor.ElementEditPane.addVariableButton}
>
<Trans i18nKey="dashboard.edit-pane.variables.add-variable">Add variable</Trans> <Trans i18nKey="dashboard.edit-pane.variables.add-variable">Add variable</Trans>
</Button> </Button>
</Box> </Box>
@ -115,6 +123,7 @@ function VariableTypeSelection({ onAddVariable }: VariableTypeSelectionProps) {
onClick={() => onAddVariable(option.value!)} onClick={() => onAddVariable(option.value!)}
key={option.value} key={option.value}
title={t('dashboard.edit-pane.variables.select-type-card-tooltip', 'Click to select type')} title={t('dashboard.edit-pane.variables.select-type-card-tooltip', 'Click to select type')}
data-testid={selectors.components.PanelEditor.ElementEditPane.variableType(option.value!)}
> >
<Card.Heading>{option.label}</Card.Heading> <Card.Heading>{option.label}</Card.Heading>
<Card.Description className={styles.cardDescription}>{option.description}</Card.Description> <Card.Description className={styles.cardDescription}>{option.description}</Card.Description>