From 46403f1c1b1e90fc69ff22c815f4c45e4b582525 Mon Sep 17 00:00:00 2001 From: Pete Harverson Date: Wed, 19 Jul 2023 14:03:40 +0100 Subject: [PATCH] [ML] Adds a11y tests for categorization and recognizer job wizards (#162126) ## Summary Adds accessibility tests for the categorization and data recognizer anomaly detection job wizards. As part of this PR I have split out the tests for the anomaly detection pages into a separate file as the original `ml.ts` file was getting very large. Part of https://github.com/elastic/kibana/issues/160712 Part of #88496 ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --- .../components/job_settings_form.tsx | 7 +- .../jobs/new_job/recognize/page.tsx | 2 +- x-pack/test/accessibility/apps/ml.ts | 494 +------------ .../apps/ml_anomaly_detection.ts | 650 ++++++++++++++++++ x-pack/test/accessibility/config.ts | 1 + .../map_to_ml.ts | 2 +- x-pack/test/functional/services/ml/index.ts | 3 + .../services/ml/job_type_selection.ts | 9 + .../services/ml/job_wizard_recognizer.ts | 38 + 9 files changed, 710 insertions(+), 496 deletions(-) create mode 100644 x-pack/test/accessibility/apps/ml_anomaly_detection.ts create mode 100644 x-pack/test/functional/services/ml/job_wizard_recognizer.ts diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/recognize/components/job_settings_form.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/recognize/components/job_settings_form.tsx index dab33eaf74801c..71d01ff161aaed 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/recognize/components/job_settings_form.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/recognize/components/job_settings_form.tsx @@ -154,6 +154,7 @@ export const JobSettingsForm: FC = ({ value={jobPrefix} onChange={({ target: { value } }) => setJobPrefix(value)} isInvalid={!!validationResult.jobPrefix} + data-test-subj="mlJobRecognizerWizardInputJobIdPrefix" /> @@ -210,6 +211,7 @@ export const JobSettingsForm: FC = ({ /> } paddingSize="l" + data-test-subj="mlJobWizardToggleAdvancedSection" > = ({ /> } > - + = ({ moduleId, existingGroupIds }) => { {jobsAwaitingNodeCount > 0 && } - + diff --git a/x-pack/test/accessibility/apps/ml.ts b/x-pack/test/accessibility/apps/ml.ts index 9043b06dbc443e..dd2fad6de05de9 100644 --- a/x-pack/test/accessibility/apps/ml.ts +++ b/x-pack/test/accessibility/apps/ml.ts @@ -7,22 +7,11 @@ import { FtrProviderContext } from '../ftr_provider_context'; -interface Detector { - identifier: string; - function: string; - field?: string; - byField?: string; - overField?: string; - partitionField?: string; - excludeFrequent?: string; - description?: string; -} - export default function ({ getService }: FtrProviderContext) { const a11y = getService('a11y'); const ml = getService('ml'); - describe('ml Accessibility', () => { + describe('ml Accessibility', function () { const esArchiver = getService('esArchiver'); before(async () => { @@ -53,44 +42,20 @@ export default function ({ getService }: FtrProviderContext) { 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 dfaOutlierResultsJobId = '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 egsIndexPattern = 'ft_egs_regression'; const bmIndexPattern = 'ft_bank_marketing'; const ecExpectedTotalCount = '287'; - const adJobAggAndFieldIdentifier = 'Mean(responsetime)'; - const adJobBucketSpan = '30m'; - const adSingleMetricJobId = `fq_single_a11y_${Date.now()}`; - const adMultiSplitField = 'airline'; - const adMultiMetricJobId = `fq_multi_a11y_${Date.now()}`; - const adMultiMetricJobDescription = - 'Multi metric job based on the farequote dataset with 30m bucketspan and mean(responsetime) split by airline'; - const dfaOutlierJobType = 'outlier_detection'; const dfaOutlierJobId = `ihp_outlier_ally_${Date.now()}`; const dfaRegressionJobType = 'regression'; @@ -106,169 +71,32 @@ export default function ({ getService }: FtrProviderContext) { '../../functional/apps/ml/data_visualizer/files_to_import/artificial_server_log' ); - const advancedJobTestData = { - suiteTitle: 'with multiple metric detectors and custom datafeed settings', - jobSource: ecIndexPattern, - jobId: `ec_advanced_1_${Date.now()}`, - get jobIdClone(): string { - return `${this.jobId}_clone`; - }, - jobDescription: `Create advanced job from ${ecIndexPattern} dataset with multiple metric detectors and custom datafeed settings`, - jobGroups: ['automated', 'ecommerce', 'advanced'], - get jobGroupsClone(): string[] { - return [...this.jobGroups, 'clone']; - }, - pickFieldsConfig: { - detectors: [ - { - identifier: 'high_count', - function: 'high_count', - description: 'high_count detector without split', - } as Detector, - { - identifier: 'mean("products.base_price") by "category.keyword"', - function: 'mean', - field: 'products.base_price', - byField: 'category.keyword', - } as Detector, - { - identifier: 'sum("products.discount_amount") over customer_id', - function: 'sum', - field: 'products.discount_amount', - overField: 'customer_id', - } as Detector, - { - identifier: 'median(total_quantity) partition_field_name=customer_gender', - function: 'median', - field: 'total_quantity', - partitionField: 'customer_gender', - } as Detector, - { - identifier: - 'max(total_quantity) by "geoip.continent_name" over customer_id partition_field_name=customer_gender', - function: 'max', - field: 'total_quantity', - byField: 'geoip.continent_name', - overField: 'customer_id', - partitionField: 'customer_gender', - } as Detector, - ], - influencers: [ - 'customer_id', - 'category.keyword', - 'geoip.continent_name', - 'customer_gender', - ], - bucketSpan: '1h', - memoryLimit: '10mb', - }, - datafeedConfig: { - queryDelay: '55s', - frequency: '350s', - scrollSize: '999', - }, - }; - const populationJobTestData = { - suiteTitle: 'population job', - jobSource: ecIndexPattern, - jobId: `ec_population_1_${Date.now()}`, - get jobIdClone(): string { - return `${this.jobId}_clone`; - }, - jobDescription: - 'Create population job based on the ecommerce sample dataset with 2h bucketspan over customer_id' + - ' - detectors: (Mean(products.base_price) by customer_gender), (Mean(products.quantity) by category.leyword)', - jobGroups: ['automated', 'ecommerce', 'population'], - get jobGroupsClone(): string[] { - return [...this.jobGroups, 'clone']; - }, - populationField: 'customer_id', - pickFieldsConfig: { - detectors: [ - { - identifier: 'Mean(products.base_price)', - splitField: 'customer_gender', - frontCardTitle: 'FEMALE', - numberOfBackCards: 1, - }, - { - identifier: 'Mean(products.quantity)', - splitField: 'category.keyword', - frontCardTitle: "Men's Clothing", - numberOfBackCards: 5, - }, - ], - influencers: [ - 'customer_id', - 'category.keyword', - 'geoip.continent_name', - 'customer_gender', - ], - bucketSpan: '2h', - memoryLimit: '8mb', - }, - datafeedConfig: { - queryDelay: '55s', - frequency: '350s', - scrollSize: '999', - }, - }; - before(async () => { - await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/farequote'); await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/ihp_outlier'); await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/egs_regression'); await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/bm_classification'); await esArchiver.loadIfNeeded( 'x-pack/test/functional/es_archives/ml/module_sample_ecommerce' ); - await ml.testResources.createIndexPatternIfNeeded(fqIndexPattern, '@timestamp'); await ml.testResources.createIndexPatternIfNeeded(ihpIndexPattern); await ml.testResources.createIndexPatternIfNeeded(egsIndexPattern); await ml.testResources.createIndexPatternIfNeeded(bmIndexPattern); 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(dfaOutlierResultsJobId) ); - - 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-${dfaOutlierResultsJobId}`); - await ml.api.deleteCalendar(calendarId); - await ml.api.deleteFilter(filterId); - await ml.testResources.deleteIndexPatternByTitle(fqIndexPattern); await ml.testResources.deleteIndexPatternByTitle(ihpIndexPattern); await ml.testResources.deleteIndexPatternByTitle(egsIndexPattern); await ml.testResources.deleteIndexPatternByTitle(bmIndexPattern); await ml.testResources.deleteIndexPatternByTitle(ecIndexPattern); - await esArchiver.unload('x-pack/test/functional/es_archives/ml/farequote'); await esArchiver.unload('x-pack/test/functional/es_archives/ml/ihp_outlier'); await esArchiver.unload('x-pack/test/functional/es_archives/ml/egs_regression'); await esArchiver.unload('x-pack/test/functional/es_archives/ml/bm_classification'); @@ -282,299 +110,6 @@ export default function ({ getService }: FtrProviderContext) { 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 create multi metric job and move to time range step', async () => { - // Proceed all the way to the step for selecting the time range - // as the other steps have already been tested for the single metric job - await ml.navigation.navigateToAnomalyDetection(); - await ml.jobManagement.navigateToNewJobSourceSelection(); - await ml.jobSourceSelection.selectSourceForAnomalyDetectionJob(fqIndexPattern); - await ml.jobTypeSelection.selectMultiMetricJob(); - 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 multi 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, false); - await ml.testExecution.logTestStep('job creation selects split field'); - await ml.jobWizardMultiMetric.selectSplitField(adMultiSplitField); - await ml.testExecution.logTestStep('job creation inputs the bucket span'); - await ml.jobWizardCommon.setBucketSpan(adJobBucketSpan); - await a11y.testAppSnapshot(); - }); - - it('anomaly detection create multi metric job details step', async () => { - await ml.jobWizardCommon.advanceToJobDetailsSection(); - await ml.testExecution.logTestStep('job creation inputs the job id'); - await ml.jobWizardCommon.setJobId(adMultiMetricJobId); - await ml.testExecution.logTestStep('job creation inputs the job description'); - await ml.jobWizardCommon.setJobDescription(adMultiMetricJobDescription); - 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 multi metric job validation step', async () => { - await ml.jobWizardCommon.advanceToValidationSection(); - await a11y.testAppSnapshot(); - }); - - it('anomaly detection create multi metric job summary step', async () => { - await ml.jobWizardCommon.advanceToSummarySection(); - await a11y.testAppSnapshot(); - }); - - it('anomaly detection create advanced job open wizard', async () => { - await ml.navigation.navigateToMl(); - await ml.navigation.navigateToJobManagement(); - - await ml.jobManagement.navigateToNewJobSourceSelection(); - - await ml.jobSourceSelection.selectSourceForAnomalyDetectionJob( - advancedJobTestData.jobSource - ); - - await ml.jobTypeSelection.selectAdvancedJob(); - await a11y.testAppSnapshot(); - }); - - it('anomaly detection create advanced job pick fields step', async () => { - await ml.jobWizardCommon.advanceToPickFieldsSection(); - await a11y.testAppSnapshot(); - - for (const detector of advancedJobTestData.pickFieldsConfig.detectors) { - await ml.jobWizardAdvanced.openCreateDetectorModal(); - await a11y.testAppSnapshot(); - - await ml.jobWizardAdvanced.selectDetectorFunction(detector.function); - if (detector.hasOwnProperty('field')) { - await ml.jobWizardAdvanced.selectDetectorField(detector.field!); - } - if (detector.hasOwnProperty('byField')) { - await ml.jobWizardAdvanced.selectDetectorByField(detector.byField!); - } - if (detector.hasOwnProperty('overField')) { - await ml.jobWizardAdvanced.selectDetectorOverField(detector.overField!); - } - if (detector.hasOwnProperty('partitionField')) { - await ml.jobWizardAdvanced.selectDetectorPartitionField(detector.partitionField!); - } - if (detector.hasOwnProperty('excludeFrequent')) { - await ml.jobWizardAdvanced.selectDetectorExcludeFrequent(detector.excludeFrequent!); - } - if (detector.hasOwnProperty('description')) { - await ml.jobWizardAdvanced.setDetectorDescription(detector.description!); - } - - await ml.jobWizardAdvanced.confirmAddDetectorModal(); - } - - await ml.testExecution.logTestStep('job creation inputs the bucket span'); - await ml.jobWizardCommon.setBucketSpan(advancedJobTestData.pickFieldsConfig.bucketSpan); - - await ml.testExecution.logTestStep('job creation inputs influencers'); - for (const influencer of advancedJobTestData.pickFieldsConfig.influencers) { - await ml.jobWizardCommon.addInfluencer(influencer); - } - - await ml.testExecution.logTestStep('job creation inputs the model memory limit'); - await ml.jobWizardCommon.setModelMemoryLimit( - advancedJobTestData.pickFieldsConfig.memoryLimit, - { - withAdvancedSection: false, - } - ); - await a11y.testAppSnapshot(); - }); - - it('anomaly detection create advanced job job details step', async () => { - await ml.jobWizardCommon.advanceToJobDetailsSection(); - await ml.jobWizardCommon.setJobId(advancedJobTestData.jobId); - await ml.jobWizardCommon.setJobDescription(advancedJobTestData.jobDescription); - for (const jobGroup of advancedJobTestData.jobGroups) { - await ml.jobWizardCommon.addJobGroup(jobGroup); - } - await ml.jobWizardCommon.ensureAdditionalSettingsSectionOpen(); - await ml.jobWizardCommon.addCustomUrl({ label: 'check-kibana-dashboard' }); - await ml.jobWizardCommon.addCalendar(calendarId); - await a11y.testAppSnapshot(); - }); - - it('anomaly detection create advanced job validation step', async () => { - await ml.jobWizardCommon.advanceToValidationSection(); - await a11y.testAppSnapshot(); - }); - - it('anomaly detection create advanced job job summary step', async () => { - await ml.jobWizardCommon.advanceToSummarySection(); - await a11y.testAppSnapshot(); - }); - - it('anomaly detection create population job open wizard', async () => { - await ml.navigation.navigateToJobManagement(); - await ml.jobManagement.navigateToNewJobSourceSelection(); - - await ml.jobSourceSelection.selectSourceForAnomalyDetectionJob(ecIndexPattern); - - await ml.testExecution.logTestStep('job creation loads the population job wizard page'); - await ml.jobTypeSelection.selectPopulationJob(); - await a11y.testAppSnapshot(); - }); - - it('anomaly detection create population job pick fields step', async () => { - await ml.jobWizardCommon.advanceToPickFieldsSection(); - await ml.jobWizardPopulation.selectPopulationField(populationJobTestData.populationField); - for (const [ - index, - detector, - ] of populationJobTestData.pickFieldsConfig.detectors.entries()) { - await ml.jobWizardCommon.selectAggAndField(detector.identifier, false); - await ml.jobWizardCommon.assertDetectorPreviewExists( - detector.identifier, - index, - 'SCATTER' - ); - } - - for (const [ - index, - detector, - ] of populationJobTestData.pickFieldsConfig.detectors.entries()) { - await ml.jobWizardPopulation.assertDetectorSplitFieldInputExists(index); - await ml.jobWizardPopulation.selectDetectorSplitField(index, detector.splitField); - } - await ml.jobWizardCommon.setBucketSpan(populationJobTestData.pickFieldsConfig.bucketSpan); - await a11y.testAppSnapshot(); - }); - - it('anomaly detection create population job details step', async () => { - await ml.jobWizardCommon.advanceToJobDetailsSection(); - - await ml.jobWizardCommon.setJobId(populationJobTestData.jobId); - await ml.jobWizardCommon.setJobDescription(populationJobTestData.jobDescription); - for (const jobGroup of populationJobTestData.jobGroups) { - await ml.jobWizardCommon.addJobGroup(jobGroup); - } - await ml.jobWizardCommon.ensureAdditionalSettingsSectionOpen(); - await ml.jobWizardCommon.addCustomUrl({ label: 'check-kibana-dashboard' }); - await ml.jobWizardCommon.addCalendar(calendarId); - - await ml.testExecution.logTestStep('job creation inputs the model memory limit'); - await ml.jobWizardCommon.setModelMemoryLimit( - populationJobTestData.pickFieldsConfig.memoryLimit - ); - - await a11y.testAppSnapshot(); - }); - - it('anomaly detection create population job validation step', async () => { - await ml.jobWizardCommon.advanceToValidationSection(); - await a11y.testAppSnapshot(); - }); - - it('anomaly detection create population 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('anomaly detection forecasting from Single Metric Viewer page', async () => { - await ml.testExecution.logTestStep('opens the forecasting modal showing no forecasts'); - await ml.forecast.openForecastModal(); - await a11y.testAppSnapshot(); - - await ml.testExecution.logTestStep('run the forecast and close the modal'); - await ml.forecast.clickForecastModalRunButton(); - - await ml.testExecution.logTestStep('opens the forecasting modal showing a forecast'); - await ml.forecast.openForecastModal(); - await a11y.testAppSnapshot(); - - await ml.testExecution.logTestStep('closes the forecasting modal'); - await ml.forecast.closeForecastModal(); - }); - - it('anomaly detection Anomaly Explorer page', async () => { - await ml.singleMetricViewer.openAnomalyExplorer(); - await ml.commonUI.waitForMlLoadingIndicatorToDisappear(); - await a11y.testAppSnapshot(); - }); - it('data frame analytics page', async () => { await ml.navigation.navigateToDataFrameAnalytics(); await a11y.testAppSnapshot(); @@ -734,33 +269,6 @@ export default function ({ getService }: FtrProviderContext) { 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(); diff --git a/x-pack/test/accessibility/apps/ml_anomaly_detection.ts b/x-pack/test/accessibility/apps/ml_anomaly_detection.ts new file mode 100644 index 00000000000000..c8cff8616f23c8 --- /dev/null +++ b/x-pack/test/accessibility/apps/ml_anomaly_detection.ts @@ -0,0 +1,650 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../ftr_provider_context'; + +interface Detector { + identifier: string; + function: string; + field?: string; + byField?: string; + overField?: string; + partitionField?: string; + excludeFrequent?: string; + description?: string; +} + +export default function ({ getService }: FtrProviderContext) { + const a11y = getService('a11y'); + const ml = getService('ml'); + + describe('machine learning anomaly detection Accessibility', function () { + 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 () => { + // NOTE: Logout needs to happen before anything else to avoid flaky behavior + await ml.securityUI.logout(); + }); + + describe('with no data loaded', function () { + it('anomaly detection page', async () => { + await ml.navigation.navigateToMl(); + await ml.navigation.navigateToAnomalyDetection(); + await a11y.testAppSnapshot(); + }); + + it('anomaly detection settings page', async () => { + await ml.navigation.navigateToSettings(); + await a11y.testAppSnapshot(); + }); + }); + + describe('with data loaded', function () { + const adJobId = 'fq_single_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 categorizationIndexPattern = 'ft_categorization_small'; + + const adJobAggAndFieldIdentifier = 'Mean(responsetime)'; + const adJobBucketSpan = '30m'; + const adSingleMetricJobId = `fq_single_a11y_${Date.now()}`; + const adMultiSplitField = 'airline'; + const adMultiMetricJobId = `fq_multi_a11y_${Date.now()}`; + const adMultiMetricJobDescription = + 'Multi metric job based on the farequote dataset with 30m bucketspan and mean(responsetime) split by airline'; + const adCategorizationDetectorType = 'Rare'; + const adCategorizationFieldIdentifier = 'field1'; + const adCategorizationJobId = `categorization_a11y_${Date.now()}`; + const adCategorizationJobDescription = + 'categorization job based on the ft_categorization dataset looking for rare field1 values'; + const adRecognizerJobModuleId = 'sample_data_ecommerce'; + const adRecognizerJobIdPrefix = 'ally_'; + + const advancedJobTestData = { + suiteTitle: 'with multiple metric detectors and custom datafeed settings', + jobSource: ecIndexPattern, + jobId: `ec_advanced_1_${Date.now()}`, + get jobIdClone(): string { + return `${this.jobId}_clone`; + }, + jobDescription: `Create advanced job from ${ecIndexPattern} dataset with multiple metric detectors and custom datafeed settings`, + jobGroups: ['automated', 'ecommerce', 'advanced'], + get jobGroupsClone(): string[] { + return [...this.jobGroups, 'clone']; + }, + pickFieldsConfig: { + detectors: [ + { + identifier: 'high_count', + function: 'high_count', + description: 'high_count detector without split', + } as Detector, + { + identifier: 'mean("products.base_price") by "category.keyword"', + function: 'mean', + field: 'products.base_price', + byField: 'category.keyword', + } as Detector, + { + identifier: 'sum("products.discount_amount") over customer_id', + function: 'sum', + field: 'products.discount_amount', + overField: 'customer_id', + } as Detector, + { + identifier: 'median(total_quantity) partition_field_name=customer_gender', + function: 'median', + field: 'total_quantity', + partitionField: 'customer_gender', + } as Detector, + { + identifier: + 'max(total_quantity) by "geoip.continent_name" over customer_id partition_field_name=customer_gender', + function: 'max', + field: 'total_quantity', + byField: 'geoip.continent_name', + overField: 'customer_id', + partitionField: 'customer_gender', + } as Detector, + ], + influencers: [ + 'customer_id', + 'category.keyword', + 'geoip.continent_name', + 'customer_gender', + ], + bucketSpan: '1h', + memoryLimit: '10mb', + }, + datafeedConfig: { + queryDelay: '55s', + frequency: '350s', + scrollSize: '999', + }, + }; + const populationJobTestData = { + suiteTitle: 'population job', + jobSource: ecIndexPattern, + jobId: `ec_population_1_${Date.now()}`, + get jobIdClone(): string { + return `${this.jobId}_clone`; + }, + jobDescription: + 'Create population job based on the ecommerce sample dataset with 2h bucketspan over customer_id' + + ' - detectors: (Mean(products.base_price) by customer_gender), (Mean(products.quantity) by category.leyword)', + jobGroups: ['automated', 'ecommerce', 'population'], + get jobGroupsClone(): string[] { + return [...this.jobGroups, 'clone']; + }, + populationField: 'customer_id', + pickFieldsConfig: { + detectors: [ + { + identifier: 'Mean(products.base_price)', + splitField: 'customer_gender', + frontCardTitle: 'FEMALE', + numberOfBackCards: 1, + }, + { + identifier: 'Mean(products.quantity)', + splitField: 'category.keyword', + frontCardTitle: "Men's Clothing", + numberOfBackCards: 5, + }, + ], + influencers: [ + 'customer_id', + 'category.keyword', + 'geoip.continent_name', + 'customer_gender', + ], + bucketSpan: '2h', + memoryLimit: '8mb', + }, + datafeedConfig: { + queryDelay: '55s', + frequency: '350s', + scrollSize: '999', + }, + }; + + before(async () => { + await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/farequote'); + await esArchiver.loadIfNeeded( + 'x-pack/test/functional/es_archives/ml/module_sample_ecommerce' + ); + await esArchiver.loadIfNeeded( + 'x-pack/test/functional/es_archives/ml/categorization_small' + ); + await ml.testResources.createIndexPatternIfNeeded(fqIndexPattern, '@timestamp'); + await ml.testResources.createIndexPatternIfNeeded(ecIndexPattern, 'order_date'); + await ml.testResources.createIndexPatternIfNeeded( + 'ft_categorization_small', + '@timestamp' + ); + await ml.testResources.setKibanaTimeZoneToUTC(); + + await ml.api.createAndRunAnomalyDetectionLookbackJob( + ml.commonConfig.getADFqMultiMetricJobConfig(adJobId), + ml.commonConfig.getADFqDatafeedConfig(adJobId) + ); + + 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.deleteCalendar(calendarId); + await ml.api.deleteFilter(filterId); + + await ml.testResources.deleteIndexPatternByTitle(fqIndexPattern); + await ml.testResources.deleteIndexPatternByTitle(ecIndexPattern); + await ml.testResources.deleteIndexPatternByTitle(categorizationIndexPattern); + await esArchiver.unload('x-pack/test/functional/es_archives/ml/farequote'); + await esArchiver.unload('x-pack/test/functional/es_archives/ml/module_sample_ecommerce'); + await esArchiver.unload('x-pack/test/functional/es_archives/ml/categorization_small'); + await ml.testResources.resetKibanaTimeZone(); + }); + + it('anomaly detection jobs list page', async () => { + await ml.navigation.navigateToMl(); + 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 create multi metric job and move to time range step', async () => { + // Proceed all the way to the step for selecting the time range + // as the other steps have already been tested for the single metric job + await ml.navigation.navigateToAnomalyDetection(); + await ml.jobManagement.navigateToNewJobSourceSelection(); + await ml.jobSourceSelection.selectSourceForAnomalyDetectionJob(fqIndexPattern); + await ml.jobTypeSelection.selectMultiMetricJob(); + 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 multi 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, false); + await ml.testExecution.logTestStep('job creation selects split field'); + await ml.jobWizardMultiMetric.selectSplitField(adMultiSplitField); + await ml.testExecution.logTestStep('job creation inputs the bucket span'); + await ml.jobWizardCommon.setBucketSpan(adJobBucketSpan); + await a11y.testAppSnapshot(); + }); + + it('anomaly detection create multi metric job details step', async () => { + await ml.jobWizardCommon.advanceToJobDetailsSection(); + await ml.testExecution.logTestStep('job creation inputs the job id'); + await ml.jobWizardCommon.setJobId(adMultiMetricJobId); + await ml.testExecution.logTestStep('job creation inputs the job description'); + await ml.jobWizardCommon.setJobDescription(adMultiMetricJobDescription); + 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 multi metric job validation step', async () => { + await ml.jobWizardCommon.advanceToValidationSection(); + await a11y.testAppSnapshot(); + }); + + it('anomaly detection create multi metric job summary step', async () => { + await ml.jobWizardCommon.advanceToSummarySection(); + await a11y.testAppSnapshot(); + }); + + it('anomaly detection create advanced job open wizard', async () => { + await ml.navigation.navigateToMl(); + await ml.navigation.navigateToJobManagement(); + + await ml.jobManagement.navigateToNewJobSourceSelection(); + + await ml.jobSourceSelection.selectSourceForAnomalyDetectionJob( + advancedJobTestData.jobSource + ); + + await ml.jobTypeSelection.selectAdvancedJob(); + await a11y.testAppSnapshot(); + }); + + it('anomaly detection create advanced job pick fields step', async () => { + await ml.jobWizardCommon.advanceToPickFieldsSection(); + await a11y.testAppSnapshot(); + + for (const detector of advancedJobTestData.pickFieldsConfig.detectors) { + await ml.jobWizardAdvanced.openCreateDetectorModal(); + await a11y.testAppSnapshot(); + + await ml.jobWizardAdvanced.selectDetectorFunction(detector.function); + if (detector.hasOwnProperty('field')) { + await ml.jobWizardAdvanced.selectDetectorField(detector.field!); + } + if (detector.hasOwnProperty('byField')) { + await ml.jobWizardAdvanced.selectDetectorByField(detector.byField!); + } + if (detector.hasOwnProperty('overField')) { + await ml.jobWizardAdvanced.selectDetectorOverField(detector.overField!); + } + if (detector.hasOwnProperty('partitionField')) { + await ml.jobWizardAdvanced.selectDetectorPartitionField(detector.partitionField!); + } + if (detector.hasOwnProperty('excludeFrequent')) { + await ml.jobWizardAdvanced.selectDetectorExcludeFrequent(detector.excludeFrequent!); + } + if (detector.hasOwnProperty('description')) { + await ml.jobWizardAdvanced.setDetectorDescription(detector.description!); + } + + await ml.jobWizardAdvanced.confirmAddDetectorModal(); + } + + await ml.testExecution.logTestStep('job creation inputs the bucket span'); + await ml.jobWizardCommon.setBucketSpan(advancedJobTestData.pickFieldsConfig.bucketSpan); + + await ml.testExecution.logTestStep('job creation inputs influencers'); + for (const influencer of advancedJobTestData.pickFieldsConfig.influencers) { + await ml.jobWizardCommon.addInfluencer(influencer); + } + + await ml.testExecution.logTestStep('job creation inputs the model memory limit'); + await ml.jobWizardCommon.setModelMemoryLimit( + advancedJobTestData.pickFieldsConfig.memoryLimit, + { + withAdvancedSection: false, + } + ); + await a11y.testAppSnapshot(); + }); + + it('anomaly detection create advanced job job details step', async () => { + await ml.jobWizardCommon.advanceToJobDetailsSection(); + await ml.jobWizardCommon.setJobId(advancedJobTestData.jobId); + await ml.jobWizardCommon.setJobDescription(advancedJobTestData.jobDescription); + for (const jobGroup of advancedJobTestData.jobGroups) { + await ml.jobWizardCommon.addJobGroup(jobGroup); + } + await ml.jobWizardCommon.ensureAdditionalSettingsSectionOpen(); + await ml.jobWizardCommon.addCustomUrl({ label: 'check-kibana-dashboard' }); + await ml.jobWizardCommon.addCalendar(calendarId); + await a11y.testAppSnapshot(); + }); + + it('anomaly detection create advanced job validation step', async () => { + await ml.jobWizardCommon.advanceToValidationSection(); + await a11y.testAppSnapshot(); + }); + + it('anomaly detection create advanced job job summary step', async () => { + await ml.jobWizardCommon.advanceToSummarySection(); + await a11y.testAppSnapshot(); + }); + + it('anomaly detection create population job open wizard', async () => { + await ml.navigation.navigateToJobManagement(); + await ml.jobManagement.navigateToNewJobSourceSelection(); + + await ml.jobSourceSelection.selectSourceForAnomalyDetectionJob(ecIndexPattern); + + await ml.testExecution.logTestStep('job creation loads the population job wizard page'); + await ml.jobTypeSelection.selectPopulationJob(); + await a11y.testAppSnapshot(); + }); + + it('anomaly detection create population job pick fields step', async () => { + await ml.jobWizardCommon.advanceToPickFieldsSection(); + await ml.jobWizardPopulation.selectPopulationField(populationJobTestData.populationField); + for (const [ + index, + detector, + ] of populationJobTestData.pickFieldsConfig.detectors.entries()) { + await ml.jobWizardCommon.selectAggAndField(detector.identifier, false); + await ml.jobWizardCommon.assertDetectorPreviewExists( + detector.identifier, + index, + 'SCATTER' + ); + } + + for (const [ + index, + detector, + ] of populationJobTestData.pickFieldsConfig.detectors.entries()) { + await ml.jobWizardPopulation.assertDetectorSplitFieldInputExists(index); + await ml.jobWizardPopulation.selectDetectorSplitField(index, detector.splitField); + } + await ml.jobWizardCommon.setBucketSpan(populationJobTestData.pickFieldsConfig.bucketSpan); + await a11y.testAppSnapshot(); + }); + + it('anomaly detection create population job details step', async () => { + await ml.jobWizardCommon.advanceToJobDetailsSection(); + + await ml.jobWizardCommon.setJobId(populationJobTestData.jobId); + await ml.jobWizardCommon.setJobDescription(populationJobTestData.jobDescription); + for (const jobGroup of populationJobTestData.jobGroups) { + await ml.jobWizardCommon.addJobGroup(jobGroup); + } + await ml.jobWizardCommon.ensureAdditionalSettingsSectionOpen(); + await ml.jobWizardCommon.addCustomUrl({ label: 'check-kibana-dashboard' }); + await ml.jobWizardCommon.addCalendar(calendarId); + + await ml.testExecution.logTestStep('job creation inputs the model memory limit'); + await ml.jobWizardCommon.setModelMemoryLimit( + populationJobTestData.pickFieldsConfig.memoryLimit + ); + + await a11y.testAppSnapshot(); + }); + + it('anomaly detection create population job validation step', async () => { + await ml.jobWizardCommon.advanceToValidationSection(); + await a11y.testAppSnapshot(); + }); + + it('anomaly detection create population job summary step', async () => { + await ml.jobWizardCommon.advanceToSummarySection(); + await a11y.testAppSnapshot(); + }); + + it('anomaly detection create categorization job and move to time range step', async () => { + await ml.navigation.navigateToJobManagement(); + await ml.jobManagement.navigateToNewJobSourceSelection(); + + await ml.jobSourceSelection.selectSourceForAnomalyDetectionJob( + categorizationIndexPattern + ); + + await ml.testExecution.logTestStep( + 'job creation loads the categorization job wizard page' + ); + await ml.jobTypeSelection.selectCategorizationJob(); + await ml.testExecution.logTestStep('job creation set the time range'); + await ml.jobWizardCommon.clickUseFullDataButton( + 'Apr 5, 2019 @ 11:25:35.770', + 'Nov 21, 2019 @ 00:01:13.923' + ); + await a11y.testAppSnapshot(); + }); + + it('anomaly detection create categorization job pick fields step', async () => { + await ml.jobWizardCommon.advanceToPickFieldsSection(); + await ml.testExecution.logTestStep( + `job creation selects ${adCategorizationDetectorType} detector type` + ); + await ml.jobWizardCategorization.selectCategorizationDetectorType( + adCategorizationDetectorType + ); + await ml.testExecution.logTestStep(`job creation selects the categorization field`); + await ml.jobWizardCategorization.selectCategorizationField( + adCategorizationFieldIdentifier + ); + await a11y.testAppSnapshot(); + }); + + it('anomaly detection create categorization job details step', async () => { + await ml.jobWizardCommon.advanceToJobDetailsSection(); + await ml.testExecution.logTestStep('job creation inputs the job id'); + await ml.jobWizardCommon.setJobId(adCategorizationJobId); + await ml.testExecution.logTestStep('job creation inputs the job description'); + await ml.jobWizardCommon.setJobDescription(adCategorizationJobDescription); + 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 categorization job validation step', async () => { + await ml.jobWizardCommon.advanceToValidationSection(); + await a11y.testAppSnapshot(); + }); + + it('anomaly detection create categorization job summary step', async () => { + await ml.jobWizardCommon.advanceToSummarySection(); + await a11y.testAppSnapshot(); + }); + + it('anomaly detection create job from data recognizer module open wizard', async () => { + await ml.navigation.navigateToJobManagement(); + await ml.jobManagement.navigateToNewJobSourceSelection(); + await ml.jobSourceSelection.selectSourceForAnomalyDetectionJob(ecIndexPattern); + await ml.testExecution.logTestStep( + `job creation loads the data recognizer job wizard page for the ${adRecognizerJobModuleId} module` + ); + await ml.jobTypeSelection.selectRecognizerJob(adRecognizerJobModuleId); + await a11y.testAppSnapshot(); + }); + + it('anomaly detection create data recognizer job details step', async () => { + await ml.testExecution.logTestStep('job creation inputs the job id prefix'); + await ml.jobWizardRecognizer.setJobIdPrefix(adRecognizerJobIdPrefix); + await ml.testExecution.logTestStep('job creation opens the advanced section'); + await ml.jobWizardCommon.ensureAdvancedSectionOpen(); + 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('anomaly detection forecasting from Single Metric Viewer page', async () => { + await ml.testExecution.logTestStep('opens the forecasting modal showing no forecasts'); + await ml.forecast.openForecastModal(); + await a11y.testAppSnapshot(); + + await ml.testExecution.logTestStep('run the forecast and close the modal'); + await ml.forecast.clickForecastModalRunButton(); + + await ml.testExecution.logTestStep('opens the forecasting modal showing a forecast'); + await ml.forecast.openForecastModal(); + await a11y.testAppSnapshot(); + + await ml.testExecution.logTestStep('closes the forecasting modal'); + await ml.forecast.closeForecastModal(); + }); + + it('anomaly detection Anomaly Explorer page', async () => { + await ml.singleMetricViewer.openAnomalyExplorer(); + await ml.commonUI.waitForMlLoadingIndicatorToDisappear(); + await a11y.testAppSnapshot(); + }); + + it('anomaly detection settings page', async () => { + await ml.navigation.navigateToMl(); + await ml.navigation.navigateToSettings(); + await a11y.testAppSnapshot(); + }); + + it('anomaly detection calendar management page', async () => { + await ml.settings.navigateToCalendarManagement(); + await a11y.testAppSnapshot(); + }); + + it('anomaly detection edit calendar page', async () => { + await ml.settingsCalendar.openCalendarEditForm(calendarId); + await a11y.testAppSnapshot(); + }); + + it('anomaly detection filter list management page', async () => { + await ml.navigation.navigateToSettings(); + await ml.settings.navigateToFilterListsManagement(); + await a11y.testAppSnapshot(); + }); + + it('anomaly detection edit filter list page', async () => { + await ml.settingsFilterList.openFilterListEditForm(filterId); + await a11y.testAppSnapshot(); + }); + }); + }); + }); +} diff --git a/x-pack/test/accessibility/config.ts b/x-pack/test/accessibility/config.ts index dd059ff56e3b77..1065fc863e3581 100644 --- a/x-pack/test/accessibility/config.ts +++ b/x-pack/test/accessibility/config.ts @@ -34,6 +34,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { require.resolve('./apps/ingest_node_pipelines'), require.resolve('./apps/index_lifecycle_management'), require.resolve('./apps/ml'), + require.resolve('./apps/ml_anomaly_detection'), require.resolve('./apps/transform'), require.resolve('./apps/lens'), require.resolve('./apps/upgrade_assistant'), diff --git a/x-pack/test/functional/apps/ml/anomaly_detection_integrations/map_to_ml.ts b/x-pack/test/functional/apps/ml/anomaly_detection_integrations/map_to_ml.ts index a5a87b7ed18472..63cac23c5ed1d8 100644 --- a/x-pack/test/functional/apps/ml/anomaly_detection_integrations/map_to_ml.ts +++ b/x-pack/test/functional/apps/ml/anomaly_detection_integrations/map_to_ml.ts @@ -77,7 +77,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await ml.commonUI.waitForMlLoadingIndicatorToDisappear(); - await ml.testExecution.logTestStep('Exploror page loaded'); + await ml.testExecution.logTestStep('Explorer page loaded'); await ml.lensVisualizations.anomalyExplorerPageLoaded(); await ml.testExecution.logTestStep('pre-fills the job selection'); diff --git a/x-pack/test/functional/services/ml/index.ts b/x-pack/test/functional/services/ml/index.ts index dcff82f60a02dd..b920b173e2080e 100644 --- a/x-pack/test/functional/services/ml/index.ts +++ b/x-pack/test/functional/services/ml/index.ts @@ -36,6 +36,7 @@ import { MachineLearningJobWizardCommonProvider } from './job_wizard_common'; import { MachineLearningJobWizardCategorizationProvider } from './job_wizard_categorization'; import { MachineLearningJobWizardMultiMetricProvider } from './job_wizard_multi_metric'; import { MachineLearningJobWizardPopulationProvider } from './job_wizard_population'; +import { MachineLearningJobWizardRecognizerProvider } from './job_wizard_recognizer'; import { MachineLearningJobWizardGeoProvider } from './job_wizard_geo'; import { MachineLearningLensVisualizationsProvider } from './lens_visualizations'; import { MachineLearningNavigationProvider } from './navigation'; @@ -123,6 +124,7 @@ export function MachineLearningProvider(context: FtrProviderContext) { context, commonFieldStatsFlyout ); + const jobWizardRecognizer = MachineLearningJobWizardRecognizerProvider(context, commonUI); const jobWizardCommon = MachineLearningJobWizardCommonProvider( context, commonUI, @@ -200,6 +202,7 @@ export function MachineLearningProvider(context: FtrProviderContext) { jobWizardGeo, jobWizardMultiMetric, jobWizardPopulation, + jobWizardRecognizer, lensVisualizations, mlNodesPanel, navigation, diff --git a/x-pack/test/functional/services/ml/job_type_selection.ts b/x-pack/test/functional/services/ml/job_type_selection.ts index abdf96633b1aec..efce869d9ab767 100644 --- a/x-pack/test/functional/services/ml/job_type_selection.ts +++ b/x-pack/test/functional/services/ml/job_type_selection.ts @@ -64,5 +64,14 @@ export function MachineLearningJobTypeSelectionProvider({ getService }: FtrProvi async assertCategorizationJobWizardOpen() { await testSubjects.existOrFail('mlPageJobWizard categorization'); }, + + async selectRecognizerJob(moduleId: string) { + await testSubjects.clickWhenNotDisabledWithoutRetry(`mlRecognizerCard ${moduleId}`); + await this.assertRecognizerJobWizardOpen(); + }, + + async assertRecognizerJobWizardOpen() { + await testSubjects.existOrFail('mlPageJobWizard recognizer'); + }, }; } diff --git a/x-pack/test/functional/services/ml/job_wizard_recognizer.ts b/x-pack/test/functional/services/ml/job_wizard_recognizer.ts new file mode 100644 index 00000000000000..b59692ea0f803c --- /dev/null +++ b/x-pack/test/functional/services/ml/job_wizard_recognizer.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; + +import type { FtrProviderContext } from '../../ftr_provider_context'; +import type { MlCommonUI } from './common_ui'; + +export function MachineLearningJobWizardRecognizerProvider( + { getService }: FtrProviderContext, + mlCommonUI: MlCommonUI +) { + const testSubjects = getService('testSubjects'); + + return { + async assertJobIdValue(expectedValue: string) { + const actualJobPrefixId = await testSubjects.getAttribute( + 'mlJobRecognizerWizardInputJobIdPrefix', + 'value' + ); + expect(actualJobPrefixId).to.eql( + expectedValue, + `Expected job id prefix value to be '${expectedValue}' (got '${actualJobPrefixId}')` + ); + }, + + async setJobIdPrefix(prefix: string) { + await mlCommonUI.setValueWithChecks('mlJobRecognizerWizardInputJobIdPrefix', prefix, { + clearWithKeyboard: true, + }); + await this.assertJobIdValue(prefix); + }, + }; +}