ReturnToPrevious: Add e2e test (#83115)

* feat: add incomplete unit test

* refactor: add idea for unit test

* feat: create new e2e test

* feat: add some steps

* feat: add comment

* feat: complete prep work

* feat: complete clean up

* rebase

* feat: add more steps to test flow

* refactor: remove unit test

* refactor: clean up

* refactor: create a provisioned alert rule

* refactor: change location and content

* refactor: e2e test

* refactor: betterer

* refactor: move provisioned alert rule

* refactor: make provisioning file available remote

* refactor: clean up test

* refactor: move provisioned alert rule

* refactor: remove wait()

* feat: restructure first test and add more tests

* feat: add another provisioned alert rule

* feat: add a new test

* feat: complete new test

* refactor: replace data-testid in alert rules

* refactor: replace data-testid

* refactor: fix tests for drone

* refactor: fix third test after review

* refactor: fix last test

* temp

* refactor: improve some things

* refactor: adjust unit tests

* refactor: remove assertions for alert rule details view

* refactor: remove assertions

* refactor: add check for button text

* refactor: remove session storage

* refactor: apply changes from code review

* refactor: add codeowner

* refactor

* refactor

* refactor: clean up

* refactor: clean up

* refactor: clean up

* refactor: increase pa11y threshold for /alerting/list
This commit is contained in:
Laura Benz 2024-03-21 09:05:51 +01:00 committed by GitHub
parent 9298a5fcb5
commit d8a116e696
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 281 additions and 13 deletions

1
.github/CODEOWNERS vendored
View File

@ -172,6 +172,7 @@
/devenv/bulk-dashboards/ @grafana/dashboards-squad /devenv/bulk-dashboards/ @grafana/dashboards-squad
/devenv/bulk-folders/ @grafana/grafana-frontend-platform /devenv/bulk-folders/ @grafana/grafana-frontend-platform
/devenv/create_docker_compose.sh @grafana/backend-platform /devenv/create_docker_compose.sh @grafana/backend-platform
/devenv/alert_rules.yaml @grafana/alerting-backend-product
/devenv/dashboards.yaml @grafana/dashboards-squad /devenv/dashboards.yaml @grafana/dashboards-squad
/devenv/datasources.yaml @grafana/backend-platform /devenv/datasources.yaml @grafana/backend-platform
/devenv/datasources_docker.yaml @grafana/backend-platform /devenv/datasources_docker.yaml @grafana/backend-platform

View File

@ -103,7 +103,7 @@ var config = {
rootElement: '.main-view', rootElement: '.main-view',
// the unified alerting promotion alert's content contrast is too low // the unified alerting promotion alert's content contrast is too low
// see https://github.com/grafana/grafana/pull/41829 // see https://github.com/grafana/grafana/pull/41829
threshold: 6, threshold: 7,
}, },
{ {
url: '${HOST}/datasources', url: '${HOST}/datasources',

158
devenv/alert_rules.yaml Normal file
View File

@ -0,0 +1,158 @@
apiVersion: 1
groups:
- orgId: 1
name: testEvaluationGroup
folder: gdev dashboards
interval: 5m
rules:
- uid: bddn0v6f1kgzkc
title: e2e-ReturnToPrevious-test
condition: C
data:
- refId: A
relativeTimeRange:
from: 600
to: 0
datasourceUid: PD8C576611E62080A
model:
intervalMs: 1000
maxDataPoints: 43200
refId: A
- refId: B
datasourceUid: __expr__
model:
conditions:
- evaluator:
params: []
type: gt
operator:
type: and
query:
params:
- B
reducer:
params: []
type: last
type: query
datasource:
type: __expr__
uid: __expr__
expression: A
intervalMs: 1000
maxDataPoints: 43200
reducer: last
refId: B
type: reduce
- refId: C
datasourceUid: __expr__
model:
conditions:
- evaluator:
params:
- 0
type: gt
operator:
type: and
query:
params:
- C
reducer:
params: []
type: last
type: query
datasource:
type: __expr__
uid: __expr__
expression: B
intervalMs: 1000
maxDataPoints: 43200
refId: C
type: threshold
dashboardUid: j6T00KRZz
panelId: 7
noDataState: NoData
execErrState: Error
for: 5m
annotations:
__dashboardUid__: j6T00KRZz
__panelId__: "7"
labels: {}
isPaused: false
- orgId: 1
name: testEvaluationGroup2
folder: gdev dashboards
interval: 10m
rules:
- uid: dddyksihq7h1ca
title: e2e-ReturnToPrevious-test-2
condition: C
data:
- refId: A
relativeTimeRange:
from: 600
to: 0
datasourceUid: PD8C576611E62080A
model:
intervalMs: 1000
maxDataPoints: 43200
refId: A
- refId: B
datasourceUid: __expr__
model:
conditions:
- evaluator:
params: []
type: gt
operator:
type: and
query:
params:
- B
reducer:
params: []
type: last
type: query
datasource:
type: __expr__
uid: __expr__
expression: A
intervalMs: 1000
maxDataPoints: 43200
reducer: last
refId: B
type: reduce
- refId: C
datasourceUid: __expr__
model:
conditions:
- evaluator:
params:
- 0
type: gt
operator:
type: and
query:
params:
- C
reducer:
params: []
type: last
type: query
datasource:
type: __expr__
uid: __expr__
expression: B
intervalMs: 1000
maxDataPoints: 43200
refId: C
type: threshold
dashboardUid: j6T00KRZz
panelId: 3
noDataState: NoData
execErrState: Error
for: 10m
annotations:
__dashboardUid__: j6T00KRZz
__panelId__: "3"
labels: {}
isPaused: false

View File

@ -0,0 +1,86 @@
import { e2e } from '../utils';
describe('ReturnToPrevious button', () => {
beforeEach(() => {
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'));
cy.window().then((win) => {
win.localStorage.setItem('grafana.featureToggles', 'returnToPrevious=1');
});
cy.visit('/alerting/list');
e2e.components.AlertRules.groupToggle().first().click();
e2e.components.AlertRules.toggle().click();
cy.get('a[title="View"]').click();
cy.url().as('alertRuleUrl');
cy.get('a').contains('View panel').click();
});
it('should appear when changing context and go back to alert rule when clicking "Back"', () => {
// make sure the dashboard finished loading
cy.get('button[aria-label*="BarChart - Label Rotation & Skipping"]').should('be.visible');
// check whether all elements of RTP are available
e2e.components.ReturnToPrevious.buttonGroup().should('be.visible');
e2e.components.ReturnToPrevious.dismissButton().should('be.visible');
e2e.components.ReturnToPrevious.backButton()
.find('span')
.contains('Back to e2e-ReturnToPrevious-test')
.should('be.visible')
.click();
// check whether the RTP button leads back to alert rule
cy.get('@alertRuleUrl').then((alertRuleUrl) => {
cy.url().should('eq', alertRuleUrl);
});
});
it('should disappear when clicking "Dismiss"', () => {
e2e.components.ReturnToPrevious.dismissButton().should('be.visible').click();
e2e.components.ReturnToPrevious.buttonGroup().should('not.exist');
});
it('should not persist when going back to the alert rule details view', () => {
e2e.components.ReturnToPrevious.buttonGroup().should('be.visible');
// make sure the dashboard finished loading
cy.get('button[aria-label*="BarChart - Label Rotation & Skipping"]').should('be.visible');
cy.visit('/alerting/list');
e2e.components.AlertRules.groupToggle().first().click();
cy.get('a[title="View"]').click();
e2e.components.ReturnToPrevious.buttonGroup().should('not.exist');
});
it('should override the button label and change the href when user changes alert rules', () => {
// make sure the dashboard finished loading
cy.get('button[aria-label*="BarChart - Label Rotation & Skipping"]').should('be.visible');
e2e.components.ReturnToPrevious.backButton()
.find('span')
.contains('Back to e2e-ReturnToPrevious-test')
.should('be.visible');
cy.visit('/alerting/list');
e2e.components.AlertRules.groupToggle().last().click();
cy.get('a[title="View"]').click();
cy.url().as('alertRule2Url');
cy.get('a').contains('View panel').click();
// make sure the dashboard finished loading
cy.get('button[aria-label*="BarChart - Label Rotation & Skipping"]').should('be.visible');
e2e.components.ReturnToPrevious.backButton()
.find('span')
.contains('Back to e2e-ReturnToPrevious-test-2')
.should('be.visible')
.click();
e2e.components.ReturnToPrevious.buttonGroup().should('not.exist');
// check whether the RTP button leads back to alert rule
cy.get('@alertRule2Url').then((alertRule2Url) => {
cy.url().should('eq', alertRule2Url);
});
});
});

View File

@ -293,6 +293,11 @@ export const Components = {
AlertTab: { AlertTab: {
content: 'data-testid Alert editor tab content', content: 'data-testid Alert editor tab content',
}, },
AlertRules: {
groupToggle: 'data-testid group-collapse-toggle',
toggle: 'data-testid collapse-toggle',
expandedContent: 'data-testid expanded-content',
},
Alert: { Alert: {
/** /**
* @deprecated use alertV2 from Grafana 8.3 instead * @deprecated use alertV2 from Grafana 8.3 instead
@ -537,6 +542,11 @@ export const Components = {
Tooltip: { Tooltip: {
container: 'data-testid tooltip', container: 'data-testid tooltip',
}, },
ReturnToPrevious: {
buttonGroup: 'data-testid dismissable button group',
backButton: 'data-testid back',
dismissButton: 'data-testid dismiss',
},
SQLQueryEditor: { SQLQueryEditor: {
selectColumn: 'data-testid select-column', selectColumn: 'data-testid select-column',
selectAggregation: 'data-testid select-aggregation', selectAggregation: 'data-testid select-aggregation',

View File

@ -2,6 +2,7 @@ import { css } from '@emotion/css';
import React from 'react'; import React from 'react';
import { GrafanaTheme2 } from '@grafana/data'; import { GrafanaTheme2 } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { Button, ButtonGroup, useStyles2 } from '@grafana/ui'; import { Button, ButtonGroup, useStyles2 } from '@grafana/ui';
import { t } from 'app/core/internationalization'; import { t } from 'app/core/internationalization';
@ -24,6 +25,7 @@ export const DismissableButton = ({ label, onClick, onDismiss }: DismissableButt
onClick={onClick} onClick={onClick}
title={label} title={label}
className={styles.mainDismissableButton} className={styles.mainDismissableButton}
data-testid={selectors.components.ReturnToPrevious.backButton}
> >
{label} {label}
</Button> </Button>
@ -34,6 +36,7 @@ export const DismissableButton = ({ label, onClick, onDismiss }: DismissableButt
fill="outline" fill="outline"
size="sm" size="sm"
onClick={onDismiss} onClick={onDismiss}
data-testid={selectors.components.ReturnToPrevious.dismissButton}
/> />
</ButtonGroup> </ButtonGroup>
); );

View File

@ -2,6 +2,7 @@ import { css } from '@emotion/css';
import React, { useCallback } from 'react'; import React, { useCallback } from 'react';
import { GrafanaTheme2 } from '@grafana/data'; import { GrafanaTheme2 } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { locationService } from '@grafana/runtime'; import { locationService } from '@grafana/runtime';
import { useStyles2 } from '@grafana/ui'; import { useStyles2 } from '@grafana/ui';
import { useGrafana } from 'app/core/context/GrafanaContext'; import { useGrafana } from 'app/core/context/GrafanaContext';
@ -28,7 +29,7 @@ export const ReturnToPrevious = ({ href, title }: ReturnToPreviousProps) => {
}, [chrome]); }, [chrome]);
return ( return (
<div className={styles.returnToPrevious}> <div className={styles.returnToPrevious} data-testid={selectors.components.ReturnToPrevious.buttonGroup}>
<DismissableButton <DismissableButton
label={t('return-to-previous.button.label', 'Back to {{title}}', { title })} label={t('return-to-previous.button.label', 'Back to {{title}}', { title })}
onClick={handleOnClick} onClick={handleOnClick}

View File

@ -4,6 +4,7 @@ import React from 'react';
import { TestProvider } from 'test/helpers/TestProvider'; import { TestProvider } from 'test/helpers/TestProvider';
import { byRole, byTestId, byText } from 'testing-library-selector'; import { byRole, byTestId, byText } from 'testing-library-selector';
import { selectors } from '@grafana/e2e-selectors';
import { setDataSourceSrv } from '@grafana/runtime'; import { setDataSourceSrv } from '@grafana/runtime';
import { AccessControlAction } from 'app/types'; import { AccessControlAction } from 'app/types';
@ -48,7 +49,7 @@ const ui = {
groupCollapseToggle: byTestId('alert-group-collapse-toggle'), groupCollapseToggle: byTestId('alert-group-collapse-toggle'),
groupTable: byTestId('alert-group-table'), groupTable: byTestId('alert-group-table'),
row: byTestId('row'), row: byTestId('row'),
collapseToggle: byTestId('collapse-toggle'), collapseToggle: byTestId(selectors.components.AlertRules.toggle),
silenceButton: byText('Silence'), silenceButton: byText('Silence'),
sourceButton: byText('See source'), sourceButton: byText('See source'),
matcherInput: byTestId('search-query-input'), matcherInput: byTestId('search-query-input'),

View File

@ -6,6 +6,7 @@ import { TestProvider } from 'test/helpers/TestProvider';
import { byRole, byTestId, byText } from 'testing-library-selector'; import { byRole, byTestId, byText } from 'testing-library-selector';
import { PluginExtensionTypes } from '@grafana/data'; import { PluginExtensionTypes } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { import {
DataSourceSrv, DataSourceSrv,
getPluginLinkExtensions, getPluginLinkExtensions,
@ -113,11 +114,11 @@ const dataSources = {
const ui = { const ui = {
ruleGroup: byTestId('rule-group'), ruleGroup: byTestId('rule-group'),
cloudRulesSourceErrors: byTestId('cloud-rulessource-errors'), cloudRulesSourceErrors: byTestId('cloud-rulessource-errors'),
groupCollapseToggle: byTestId('group-collapse-toggle'), groupCollapseToggle: byTestId(selectors.components.AlertRules.groupToggle),
ruleCollapseToggle: byTestId('collapse-toggle'), ruleCollapseToggle: byTestId(selectors.components.AlertRules.toggle),
rulesTable: byTestId('rules-table'), rulesTable: byTestId('rules-table'),
ruleRow: byTestId('row'), ruleRow: byTestId('row'),
expandedContent: byTestId('expanded-content'), expandedContent: byTestId(selectors.components.AlertRules.expandedContent),
rulesFilterInput: byTestId('search-query-input'), rulesFilterInput: byTestId('search-query-input'),
moreErrorsButton: byRole('button', { name: /more errors/ }), moreErrorsButton: byRole('button', { name: /more errors/ }),
editCloudGroupIcon: byTestId('edit-group'), editCloudGroupIcon: byTestId('edit-group'),
@ -399,13 +400,13 @@ describe('RuleList', () => {
// expand details of an instance // expand details of an instance
await userEvent.click(ui.ruleCollapseToggle.get(instanceRows![0])); await userEvent.click(ui.ruleCollapseToggle.get(instanceRows![0]));
const alertDetails = byTestId('expanded-content').get(instanceRows[0]); const alertDetails = byTestId(selectors.components.AlertRules.expandedContent).get(instanceRows[0]);
expect(alertDetails).toHaveTextContent('Value2e+10'); expect(alertDetails).toHaveTextContent('Value2e+10');
expect(alertDetails).toHaveTextContent('messagefirst alert message'); expect(alertDetails).toHaveTextContent('messagefirst alert message');
// collapse everything again // collapse everything again
await userEvent.click(ui.ruleCollapseToggle.get(instanceRows![0])); await userEvent.click(ui.ruleCollapseToggle.get(instanceRows![0]));
expect(byTestId('expanded-content').query(instanceRows[0])).not.toBeInTheDocument(); expect(byTestId(selectors.components.AlertRules.expandedContent).query(instanceRows[0])).not.toBeInTheDocument();
await userEvent.click(ui.ruleCollapseToggle.getAll(ruleRows[1])[0]); await userEvent.click(ui.ruleCollapseToggle.getAll(ruleRows[1])[0]);
await userEvent.click(ui.groupCollapseToggle.get(groups[1])); await userEvent.click(ui.groupCollapseToggle.get(groups[1]));
expect(ui.rulesTable.query()).not.toBeInTheDocument(); expect(ui.rulesTable.query()).not.toBeInTheDocument();

View File

@ -2,6 +2,7 @@ import { css, cx } from '@emotion/css';
import React, { ReactNode, useState } from 'react'; import React, { ReactNode, useState } from 'react';
import { GrafanaTheme2 } from '@grafana/data'; import { GrafanaTheme2 } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { IconButton, Pagination, useStyles2 } from '@grafana/ui'; import { IconButton, Pagination, useStyles2 } from '@grafana/ui';
import { usePagination } from '../hooks/usePagination'; import { usePagination } from '../hooks/usePagination';
@ -125,7 +126,7 @@ export const DynamicTable = <T extends object>({
<div className={cx(styles.cell, styles.expandCell)}> <div className={cx(styles.cell, styles.expandCell)}>
<IconButton <IconButton
tooltip={`${isItemExpanded ? 'Collapse' : 'Expand'} row`} tooltip={`${isItemExpanded ? 'Collapse' : 'Expand'} row`}
data-testid="collapse-toggle" data-testid={selectors.components.AlertRules.toggle}
name={isItemExpanded ? 'angle-down' : 'angle-right'} name={isItemExpanded ? 'angle-down' : 'angle-right'}
onClick={() => toggleExpanded(item)} onClick={() => toggleExpanded(item)}
/> />
@ -141,7 +142,10 @@ export const DynamicTable = <T extends object>({
</div> </div>
))} ))}
{isItemExpanded && renderExpandedContent && ( {isItemExpanded && renderExpandedContent && (
<div className={styles.expandedContentRow} data-testid="expanded-content"> <div
className={styles.expandedContentRow}
data-testid={selectors.components.AlertRules.expandedContent}
>
{renderExpandedContent(item, index, items)} {renderExpandedContent(item, index, items)}
</div> </div>
)} )}

View File

@ -95,8 +95,8 @@ const AnnotationsStep = () => {
Add more context in your notification messages. Add more context in your notification messages.
</Text> </Text>
<NeedHelpInfo <NeedHelpInfo
contentText={`Annotations add metadata to provide more information on the alert in your alert notification messages. contentText={`Annotations add metadata to provide more information on the alert in your alert notification messages.
For example, add a Summary annotation to tell you which value caused the alert to fire or which server it happened on. For example, add a Summary annotation to tell you which value caused the alert to fire or which server it happened on.
Annotations can contain a combination of text and template code.`} Annotations can contain a combination of text and template code.`}
title="Annotations" title="Annotations"
/> />

View File

@ -3,6 +3,7 @@ import pluralize from 'pluralize';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { GrafanaTheme2 } from '@grafana/data'; import { GrafanaTheme2 } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { Badge, ConfirmModal, HorizontalGroup, Icon, Spinner, Stack, Tooltip, useStyles2 } from '@grafana/ui'; import { Badge, ConfirmModal, HorizontalGroup, Icon, Spinner, Stack, Tooltip, useStyles2 } from '@grafana/ui';
import { useDispatch } from 'app/types'; import { useDispatch } from 'app/types';
import { CombinedRuleGroup, CombinedRuleNamespace } from 'app/types/unified-alerting'; import { CombinedRuleGroup, CombinedRuleNamespace } from 'app/types/unified-alerting';
@ -225,7 +226,7 @@ export const RulesGroup = React.memo(({ group, namespace, expandAll, viewMode }:
className={styles.collapseToggle} className={styles.collapseToggle}
isCollapsed={isCollapsed} isCollapsed={isCollapsed}
onToggle={setIsCollapsed} onToggle={setIsCollapsed}
data-testid="group-collapse-toggle" data-testid={selectors.components.AlertRules.groupToggle}
/> />
<Icon name={isCollapsed ? 'folder' : 'folder-open'} /> <Icon name={isCollapsed ? 'folder' : 'folder-open'} />
{isCloudRulesSource(rulesSource) && ( {isCloudRulesSource(rulesSource) && (

View File

@ -30,6 +30,7 @@ mkdir $RUNDIR/conf
mkdir $PROV_DIR mkdir $PROV_DIR
mkdir $PROV_DIR/datasources mkdir $PROV_DIR/datasources
mkdir $PROV_DIR/dashboards mkdir $PROV_DIR/dashboards
mkdir $PROV_DIR/alerting
cp ./scripts/grafana-server/custom.ini $RUNDIR/conf/custom.ini cp ./scripts/grafana-server/custom.ini $RUNDIR/conf/custom.ini
cp ./conf/defaults.ini $RUNDIR/conf/defaults.ini cp ./conf/defaults.ini $RUNDIR/conf/defaults.ini
@ -49,6 +50,7 @@ echo -e "Copy provisioning setup from devenv"
cp devenv/datasources.yaml $PROV_DIR/datasources cp devenv/datasources.yaml $PROV_DIR/datasources
cp devenv/dashboards.yaml $PROV_DIR/dashboards cp devenv/dashboards.yaml $PROV_DIR/dashboards
cp devenv/alert_rules.yaml $PROV_DIR/alerting
cp -r devenv $RUNDIR cp -r devenv $RUNDIR