diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts index 41665cf6d20a4..d7a963ba4efa7 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts @@ -91,12 +91,12 @@ import { goToAboutStepTab, goToActionsStepTab, goToScheduleStepTab, + waitForAlertsToPopulate, waitForTheRuleToBeExecuted, } from '../tasks/create_new_rule'; import { saveEditedRule, waitForKibana } from '../tasks/edit_rule'; import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; -import { refreshPage } from '../tasks/security_header'; import { DETECTIONS_URL } from '../urls/navigation'; @@ -197,14 +197,10 @@ describe('Custom detection rules creation', () => { ); }); - refreshPage(); waitForTheRuleToBeExecuted(); + waitForAlertsToPopulate(); - cy.get(NUMBER_OF_ALERTS) - .invoke('text') - .then((numberOfAlertsText) => { - cy.wrap(parseInt(numberOfAlertsText, 10)).should('be.above', 0); - }); + cy.get(NUMBER_OF_ALERTS).invoke('text').then(parseFloat).should('be.above', 0); cy.get(ALERT_RULE_NAME).first().should('have.text', newRule.name); cy.get(ALERT_RULE_VERSION).first().should('have.text', '1'); cy.get(ALERT_RULE_METHOD).first().should('have.text', 'query'); diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_eql.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_eql.spec.ts index 5502f35d6f0f8..1b76e74d47bb1 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_eql.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_eql.spec.ts @@ -67,11 +67,11 @@ import { fillDefineEqlRuleAndContinue, fillScheduleRuleAndContinue, selectEqlRuleType, + waitForAlertsToPopulate, waitForTheRuleToBeExecuted, } from '../tasks/create_new_rule'; import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; -import { refreshPage } from '../tasks/security_header'; import { DETECTIONS_URL } from '../urls/navigation'; @@ -87,8 +87,7 @@ const expectedNumberOfRules = 1; const expectedNumberOfAlerts = 7; const expectedNumberOfSequenceAlerts = 1; -// Failing: See https://github.com/elastic/kibana/issues/79522 -describe.skip('Detection rules, EQL', () => { +describe('Detection rules, EQL', () => { beforeEach(() => { esArchiverLoad('timeline'); }); @@ -160,14 +159,10 @@ describe.skip('Detection rules, EQL', () => { ); }); - refreshPage(); waitForTheRuleToBeExecuted(); + waitForAlertsToPopulate(); - cy.get(NUMBER_OF_ALERTS) - .invoke('text') - .then((numberOfAlertsText) => { - cy.wrap(parseInt(numberOfAlertsText, 10)).should('eql', expectedNumberOfAlerts); - }); + cy.get(NUMBER_OF_ALERTS).should('have.text', expectedNumberOfAlerts); cy.get(ALERT_RULE_NAME).first().should('have.text', eqlRule.name); cy.get(ALERT_RULE_VERSION).first().should('have.text', '1'); cy.get(ALERT_RULE_METHOD).first().should('have.text', 'eql'); @@ -199,14 +194,10 @@ describe.skip('Detection rules, EQL', () => { filterByCustomRules(); goToRuleDetails(); - refreshPage(); waitForTheRuleToBeExecuted(); + waitForAlertsToPopulate(); - cy.get(NUMBER_OF_ALERTS) - .invoke('text') - .then((numberOfAlertsText) => { - cy.wrap(parseInt(numberOfAlertsText, 10)).should('eql', expectedNumberOfSequenceAlerts); - }); + cy.get(NUMBER_OF_ALERTS).should('have.text', expectedNumberOfSequenceAlerts); cy.get(ALERT_RULE_NAME).first().should('have.text', eqlSequenceRule.name); cy.get(ALERT_RULE_VERSION).first().should('have.text', '1'); cy.get(ALERT_RULE_METHOD).first().should('have.text', 'eql'); diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_override.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_override.spec.ts index edf7305f6916e..e31fe2e9a3911 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_override.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_override.spec.ts @@ -72,11 +72,11 @@ import { fillAboutRuleWithOverrideAndContinue, fillDefineCustomRuleWithImportedQueryAndContinue, fillScheduleRuleAndContinue, + waitForAlertsToPopulate, waitForTheRuleToBeExecuted, } from '../tasks/create_new_rule'; import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; -import { refreshPage } from '../tasks/security_header'; import { DETECTIONS_URL } from '../urls/navigation'; @@ -179,14 +179,10 @@ describe('Detection rules, override', () => { ); }); - refreshPage(); waitForTheRuleToBeExecuted(); + waitForAlertsToPopulate(); - cy.get(NUMBER_OF_ALERTS) - .invoke('text') - .then((numberOfAlertsText) => { - cy.wrap(parseInt(numberOfAlertsText, 10)).should('be.above', 0); - }); + cy.get(NUMBER_OF_ALERTS).invoke('text').then(parseFloat).should('be.above', 0); cy.get(ALERT_RULE_NAME).first().should('have.text', 'auditbeat'); cy.get(ALERT_RULE_VERSION).first().should('have.text', '1'); cy.get(ALERT_RULE_METHOD).first().should('have.text', 'query'); diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_threshold.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_threshold.spec.ts index 5095e856e3f65..f997b9eb3bc51 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_threshold.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_threshold.spec.ts @@ -68,11 +68,11 @@ import { fillDefineThresholdRuleAndContinue, fillScheduleRuleAndContinue, selectThresholdRuleType, + waitForAlertsToPopulate, waitForTheRuleToBeExecuted, } from '../tasks/create_new_rule'; import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; -import { refreshPage } from '../tasks/security_header'; import { DETECTIONS_URL } from '../urls/navigation'; @@ -162,14 +162,10 @@ describe('Detection rules, threshold', () => { ); }); - refreshPage(); waitForTheRuleToBeExecuted(); + waitForAlertsToPopulate(); - cy.get(NUMBER_OF_ALERTS) - .invoke('text') - .then((numberOfAlertsText) => { - cy.wrap(parseInt(numberOfAlertsText, 10)).should('be.below', 100); - }); + cy.get(NUMBER_OF_ALERTS).invoke('text').then(parseFloat).should('be.below', 100); cy.get(ALERT_RULE_NAME).first().should('have.text', newThresholdRule.name); cy.get(ALERT_RULE_VERSION).first().should('have.text', '1'); cy.get(ALERT_RULE_METHOD).first().should('have.text', 'threshold'); diff --git a/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts b/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts index e1ab5ff30572f..5e7dce6966195 100644 --- a/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts +++ b/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts @@ -31,7 +31,11 @@ export const COMBO_BOX_INPUT = '[data-test-subj="comboBoxInput"]'; export const CREATE_AND_ACTIVATE_BTN = '[data-test-subj="create-activate"]'; -export const CUSTOM_QUERY_INPUT = '[data-test-subj="queryInput"]'; +export const CUSTOM_QUERY_INPUT = + '[data-test-subj="detectionEngineStepDefineRuleQueryBar"] [data-test-subj="queryInput"]'; + +export const THREAT_MATCH_QUERY_INPUT = + '[data-test-subj="detectionEngineStepDefineThreatRuleQueryBar"] [data-test-subj="queryInput"]'; export const DEFINE_CONTINUE_BUTTON = '[data-test-subj="define-continue"]'; diff --git a/x-pack/plugins/security_solution/cypress/support/commands.js b/x-pack/plugins/security_solution/cypress/support/commands.js index 0dbcb1af4642f..dbd60cdd31a5a 100644 --- a/x-pack/plugins/security_solution/cypress/support/commands.js +++ b/x-pack/plugins/security_solution/cypress/support/commands.js @@ -69,3 +69,36 @@ Cypress.Commands.add( }); } ); + +const waitUntil = (subject, fn, options = {}) => { + const { interval = 200, timeout = 5000 } = options; + let attempts = Math.floor(timeout / interval); + + const completeOrRetry = (result) => { + if (result) { + return result; + } + if (attempts < 1) { + throw new Error(`Timed out while retrying, last result was: {${result}}`); + } + cy.wait(interval, { log: false }).then(() => { + attempts--; + // eslint-disable-next-line no-use-before-define + return evaluate(); + }); + }; + + const evaluate = () => { + const result = fn(subject); + + if (result && result.then) { + return result.then(completeOrRetry); + } else { + return completeOrRetry(result); + } + }; + + return evaluate(); +}; + +Cypress.Commands.add('waitUntil', { prevSubject: 'optional' }, waitUntil); diff --git a/x-pack/plugins/security_solution/cypress/support/index.d.ts b/x-pack/plugins/security_solution/cypress/support/index.d.ts index 59180507cbade..0cf3cf614cdb9 100644 --- a/x-pack/plugins/security_solution/cypress/support/index.d.ts +++ b/x-pack/plugins/security_solution/cypress/support/index.d.ts @@ -14,5 +14,12 @@ declare namespace Cypress { searchStrategyName?: string ): Chainable; attachFile(fileName: string, fileType?: string): Chainable; + waitUntil( + fn: (subject: Subject) => boolean | Chainable, + options?: { + interval: number; + timeout: number; + } + ): Chainable; } } diff --git a/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts b/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts index fa3c219595c72..5b2c365dfd8c3 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts @@ -11,6 +11,7 @@ import { OverrideRule, ThresholdRule, } from '../objects/rule'; +import { NUMBER_OF_ALERTS } from '../screens/alerts'; import { ABOUT_CONTINUE_BTN, ABOUT_EDIT_TAB, @@ -62,6 +63,7 @@ import { EQL_QUERY_INPUT, } from '../screens/create_new_rule'; import { TIMELINE } from '../screens/timelines'; +import { refreshPage } from './security_header'; export const createAndActivateRule = () => { cy.get(SCHEDULE_CONTINUE_BUTTON).click({ force: true }); @@ -263,12 +265,27 @@ export const selectThresholdRuleType = () => { cy.get(THRESHOLD_TYPE).click({ force: true }); }; -export const waitForTheRuleToBeExecuted = async () => { - let status = ''; - while (status !== 'succeeded') { +export const waitForTheRuleToBeExecuted = () => { + cy.waitUntil(() => { cy.get(REFRESH_BUTTON).click(); - status = await cy.get(RULE_STATUS).invoke('text').promisify(); - } + return cy + .get(RULE_STATUS) + .invoke('text') + .then((ruleStatus) => ruleStatus === 'succeeded'); + }); +}; + +export const waitForAlertsToPopulate = async () => { + cy.waitUntil(() => { + refreshPage(); + return cy + .get(NUMBER_OF_ALERTS) + .invoke('text') + .then((countText) => { + const alertCount = parseInt(countText, 10) || 0; + return alertCount > 0; + }); + }); }; export const selectEqlRuleType = () => { diff --git a/x-pack/plugins/security_solution/cypress/tasks/security_header.ts b/x-pack/plugins/security_solution/cypress/tasks/security_header.ts index 28efc47120d32..a28767b489e46 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/security_header.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/security_header.ts @@ -19,9 +19,9 @@ export const navigateFromHeaderTo = (page: string) => { }; export const refreshPage = () => { - cy.get(REFRESH_BUTTON).click({ force: true }).invoke('text').should('not.equal', 'Updating'); + cy.get(REFRESH_BUTTON).click({ force: true }).should('not.have.text', 'Updating'); }; export const waitForThePageToBeUpdated = () => { - cy.get(REFRESH_BUTTON).should('not.equal', 'Updating'); + cy.get(REFRESH_BUTTON).should('not.have.text', 'Updating'); };