mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Detection Engine] Cypress - Add more robust selection from our DataView dropdown component (#213510)
This addresses some recent cypress failures: * https://github.com/elastic/kibana/issues/212743 (Rule Creation with a DataView) * https://github.com/elastic/kibana/issues/212742 (Rule Creation + Edit with a DataView) * https://github.com/elastic/kibana/issues/213752 (Rule Creation + Filter with a DataView) This appears (as much as a cypress failure can 😓) to be caused by an incorrect/false-positive assertion, leading to us (very occasionally) interacting with the combobox before it's ready. We were calling `.should('not.be.disabled')` on an element that could never be disabled. By calling that instead on the inner `input` that actually is enabled/disabled, we have the sanity check that was originally intended. This PR also adds a post-action check (`.should('contains', thingThatWasTyped)`) so that if the action fails, the test doesn't fail inscrutably at a later step. ### Evidence * https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/8003 (50x) * https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/8004 (200x) * https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/8033 (200x) ### Significance **Note also** that some initial investigation found this pattern in several places in our test suite. I'm going to follow up on this focused PR with a more comprehensive one (once this is proven out in the flaky runner). ### 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 - [x] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed
This commit is contained in:
parent
3df90c8f2a
commit
02409dbd65
5 changed files with 201 additions and 127 deletions
|
@ -79,129 +79,124 @@ import { fillAddFilterForm } from '../../../../tasks/search_bar';
|
|||
import { CREATE_RULE_URL } from '../../../../urls/navigation';
|
||||
|
||||
// Skipping in MKI due to flake
|
||||
// Failing: See https://github.com/elastic/kibana/issues/212743
|
||||
describe.skip(
|
||||
'Custom query rules',
|
||||
{ tags: ['@ess', '@serverless', '@skipInServerlessMKI'] },
|
||||
() => {
|
||||
describe('Custom detection rules creation with data views', () => {
|
||||
const rule = getDataViewRule();
|
||||
const expectedUrls = rule.references?.join('');
|
||||
const expectedFalsePositives = rule.false_positives?.join('');
|
||||
const expectedTags = rule.tags?.join('');
|
||||
const mitreAttack = rule.threat;
|
||||
const expectedMitre = formatMitreAttackDescription(mitreAttack ?? []);
|
||||
const expectedNumberOfRules = 1;
|
||||
describe('Custom query rules', { tags: ['@ess', '@serverless', '@skipInServerlessMKI'] }, () => {
|
||||
describe('Custom detection rules creation with data views', () => {
|
||||
const rule = getDataViewRule();
|
||||
const expectedUrls = rule.references?.join('');
|
||||
const expectedFalsePositives = rule.false_positives?.join('');
|
||||
const expectedTags = rule.tags?.join('');
|
||||
const mitreAttack = rule.threat;
|
||||
const expectedMitre = formatMitreAttackDescription(mitreAttack ?? []);
|
||||
const expectedNumberOfRules = 1;
|
||||
|
||||
beforeEach(() => {
|
||||
if (rule.data_view_id != null) {
|
||||
postDataView(rule.data_view_id);
|
||||
}
|
||||
deleteAlertsAndRules();
|
||||
login();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (rule.data_view_id != null) {
|
||||
deleteDataView(rule.data_view_id);
|
||||
}
|
||||
});
|
||||
|
||||
it('Creates and enables a new rule', function () {
|
||||
visit(CREATE_RULE_URL);
|
||||
fillDefineCustomRuleAndContinue(rule);
|
||||
fillAboutRuleAndContinue(rule);
|
||||
fillScheduleRuleAndContinue(rule);
|
||||
createAndEnableRule();
|
||||
openRuleManagementPageViaBreadcrumbs();
|
||||
|
||||
cy.get(CUSTOM_RULES_BTN).should('have.text', 'Custom rules (1)');
|
||||
|
||||
getRulesManagementTableRows().should('have.length', expectedNumberOfRules);
|
||||
cy.get(RULE_NAME).should('have.text', rule.name);
|
||||
cy.get(RISK_SCORE).should('have.text', rule.risk_score);
|
||||
cy.get(SEVERITY).should('have.text', 'High');
|
||||
cy.get(RULE_SWITCH).should('have.attr', 'aria-checked', 'true');
|
||||
|
||||
goToRuleDetailsOf(rule.name);
|
||||
|
||||
cy.get(RULE_NAME_HEADER).should('contain', `${rule.name}`);
|
||||
cy.get(ABOUT_RULE_DESCRIPTION).should('have.text', rule.description);
|
||||
cy.get(ABOUT_DETAILS).within(() => {
|
||||
getDetails(SEVERITY_DETAILS).should('have.text', 'High');
|
||||
getDetails(RISK_SCORE_DETAILS).should('have.text', rule.risk_score);
|
||||
getDetails(REFERENCE_URLS_DETAILS).should((details) => {
|
||||
expect(removeExternalLinkText(details.text())).equal(expectedUrls);
|
||||
});
|
||||
getDetails(FALSE_POSITIVES_DETAILS).should('have.text', expectedFalsePositives);
|
||||
getDetails(MITRE_ATTACK_DETAILS).should((mitre) => {
|
||||
expect(removeExternalLinkText(mitre.text())).equal(expectedMitre);
|
||||
});
|
||||
getDetails(TAGS_DETAILS).should('have.text', expectedTags);
|
||||
});
|
||||
cy.get(INVESTIGATION_NOTES_TOGGLE).click();
|
||||
cy.get(ABOUT_INVESTIGATION_NOTES).should('have.text', INVESTIGATION_NOTES_MARKDOWN);
|
||||
cy.get(DEFINITION_DETAILS).within(() => {
|
||||
getDetails(DATA_VIEW_DETAILS).should('have.text', rule.data_view_id);
|
||||
getDetails(CUSTOM_QUERY_DETAILS).should('have.text', rule.query);
|
||||
getDetails(RULE_TYPE_DETAILS).should('have.text', 'Query');
|
||||
getDetails(TIMELINE_TEMPLATE_DETAILS).should('have.text', 'None');
|
||||
});
|
||||
cy.get(DEFINITION_DETAILS).should('not.contain', INDEX_PATTERNS_DETAILS);
|
||||
cy.get(SCHEDULE_DETAILS).within(() => {
|
||||
getDetails(RUNS_EVERY_DETAILS)
|
||||
.find(INTERVAL_ABBR_VALUE)
|
||||
.should('have.text', `${rule.interval}`);
|
||||
const humanizedDuration = getHumanizedDuration(
|
||||
rule.from ?? 'now-6m',
|
||||
rule.interval ?? '5m'
|
||||
);
|
||||
getDetails(ADDITIONAL_LOOK_BACK_DETAILS)
|
||||
.find(INTERVAL_ABBR_VALUE)
|
||||
.should('have.text', `${humanizedDuration}`);
|
||||
});
|
||||
|
||||
waitForTheRuleToBeExecuted();
|
||||
waitForAlertsToPopulate();
|
||||
|
||||
cy.get(ALERTS_COUNT)
|
||||
.invoke('text')
|
||||
.should('match', /^[1-9].+$/);
|
||||
cy.get(ALERT_GRID_CELL).contains(rule.name);
|
||||
});
|
||||
|
||||
it('Creates and edits a new rule with a data view', function () {
|
||||
visit(CREATE_RULE_URL);
|
||||
fillDefineCustomRuleAndContinue(rule);
|
||||
cy.get(RULE_NAME_INPUT).clear();
|
||||
cy.get(RULE_NAME_INPUT).type(rule.name);
|
||||
cy.get(RULE_DESCRIPTION_INPUT).clear();
|
||||
cy.get(RULE_DESCRIPTION_INPUT).type(rule.description);
|
||||
|
||||
cy.get(ABOUT_CONTINUE_BTN).should('exist').click();
|
||||
|
||||
fillScheduleRuleAndContinue(rule);
|
||||
createRuleWithoutEnabling();
|
||||
openRuleManagementPageViaBreadcrumbs();
|
||||
|
||||
goToRuleDetailsOf(rule.name);
|
||||
|
||||
cy.get(EDIT_RULE_SETTINGS_LINK).click();
|
||||
|
||||
cy.get(RULE_NAME_HEADER).should('contain', 'Edit rule settings');
|
||||
});
|
||||
|
||||
it('Adds filter on define step', () => {
|
||||
visit(CREATE_RULE_URL);
|
||||
fillDefineCustomRule(rule);
|
||||
openAddFilterPopover();
|
||||
fillAddFilterForm({
|
||||
key: 'host.name',
|
||||
operator: 'exists',
|
||||
});
|
||||
// Check that newly added filter exists
|
||||
cy.get(GLOBAL_SEARCH_BAR_FILTER_ITEM).should('have.text', 'host.name: exists');
|
||||
});
|
||||
beforeEach(() => {
|
||||
if (rule.data_view_id != null) {
|
||||
postDataView(rule.data_view_id);
|
||||
}
|
||||
deleteAlertsAndRules();
|
||||
login();
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
afterEach(() => {
|
||||
if (rule.data_view_id != null) {
|
||||
deleteDataView(rule.data_view_id);
|
||||
}
|
||||
});
|
||||
|
||||
it('Creates and enables a new rule', function () {
|
||||
visit(CREATE_RULE_URL);
|
||||
fillDefineCustomRuleAndContinue(rule);
|
||||
fillAboutRuleAndContinue(rule);
|
||||
fillScheduleRuleAndContinue(rule);
|
||||
createAndEnableRule();
|
||||
openRuleManagementPageViaBreadcrumbs();
|
||||
|
||||
cy.get(CUSTOM_RULES_BTN).should('have.text', 'Custom rules (1)');
|
||||
|
||||
getRulesManagementTableRows().should('have.length', expectedNumberOfRules);
|
||||
cy.get(RULE_NAME).should('have.text', rule.name);
|
||||
cy.get(RISK_SCORE).should('have.text', rule.risk_score);
|
||||
cy.get(SEVERITY).should('have.text', 'High');
|
||||
cy.get(RULE_SWITCH).should('have.attr', 'aria-checked', 'true');
|
||||
|
||||
goToRuleDetailsOf(rule.name);
|
||||
|
||||
cy.get(RULE_NAME_HEADER).should('contain', `${rule.name}`);
|
||||
cy.get(ABOUT_RULE_DESCRIPTION).should('have.text', rule.description);
|
||||
cy.get(ABOUT_DETAILS).within(() => {
|
||||
getDetails(SEVERITY_DETAILS).should('have.text', 'High');
|
||||
getDetails(RISK_SCORE_DETAILS).should('have.text', rule.risk_score);
|
||||
getDetails(REFERENCE_URLS_DETAILS).should((details) => {
|
||||
expect(removeExternalLinkText(details.text())).equal(expectedUrls);
|
||||
});
|
||||
getDetails(FALSE_POSITIVES_DETAILS).should('have.text', expectedFalsePositives);
|
||||
getDetails(MITRE_ATTACK_DETAILS).should((mitre) => {
|
||||
expect(removeExternalLinkText(mitre.text())).equal(expectedMitre);
|
||||
});
|
||||
getDetails(TAGS_DETAILS).should('have.text', expectedTags);
|
||||
});
|
||||
cy.get(INVESTIGATION_NOTES_TOGGLE).click();
|
||||
cy.get(ABOUT_INVESTIGATION_NOTES).should('have.text', INVESTIGATION_NOTES_MARKDOWN);
|
||||
cy.get(DEFINITION_DETAILS).within(() => {
|
||||
getDetails(DATA_VIEW_DETAILS).should('have.text', rule.data_view_id);
|
||||
getDetails(CUSTOM_QUERY_DETAILS).should('have.text', rule.query);
|
||||
getDetails(RULE_TYPE_DETAILS).should('have.text', 'Query');
|
||||
getDetails(TIMELINE_TEMPLATE_DETAILS).should('have.text', 'None');
|
||||
});
|
||||
cy.get(DEFINITION_DETAILS).should('not.contain', INDEX_PATTERNS_DETAILS);
|
||||
cy.get(SCHEDULE_DETAILS).within(() => {
|
||||
getDetails(RUNS_EVERY_DETAILS)
|
||||
.find(INTERVAL_ABBR_VALUE)
|
||||
.should('have.text', `${rule.interval}`);
|
||||
const humanizedDuration = getHumanizedDuration(
|
||||
rule.from ?? 'now-6m',
|
||||
rule.interval ?? '5m'
|
||||
);
|
||||
getDetails(ADDITIONAL_LOOK_BACK_DETAILS)
|
||||
.find(INTERVAL_ABBR_VALUE)
|
||||
.should('have.text', `${humanizedDuration}`);
|
||||
});
|
||||
|
||||
waitForTheRuleToBeExecuted();
|
||||
waitForAlertsToPopulate();
|
||||
|
||||
cy.get(ALERTS_COUNT)
|
||||
.invoke('text')
|
||||
.should('match', /^[1-9].+$/);
|
||||
cy.get(ALERT_GRID_CELL).contains(rule.name);
|
||||
});
|
||||
|
||||
it('Creates and edits a new rule with a data view', function () {
|
||||
visit(CREATE_RULE_URL);
|
||||
fillDefineCustomRuleAndContinue(rule);
|
||||
cy.get(RULE_NAME_INPUT).clear();
|
||||
cy.get(RULE_NAME_INPUT).type(rule.name);
|
||||
cy.get(RULE_DESCRIPTION_INPUT).clear();
|
||||
cy.get(RULE_DESCRIPTION_INPUT).type(rule.description);
|
||||
|
||||
cy.get(ABOUT_CONTINUE_BTN).should('exist').click();
|
||||
|
||||
fillScheduleRuleAndContinue(rule);
|
||||
createRuleWithoutEnabling();
|
||||
openRuleManagementPageViaBreadcrumbs();
|
||||
|
||||
goToRuleDetailsOf(rule.name);
|
||||
|
||||
cy.get(EDIT_RULE_SETTINGS_LINK).click();
|
||||
|
||||
cy.get(RULE_NAME_HEADER).should('contain', 'Edit rule settings');
|
||||
});
|
||||
|
||||
it('Adds filter on define step', () => {
|
||||
visit(CREATE_RULE_URL);
|
||||
fillDefineCustomRule(rule);
|
||||
openAddFilterPopover();
|
||||
fillAddFilterForm({
|
||||
key: 'host.name',
|
||||
operator: 'exists',
|
||||
});
|
||||
// Check that newly added filter exists
|
||||
cy.get(GLOBAL_SEARCH_BAR_FILTER_ITEM).should('have.text', 'host.name: exists');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -108,8 +108,7 @@ export const CUSTOM_QUERY_REQUIRED = 'A custom query is required.';
|
|||
|
||||
export const THREAT_MATCH_QUERY_REQUIRED = 'An indicator index query is required.';
|
||||
|
||||
export const DATA_VIEW_COMBO_BOX =
|
||||
'[data-test-subj="pick-rule-data-source"] [data-test-subj="comboBoxInput"]';
|
||||
export const DATA_VIEW_COMBO_BOX = '[data-test-subj="pick-rule-data-source"]';
|
||||
|
||||
export const DATA_VIEW_OPTION = '[data-test-subj="rule-index-toggle-dataView"]';
|
||||
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export const EUI_COMBO_BOX_SELECTOR = '[data-test-subj="comboBoxInput"]';
|
||||
|
||||
export const EUI_COMBO_BOX_SELECTIONS_SELECTOR = `${EUI_COMBO_BOX_SELECTOR} [data-test-subj="euiComboBoxPill"]`;
|
||||
|
||||
export const EUI_COMBO_BOX_INPUT_SELECTOR = `${EUI_COMBO_BOX_SELECTOR} input`;
|
||||
|
||||
/**
|
||||
* @param parentSelector CSS Selector targeting the parent EuiComboBox component
|
||||
* @returns A selector targeting the inner combobox element to be interacted with
|
||||
*/
|
||||
export const getComboBoxSelector = (parentSelector?: string): string =>
|
||||
`${parentSelector} ${EUI_COMBO_BOX_SELECTOR}`;
|
||||
|
||||
/**
|
||||
* @param parentSelector CSS Selector targeting the parent EuiComboBox component
|
||||
* @returns A selector targeting the actual combobox <input /> element
|
||||
*/
|
||||
export const getComboBoxInputSelector = (parentSelector?: string): string =>
|
||||
`${parentSelector} ${EUI_COMBO_BOX_INPUT_SELECTOR}`;
|
||||
|
||||
/**
|
||||
* @param parentSelector CSS Selector targeting the parent EuiComboBox component
|
||||
* @returns A selector targeting the selected options on the EuiComboBox
|
||||
*/
|
||||
export const getComboBoxSelectionsSelector = (parentSelector?: string): string =>
|
||||
`${parentSelector} ${EUI_COMBO_BOX_SELECTIONS_SELECTOR}`;
|
|
@ -159,6 +159,7 @@ import { waitForAlerts } from './alerts';
|
|||
import { refreshPage } from './security_header';
|
||||
import { COMBO_BOX_OPTION, TOOLTIP } from '../screens/common';
|
||||
import { EMPTY_ALERT_TABLE } from '../screens/alerts';
|
||||
import { fillComboBox } from './eui_form_interactions';
|
||||
|
||||
export const createAndEnableRule = () => {
|
||||
cy.get(CREATE_AND_ENABLE_BTN).click();
|
||||
|
@ -463,7 +464,7 @@ export const removeAlertsIndex = () => {
|
|||
export const fillDefineCustomRule = (rule: QueryRuleCreateProps) => {
|
||||
if (rule.data_view_id !== undefined) {
|
||||
cy.get(DATA_VIEW_OPTION).click();
|
||||
cy.get(DATA_VIEW_COMBO_BOX).type(`${rule.data_view_id}{enter}`);
|
||||
fillComboBox({ parentSelector: DATA_VIEW_COMBO_BOX, options: rule.data_view_id });
|
||||
}
|
||||
cy.get(CUSTOM_QUERY_INPUT)
|
||||
.first()
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* 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 {
|
||||
getComboBoxInputSelector,
|
||||
getComboBoxSelectionsSelector,
|
||||
getComboBoxSelector,
|
||||
} from '../screens/eui_form_interactions';
|
||||
|
||||
/**
|
||||
* Fills an EuiComboBox in a robust way. Ensures that the component is ready to
|
||||
* be interacted with, and also that each option was successfully chosen.
|
||||
*
|
||||
* @param parentSelector CSS Selector targeting the parent EuiComboBox component
|
||||
* @param options A string (or strings) to be chosen from the EuiComboBox
|
||||
*/
|
||||
export const fillComboBox = ({
|
||||
parentSelector,
|
||||
options,
|
||||
}: {
|
||||
parentSelector?: string;
|
||||
options: string | string[];
|
||||
}) => {
|
||||
const _options = options instanceof Array ? options : [options];
|
||||
|
||||
const comboBoxSelector = getComboBoxSelector(parentSelector);
|
||||
const comboBoxInputSelector = getComboBoxInputSelector(parentSelector);
|
||||
const comboBoxSelectionsSelector = getComboBoxSelectionsSelector(parentSelector);
|
||||
|
||||
cy.get(comboBoxInputSelector).should('not.be.disabled');
|
||||
|
||||
_options.forEach((option, index) => {
|
||||
cy.get(comboBoxSelector).type(`${option}{downArrow}{enter}`);
|
||||
|
||||
if (index === 0) {
|
||||
// If we're filling a combobox that only allows a single value, there will be no "selections" to assert upon
|
||||
cy.get(comboBoxSelector).should('contain', option);
|
||||
} else {
|
||||
cy.get(comboBoxSelectionsSelector).eq(index).should('have.text', option);
|
||||
}
|
||||
});
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue