[ML] Adds a11y tests for ML plugin (#88323)

* [ML] Adds a11y tests for ML plugin

* [ML] Edits to a11y tests following review
This commit is contained in:
Pete Harverson 2021-01-15 16:59:31 +00:00 committed by GitHub
parent b36273693a
commit c0a833d8ea
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 401 additions and 42 deletions

View file

@ -309,7 +309,7 @@ export const DataGrid: FC<Props> = memo(
})}
>
<EuiButtonEmpty
aria-checked={chartsVisible}
aria-pressed={chartsVisible}
className={`euiDataGrid__controlBtn${
chartsVisible ? ' euiDataGrid__controlBtn--active' : ''
}`}

View file

@ -99,8 +99,8 @@ export const MultiSelectPicker: FC<{
return (
<EuiFilterGroup data-test-subj={dataTestSubj}>
<EuiPopover
ownFocus
data-test-subj={`${dataTestSubj}-popover`}
id="popoverExampleMultiSelect"
button={button}
isOpen={isPopoverOpen}
closePopover={closePopover}

View file

@ -27,7 +27,7 @@ export const AdditionalSection: FC<Props> = ({ additionalExpanded, setAdditional
<Fragment>
<EuiSpacer />
<EuiAccordion
id="advanced-section"
id="additional-section"
buttonContent={buttonContent}
onToggle={setAdditionalExpanded}
initialIsOpen={additionalExpanded}

View file

@ -1,13 +1,13 @@
# Documentation for ML UI developers
This plugin provides access to the machine learning features provided by
This plugin provides access to the machine learning features provided by
Elastic.
## Requirements
To use machine learning features, you must have a Platinum or Enterprise license
or a free 14-day Trial. File Data Visualizer requires a Basic license. For more
info, refer to
To use machine learning features, you must have a Platinum or Enterprise license
or a free 14-day Trial. File Data Visualizer requires a Basic license. For more
info, refer to
[Set up machine learning features](https://www.elastic.co/guide/en/machine-learning/master/setup.html).
## Setup local environment
@ -16,7 +16,7 @@ info, refer to
1. Fork and clone the [Kibana repo](https://github.com/elastic/kibana).
1. Install `nvm`, `node`, `yarn` (for example, by using Homebrew). See
1. Install `nvm`, `node`, `yarn` (for example, by using Homebrew). See
[Install dependencies](https://www.elastic.co/guide/en/kibana/master/development-getting-started.html#_install_dependencies).
1. Make sure that Elasticsearch is deployed and running on localhost:9200.
@ -25,45 +25,44 @@ info, refer to
1. Fetch the latest changes from the repository.
1. Checkout the branch of the version you want to use. For example, if you want
1. Checkout the branch of the version you want to use. For example, if you want
to use a 7.9 version, run `git checkout 7.9`.
1. Run `nvm use`. The response shows the Node version that the environment uses.
If you need to update your Node version, the response message contains the
1. Run `nvm use`. The response shows the Node version that the environment uses.
If you need to update your Node version, the response message contains the
command you need to run to do it.
1. Run `yarn kbn bootstrap`. It takes all the dependencies in the code and
installs/checks them. It is recommended to use it every time when you switch
1. Run `yarn kbn bootstrap`. It takes all the dependencies in the code and
installs/checks them. It is recommended to use it every time when you switch
between branches.
1. Make a copy of `kibana.yml` and save as `kibana.dev.yml`. (Git will not track
the changes in `kibana.dev.yml` but yarn will use it.)
1. Provide the appropriate password and user name in `kibana.dev.yml`.
1. Run `yarn start` to start Kibana.
1. Go to http://localhost:560x/xxx (check the terminal message for the exact
1. Go to http://localhost:560x/xxx (check the terminal message for the exact
path).
For more details, refer to this [getting started](https://www.elastic.co/guide/en/kibana/master/development-getting-started.html) page.
### Adding sample data to Kibana
Kibana has sample data sets that you can add to your setup so that you can test
Kibana has sample data sets that you can add to your setup so that you can test
different configurations on sample data.
1. Click the Elastic logo in the upper left hand corner of your browser to
1. Click the Elastic logo in the upper left hand corner of your browser to
navigate to the Kibana home page.
1. Click *Load a data set and a Kibana dashboard*.
1. Click _Load a data set and a Kibana dashboard_.
1. Pick a data set or feel free to click *Add* on all of the available sample
1. Pick a data set or feel free to click _Add_ on all of the available sample
data sets.
These data sets are now ready be analyzed in ML jobs in Kibana.
## Running tests
### Jest tests
@ -72,25 +71,25 @@ Documentation: https://www.elastic.co/guide/en/kibana/current/development-tests.
Run the test following jest tests from `kibana/x-pack/plugins/ml`.
New snapshots, all plugins:
New snapshots, all plugins:
```
yarn test:jest
```
Update snapshots for the ML plugin:
Update snapshots for the ML plugin:
```
yarn test:jest -u
```
Update snapshots for a specific directory only:
Update snapshots for a specific directory only:
```
yarn test:jest public/application/settings/filter_lists
```
Run tests with verbose output:
Run tests with verbose output:
```
yarn test:jest --verbose
@ -98,44 +97,52 @@ yarn test:jest --verbose
### Functional tests
Before running the test server, make sure to quit all other instances of
Before running the test server, make sure to quit all other instances of
Elasticsearch.
Run the following commands from the `x-pack` directory and use separate terminals
for test server and test runner. The test server command starts an Elasticsearch
and Kibana instance that the tests will be run against.
1. Functional UI tests with `Trial` license (default config):
1. Functional UI tests with `Trial` license (default config):
node scripts/functional_tests_server.js
node scripts/functional_test_runner.js --include-tag mlqa
ML functional `Trial` license tests are located in `x-pack/test/functional/apps/ml`.
1. Functional UI tests with `Basic` license:
1. Functional UI tests with `Basic` license:
node scripts/functional_tests_server.js --config test/functional_basic/config.ts
node scripts/functional_test_runner.js --config test/functional_basic/config.ts --include-tag mlqa
ML functional `Basic` license tests are located in `x-pack/test/functional_basic/apps/ml`.
1. API integration tests with `Trial` license:
1. API integration tests with `Trial` license:
node scripts/functional_tests_server.js
node scripts/functional_test_runner.js --config test/api_integration/config.ts --include-tag mlqa
ML API integration `Trial` license tests are located in `x-pack/test/api_integration/apis/ml`.
node scripts/functional_tests_server.js
node scripts/functional_test_runner.js --config test/api_integration/config.ts --include-tag mlqa
1. API integration tests with `Basic` license:
ML API integration `Trial` license tests are located in `x-pack/test/api_integration/apis/ml`.
node scripts/functional_tests_server.js --config test/api_integration_basic/config.ts
node scripts/functional_test_runner.js --config test/api_integration_basic/config.ts --include-tag mlqa
ML API integration `Basic` license tests are located in `x-pack/test/api_integration_basic/apis/ml`.
1. API integration tests with `Basic` license:
node scripts/functional_tests_server.js --config test/api_integration_basic/config.ts
node scripts/functional_test_runner.js --config test/api_integration_basic/config.ts --include-tag mlqa
ML API integration `Basic` license tests are located in `x-pack/test/api_integration_basic/apis/ml`.
1. Accessibility tests:
We maintain a suite of accessibility tests (you may see them referred to elsewhere as `a11y` tests). These tests render each of our pages and ensure that the inputs and other elements contain the attributes necessary to ensure all users are able to make use of ML (for example, users relying on screen readers).
node scripts/functional_tests_server --config test/accessibility/config.ts
node scripts/functional_test_runner.js --config test/accessibility/config.ts --grep=ml
ML accessibility tests are located in `x-pack/test/accessibility/app`.
## Shared functions
You can find the ML shared functions in the following files in GitHub:
```
@ -152,7 +159,7 @@ These functions are shared from the root of the ML plugin, you can import them w
import { MlPluginSetup } from '../../../../ml/server';
```
or
or
```
import { ANOMALY_SEVERITY } from '../../ml/common';

View file

@ -0,0 +1,351 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import path from 'path';
import { FtrProviderContext } from '../ftr_provider_context';
export default function ({ getService }: FtrProviderContext) {
const a11y = getService('a11y');
const ml = getService('ml');
describe('ml', () => {
const esArchiver = getService('esArchiver');
before(async () => {
await ml.securityCommon.createMlRoles();
await ml.securityCommon.createMlUsers();
});
after(async () => {
await ml.securityCommon.cleanMlUsers();
await ml.securityCommon.cleanMlRoles();
});
describe('for user with full ML access', function () {
before(async () => {
await ml.securityUI.loginAsMlPowerUser();
await ml.api.cleanMlIndices();
});
after(async () => {
await ml.securityUI.logout();
});
describe('with no data loaded', function () {
it('overview page', async () => {
await ml.navigation.navigateToMl();
await ml.navigation.navigateToOverview();
await a11y.testAppSnapshot();
});
it('anomaly detection page', async () => {
await ml.navigation.navigateToAnomalyDetection();
await a11y.testAppSnapshot();
});
it('data frame analytics page', async () => {
await ml.navigation.navigateToDataFrameAnalytics();
await a11y.testAppSnapshot();
});
it('settings page', async () => {
await ml.navigation.navigateToSettings();
await a11y.testAppSnapshot();
});
});
describe('with data loaded', function () {
const adJobId = 'fq_single_a11y';
const dfaOutlierJobId = 'iph_outlier_a11y';
const calendarId = 'calendar_a11y';
const eventDescription = 'calendar_event_a11y';
const filterId = 'filter_a11y';
const filterItems = ['filter_item_a11y'];
const fqIndexPattern = 'ft_farequote';
const ecIndexPattern = 'ft_module_sample_ecommerce';
const ihpIndexPattern = 'ft_ihp_outlier';
const ecExpectedTotalCount = '287';
const adJobAggAndFieldIdentifier = 'Mean(responsetime)';
const adJobBucketSpan = '30m';
const adSingleMetricJobId = `fq_single_a11y_${Date.now()}`;
const dfaJobType = 'outlier_detection';
const dfaJobId = `ihp_ally_${Date.now()}`;
const uploadFilePath = path.join(
__dirname,
'..',
'..',
'functional',
'apps',
'ml',
'data_visualizer',
'files_to_import',
'artificial_server_log'
);
before(async () => {
await esArchiver.loadIfNeeded('ml/farequote');
await esArchiver.loadIfNeeded('ml/ihp_outlier');
await esArchiver.loadIfNeeded('ml/module_sample_ecommerce');
await ml.testResources.createIndexPatternIfNeeded(fqIndexPattern, '@timestamp');
await ml.testResources.createIndexPatternIfNeeded(ihpIndexPattern, '@timestamp');
await ml.testResources.createIndexPatternIfNeeded(ecIndexPattern, 'order_date');
await ml.testResources.setKibanaTimeZoneToUTC();
await ml.api.createAndRunAnomalyDetectionLookbackJob(
ml.commonConfig.getADFqMultiMetricJobConfig(adJobId),
ml.commonConfig.getADFqDatafeedConfig(adJobId)
);
await ml.api.createAndRunDFAJob(
ml.commonConfig.getDFAIhpOutlierDetectionJobConfig(dfaOutlierJobId)
);
await ml.api.createCalendar(calendarId, {
calendar_id: calendarId,
job_ids: [],
description: 'Test calendar',
});
await ml.api.createCalendarEvents(calendarId, [
{
description: eventDescription,
start_time: 1513641600000,
end_time: 1513728000000,
},
]);
await ml.api.createFilter(filterId, {
description: 'Test filter list',
items: filterItems,
});
});
after(async () => {
await ml.api.cleanMlIndices();
await ml.api.deleteIndices(`user-${dfaOutlierJobId}`);
await ml.api.deleteCalendar(calendarId);
await ml.api.deleteFilter(filterId);
await ml.testResources.deleteIndexPatternByTitle(fqIndexPattern);
await ml.testResources.deleteIndexPatternByTitle(ihpIndexPattern);
await ml.testResources.deleteIndexPatternByTitle(ecIndexPattern);
await esArchiver.unload('ml/farequote');
await esArchiver.unload('ml/ihp_outlier');
await esArchiver.unload('ml/module_sample_ecommerce');
await ml.testResources.resetKibanaTimeZone();
});
it('overview page', async () => {
await ml.navigation.navigateToMl();
await ml.navigation.navigateToOverview();
await a11y.testAppSnapshot();
});
it('anomaly detection jobs list page', async () => {
await ml.navigation.navigateToAnomalyDetection();
await a11y.testAppSnapshot();
});
it('anomaly detection create job select index pattern page', async () => {
await ml.jobManagement.navigateToNewJobSourceSelection();
await a11y.testAppSnapshot();
});
it('anomaly detection create job select type page', async () => {
await ml.jobSourceSelection.selectSourceForAnomalyDetectionJob(fqIndexPattern);
await a11y.testAppSnapshot();
});
it('anomaly detection create single metric job time range step', async () => {
await ml.jobTypeSelection.selectSingleMetricJob();
await ml.testExecution.logTestStep('job creation set the time range');
await ml.jobWizardCommon.clickUseFullDataButton(
'Feb 7, 2016 @ 00:00:00.000',
'Feb 11, 2016 @ 23:59:54.000'
);
await a11y.testAppSnapshot();
});
it('anomaly detection create single metric job pick fields step', async () => {
await ml.jobWizardCommon.advanceToPickFieldsSection();
await ml.testExecution.logTestStep('job creation selects field and aggregation');
await ml.jobWizardCommon.selectAggAndField(adJobAggAndFieldIdentifier, true);
await ml.testExecution.logTestStep('job creation inputs the bucket span');
await ml.jobWizardCommon.setBucketSpan(adJobBucketSpan);
await a11y.testAppSnapshot();
});
it('anomaly detection create single metric job details step', async () => {
await ml.jobWizardCommon.advanceToJobDetailsSection();
await ml.testExecution.logTestStep('job creation inputs the job id');
await ml.jobWizardCommon.setJobId(adSingleMetricJobId);
await ml.testExecution.logTestStep('job creation opens the additional settings section');
await ml.jobWizardCommon.ensureAdditionalSettingsSectionOpen();
await ml.testExecution.logTestStep('job creation opens the advanced section');
await ml.jobWizardCommon.ensureAdvancedSectionOpen();
await a11y.testAppSnapshot();
});
it('anomaly detection create single metric job validation step', async () => {
await ml.jobWizardCommon.advanceToValidationSection();
await a11y.testAppSnapshot();
});
it('anomaly detection create single metric job summary step', async () => {
await ml.jobWizardCommon.advanceToSummarySection();
await a11y.testAppSnapshot();
});
it('anomaly detection Single Metric Viewer page', async () => {
await ml.navigation.navigateToMl();
await ml.navigation.navigateToAnomalyDetection();
await ml.jobTable.clickOpenJobInSingleMetricViewerButton(adJobId);
await ml.commonUI.waitForMlLoadingIndicatorToDisappear();
await ml.testExecution.logTestStep('should input the airline entity value');
await ml.singleMetricViewer.assertEntityInputExist('airline');
await ml.singleMetricViewer.assertEntityInputSelection('airline', []);
await ml.singleMetricViewer.selectEntityValue('airline', 'AAL');
await a11y.testAppSnapshot();
});
it.skip('anomaly detection Anomaly Explorer page', async () => {
// Skip test until the dots used in the Elastic chart legend no longer have duplicate ids
// see https://github.com/elastic/elastic-charts/issues/970
await ml.singleMetricViewer.openAnomalyExplorer();
await ml.commonUI.waitForMlLoadingIndicatorToDisappear();
await a11y.testAppSnapshot();
});
it('data frame analytics page', async () => {
await ml.navigation.navigateToDataFrameAnalytics();
await a11y.testAppSnapshot();
});
it('data frame analytics outlier job exploration page', async () => {
await ml.dataFrameAnalyticsTable.openResultsView(dfaOutlierJobId);
await ml.dataFrameAnalyticsResults.assertOutlierTablePanelExists();
await ml.dataFrameAnalyticsResults.assertResultsTableExists();
await ml.dataFrameAnalyticsResults.assertResultsTableNotEmpty();
await a11y.testAppSnapshot();
});
it('data frame analytics create job select index pattern modal', async () => {
await ml.navigation.navigateToDataFrameAnalytics();
await ml.dataFrameAnalytics.startAnalyticsCreation();
await a11y.testAppSnapshot();
});
it('data frame analytics create job configuration step', async () => {
await ml.testExecution.logTestStep(
'job creation selects the source data and loads the DFA job wizard page'
);
await ml.jobSourceSelection.selectSourceForAnalyticsJob(ihpIndexPattern);
await ml.dataFrameAnalyticsCreation.assertConfigurationStepActive();
await a11y.testAppSnapshot();
});
it('data frame analytics create job configuration step for outlier job', async () => {
await ml.testExecution.logTestStep('selects the outlier job type');
await ml.dataFrameAnalyticsCreation.assertJobTypeSelectExists();
await ml.dataFrameAnalyticsCreation.selectJobType(dfaJobType);
await ml.testExecution.logTestStep('displays the source data preview');
await ml.dataFrameAnalyticsCreation.assertSourceDataPreviewExists();
await ml.testExecution.logTestStep('enables the source data preview histogram charts');
await ml.dataFrameAnalyticsCreation.enableSourceDataPreviewHistogramCharts();
await a11y.testAppSnapshot();
});
it('data frame analytics create job additional options step for outlier job', async () => {
await ml.dataFrameAnalyticsCreation.continueToAdditionalOptionsStep();
await a11y.testAppSnapshot();
});
it('data frame analytics create job additional options step for outlier job', async () => {
await ml.dataFrameAnalyticsCreation.continueToDetailsStep();
await ml.dataFrameAnalyticsCreation.setJobId(dfaJobId);
await a11y.testAppSnapshot();
});
it('data frame analytics create job create step for outlier job', async () => {
await ml.dataFrameAnalyticsCreation.continueToCreateStep();
await a11y.testAppSnapshot();
});
it('settings page', async () => {
await ml.navigation.navigateToMl();
await ml.navigation.navigateToSettings();
await a11y.testAppSnapshot();
});
it('calendar management page', async () => {
await ml.settings.navigateToCalendarManagement();
await a11y.testAppSnapshot();
});
it('edit calendar page', async () => {
await ml.settingsCalendar.openCalendarEditForm(calendarId);
await a11y.testAppSnapshot();
});
it('filter list management page', async () => {
await ml.navigation.navigateToSettings();
await ml.settings.navigateToFilterListsManagement();
await a11y.testAppSnapshot();
});
it('edit filter list page', async () => {
await ml.settingsFilterList.openFilterListEditForm(filterId);
await a11y.testAppSnapshot();
});
it('data visualizer selector page', async () => {
await ml.navigation.navigateToDataVisualizer();
await a11y.testAppSnapshot();
});
it('index data visualizer select index pattern page', async () => {
await ml.dataVisualizer.navigateToIndexPatternSelection();
await a11y.testAppSnapshot();
});
it('index data visualizer page for selected index', async () => {
await ml.jobSourceSelection.selectSourceForIndexBasedDataVisualizer(ecIndexPattern);
await ml.testExecution.logTestStep('should display the time range step');
await ml.dataVisualizerIndexBased.assertTimeRangeSelectorSectionExists();
await ml.testExecution.logTestStep('should load data for full time range');
await ml.dataVisualizerIndexBased.clickUseFullDataButton(ecExpectedTotalCount);
await a11y.testAppSnapshot();
});
it('file data visualizer select file page', async () => {
await ml.navigation.navigateToDataVisualizer();
await ml.dataVisualizer.navigateToFileUpload();
await a11y.testAppSnapshot();
});
it('file data visualizer file details page', async () => {
await ml.testExecution.logTestStep(
'should select a file and load visualizer result page'
);
await ml.dataVisualizerFileBased.selectFile(uploadFilePath);
await a11y.testAppSnapshot();
});
it('file data visualizer import data page', async () => {
await ml.dataVisualizerFileBased.navigateToFileImport();
await a11y.testAppSnapshot();
});
});
});
});
}

View file

@ -27,6 +27,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
require.resolve('./apps/roles'),
require.resolve('./apps/kibana_overview'),
require.resolve('./apps/ingest_node_pipelines'),
require.resolve('./apps/ml'),
],
pageObjects,

View file

@ -125,7 +125,7 @@ export function MachineLearningDataFrameAnalyticsCreationProvider(
const actualCheckState =
(await testSubjects.getAttribute(
'mlAnalyticsCreationDataGridHistogramButton',
'aria-checked'
'aria-pressed'
)) === 'true';
expect(actualCheckState).to.eql(
expectedCheckState,

View file

@ -175,7 +175,7 @@ export function TransformWizardProvider({ getService }: FtrProviderContext) {
const actualCheckState =
(await testSubjects.getAttribute(
'transformIndexPreviewHistogramButton',
'aria-checked'
'aria-pressed'
)) === 'true';
expect(actualCheckState).to.eql(
expectedCheckState,