mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
Add a11y test coverage to Rule Creation Flow for Detections tab (#94377)
[Security Solution] Add a11y test coverage to Detections rule creation flow (#80060)
This commit is contained in:
parent
c5e3e78de8
commit
d70d02ee83
9 changed files with 330 additions and 12 deletions
|
@ -463,6 +463,21 @@ export function CommonPageProvider({ getService, getPageObjects }: FtrProviderCo
|
|||
async getWelcomeText() {
|
||||
return await testSubjects.getVisibleText('global-banner-item');
|
||||
}
|
||||
|
||||
/**
|
||||
* Clicks on an element, and validates that the desired effect has taken place
|
||||
* by confirming the existence of a validator
|
||||
*/
|
||||
async clickAndValidate(
|
||||
clickTarget: string,
|
||||
validator: string,
|
||||
isValidatorCssString: boolean = false,
|
||||
topOffset?: number
|
||||
) {
|
||||
await testSubjects.click(clickTarget, undefined, topOffset);
|
||||
const validate = isValidatorCssString ? find.byCssSelector : testSubjects.exists;
|
||||
await validate(validator);
|
||||
}
|
||||
}
|
||||
|
||||
return new CommonPage();
|
||||
|
|
|
@ -79,11 +79,11 @@ export async function FindProvider({ getService }: FtrProviderContext) {
|
|||
return wrap(await driver.switchTo().activeElement());
|
||||
}
|
||||
|
||||
public async setValue(selector: string, text: string): Promise<void> {
|
||||
public async setValue(selector: string, text: string, topOffset?: number): Promise<void> {
|
||||
log.debug(`Find.setValue('${selector}', '${text}')`);
|
||||
return await retry.try(async () => {
|
||||
const element = await this.byCssSelector(selector);
|
||||
await element.click();
|
||||
await element.click(topOffset);
|
||||
|
||||
// in case the input element is actually a child of the testSubject, we
|
||||
// call clearValue() and type() on the element that is focused after
|
||||
|
@ -413,14 +413,15 @@ export async function FindProvider({ getService }: FtrProviderContext) {
|
|||
|
||||
public async clickByCssSelector(
|
||||
selector: string,
|
||||
timeout: number = defaultFindTimeout
|
||||
timeout: number = defaultFindTimeout,
|
||||
topOffset?: number
|
||||
): Promise<void> {
|
||||
log.debug(`Find.clickByCssSelector('${selector}') with timeout=${timeout}`);
|
||||
await retry.try(async () => {
|
||||
const element = await this.byCssSelector(selector, timeout);
|
||||
if (element) {
|
||||
// await element.moveMouseTo();
|
||||
await element.click();
|
||||
await element.click(topOffset);
|
||||
} else {
|
||||
throw new Error(`Element with css='${selector}' is not found`);
|
||||
}
|
||||
|
|
|
@ -100,9 +100,13 @@ export function TestSubjectsProvider({ getService }: FtrProviderContext) {
|
|||
await find.clickByCssSelectorWhenNotDisabled(testSubjSelector(selector), { timeout });
|
||||
}
|
||||
|
||||
public async click(selector: string, timeout: number = FIND_TIME): Promise<void> {
|
||||
public async click(
|
||||
selector: string,
|
||||
timeout: number = FIND_TIME,
|
||||
topOffset?: number
|
||||
): Promise<void> {
|
||||
log.debug(`TestSubjects.click(${selector})`);
|
||||
await find.clickByCssSelector(testSubjSelector(selector), timeout);
|
||||
await find.clickByCssSelector(testSubjSelector(selector), timeout, topOffset);
|
||||
}
|
||||
|
||||
public async doubleClick(selector: string, timeout: number = FIND_TIME): Promise<void> {
|
||||
|
@ -187,12 +191,13 @@ export function TestSubjectsProvider({ getService }: FtrProviderContext) {
|
|||
public async setValue(
|
||||
selector: string,
|
||||
text: string,
|
||||
options: SetValueOptions = {}
|
||||
options: SetValueOptions = {},
|
||||
topOffset?: number
|
||||
): Promise<void> {
|
||||
return await retry.try(async () => {
|
||||
const { clearWithKeyboard = false, typeCharByChar = false } = options;
|
||||
log.debug(`TestSubjects.setValue(${selector}, ${text})`);
|
||||
await this.click(selector);
|
||||
await this.click(selector, undefined, topOffset);
|
||||
// in case the input element is actually a child of the testSubject, we
|
||||
// call clearValue() and type() on the element that is focused after
|
||||
// clicking on the testSubject
|
||||
|
|
|
@ -182,9 +182,9 @@ export class WebElementWrapper {
|
|||
*
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
public async click() {
|
||||
public async click(topOffset?: number) {
|
||||
await this.retryCall(async function click(wrapper) {
|
||||
await wrapper.scrollIntoViewIfNecessary();
|
||||
await wrapper.scrollIntoViewIfNecessary(topOffset);
|
||||
await wrapper._webElement.click();
|
||||
});
|
||||
}
|
||||
|
@ -693,11 +693,11 @@ export class WebElementWrapper {
|
|||
* @nonstandard
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
public async scrollIntoViewIfNecessary(): Promise<void> {
|
||||
public async scrollIntoViewIfNecessary(topOffset?: number): Promise<void> {
|
||||
await this.driver.executeScript(
|
||||
scrollIntoViewIfNecessary,
|
||||
this._webElement,
|
||||
this.fixedHeaderHeight
|
||||
topOffset || this.fixedHeaderHeight
|
||||
);
|
||||
}
|
||||
|
||||
|
|
142
x-pack/test/accessibility/apps/security_solution.ts
Normal file
142
x-pack/test/accessibility/apps/security_solution.ts
Normal file
|
@ -0,0 +1,142 @@
|
|||
/*
|
||||
* 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';
|
||||
|
||||
export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||
const a11y = getService('a11y');
|
||||
const { common, detections } = getPageObjects(['common', 'detections']);
|
||||
const security = getService('security');
|
||||
const toasts = getService('toasts');
|
||||
const testSubjects = getService('testSubjects');
|
||||
|
||||
describe('Security Solution', () => {
|
||||
before(async () => {
|
||||
await security.testUser.setRoles(['superuser'], false);
|
||||
await common.navigateToApp('security');
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await security.testUser.restoreDefaults(false);
|
||||
});
|
||||
|
||||
describe('Detections', () => {
|
||||
describe('Create Rule Flow', () => {
|
||||
beforeEach(async () => {
|
||||
await detections.navigateToCreateRule();
|
||||
});
|
||||
|
||||
describe('Custom Query Rule', () => {
|
||||
describe('Define Step', () => {
|
||||
it('default view meets a11y requirements', async () => {
|
||||
await toasts.dismissAllToasts();
|
||||
await testSubjects.click('customRuleType');
|
||||
await a11y.testAppSnapshot();
|
||||
});
|
||||
|
||||
describe('import query modal', () => {
|
||||
it('contents of the default tab meets a11y requirements', async () => {
|
||||
await detections.openImportQueryModal();
|
||||
await a11y.testAppSnapshot();
|
||||
});
|
||||
|
||||
it('contents of the templates tab meets a11y requirements', async () => {
|
||||
await common.scrollKibanaBodyTop();
|
||||
await detections.openImportQueryModal();
|
||||
await detections.viewTemplatesInImportQueryModal();
|
||||
await a11y.testAppSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
it('preview section meets a11y requirements', async () => {
|
||||
await detections.addCustomQuery('_id');
|
||||
await detections.preview();
|
||||
await a11y.testAppSnapshot();
|
||||
});
|
||||
|
||||
describe('About Step', () => {
|
||||
beforeEach(async () => {
|
||||
await detections.addCustomQuery('_id');
|
||||
await detections.continue('define');
|
||||
});
|
||||
|
||||
it('default view meets a11y requirements', async () => {
|
||||
await a11y.testAppSnapshot();
|
||||
});
|
||||
|
||||
it('advanced settings view meets a11y requirements', async () => {
|
||||
await detections.revealAdvancedSettings();
|
||||
await a11y.testAppSnapshot();
|
||||
});
|
||||
|
||||
describe('Schedule Step', () => {
|
||||
beforeEach(async () => {
|
||||
await detections.addNameAndDescription();
|
||||
await detections.continue('about');
|
||||
});
|
||||
|
||||
it('meets a11y requirements', async () => {
|
||||
await a11y.testAppSnapshot();
|
||||
});
|
||||
|
||||
describe('Actions Step', () => {
|
||||
it('meets a11y requirements', async () => {
|
||||
await detections.continue('schedule');
|
||||
await a11y.testAppSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Machine Learning Rule First Step', () => {
|
||||
it('default view meets a11y requirements', async () => {
|
||||
await detections.selectMLRule();
|
||||
await a11y.testAppSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Threshold Rule Rule First Step', () => {
|
||||
beforeEach(async () => {
|
||||
await detections.selectThresholdRule();
|
||||
});
|
||||
|
||||
it('default view meets a11y requirements', async () => {
|
||||
await a11y.testAppSnapshot();
|
||||
});
|
||||
|
||||
it('preview section meets a11y requirements', async () => {
|
||||
await detections.addCustomQuery('_id');
|
||||
await detections.preview();
|
||||
await a11y.testAppSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Event Correlation Rule First Step', () => {
|
||||
beforeEach(async () => {
|
||||
await detections.selectEQLRule();
|
||||
});
|
||||
|
||||
it('default view meets a11y requirements', async () => {
|
||||
await a11y.testAppSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Indicator Match Rule First Step', () => {
|
||||
beforeEach(async () => {
|
||||
await detections.selectIndicatorMatchRule();
|
||||
});
|
||||
|
||||
it('default view meets a11y requirements', async () => {
|
||||
await a11y.testAppSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -34,6 +34,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
|
|||
require.resolve('./apps/lens'),
|
||||
require.resolve('./apps/upgrade_assistant'),
|
||||
require.resolve('./apps/canvas'),
|
||||
require.resolve('./apps/security_solution'),
|
||||
],
|
||||
|
||||
pageObjects,
|
||||
|
|
|
@ -197,6 +197,9 @@ export default async function ({ readConfigFile }) {
|
|||
reporting: {
|
||||
pathname: '/app/management/insightsAndAlerting/reporting',
|
||||
},
|
||||
securitySolution: {
|
||||
pathname: '/app/security',
|
||||
},
|
||||
},
|
||||
|
||||
// choose where esArchiver should load archives from
|
||||
|
|
|
@ -40,6 +40,7 @@ import { IngestPipelinesPageProvider } from './ingest_pipelines_page';
|
|||
import { TagManagementPageProvider } from './tag_management_page';
|
||||
import { NavigationalSearchProvider } from './navigational_search';
|
||||
import { SearchSessionsPageProvider } from './search_sessions_management_page';
|
||||
import { DetectionsPageProvider } from '../../security_solution_ftr/page_objects/detections';
|
||||
|
||||
// just like services, PageObjects are defined as a map of
|
||||
// names to Providers. Merge in Kibana's or pick specific ones
|
||||
|
@ -77,4 +78,5 @@ export const pageObjects = {
|
|||
roleMappings: RoleMappingsPageProvider,
|
||||
ingestPipelines: IngestPipelinesPageProvider,
|
||||
navigationalSearch: NavigationalSearchProvider,
|
||||
detections: DetectionsPageProvider,
|
||||
};
|
||||
|
|
|
@ -0,0 +1,149 @@
|
|||
/*
|
||||
* 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 '../../../functional/ftr_provider_context';
|
||||
import { WebElementWrapper } from '../../../../../test/functional/services/lib/web_element_wrapper';
|
||||
|
||||
export function DetectionsPageProvider({ getService, getPageObjects }: FtrProviderContext) {
|
||||
const find = getService('find');
|
||||
const { common } = getPageObjects(['common']);
|
||||
const testSubjects = getService('testSubjects');
|
||||
|
||||
class DetectionsPage {
|
||||
async navigateHome(): Promise<void> {
|
||||
await this.navigateToDetectionsPage();
|
||||
}
|
||||
|
||||
async navigateToRules(): Promise<void> {
|
||||
await this.navigateToDetectionsPage('rules');
|
||||
}
|
||||
|
||||
async navigateToRuleMonitoring(): Promise<void> {
|
||||
await common.clickAndValidate('allRulesTableTab-monitoring', 'monitoring-table');
|
||||
}
|
||||
|
||||
async navigateToExceptionList(): Promise<void> {
|
||||
await common.clickAndValidate('allRulesTableTab-exceptions', 'exceptions-table');
|
||||
}
|
||||
|
||||
async navigateToCreateRule(): Promise<void> {
|
||||
await this.navigateToDetectionsPage('rules/create');
|
||||
}
|
||||
|
||||
async replaceIndexPattern(): Promise<void> {
|
||||
const buttons = await find.allByCssSelector('[data-test-subj="comboBoxInput"] button');
|
||||
await buttons.map(async (button: WebElementWrapper) => await button.click());
|
||||
await testSubjects.setValue('comboBoxSearchInput', '*');
|
||||
}
|
||||
|
||||
async openImportQueryModal(): Promise<void> {
|
||||
const element = await testSubjects.find('importQueryFromSavedTimeline');
|
||||
await element.click(500);
|
||||
await testSubjects.exists('open-timeline-modal-body-filter-default');
|
||||
}
|
||||
|
||||
async viewTemplatesInImportQueryModal(): Promise<void> {
|
||||
await common.clickAndValidate('open-timeline-modal-body-filter-template', 'timelines-table');
|
||||
}
|
||||
|
||||
async closeImportQueryModal(): Promise<void> {
|
||||
await find.clickByCssSelector('.euiButtonIcon.euiButtonIcon--text.euiModal__closeIcon');
|
||||
}
|
||||
|
||||
async selectMachineLearningJob(): Promise<void> {
|
||||
await find.clickByCssSelector('[data-test-subj="mlJobSelect"] button');
|
||||
await find.clickByCssSelector('#high_distinct_count_error_message');
|
||||
}
|
||||
|
||||
async openAddFilterPopover(): Promise<void> {
|
||||
const addButtons = await testSubjects.findAll('addFilter');
|
||||
await addButtons[1].click();
|
||||
await testSubjects.exists('saveFilter');
|
||||
}
|
||||
|
||||
async closeAddFilterPopover(): Promise<void> {
|
||||
await testSubjects.click('cancelSaveFilter');
|
||||
}
|
||||
|
||||
async toggleFilterActions(): Promise<void> {
|
||||
const filterActions = await testSubjects.findAll('addFilter');
|
||||
await filterActions[1].click();
|
||||
}
|
||||
|
||||
async toggleSavedQueries(): Promise<void> {
|
||||
const filterActions = await find.allByCssSelector(
|
||||
'[data-test-subj="saved-query-management-popover-button"]'
|
||||
);
|
||||
await filterActions[1].click();
|
||||
}
|
||||
|
||||
async addNameAndDescription(
|
||||
name: string = 'test rule name',
|
||||
description: string = 'test rule description'
|
||||
): Promise<void> {
|
||||
await find.setValue(`[aria-describedby="detectionEngineStepAboutRuleName"]`, name, 500);
|
||||
await find.setValue(
|
||||
`[aria-describedby="detectionEngineStepAboutRuleDescription"]`,
|
||||
description,
|
||||
500
|
||||
);
|
||||
}
|
||||
|
||||
async goBackToAllRules(): Promise<void> {
|
||||
await common.clickAndValidate('ruleDetailsBackToAllRules', 'create-new-rule');
|
||||
}
|
||||
|
||||
async revealAdvancedSettings(): Promise<void> {
|
||||
await common.clickAndValidate(
|
||||
'advancedSettings',
|
||||
'detectionEngineStepAboutRuleReferenceUrls'
|
||||
);
|
||||
}
|
||||
|
||||
async preview(): Promise<void> {
|
||||
await common.clickAndValidate(
|
||||
'queryPreviewButton',
|
||||
'queryPreviewCustomHistogram',
|
||||
undefined,
|
||||
500
|
||||
);
|
||||
}
|
||||
|
||||
async continue(prefix: string): Promise<void> {
|
||||
await testSubjects.click(`${prefix}-continue`);
|
||||
}
|
||||
|
||||
async addCustomQuery(query: string): Promise<void> {
|
||||
await testSubjects.setValue('queryInput', query, undefined, 500);
|
||||
}
|
||||
|
||||
async selectMLRule(): Promise<void> {
|
||||
await common.clickAndValidate('machineLearningRuleType', 'mlJobSelect');
|
||||
}
|
||||
|
||||
async selectEQLRule(): Promise<void> {
|
||||
await common.clickAndValidate('eqlRuleType', 'eqlQueryBarTextInput');
|
||||
}
|
||||
|
||||
async selectIndicatorMatchRule(): Promise<void> {
|
||||
await common.clickAndValidate('threatMatchRuleType', 'comboBoxInput');
|
||||
}
|
||||
|
||||
async selectThresholdRule(): Promise<void> {
|
||||
await common.clickAndValidate('thresholdRuleType', 'input');
|
||||
}
|
||||
|
||||
private async navigateToDetectionsPage(path: string = ''): Promise<void> {
|
||||
const subUrl = `detections${path ? `/${path}` : ''}`;
|
||||
await common.navigateToUrl('securitySolution', subUrl, {
|
||||
shouldUseHashForSubUrl: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return new DetectionsPage();
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue