Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dashboard a11y tests #58122

Merged
merged 15 commits into from
Mar 2, 2020
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,7 @@
"@typescript-eslint/parser": "^2.15.0",
"angular-mocks": "^1.7.9",
"archiver": "^3.1.1",
"axe-core": "^3.3.2",
"axe-core": "^3.4.1",
"babel-eslint": "^10.0.3",
"babel-jest": "^24.9.0",
"babel-plugin-dynamic-import-node": "^2.3.0",
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,28 @@ class ListControlUi extends PureComponent<ListControlUiProps, ListControlUiState
state = {
isLoading: false,
};
private textInput: HTMLElement | null;

constructor(props: ListControlUiProps) {
super(props);
this.textInput = null;
}

componentDidMount = () => {
if (this.textInput) {
this.textInput.setAttribute('focusable', 'false');
majagrubic marked this conversation as resolved.
Show resolved Hide resolved
}
this.isMounted = true;
};

componentWillUnmount = () => {
this.isMounted = false;
};

setTextInputRef = (ref: HTMLElement) => {
this.textInput = ref;
};

handleOnChange = (selectedOptions: any[]) => {
const selectedValues = selectedOptions.map(({ value }) => {
return value;
Expand Down Expand Up @@ -143,6 +156,7 @@ class ListControlUi extends PureComponent<ListControlUiProps, ListControlUiState
onChange={this.handleOnChange}
singleSelection={!this.props.multiselect}
data-test-subj={`listControlSelect${this.props.controlIndex}`}
inputRef={this.setTextInputRef}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I'm not 100% sure where this is getting rendered so I wasn't able to test it but while poking around this pointed to a much larger issue (that I think this is covering up) of, why is so much of dashboard wrapped in a aria-hidden="true" and with a screen reader only warning saying it's not accessible?

This is concerning on several levels...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, (almost?) all visualizations have area-hidden=true attribute. Their DOM structure is usually just a bunch of divs and an svg. Input control visualization, however, has this inner input field, which was focusable before and the test was failing:
Screenshot 2020-02-21 at 14 07 08

Copy link
Contributor Author

@majagrubic majagrubic Feb 21, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean that much of dashboard is wrapped in "area-hidden=true"? If the point here is that dashboard is basically useless when all the visualizations are hidden in accessibility mode, I completely agree. I think it doesn't make much sense to show the dashboard in the first place, but I kinda assumed you and Bhavya had already discussed that

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hadn't done an a11y deep dive into the Dashboard page before so this is the first that I'm seeing it... I guess it's too large to address in this PR though. I'll focus on helping you merge this so that we can get the tests merged and I'll take a note to revisit it later. Might ping you about it later depending on how investigation goes.

/>
);
}
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,9 @@ export class DashboardCloneModal extends React.Component<Props, State> {

<EuiFieldText
autoFocus
aria-label={i18n.translate('kbn.dashboard.cloneModal.cloneDashboardTitleAriaLabel', {
defaultMessage: 'Cloned Dashboard Title',
})}
data-test-subj="clonedDashboardTitle"
value={this.state.newDashboardName}
onChange={this.onInputChange}
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
* under the License.
*/
import React from 'react';
import { i18n } from '@kbn/i18n';

interface Props {
hasPreviousPage: boolean;
Expand All @@ -33,6 +34,12 @@ export function ToolBarPagerButtons(props: Props) {
onClick={() => props.onPagePrevious()}
disabled={!props.hasPreviousPage}
data-test-subj="btnPrevPage"
aria-label={i18n.translate(
'kbn.discover.docTable.pager.toolbarPagerButtons.previousButtonAriaLabel',
{
defaultMessage: 'Previous page in table',
}
)}
>
<span className="kuiButton__icon kuiIcon fa-chevron-left" />
</button>
Expand All @@ -41,6 +48,12 @@ export function ToolBarPagerButtons(props: Props) {
onClick={() => props.onPageNext()}
disabled={!props.hasNextPage}
data-test-subj="btnNextPage"
aria-label={i18n.translate(
'kbn.ddiscover.docTable.pager.toolbarPagerButtons.nextButtonAriaLabel',
{
defaultMessage: 'Next page in table',
}
)}
>
<span className="kuiButton__icon kuiIcon fa-chevron-right" />
</button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ export class ValidatedDualRange extends Component {
fullWidth={fullWidth}
value={this.state.value}
onChange={this._onChange}
focusable={false}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a comment above to make focusable again when #59039 is fixed

{...rest}
/>
</EuiFormRow>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export function QueryLanguageSwitcher(props: Props) {

return (
<EuiPopover
id="popover"
id="queryLanguageSwitcherPopover"
anchorClassName="euiFormControlLayout__append"
ownFocus
anchorPosition={props.anchorPosition || 'downRight'}
Expand Down
131 changes: 123 additions & 8 deletions test/accessibility/apps/dashboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,27 +20,36 @@
import { FtrProviderContext } from '../ftr_provider_context';

export default function({ getService, getPageObjects }: FtrProviderContext) {
const PageObjects = getPageObjects(['common', 'dashboard', 'header']);
const PageObjects = getPageObjects(['common', 'dashboard', 'header', 'home', 'settings']);
const a11y = getService('a11y');
const esArchiver = getService('esArchiver');
const kibanaServer = getService('kibanaServer');
const dashboardAddPanel = getService('dashboardAddPanel');
const testSubjects = getService('testSubjects');
const listingTable = getService('listingTable');

describe('Dashboard', () => {
const dashboardName = 'Dashboard Listing A11y';
const clonedDashboardName = 'Dashboard Listing A11y Copy';

before(async () => {
await esArchiver.loadIfNeeded('logstash_functional');
await kibanaServer.uiSettings.update({
defaultIndex: 'logstash-*',
});
await PageObjects.common.navigateToUrl('home', 'tutorial_directory/sampleData');
await PageObjects.home.addSampleDataSet('flights');
});

after(async () => {
await PageObjects.common.navigateToApp('dashboard');
await listingTable.searchForItemWithName(dashboardName);
await listingTable.checkListingSelectAllCheckbox();
await listingTable.clickDeleteSelected();
await PageObjects.common.clickConfirmOnModal();
});

it('dashboard', async () => {
await PageObjects.common.navigateToApp('dashboard');
await a11y.testAppSnapshot();
});

it('create dashboard button', async () => {
await PageObjects.dashboard.clickCreateDashboardPrompt();
await PageObjects.dashboard.clickNewDashboard();
await a11y.testAppSnapshot();
});

Expand All @@ -49,9 +58,115 @@ export default function({ getService, getPageObjects }: FtrProviderContext) {
await a11y.testAppSnapshot();
});

it('Open Edit mode', async () => {
await PageObjects.dashboard.switchToEditMode();
await a11y.testAppSnapshot();
});

it('Open add panel', async () => {
await dashboardAddPanel.clickOpenAddPanel();
await a11y.testAppSnapshot();
});

it('add a visualization', async () => {
await testSubjects.click('savedObjectTitle[Flights]-Delay-Buckets');
await a11y.testAppSnapshot();
});

it('add a saved search', async () => {
await dashboardAddPanel.addSavedSearch('[Flights] Flight Log');
await a11y.testAppSnapshot();
});

it('save the dashboard', async () => {
await PageObjects.dashboard.saveDashboard(dashboardName);
await a11y.testAppSnapshot();
});

it('Open Edit mode', async () => {
await PageObjects.dashboard.switchToEditMode();
await a11y.testAppSnapshot();
});

it('open options menu', async () => {
await PageObjects.dashboard.openOptions();
await a11y.testAppSnapshot();
});

it('Should be able to hide panel titles', async () => {
await testSubjects.click('dashboardPanelTitlesCheckbox');
await a11y.testAppSnapshot();
});

it('Should be able display panels without margins', async () => {
await testSubjects.click('dashboardMarginsCheckbox');
await a11y.testAppSnapshot();
});

it('Open add panel', async () => {
await dashboardAddPanel.clickOpenAddPanel();
await a11y.testAppSnapshot();
});

it('Add one more saved object to cancel it', async () => {
await testSubjects.click('savedObjectTitle[Flights]-Average-Ticket-Price');
await a11y.testAppSnapshot();
});

it('Close add panel', async () => {
await dashboardAddPanel.closeAddPanel();
await a11y.testAppSnapshot();
});

it('Exit out of edit mode', async () => {
await PageObjects.dashboard.clickCancelOutOfEditMode();
await a11y.testAppSnapshot();
});

it('Discard changes', async () => {
await PageObjects.common.clickConfirmOnModal();
await PageObjects.dashboard.getIsInViewMode();
await a11y.testAppSnapshot();
});

it('Test full screen', async () => {
await PageObjects.dashboard.clickFullScreenMode();
await a11y.testAppSnapshot();
});

it('Exit out of full screen mode', async () => {
await PageObjects.dashboard.exitFullScreenMode();
await a11y.testAppSnapshot();
});

it('Make a clone of the dashboard', async () => {
await PageObjects.dashboard.clickClone();
await a11y.testAppSnapshot();
});

it('Confirm clone with *copy* appended', async () => {
await PageObjects.dashboard.confirmClone();
await a11y.testAppSnapshot();
});

it('Dashboard listing table', async () => {
await PageObjects.dashboard.gotoDashboardLandingPage();
await a11y.testAppSnapshot();
});

it('Delete a11y clone dashboard', async () => {
await listingTable.searchForItemWithName(clonedDashboardName);
await listingTable.checkListingSelectAllCheckbox();
await listingTable.clickDeleteSelected();
await a11y.testAppSnapshot();
await PageObjects.common.clickConfirmOnModal();
await listingTable.searchForItemWithName('');
});

// Blocked by https://github.com/elastic/kibana/issues/38980
it.skip('Open flight dashboard', async () => {
await testSubjects.click('dashboardListingTitleLink-[Flights]-Global-Flight-Dashboard');
await a11y.testAppSnapshot();
});
});
}
7 changes: 7 additions & 0 deletions test/functional/page_objects/dashboard_page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,13 @@ export function DashboardPageProvider({ getService, getPageObjects }: FtrProvide
await this.waitForRenderComplete();
}

public async exitFullScreenMode() {
log.debug(`exitFullScreenMode`);
const logoButton = await this.getExitFullScreenLogoButton();
await logoButton.moveMouseTo();
await this.clickExitFullScreenTextButton();
}

public async fullScreenModeMenuItemExists() {
return await testSubjects.exists('dashboardFullScreenMode');
}
Expand Down
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7121,10 +7121,10 @@ aws4@^1.6.0:
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e"
integrity sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=

axe-core@^3.3.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-3.3.2.tgz#7baf3c55a5cf1621534a2c38735f5a1bf2f7e1a8"
integrity sha512-lRdxsRt7yNhqpcXQk1ao1BL73OZDzmFCWOG0mC4tGR/r14ohH2payjHwCMQjHGbBKm924eDlmG7utAGHiX/A6g==
axe-core@^3.4.1:
version "3.5.1"
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-3.5.1.tgz#d8d5aaef73f003e8b766ea28bb078343f3622201"
integrity sha512-mwpDgPwWB+5kMHyLjlxh4w25ClJfqSxi+c6LQ4ix349TdCUctMwJNPTkhPD1qP9SYIjFgjeVpVZWCvK9oBGwCg==

axios@^0.18.0, axios@^0.18.1:
version "0.18.1"
Expand Down