Select: Ensure screenreader correctly narrates options (#88118)

* replace select aria-label with data-testid

* update in copy pasted places as well

* update plugin-e2e to 1.2.2
This commit is contained in:
Ashley Harrison 2024-05-28 14:51:47 +01:00 committed by GitHub
parent 6205236f25
commit b4e49e3114
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 60 additions and 45 deletions

View File

@ -107,5 +107,5 @@ describe('Prometheus config', () => {
});
export function selectOption(option: string) {
cy.get("[aria-label='Select option']").contains(option).should('be.visible').click();
e2e.components.Select.option().contains(option).should('be.visible').click();
}

View File

@ -178,5 +178,5 @@ describe('Prometheus query editor', () => {
});
function selectOption(option: string) {
cy.get("[aria-label='Select option']").contains(option).should('be.visible').click();
e2e.components.Select.option().contains(option).should('be.visible').click();
}

View File

@ -118,5 +118,5 @@ describe('Prometheus variable query editor', () => {
});
function selectOption(option: string) {
cy.get("[aria-label='Select option']").contains(option).should('be.visible').click();
e2e.components.Select.option().contains(option).should('be.visible').click();
}

View File

@ -77,7 +77,7 @@
"@emotion/eslint-plugin": "11.11.0",
"@grafana/eslint-config": "7.0.0",
"@grafana/eslint-plugin": "link:./packages/grafana-eslint-rules",
"@grafana/plugin-e2e": "1.2.0",
"@grafana/plugin-e2e": "1.2.2",
"@grafana/tsconfig": "^1.3.0-rc1",
"@manypkg/get-packages": "^2.2.0",
"@playwright/test": "1.44.0",

View File

@ -420,7 +420,7 @@ export const Components = {
current: () => '[class*="-currentVisualizationItem"]',
},
Select: {
option: 'Select option',
option: 'data-testid Select option',
input: () => 'input[id*="time-options-input"]',
singleValue: () => 'div[class*="-singleValue"]',
},

View File

@ -6,6 +6,7 @@ import { MenuListProps } from 'react-select';
import { FixedSizeList as List } from 'react-window';
import { SelectableValue, toIconName } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { CustomScrollbar, Icon, getSelectStyles, useTheme2 } from '@grafana/ui';
interface SelectMenuProps {
@ -110,7 +111,7 @@ export const SelectMenuOptions = ({
data.isDisabled && styles.optionDisabled
)}
{...rest}
aria-label="Select option"
data-testid={selectors.components.Select.option}
title={data.title}
>
{icon && <Icon name={icon} className={styles.optionIcon} />}

View File

@ -4,6 +4,7 @@ import userEvent from '@testing-library/user-event';
import React from 'react';
import { DataSourceInstanceSettings, MetricFindValue } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { PrometheusDatasource } from '../../datasource';
import { PromOptions } from '../../types';
@ -69,7 +70,7 @@ describe('MetricSelect', () => {
await waitFor(() => expect(screen.getByText('random_metric')).toBeInTheDocument());
await waitFor(() => expect(screen.getByText('unique_metric')).toBeInTheDocument());
await waitFor(() => expect(screen.getByText('more_unique_metric')).toBeInTheDocument());
await waitFor(() => expect(screen.getAllByLabelText('Select option')).toHaveLength(3));
await waitFor(() => expect(screen.getAllByTestId(selectors.components.Select.option)).toHaveLength(3));
});
it('truncates list of metrics to 1000', async () => {
@ -81,7 +82,7 @@ describe('MetricSelect', () => {
render(<MetricSelect {...props} />);
await openMetricSelect();
await waitFor(() => expect(screen.getAllByLabelText('Select option')).toHaveLength(1000));
await waitFor(() => expect(screen.getAllByTestId(selectors.components.Select.option)).toHaveLength(1000));
});
it('shows option to set custom value when typing', async () => {
@ -97,7 +98,7 @@ describe('MetricSelect', () => {
await openMetricSelect();
const input = screen.getByRole('combobox');
await userEvent.type(input, 'unique');
await waitFor(() => expect(screen.getAllByLabelText('Select option')).toHaveLength(3));
await waitFor(() => expect(screen.getAllByTestId(selectors.components.Select.option)).toHaveLength(3));
});
it('searches on split words', async () => {
@ -105,7 +106,7 @@ describe('MetricSelect', () => {
await openMetricSelect();
const input = screen.getByRole('combobox');
await userEvent.type(input, 'more unique');
await waitFor(() => expect(screen.getAllByLabelText('Select option')).toHaveLength(2));
await waitFor(() => expect(screen.getAllByTestId(selectors.components.Select.option)).toHaveLength(2));
});
it('searches on multiple split words', async () => {
@ -113,7 +114,7 @@ describe('MetricSelect', () => {
await openMetricSelect();
const input = screen.getByRole('combobox');
await userEvent.type(input, 'more unique metric');
await waitFor(() => expect(screen.getAllByLabelText('Select option')).toHaveLength(2));
await waitFor(() => expect(screen.getAllByTestId(selectors.components.Select.option)).toHaveLength(2));
});
it('highlights matching string', async () => {

View File

@ -190,7 +190,7 @@ export function MetricSelect({
{...props.innerProps}
ref={props.innerRef}
className={`${styles.customOptionWidth} metric-encyclopedia-open`}
aria-label="Select option"
data-testid={selectors.components.Select.option}
onKeyDown={(e) => {
// if there is no metric and the m.e. is enabled, open the modal
if (e.code === 'Enter') {

View File

@ -3,6 +3,7 @@ import userEvent from '@testing-library/user-event';
import React from 'react';
import { createTheme, SelectableValue } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { Checkbox } from '../Forms/Checkbox';
import { RadioButtonGroup } from '../Forms/RadioButtonGroup/RadioButtonGroup';
@ -487,14 +488,14 @@ describe('Select, as AutoSaveField child, ', () => {
setupSelect();
expect(screen.getByRole('combobox')).toBeInTheDocument();
await user.click(screen.getByText('Choose'));
const selectOptions = screen.getAllByLabelText('Select option');
const selectOptions = screen.getAllByTestId(selectors.components.Select.option);
expect(selectOptions).toHaveLength(3);
});
it('triggers the function on change by selecting an option and shows the InlineToast', async () => {
setupSelect();
expect(screen.getByRole('combobox')).toBeInTheDocument();
await user.click(screen.getByText('Choose'));
const selectOptions = screen.getAllByLabelText('Select option');
const selectOptions = screen.getAllByTestId(selectors.components.Select.option);
await user.click(selectOptions[1]);
act(() => {
jest.runAllTimers();
@ -507,7 +508,7 @@ describe('Select, as AutoSaveField child, ', () => {
setupSelect({ saveErrorMessage: 'There was an error', onFinishChange: mockOnFinishChangeError });
expect(screen.getByRole('combobox')).toBeInTheDocument();
await user.click(screen.getByText('Choose'));
const selectOptions = screen.getAllByLabelText('Select option');
const selectOptions = screen.getAllByTestId(selectors.components.Select.option);
await user.click(selectOptions[1]);
act(() => {
jest.runAllTimers();

View File

@ -3,6 +3,7 @@ import { isString } from 'lodash';
import React, { PropsWithChildren, RefCallback } from 'react';
import { GrafanaTheme2, SelectableValue, getTimeZoneInfo } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { useStyles2 } from '../../../themes';
import { Icon } from '../../Icon/Icon';
@ -38,7 +39,7 @@ export const WideTimeZoneOption = (props: PropsWithChildren<Props>) => {
const timeZoneInfo = getTimeZoneInfo(data.value, timestamp);
return (
<div className={containerStyles} {...innerProps} ref={innerRef} aria-label="Select option">
<div className={containerStyles} {...innerProps} ref={innerRef} data-testid={selectors.components.Select.option}>
<div className={cx(styles.leftColumn, styles.row)}>
<div className={cx(styles.leftColumn, styles.wideRow)}>
<TimeZoneTitle title={children} />
@ -77,7 +78,7 @@ export const CompactTimeZoneOption = (props: React.PropsWithChildren<Props>) =>
const timeZoneInfo = getTimeZoneInfo(data.value, timestamp);
return (
<div className={containerStyles} {...innerProps} ref={innerRef} aria-label="Select option">
<div className={containerStyles} {...innerProps} ref={innerRef} data-testid={selectors.components.Select.option}>
<div className={styles.body}>
<div className={styles.row}>
<div className={styles.leftColumn}>

View File

@ -2,6 +2,7 @@ import { fireEvent, render, screen } from '@testing-library/react';
import React from 'react';
import { toDataFrame, FieldType } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { RefIDPicker, Props } from './FieldsByFrameRefIdMatcher';
@ -45,7 +46,7 @@ describe('RefIDPicker', () => {
const select = await screen.findByRole('combobox');
fireEvent.keyDown(select, { keyCode: 40 });
const selectOptions = screen.getAllByLabelText('Select option');
const selectOptions = screen.getAllByTestId(selectors.components.Select.option);
expect(selectOptions).toHaveLength(2);
expect(selectOptions[0]).toHaveTextContent('Query: AFrames (2): Series A, Second series');

View File

@ -4,6 +4,7 @@ import React, { useState } from 'react';
import { select } from 'react-select-event';
import { SelectableValue } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { SelectBase } from './SelectBase';
@ -224,7 +225,7 @@ describe('SelectBase', () => {
it('renders menu with provided options', async () => {
render(<SelectBase options={options} onChange={onChangeHandler} />);
await userEvent.click(screen.getByText(/choose/i));
const menuOptions = screen.getAllByLabelText('Select option');
const menuOptions = screen.getAllByTestId(selectors.components.Select.option);
expect(menuOptions).toHaveLength(2);
});
@ -250,7 +251,7 @@ describe('SelectBase', () => {
await selectOptionInTest(selectEl, 'Option 2');
await userEvent.click(screen.getByText(/option 2/i));
const menuOptions = screen.getAllByLabelText('Select option');
const menuOptions = screen.getAllByTestId(selectors.components.Select.option);
expect(menuOptions).toHaveLength(2);
});
});

View File

@ -5,6 +5,7 @@ import { MenuListProps } from 'react-select';
import { FixedSizeList as List } from 'react-window';
import { SelectableValue, toIconName } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { useTheme2 } from '../../themes/ThemeContext';
import { CustomScrollbar } from '../CustomScrollbar/CustomScrollbar';
@ -155,7 +156,7 @@ export const SelectMenuOptions = ({
data.isDisabled && styles.optionDisabled
)}
{...rest}
aria-label="Select option"
data-testid={selectors.components.Select.option}
title={data.title}
>
{icon && <Icon name={icon} className={styles.optionIcon} />}

View File

@ -38,7 +38,7 @@ describe('OldFolderPicker', () => {
const pickerContainer = screen.getByTestId(selectors.components.FolderPicker.input);
selectEvent.openMenu(pickerContainer);
const pickerOptions = await screen.findAllByLabelText('Select option');
const pickerOptions = await screen.findAllByTestId(selectors.components.Select.option);
expect(pickerOptions).toHaveLength(2);
expect(pickerOptions[0]).toHaveTextContent('Dash 1');
@ -92,7 +92,7 @@ describe('OldFolderPicker', () => {
const pickerContainer = screen.getByTestId(selectors.components.FolderPicker.input);
selectEvent.openMenu(pickerContainer);
const pickerOptions = await screen.findAllByLabelText('Select option');
const pickerOptions = await screen.findAllByTestId(selectors.components.Select.option);
expect(pickerOptions[0]).toHaveTextContent('Dashboards');
});
@ -113,7 +113,7 @@ describe('OldFolderPicker', () => {
const pickerContainer = screen.getByTestId(selectors.components.FolderPicker.input);
selectEvent.openMenu(pickerContainer);
const pickerOptions = await screen.findAllByLabelText('Select option');
const pickerOptions = await screen.findAllByTestId(selectors.components.Select.option);
expect(pickerOptions[0]).not.toHaveTextContent('Dashboards');
});
@ -134,7 +134,7 @@ describe('OldFolderPicker', () => {
const pickerContainer = screen.getByTestId(selectors.components.FolderPicker.input);
selectEvent.openMenu(pickerContainer);
const pickerOptions = await screen.findAllByLabelText('Select option');
const pickerOptions = await screen.findAllByTestId(selectors.components.Select.option);
expect(pickerOptions[0]).not.toHaveTextContent('Dashboards');
});

View File

@ -3,6 +3,7 @@ import userEvent from '@testing-library/user-event';
import React, { ReactNode } from 'react';
import { Provider } from 'react-redux';
import { selectors } from '@grafana/e2e-selectors';
import { locationService, setEchoSrv } from '@grafana/runtime';
import { DataQuery, defaultDashboard } from '@grafana/schema';
import { backendSrv } from 'app/core/services/backend_srv';
@ -56,7 +57,7 @@ describe('AddToDashboardButton', () => {
setEchoSrv(new Echo());
});
/* The Add to dashboard form brings in the DashboardPicker, which will call backendSrv.search as part of its instantiation
/* The Add to dashboard form brings in the DashboardPicker, which will call backendSrv.search as part of its instantiation
If we do not need a list of dashboards for the test, return an empty array. */
beforeEach(() => {
// Mock the search response so we don't get any refused connection errors
@ -237,9 +238,9 @@ describe('AddToDashboardButton', () => {
await userEvent.click(screen.getByRole('combobox', { name: /dashboard/i }));
await waitFor(async () => {
await screen.findByLabelText('Select option');
await screen.findByTestId(selectors.components.Select.option);
});
await userEvent.click(screen.getByLabelText('Select option'));
await userEvent.click(screen.getByTestId(selectors.components.Select.option));
await userEvent.click(screen.getByRole('button', { name: /open in new tab/i }));
@ -278,9 +279,9 @@ describe('AddToDashboardButton', () => {
await userEvent.click(screen.getByRole('combobox', { name: /dashboard/i }));
await waitFor(async () => {
await screen.findByLabelText('Select option');
await screen.findByTestId(selectors.components.Select.option);
});
await userEvent.click(screen.getByLabelText('Select option'));
await userEvent.click(screen.getByTestId(selectors.components.Select.option));
await userEvent.click(screen.getByRole('button', { name: /open dashboard$/i }));
@ -394,9 +395,9 @@ describe('AddToDashboardButton', () => {
await userEvent.click(screen.getByRole('combobox', { name: /dashboard/i }));
await waitFor(async () => {
await screen.findByLabelText('Select option');
await screen.findByTestId(selectors.components.Select.option);
});
await userEvent.click(screen.getByLabelText('Select option'));
await userEvent.click(screen.getByTestId(selectors.components.Select.option));
await userEvent.click(screen.getByRole('button', { name: /open in new tab/i }));

View File

@ -3,6 +3,8 @@ import userEvent from '@testing-library/user-event';
import React from 'react';
import { openMenu } from 'react-select-event';
import { selectors } from '@grafana/e2e-selectors';
import createMockDatasource from '../../__mocks__/datasource';
import createMockPanelData from '../../__mocks__/panelData';
import createMockQuery from '../../__mocks__/query';
@ -120,7 +122,7 @@ describe(`Azure Monitor QueryEditor`, () => {
);
const dimensionSelect = await screen.findByText('Field');
await user.click(dimensionSelect);
const options = await screen.findAllByLabelText('Select option');
const options = await screen.findAllByTestId(selectors.components.Select.option);
expect(options).toHaveLength(1);
expect(options[0]).toHaveTextContent('Test Dimension 2');
});
@ -161,7 +163,7 @@ describe(`Azure Monitor QueryEditor`, () => {
);
const labelSelect = await screen.findByText('Select value(s)');
await user.click(labelSelect);
const options = await screen.findAllByLabelText('Select option');
const options = await screen.findAllByTestId(selectors.components.Select.option);
expect(options).toHaveLength(1);
expect(options[0]).toHaveTextContent('testlabel');
});
@ -238,7 +240,7 @@ describe(`Azure Monitor QueryEditor`, () => {
);
const labelSelect = screen.getByLabelText('dimension-labels-select');
await openMenu(labelSelect);
const options = await screen.findAllByLabelText('Select option');
const options = await screen.findAllByTestId(selectors.components.Select.option);
expect(options).toHaveLength(2);
expect(options[0]).toHaveTextContent('testlabel');
expect(options[1]).toHaveTextContent('testlabel2');

View File

@ -3,6 +3,7 @@ import React, { RefCallback, SyntheticEvent, useState } from 'react';
import { lastValueFrom } from 'rxjs';
import { CoreApp, DataFrame, getDefaultTimeRange, SelectableValue, TimeRange } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { AccessoryButton } from '@grafana/experimental';
import {
HorizontalGroup,
@ -164,7 +165,7 @@ const Option = (props: React.PropsWithChildren<OptionProps>) => {
data.isDisabled && styles.optionDisabled
)}
{...innerProps}
aria-label="Select option"
data-testid={selectors.components.Select.option}
title={data.title}
onClick={onClickMultiOption}
onKeyDown={onClickMultiOption}

View File

@ -4,6 +4,7 @@ import React from 'react';
import { openMenu, select } from 'react-select-event';
import { CustomVariableModel, getDefaultTimeRange } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { getTemplateSrv } from '@grafana/runtime';
import { createMockDatasource } from '../__mocks__/cloudMonitoringDatasource';
@ -174,7 +175,7 @@ describe('VisualMetricQueryEditor', () => {
);
const service = await screen.findByLabelText('Service');
openMenu(service);
expect(screen.getAllByLabelText('Select option').length).toEqual(2);
expect(screen.getAllByTestId(selectors.components.Select.option).length).toEqual(2);
});
it('resets query to default when service changes', async () => {

View File

@ -2,6 +2,7 @@ import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import { selectors } from '@grafana/e2e-selectors';
import { Select, InlineField } from '@grafana/ui';
import { useCreatableSelectPersistedBehaviour } from './useCreatableSelectPersistedBehaviour';
@ -32,7 +33,7 @@ describe('useCreatableSelectPersistedBehaviour', () => {
// we type in the input 'Option 2', which should prompt an option creation
await userEvent.type(input, 'Option 2');
const creatableOption = screen.getByLabelText('Select option');
const creatableOption = screen.getByTestId(selectors.components.Select.option);
expect(creatableOption).toHaveTextContent('Option 2');
// we click on the creatable option to trigger its creation
@ -83,7 +84,7 @@ describe('useCreatableSelectPersistedBehaviour', () => {
// we type in the input 'Option 2', which should prompt an option creation
await userEvent.type(input, 'Option 2');
await userEvent.click(screen.getByLabelText('Select option'));
await userEvent.click(screen.getByTestId(selectors.components.Select.option));
expect(onChange).toHaveBeenLastCalledWith({ value: 'Option 2' });
});

View File

@ -2,6 +2,8 @@ import { fireEvent, render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import { selectors } from '@grafana/e2e-selectors';
import { QueryEditor, Props } from './QueryEditor';
import { scenarios } from './__mocks__/scenarios';
import { defaultQuery } from './constants';
@ -42,7 +44,7 @@ describe('Test Datasource Query Editor', () => {
let select = (await screen.findByText('Scenario')).nextSibling!.firstChild!;
await fireEvent.keyDown(select, { keyCode: 40 });
const scs = screen.getAllByLabelText('Select option');
const scs = screen.getAllByTestId(selectors.components.Select.option);
expect(scs).toHaveLength(scenarios.length);

View File

@ -3307,16 +3307,16 @@ __metadata:
languageName: unknown
linkType: soft
"@grafana/plugin-e2e@npm:1.2.0":
version: 1.2.0
resolution: "@grafana/plugin-e2e@npm:1.2.0"
"@grafana/plugin-e2e@npm:1.2.2":
version: 1.2.2
resolution: "@grafana/plugin-e2e@npm:1.2.2"
dependencies:
semver: "npm:^7.5.4"
uuid: "npm:^9.0.1"
yaml: "npm:^2.3.4"
peerDependencies:
"@playwright/test": ^1.41.2
checksum: 10/b7af12f214ef4daff0d917b6715fde181b29d72923d1d5c5a5d17e0f17084f23450be91e877d659aa3c2472002253d127d2465d80e25accb59154027228dbb8c
checksum: 10/9e537f847f90f2c3a777d6ff449154ba52bcbfd9f434bfb438e5ad42b7c688e7363831ba7694b88e09da66a70fe6eb498729a75b7a6fb9e8cbb38b4fbbd51ed3
languageName: node
linkType: hard
@ -16714,7 +16714,7 @@ __metadata:
"@grafana/lezer-logql": "npm:0.2.3"
"@grafana/monaco-logql": "npm:^0.0.7"
"@grafana/o11y-ds-frontend": "workspace:*"
"@grafana/plugin-e2e": "npm:1.2.0"
"@grafana/plugin-e2e": "npm:1.2.2"
"@grafana/prometheus": "workspace:*"
"@grafana/runtime": "workspace:*"
"@grafana/saga-icons": "workspace:*"