diff --git a/cypress.json b/cypress.json index 676ad4cb2..56235296e 100644 --- a/cypress.json +++ b/cypress.json @@ -1,7 +1,8 @@ { "viewportHeight": 900, "viewportWidth": 1440, - "defaultCommandTimeout": 10000, + "defaultCommandTimeout": 20000, + "retries": 1, "env": { "opensearch_url": "localhost:9200", "opensearch_dashboards": "http://localhost:5601", diff --git a/cypress/integration/1_detectors.spec.js b/cypress/integration/1_detectors.spec.js index 65d1027f2..75573f160 100644 --- a/cypress/integration/1_detectors.spec.js +++ b/cypress/integration/1_detectors.spec.js @@ -3,34 +3,31 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { PLUGIN_NAME } from '../support/constants'; +import { OPENSEARCH_DASHBOARDS_URL } from '../support/constants'; import sample_field_mappings from '../fixtures/sample_field_mappings.json'; import sample_index_settings from '../fixtures/sample_index_settings.json'; describe('Detectors', () => { const indexName = 'cypress-test-windows'; + const detectorName = 'test detector'; before(() => { - cy.deleteAllIndices(); + cy.cleanUpTests(); // Create test index cy.createIndex(indexName, sample_index_settings); - cy.contains('test detector').should('not.exist'); + cy.contains(detectorName).should('not.exist'); }); beforeEach(() => { // Visit Detectors page - cy.visit(`${Cypress.env('opensearch_dashboards')}/app/${PLUGIN_NAME}#/detectors`); - - //wait for page to load - cy.wait(7000); + cy.visit(`${OPENSEARCH_DASHBOARDS_URL}/detectors`); // Check that correct page is showing - cy.url().should( - 'eq', - 'http://localhost:5601/app/opensearch_security_analytics_dashboards#/detectors' - ); + cy.waitForPageLoad('detectors', { + contains: 'Threat detectors', + }); }); it('...can be created', () => { @@ -38,19 +35,15 @@ describe('Detectors', () => { cy.contains('Create detector').click({ force: true }); // Check to ensure process started - cy.contains('Define detector'); - cy.url().should( - 'eq', - 'http://localhost:5601/app/opensearch_security_analytics_dashboards#/create-detector' - ); + cy.waitForPageLoad('create-detector', { + contains: 'Define detector', + }); // Enter a name for the detector in the appropriate input cy.get(`input[placeholder="Enter a name for the detector."]`).type('test detector{enter}'); - // Select our pre-seeded data source (cypress-test-windows) - cy.get(`[data-test-subj="define-detector-select-data-source"]`).type( - 'cypress-test-windows{enter}' - ); + // Select our pre-seeded data source (check indexName) + cy.get(`[data-test-subj="define-detector-select-data-source"]`).type(`${indexName}{enter}`); // Select threat detector type (Windows logs) cy.get(`input[id="windows"]`).click({ force: true }); @@ -59,7 +52,7 @@ describe('Detectors', () => { cy.get('[data-test-subj="detection-rules-btn"]').click({ timeout: 5000 }); // find search, type USB - cy.get(`[placeholder="Search..."]`).type('USB Device Plugged').trigger('search'); + cy.triggerSearchField('Search...', 'USB Device Plugged'); // Disable all rules cy.contains('tr', 'USB Device Plugged', { timeout: 20000 }); @@ -121,21 +114,23 @@ describe('Detectors', () => { // Confirm entries user has made cy.contains('Detector details'); - cy.contains('test detector'); + cy.contains(detectorName); cy.contains('windows'); cy.contains(indexName); cy.contains('Alert on test_trigger'); // Create the detector cy.get('button').contains('Create').click({ force: true }); - - cy.wait(10000); + cy.waitForPageLoad('detector-details', { + contains: detectorName, + }); // Confirm detector active - cy.contains('There are no existing detectors.', { timeout: 20000 }).should('not.exist'); - cy.contains('test detector'); + cy.contains(detectorName); cy.contains('Active'); + cy.contains('View Alerts'); cy.contains('View Findings'); + cy.contains('Actions'); cy.contains('Detector configuration'); cy.contains('Field mappings'); cy.contains('Alert triggers'); @@ -146,21 +141,19 @@ describe('Detectors', () => { it('...basic details can be edited', () => { // Click on detector name - cy.contains('test detector').click({ force: true }); - - // Confirm on detector details page - cy.contains('test detector'); + cy.contains(detectorName).click({ force: true }); + cy.waitForPageLoad('detector-details', { + contains: detectorName, + }); // Click "Edit" button in detector details cy.get(`[data-test-subj="edit-detector-basic-details"]`).click({ force: true }); // Confirm arrival at "Edit detector details" page - cy.url().should( - 'include', - 'http://localhost:5601/app/opensearch_security_analytics_dashboards#/edit-detector-details' - ); + cy.waitForPageLoad('edit-detector-details', { + contains: 'Edit detector details', + }); - cy.wait(5000); // Change detector name cy.get(`[data-test-subj="define-detector-detector-name"]`).type('_edited'); @@ -173,18 +166,16 @@ describe('Detectors', () => { ); // Change detector scheduling - cy.get(`[data-test-subj="detector-schedule-number-select"]`).type('0'); + cy.get(`[data-test-subj="detector-schedule-number-select"]`).type('{selectall}10'); cy.get(`[data-test-subj="detector-schedule-unit-select"]`).select('Hours'); - cy.wait(7000); // Save changes to detector details cy.get(`[data-test-subj="save-basic-details-edits"]`).click({ force: true }); // Confirm taken to detector details page - cy.url({ timeout: 20000 }).should( - 'include', - 'http://localhost:5601/app/opensearch_security_analytics_dashboards#/detector-details' - ); + cy.waitForPageLoad('detector-details', { + contains: detectorName, + }); // Verify edits are applied cy.contains('test detector_edited'); @@ -195,13 +186,15 @@ describe('Detectors', () => { it('...rules can be edited', () => { // Ensure start on main detectors page - cy.url().should( - 'eq', - 'http://localhost:5601/app/opensearch_security_analytics_dashboards#/detectors' - ); + cy.waitForPageLoad('detectors', { + contains: 'Threat detectors', + }); // Click on detector name - cy.contains('test detector').click({ force: true }); + cy.contains(detectorName).click({ force: true }); + cy.waitForPageLoad('detector-details', { + contains: detectorName, + }); // Confirm number of rules before edit cy.contains('Active rules (1)'); @@ -216,7 +209,7 @@ describe('Detectors', () => { ); // Search for specific rule - cy.get(`[placeholder="Search..."]`).type('USB Device').trigger('search', { timeout: 5000 }); + cy.triggerSearchField('Search...', 'USB Device'); // Toggle single search result to unchecked cy.contains('tr', 'USB Device Plugged').within(() => { @@ -235,13 +228,12 @@ describe('Detectors', () => { cy.get(`[data-test-subj="edit-detector-rules"]`).click({ force: true }); // Confirm arrival on "Edit detector rules" page - cy.url().should( - 'include', - 'http://localhost:5601/app/opensearch_security_analytics_dashboards#/edit-detector-rules' - ); + cy.waitForPageLoad('edit-detector-rules', { + contains: 'Edit detector rules', + }); // Search for specific rule - cy.get(`[placeholder="Search..."]`).focus().type('USB').trigger('search', { timeout: 5000 }); + cy.triggerSearchField('Search...', 'USB'); // Toggle single search result to checked cy.contains('tr', 'USB Device Plugged').within(() => { @@ -251,6 +243,9 @@ describe('Detectors', () => { // Save changes cy.get(`[data-test-subj="save-detector-rules-edits"]`).click({ force: true }); + cy.waitForPageLoad('detector-details', { + contains: detectorName, + }); // Confirm 1 rule has been added to detector cy.contains('Active rules (1)'); @@ -261,7 +256,9 @@ describe('Detectors', () => { cy.contains('test detector_edited').click({ force: true }); // Confirm page - cy.contains('Detector details'); + cy.waitForPageLoad('detector-details', { + contains: 'Detector details', + }); // Click "Actions" button, the click "Delete" cy.contains('Actions').click({ force: true }); @@ -270,4 +267,6 @@ describe('Detectors', () => { // Confirm detector is deleted cy.contains('There are no existing detectors'); }); + + after(() => cy.cleanUpTests()); }); diff --git a/cypress/integration/2_rules.spec.js b/cypress/integration/2_rules.spec.js index 36fef1ecb..05bf9c77f 100644 --- a/cypress/integration/2_rules.spec.js +++ b/cypress/integration/2_rules.spec.js @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { PLUGIN_NAME, TWENTY_SECONDS_TIMEOUT } from '../support/constants'; +import { OPENSEARCH_DASHBOARDS_URL } from '../support/constants'; const uniqueId = Cypress._.random(0, 1e6); const SAMPLE_RULE = { @@ -49,212 +49,189 @@ const YAML_RULE_LINES = [ ]; describe('Rules', () => { - before(() => { - // Deleting pre-existing test rules - cy.deleteRule(SAMPLE_RULE.name); - }); + before(() => cy.cleanUpTests()); beforeEach(() => { // Visit Rules page - cy.visit(`${Cypress.env('opensearch_dashboards')}/app/${PLUGIN_NAME}#/rules`); + cy.visit(`${OPENSEARCH_DASHBOARDS_URL}/rules`); + + // Check that correct page is showing + cy.waitForPageLoad('rules', { + contains: 'Rules', + }); }); - describe('Can be created', () => { - it('manually using UI', () => { - // Click "create new rule" button - cy.get('[data-test-subj="create_rule_button"]', TWENTY_SECONDS_TIMEOUT).click({ - force: true, - }); + it('...can be created', () => { + // Click "create new rule" button + cy.get('[data-test-subj="create_rule_button"]').click({ + force: true, + }); - // Enter the name - cy.get('[data-test-subj="rule_name_field"]', TWENTY_SECONDS_TIMEOUT).type(SAMPLE_RULE.name); - - // Enter the log type - cy.get('[data-test-subj="rule_type_dropdown"]', TWENTY_SECONDS_TIMEOUT).select( - SAMPLE_RULE.logType - ); - - // Enter the description - cy.get('[data-test-subj="rule_description_field"]', TWENTY_SECONDS_TIMEOUT).type( - SAMPLE_RULE.description - ); - - // Enter the detection - cy.get('[data-test-subj="rule_detection_field"]', TWENTY_SECONDS_TIMEOUT).type( - SAMPLE_RULE.detection - ); - - // Enter the severity - cy.get('[data-test-subj="rule_severity_dropdown"]', TWENTY_SECONDS_TIMEOUT).select( - SAMPLE_RULE.severity - ); - - // Enter the tags - SAMPLE_RULE.tags.forEach((tag) => - cy - .get('[data-test-subj="rule_tags_dropdown"]', TWENTY_SECONDS_TIMEOUT) - .type(`${tag}{enter}{esc}`) - ); - - // Enter the reference - cy.get('[data-test-subj="rule_references_field_0"]', TWENTY_SECONDS_TIMEOUT).type( - SAMPLE_RULE.references - ); - - // Enter the false positive cases - cy.get('[data-test-subj="rule_false_positive_cases_field_0"]', TWENTY_SECONDS_TIMEOUT).type( - SAMPLE_RULE.falsePositive - ); - - // Enter the author - cy.get('[data-test-subj="rule_author_field"]', TWENTY_SECONDS_TIMEOUT).type( - SAMPLE_RULE.author - ); - - // Enter the log type - cy.get('[data-test-subj="rule_status_dropdown"]', TWENTY_SECONDS_TIMEOUT).select( - SAMPLE_RULE.status - ); - - // Switch to YAML editor - cy.get( - '[data-test-subj="change-editor-type"] label:nth-child(2)', - TWENTY_SECONDS_TIMEOUT - ).click({ - force: true, - }); + // Enter the name + cy.get('[data-test-subj="rule_name_field"]').type(SAMPLE_RULE.name); - YAML_RULE_LINES.forEach((line) => - cy - .get('[data-test-subj="rule_yaml_editor"]', TWENTY_SECONDS_TIMEOUT) - .contains(line, TWENTY_SECONDS_TIMEOUT) - ); + // Enter the log type + cy.get('[data-test-subj="rule_type_dropdown"]').select(SAMPLE_RULE.logType); - // Click "create" button - cy.get('[data-test-subj="create_rule_button"]', TWENTY_SECONDS_TIMEOUT).click({ - force: true, - }); + // Enter the description + cy.get('[data-test-subj="rule_description_field"]').type(SAMPLE_RULE.description); - // Wait for the page to finish loading - cy.wait(5000); - cy.contains('No items found', TWENTY_SECONDS_TIMEOUT).should('not.exist'); - - // Search for the rule - cy.get(`input[type="search"]`, TWENTY_SECONDS_TIMEOUT).type(`${SAMPLE_RULE.name}{enter}`); - - // Click the rule link to open the details flyout - cy.get(`[data-test-subj="rule_link_${SAMPLE_RULE.name}"]`, TWENTY_SECONDS_TIMEOUT).click(); - - // Confirm the flyout contains the expected values - cy.get(`[data-test-subj="rule_flyout_${SAMPLE_RULE.name}"]`, TWENTY_SECONDS_TIMEOUT) - .click({ force: true }) - .within(() => { - // Validate name - cy.get('[data-test-subj="rule_flyout_rule_name"]', TWENTY_SECONDS_TIMEOUT).contains( - SAMPLE_RULE.name, - TWENTY_SECONDS_TIMEOUT - ); - - // Validate log type - cy.get('[data-test-subj="rule_flyout_rule_log_type"]', TWENTY_SECONDS_TIMEOUT).contains( - SAMPLE_RULE.logType, - TWENTY_SECONDS_TIMEOUT - ); - - // Validate description - cy.get( - '[data-test-subj="rule_flyout_rule_description"]', - TWENTY_SECONDS_TIMEOUT - ).contains(SAMPLE_RULE.description, TWENTY_SECONDS_TIMEOUT); - - // Validate author - cy.get('[data-test-subj="rule_flyout_rule_author"]', TWENTY_SECONDS_TIMEOUT).contains( - SAMPLE_RULE.author, - TWENTY_SECONDS_TIMEOUT - ); - - // Validate source is "custom" - cy.get('[data-test-subj="rule_flyout_rule_source"]', TWENTY_SECONDS_TIMEOUT).contains( - 'Custom', - TWENTY_SECONDS_TIMEOUT - ); - - // Validate severity - cy.get('[data-test-subj="rule_flyout_rule_severity"]', TWENTY_SECONDS_TIMEOUT).contains( - SAMPLE_RULE.severity, - TWENTY_SECONDS_TIMEOUT - ); - - // Validate tags - SAMPLE_RULE.tags.forEach((tag) => - cy - .get('[data-test-subj="rule_flyout_rule_tags"]', TWENTY_SECONDS_TIMEOUT) - .contains(tag, TWENTY_SECONDS_TIMEOUT) - ); - - // Validate references - cy.get('[data-test-subj="rule_flyout_rule_references"]', TWENTY_SECONDS_TIMEOUT).contains( - SAMPLE_RULE.references, - TWENTY_SECONDS_TIMEOUT - ); - - // Validate false positives - cy.get( - '[data-test-subj="rule_flyout_rule_false_positives"]', - TWENTY_SECONDS_TIMEOUT - ).contains(SAMPLE_RULE.falsePositive, TWENTY_SECONDS_TIMEOUT); - - // Validate status - cy.get('[data-test-subj="rule_flyout_rule_status"]', TWENTY_SECONDS_TIMEOUT).contains( - SAMPLE_RULE.status, - TWENTY_SECONDS_TIMEOUT - ); - - // Validate detection - SAMPLE_RULE.detectionLine.forEach((line) => - cy - .get('[data-test-subj="rule_flyout_rule_detection"]', TWENTY_SECONDS_TIMEOUT) - .contains(line, TWENTY_SECONDS_TIMEOUT) - ); - - cy.get( - '[data-test-subj="change-editor-type"] label:nth-child(2)', - TWENTY_SECONDS_TIMEOUT - ).click({ - force: true, + // Enter the detection + cy.get('[data-test-subj="rule_detection_field"]').type(SAMPLE_RULE.detection); + + // Enter the severity + cy.get('[data-test-subj="rule_severity_dropdown"]').select(SAMPLE_RULE.severity); + + // Enter the tags + SAMPLE_RULE.tags.forEach((tag) => + cy.get('[data-test-subj="rule_tags_dropdown"]').type(`${tag}{enter}{esc}`) + ); + + // Enter the reference + cy.get('[data-test-subj="rule_references_field_0"]').type(SAMPLE_RULE.references); + + // Enter the false positive cases + cy.get('[data-test-subj="rule_false_positive_cases_field_0"]').type(SAMPLE_RULE.falsePositive); + + // Enter the author + cy.get('[data-test-subj="rule_author_field"]').type(SAMPLE_RULE.author); + + // Enter the log type + cy.get('[data-test-subj="rule_status_dropdown"]').select(SAMPLE_RULE.status); + + // Switch to YAML editor + cy.get('[data-test-subj="change-editor-type"] label:nth-child(2)').click({ + force: true, + }); + + YAML_RULE_LINES.forEach((line) => cy.get('[data-test-subj="rule_yaml_editor"]').contains(line)); + + cy.intercept({ + url: '/rules', + }).as('getRules'); + + // Click "create" button + cy.get('[data-test-subj="create_rule_button"]').click({ + force: true, + }); + + cy.waitForPageLoad('rules', { + contains: 'Rules', + }); + + cy.wait('@getRules'); + + // Search for the rule + cy.triggerSearchField('Search rules', SAMPLE_RULE.name); + + // Click the rule link to open the details flyout + cy.get(`[data-test-subj="rule_link_${SAMPLE_RULE.name}"]`).click({ force: true }); + + // Confirm the flyout contains the expected values + cy.get(`[data-test-subj="rule_flyout_${SAMPLE_RULE.name}"]`) + .click({ force: true }) + .within(() => { + // Validate name + cy.get('[data-test-subj="rule_flyout_rule_name"]').contains(SAMPLE_RULE.name); + + // Validate log type + cy.get('[data-test-subj="rule_flyout_rule_log_type"]').contains(SAMPLE_RULE.logType); + + // Validate description + cy.get('[data-test-subj="rule_flyout_rule_description"]').contains(SAMPLE_RULE.description); + + // Validate author + cy.get('[data-test-subj="rule_flyout_rule_author"]').contains(SAMPLE_RULE.author); + + // Validate source is "custom" + cy.get('[data-test-subj="rule_flyout_rule_source"]').contains('Custom'); + + // Validate severity + cy.get('[data-test-subj="rule_flyout_rule_severity"]').contains(SAMPLE_RULE.severity); + + // Validate tags + SAMPLE_RULE.tags.forEach((tag) => + cy.get('[data-test-subj="rule_flyout_rule_tags"]').contains(tag) + ); + + // Validate references + cy.get('[data-test-subj="rule_flyout_rule_references"]').contains(SAMPLE_RULE.references); + + // Validate false positives + cy.get('[data-test-subj="rule_flyout_rule_false_positives"]').contains( + SAMPLE_RULE.falsePositive + ); + + // Validate status + cy.get('[data-test-subj="rule_flyout_rule_status"]').contains(SAMPLE_RULE.status); + + // Validate detection + SAMPLE_RULE.detectionLine.forEach((line) => + cy.get('[data-test-subj="rule_flyout_rule_detection"]').contains(line) + ); + + cy.get('[data-test-subj="change-editor-type"] label:nth-child(2)').click({ + force: true, + }); + + cy.get('[data-test-subj="rule_flyout_yaml_rule"]') + .get('[class="euiCodeBlock__line"]') + .each((lineElement, lineIndex) => { + if (lineIndex >= YAML_RULE_LINES.length) { + return; + } + let line = lineElement.text().replaceAll('\n', '').trim(); + let expectedLine = YAML_RULE_LINES[lineIndex]; + + // The document ID field is generated when the document is added to the index, + // so this test just checks that the line starts with the ID key. + if (expectedLine.startsWith('id:')) { + expectedLine = 'id:'; + expect(line, `Sigma rule line ${lineIndex}`).to.contain(expectedLine); + } else { + expect(line, `Sigma rule line ${lineIndex}`).to.equal(expectedLine); + } }); - cy.get('[data-test-subj="rule_flyout_yaml_rule"]') - .get('[class="euiCodeBlock__line"]') - .each((lineElement, lineIndex) => { - if (lineIndex >= YAML_RULE_LINES.length) { - return; - } - let line = lineElement.text().replaceAll('\n', '').trim(); - let expectedLine = YAML_RULE_LINES[lineIndex]; - - // The document ID field is generated when the document is added to the index, - // so this test just checks that the line starts with the ID key. - if (expectedLine.startsWith('id:')) { - expectedLine = 'id:'; - expect(line, `Sigma rule line ${lineIndex}`).to.contain(expectedLine); - } else { - expect(line, `Sigma rule line ${lineIndex}`).to.equal(expectedLine); - } - }); - - // Close the flyout - cy.get('[data-test-subj="close-rule-details-flyout"]', TWENTY_SECONDS_TIMEOUT).click({ + // Close the flyout + cy.get('[data-test-subj="close-rule-details-flyout"]').click({ + force: true, + }); + }); + }); + + it('...can be deleted', () => { + cy.intercept({ + url: '/rules', + }).as('deleteRule'); + + cy.triggerSearchField('Search rules', SAMPLE_RULE.name); + + // Click the rule link to open the details flyout + cy.get(`[data-test-subj="rule_link_${SAMPLE_RULE.name}"]`).click({ force: true }); + + cy.get('.euiButton') + .contains('Action') + .click({ force: true }) + .then(() => { + // Confirm arrival at detectors page + cy.get( + '.euiFlexGroup > :nth-child(3) > .euiButtonEmpty > .euiButtonContent > .euiButtonEmpty__text' + ) + .click({ force: true, + }) + .then(() => { + cy.get('.euiButton').contains('Delete').click(); }); - }); - // Confirm flyout closed - cy.contains(`[data-test-subj="rule_flyout_${SAMPLE_RULE.name}"]`).should('not.exist'); - }); - }); + cy.wait('@deleteRule'); - after(() => { - // Deleting test rules - cy.deleteRule(SAMPLE_RULE.name); + // Search for sample_detector, presumably deleted + cy.triggerSearchField('Search rules', SAMPLE_RULE.name); + // Click the rule link to open the details flyout + cy.get('tbody').contains(SAMPLE_RULE.name).should('not.exist'); + }); }); + + after(() => cy.cleanUpTests()); }); diff --git a/cypress/integration/3_alerts.spec.js b/cypress/integration/3_alerts.spec.js index 71ed9a628..332328c82 100644 --- a/cypress/integration/3_alerts.spec.js +++ b/cypress/integration/3_alerts.spec.js @@ -4,12 +4,7 @@ */ import moment from 'moment'; -import { - PLUGIN_NAME, - NINETY_SECONDS, - TWENTY_SECONDS_TIMEOUT, - FEATURE_SYSTEM_INDICES, -} from '../support/constants'; +import { DETECTOR_TRIGGER_TIMEOUT, OPENSEARCH_DASHBOARDS_URL } from '../support/constants'; import sample_index_settings from '../fixtures/sample_index_settings.json'; import sample_alias_mappings from '../fixtures/sample_alias_mappings.json'; import sample_detector from '../fixtures/sample_detector.json'; @@ -44,20 +39,11 @@ const testDetector = { // but all of the alert time fields should all contain the date in this format. const date = moment(moment.now()).format('MM/DD/YY'); +const docCount = 4; describe('Alerts', () => { before(() => { // Delete any pre-existing test detectors - cy.deleteDetector(testDetectorName) - - // Delete any pre-existing test indices - .then(() => cy.deleteIndex(testIndex)) - - // Delete any pre-existing windows alerts and findings - .then(() => { - cy.deleteIndex(FEATURE_SYSTEM_INDICES.WINDOWS_ALERTS_INDEX); - cy.deleteIndex(FEATURE_SYSTEM_INDICES.WINDOWS_FINDINGS_INDEX); - }) - + cy.cleanUpTests() // Create test index .then(() => cy.createIndex(testIndex, sample_index_settings)) @@ -71,25 +57,42 @@ describe('Alerts', () => { .then(() => { // Go to the detectors table page - cy.visit(`${Cypress.env('opensearch_dashboards')}/app/${PLUGIN_NAME}#/detectors`); + cy.visit(`${OPENSEARCH_DASHBOARDS_URL}/detectors`); + + // Check that correct page is showing + cy.waitForPageLoad('detectors', { + contains: 'Threat detectors', + }); // Filter table to only show the test detector - cy.get(`input[type="search"]`, TWENTY_SECONDS_TIMEOUT).type(`${testDetector.name}{enter}`); + cy.get(`input[type="search"]`).type(`${testDetector.name}{enter}`); // Confirm detector was created - cy.get('tbody > tr', TWENTY_SECONDS_TIMEOUT).should(($tr) => { - expect($tr, '1 row').to.have.length(1); + cy.get('tbody > tr').should(($tr) => { expect($tr, 'detector name').to.contain(testDetector.name); }); }); + + // Ingest documents to the test index + for (let i = 0; i < docCount; i++) { + cy.insertDocumentToIndex(testIndex, '', sample_document); + } + + // Wait for the detector to execute + cy.wait(DETECTOR_TRIGGER_TIMEOUT); }); beforeEach(() => { // Visit Alerts table page - cy.visit(`${Cypress.env('opensearch_dashboards')}/app/${PLUGIN_NAME}#/alerts`); + cy.visit(`${OPENSEARCH_DASHBOARDS_URL}/alerts`); + + // Wait for page to load + cy.waitForPageLoad('alerts', { + contains: 'Security alerts', + }); // Filter table to only show alerts for the test detector - cy.get(`input[type="search"]`, TWENTY_SECONDS_TIMEOUT).type(`${testDetector.name}{enter}`); + cy.get(`input[type="search"]`).type(`${testDetector.name}{enter}`); // Adjust the date range picker to display alerts from today cy.get('[class="euiButtonEmpty__text euiQuickSelectPopover__buttonText"]').click({ @@ -99,37 +102,28 @@ describe('Alerts', () => { }); it('are generated', () => { - // Ingest documents to the test index - const docCount = 4; - for (let i = 0; i < docCount; i++) { - cy.insertDocumentToIndex(testIndex, '', sample_document); - } - - // Wait for the detector to execute - cy.wait(NINETY_SECONDS); - // Refresh the table cy.get('[data-test-subj="superDatePickerApplyTimeButton"]').click({ force: true }); - // Confirm the table contains 1 row - cy.get('tbody > tr', TWENTY_SECONDS_TIMEOUT).should(($tr) => - expect($tr, `${docCount} rows`).to.have.length(docCount) - ); + // Confirm there are alerts created + cy.get('tbody > tr') + .filter(`:contains(${testDetectorAlertCondition})`) + .should('have.length', docCount); }); it('contain expected values in table', () => { - // Confirm each row contains the expected values - cy.get('tbody > tr', TWENTY_SECONDS_TIMEOUT).each(($el, $index) => { - expect($el, `row number ${$index} start time`).to.contain(date); - expect($el, `row number ${$index} trigger name`).to.contain(testDetector.triggers[0].name); - expect($el, `row number ${$index} detector name`).to.contain(testDetector.name); - expect($el, `row number ${$index} status`).to.contain('Active'); - expect($el, `row number ${$index} severity`).to.contain('4 (Low)'); + // Confirm there is a row containing the expected values + cy.get('tbody > tr').should(($tr) => { + expect($tr, 'start time').to.contain(date); + expect($tr, 'trigger name').to.contain(testDetector.triggers[0].name); + expect($tr, 'detector name').to.contain(testDetector.name); + expect($tr, 'status').to.contain('Active'); + expect($tr, 'severity').to.contain('4 (Low)'); }); }); it('contain expected values in alert details flyout', () => { - cy.get('tbody > tr', TWENTY_SECONDS_TIMEOUT) + cy.get('tbody > tr') .first() .within(() => { // Click the "View details" button for the first alert @@ -159,18 +153,16 @@ describe('Alerts', () => { cy.get('[data-test-subj="text-details-group-content-detector"]').contains(testDetector.name); // Wait for the findings table to finish loading - cy.contains('Findings (1)', TWENTY_SECONDS_TIMEOUT); - cy.contains('USB Device Plugged', TWENTY_SECONDS_TIMEOUT); + cy.contains('Findings (1)'); + cy.contains('USB Device Plugged'); // Confirm alert findings contain expected values - cy.get('tbody > tr', TWENTY_SECONDS_TIMEOUT) - .should(($tr) => expect($tr, '1 row').to.have.length(1)) - .each(($el, $index) => { - expect($el, `row number ${$index} timestamp`).to.contain(date); - expect($el, `row number ${$index} rule name`).to.contain('USB Device Plugged'); - expect($el, `row number ${$index} detector name`).to.contain(testDetector.name); - expect($el, `row number ${$index} log type`).to.contain('Windows'); - }); + cy.get('tbody > tr').should(($tr) => { + expect($tr, `timestamp`).to.contain(date); + expect($tr, `rule name`).to.contain('USB Device Plugged'); + expect($tr, `detector name`).to.contain(testDetector.name); + expect($tr, `log type`).to.contain('Windows'); + }); // Close the flyout cy.get('[data-test-subj="alert-details-flyout-close-button"]').click({ force: true }); @@ -182,22 +174,22 @@ describe('Alerts', () => { it('contain expected values in finding details flyout', () => { // Open first alert details flyout - cy.get('tbody > tr', TWENTY_SECONDS_TIMEOUT) + cy.get('tbody > tr') .first() .within(() => { // Click the "View details" button for the first alert cy.get('[aria-label="View details"]').click({ force: true }); }); - cy.get('[data-test-subj="alert-details-flyout"]', TWENTY_SECONDS_TIMEOUT).within(() => { + cy.get('[data-test-subj="alert-details-flyout"]').within(() => { // Wait for findings table to finish loading - cy.contains('USB Device Plugged', TWENTY_SECONDS_TIMEOUT); + cy.contains('USB Device Plugged'); // Click the details button for the first finding - cy.get('tbody > tr', TWENTY_SECONDS_TIMEOUT) + cy.get('tbody > tr') .first() .within(() => { - cy.get('[data-test-subj="finding-details-flyout-button"]', TWENTY_SECONDS_TIMEOUT).click({ + cy.get('[data-test-subj="finding-details-flyout-button"]').click({ force: true, }); }); @@ -289,17 +281,14 @@ describe('Alerts', () => { }); // Press the "back" button - cy.get( - '[data-test-subj="finding-details-flyout-back-button"]', - TWENTY_SECONDS_TIMEOUT - ).click({ force: true }); + cy.get('[data-test-subj="finding-details-flyout-back-button"]').click({ force: true }); }); // Confirm finding details flyout closed cy.get('[data-test-subj="finding-details-flyout"]').should('not.exist'); // Confirm the expected alert details flyout rendered - cy.get('[data-test-subj="alert-details-flyout"]', TWENTY_SECONDS_TIMEOUT).within(() => { + cy.get('[data-test-subj="alert-details-flyout"]').within(() => { cy.get('[data-test-subj="text-details-group-content-alert-trigger-name"]').contains( testDetector.triggers[0].name ); @@ -310,30 +299,23 @@ describe('Alerts', () => { // Confirm the "Acknowledge" button is disabled when no alerts are selected cy.get('[data-test-subj="acknowledge-button"]').should('be.disabled'); - // Confirm all 4 alerts are currently "Active" - cy.get('tbody > tr', TWENTY_SECONDS_TIMEOUT) - .should(($tr) => expect($tr, '4 rows').to.have.length(4)) - .each(($el, $index) => { - expect($el, `row number ${$index} status`).to.contain('Active'); - }); + // Confirm there is alert which is currently "Active" + cy.get('tbody > tr').should(($tr) => { + expect($tr, `status`).to.contain('Active'); + }); // Click the checkboxes for the first and last alerts. - cy.get('tbody > tr', TWENTY_SECONDS_TIMEOUT) + cy.get('tbody > tr') .first() .within(() => { cy.get('[class="euiCheckbox__input"]').click({ force: true }); }); - cy.get('tbody > tr', TWENTY_SECONDS_TIMEOUT) - .last() - .within(() => { - cy.get('[class="euiCheckbox__input"]').click({ force: true }); - }); // Press the "Acknowledge" button cy.get('[data-test-subj="acknowledge-button"]').click({ force: true }); // Wait for acknowledge API to finish executing - cy.contains('Acknowledged', TWENTY_SECONDS_TIMEOUT); + cy.contains('Acknowledged'); // Filter the table to show only "Acknowledged" alerts cy.get('[data-text="Status"]').click({ force: true }); @@ -341,26 +323,23 @@ describe('Alerts', () => { cy.contains('Acknowledged').click({ force: true }); }); - // Confirm there are now 2 "Acknowledged" alerts - cy.get('tbody > tr', TWENTY_SECONDS_TIMEOUT) - .should(($tr) => expect($tr, '2 rows').to.have.length(2)) - .each(($el, $index) => { - expect($el, `row number ${$index} status`).to.contain('Acknowledged'); - }); + // Confirm there is an "Acknowledged" alert + cy.get('tbody > tr').should(($tr) => { + expect($tr, `alert name`).to.contain(testDetectorAlertCondition); + expect($tr, `status`).to.contain('Acknowledged'); + }); // Filter the table to show only "Active" alerts cy.get('[data-text="Status"]'); cy.get('[class="euiFilterSelect__items"]').within(() => { cy.contains('Acknowledged').click({ force: true }); - cy.contains('Active').click({ force: true }); }); // Confirm there are now 2 "Acknowledged" alerts - cy.get('tbody > tr', TWENTY_SECONDS_TIMEOUT) - .should(($tr) => expect($tr, '2 rows').to.have.length(2)) - .each(($el, $index) => { - expect($el, `row number ${$index} status`).to.contain('Active'); - }); + cy.get('tbody > tr') + .filter(`:contains(${testDetectorAlertCondition})`) + .should('contain', 'Active') + .should('contain', 'Acknowledged'); }); it('can be acknowledged via row button', () => { @@ -370,19 +349,20 @@ describe('Alerts', () => { cy.contains('Active').click({ force: true }); }); - // Confirm there are 2 "Active" alerts - cy.get('tbody > tr', TWENTY_SECONDS_TIMEOUT) - .should(($tr) => expect($tr, '2 rows').to.have.length(2)) + cy.get('tbody > tr') + .filter(`:contains(${testDetectorAlertCondition})`) + .should('have.length', 3); + + cy.get('tbody > tr') // Click the "Acknowledge" icon button in the first row .first() .within(() => { cy.get('[aria-label="Acknowledge"]').click({ force: true }); }); - // Confirm there is 1 "Active" alert - cy.get('tbody > tr', TWENTY_SECONDS_TIMEOUT).should(($tr) => - expect($tr, '1 row').to.have.length(1) - ); + cy.get('tbody > tr') + .filter(`:contains(${testDetectorAlertCondition})`) + .should('have.length', 2); // Filter the table to show only "Acknowledged" alerts cy.get('[data-text="Status"]'); @@ -392,9 +372,9 @@ describe('Alerts', () => { }); // Confirm there are now 3 "Acknowledged" alerts - cy.get('tbody > tr', TWENTY_SECONDS_TIMEOUT).should(($tr) => - expect($tr, '3 rows').to.have.length(3) - ); + cy.get('tbody > tr') + .filter(`:contains(${testDetectorAlertCondition})`) + .should('have.length', 2); }); it('can be acknowledged via flyout button', () => { @@ -404,7 +384,7 @@ describe('Alerts', () => { cy.contains('Active').click({ force: true }); }); - cy.get('tbody > tr', TWENTY_SECONDS_TIMEOUT) + cy.get('tbody > tr') .first() .within(() => { // Click the "View details" button for the first alert @@ -419,10 +399,7 @@ describe('Alerts', () => { cy.get('[data-test-subj="alert-details-flyout-acknowledge-button"]').click({ force: true }); // Confirm the alert is now "Acknowledged" - cy.get( - '[data-test-subj="text-details-group-content-alert-status"]', - TWENTY_SECONDS_TIMEOUT - ).contains('Active'); + cy.get('[data-test-subj="text-details-group-content-alert-status"]').contains('Active'); // Confirm the "Acknowledge" button is disabled cy.get('[data-test-subj="alert-details-flyout-acknowledge-button"]').should('be.disabled'); @@ -431,22 +408,22 @@ describe('Alerts', () => { it('detector name hyperlink on finding details flyout redirects to the detector details page', () => { // Open first alert details flyout - cy.get('tbody > tr', TWENTY_SECONDS_TIMEOUT) + cy.get('tbody > tr') .first() .within(() => { // Click the "View details" button for the first alert cy.get('[aria-label="View details"]').click({ force: true }); }); - cy.get('[data-test-subj="alert-details-flyout"]', TWENTY_SECONDS_TIMEOUT).within(() => { + cy.get('[data-test-subj="alert-details-flyout"]').within(() => { // Wait for findings table to finish loading - cy.contains('USB Device Plugged', TWENTY_SECONDS_TIMEOUT); + cy.contains('USB Device Plugged'); // Click the details button for the first finding - cy.get('tbody > tr', TWENTY_SECONDS_TIMEOUT) + cy.get('tbody > tr') .first() .within(() => { - cy.get('[data-test-subj="finding-details-flyout-button"]', TWENTY_SECONDS_TIMEOUT).click({ + cy.get('[data-test-subj="finding-details-flyout-button"]').click({ force: true, }); }); @@ -461,17 +438,8 @@ describe('Alerts', () => { }); // Confirm the detector details page is for the expected detector - cy.get('[data-test-subj="detector-details-detector-name"]', TWENTY_SECONDS_TIMEOUT).contains( - testDetector.name, - TWENTY_SECONDS_TIMEOUT - ); + cy.get('[data-test-subj="detector-details-detector-name"]').contains(testDetector.name); }); - after(() => { - // Clean up test resources - cy.deleteDetector(testDetectorName); - cy.deleteIndex(FEATURE_SYSTEM_INDICES.WINDOWS_ALERTS_INDEX); - cy.deleteIndex(FEATURE_SYSTEM_INDICES.WINDOWS_FINDINGS_INDEX); - cy.deleteIndex(testIndex); - }); + after(() => cy.cleanUpTests()); }); diff --git a/cypress/integration/4_findings.spec.js b/cypress/integration/4_findings.spec.js index 6eedc94f9..cd8025433 100644 --- a/cypress/integration/4_findings.spec.js +++ b/cypress/integration/4_findings.spec.js @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { PLUGIN_NAME, TWENTY_SECONDS_TIMEOUT } from '../support/constants'; +import { DETECTOR_TRIGGER_TIMEOUT, OPENSEARCH_DASHBOARDS_URL } from '../support/constants'; import sample_document from '../fixtures/sample_document.json'; import sample_index_settings from '../fixtures/sample_index_settings.json'; import sample_field_mappings from '../fixtures/sample_field_mappings.json'; @@ -14,30 +14,34 @@ describe('Findings', () => { const indexName = 'cypress-test-windows'; before(() => { - cy.deleteAllIndices(); + cy.cleanUpTests(); // Visit Findings page - cy.visit(`${Cypress.env('opensearch_dashboards')}/app/${PLUGIN_NAME}#/findings`); + cy.visit(`${OPENSEARCH_DASHBOARDS_URL}/findings`); // create test index, mappings, and detector cy.createIndex(indexName, sample_index_settings); cy.createAliasMappings(indexName, 'windows', sample_field_mappings, true); cy.createDetector(sample_detector); - }); - - it('displays findings based on recently ingested data', () => { - // Confirm arrival at Findings page - cy.url({ timeout: 2000 }).should( - 'include', - 'opensearch_security_analytics_dashboards#/findings' - ); // Ingest a new document cy.ingestDocument(indexName, sample_document); // wait for detector interval to pass - cy.wait(60000); + cy.wait(DETECTOR_TRIGGER_TIMEOUT); + }); + + beforeEach(() => { + // Visit Alerts table page + cy.visit(`${OPENSEARCH_DASHBOARDS_URL}/findings`); + // Wait for page to load + cy.waitForPageLoad('findings', { + contains: 'Findings', + }); + }); + + it('displays findings based on recently ingested data', () => { // Click refresh cy.get('button').contains('Refresh').click({ force: true }); @@ -50,41 +54,45 @@ describe('Findings', () => { cy.contains('Low'); }); - it('displays finding details flyout when user clicks on Finding ID or View details icon', () => { + it('displays finding details flyout when user clicks on View details icon', () => { // filter table to show only sample_detector findings - cy.get(`[placeholder="Search findings"]`).type('sample_detector').trigger('Search'); + cy.triggerSearchField('Search findings', 'sample_detector'); - // Click findingId to trigger Finding details flyout - cy.get(`[data-test-subj="finding-details-flyout-button"]`, { timeout: 2000 }).eq(0).click(); + // Click View details icon + cy.getTableFirstRow('[data-test-subj="view-details-icon"]').then(($el) => { + cy.get($el).click({ force: true }); + }); // Confirm flyout contents cy.contains('Finding details'); cy.contains('Rule details'); // Close Flyout - cy.get(`[data-test-subj="close-finding-details-flyout"]`).then(($el) => { - cy.get($el).click({ force: true }); - }); + cy.get('.euiFlexItem--flexGrowZero > .euiButtonIcon').click({ force: true }); + }); - // wait for icon to become clickable - in this case, timeout insufficient. - cy.wait(1000); + it('displays finding details flyout when user clicks on Finding ID', () => { + // filter table to show only sample_detector findings + cy.triggerSearchField('Search findings', 'sample_detector'); - // Click View details icon - cy.get(`[data-test-subj="view-details-icon"]`).eq(0).click({ force: true }); + // Click findingId to trigger Finding details flyout + cy.getTableFirstRow('[data-test-subj="finding-details-flyout-button"]').then(($el) => { + cy.get($el).click({ force: true }); + }); // Confirm flyout contents cy.contains('Finding details'); cy.contains('Rule details'); // Close Flyout - cy.get(`[data-test-subj="close-finding-details-flyout"]`).then(($el) => { - cy.get($el).click({ force: true }); - }); + cy.get('.euiFlexItem--flexGrowZero > .euiButtonIcon').click({ force: true }); }); it('allows user to view details about rules that were triggered', () => { + // filter table to show only sample_detector findings + cy.triggerSearchField('Search findings', 'sample_detector'); + // open Finding details flyout via finding id link. cy.wait essential, timeout insufficient. - cy.wait(1000); cy.get(`[data-test-subj="view-details-icon"]`).eq(0).click({ force: true }); // open rule details inside flyout @@ -107,49 +115,64 @@ describe('Findings', () => { // see github issue #124 at https://github.com/opensearch-project/security-analytics-dashboards-plugin/issues/124 it('opens rule details flyout when rule name inside accordion drop down is clicked', () => { + // filter table to show only sample_detector findings + cy.triggerSearchField('Search findings', 'sample_detector'); + + // open Finding details flyout via finding id link. cy.wait essential, timeout insufficient. + cy.getTableFirstRow('[data-test-subj="view-details-icon"]').then(($el) => { + cy.get($el).click({ force: true }); + }); + // Click rule link cy.get(`[data-test-subj="finding-details-flyout-USB Device Plugged-details"]`).click({ force: true, }); // Validate flyout appearance - cy.get('[data-test-subj="rule_flyout_USB Device Plugged"]', TWENTY_SECONDS_TIMEOUT).within( - () => { - cy.get('[data-test-subj="rule_flyout_rule_name"]', TWENTY_SECONDS_TIMEOUT).contains( - 'USB Device Plugged', - TWENTY_SECONDS_TIMEOUT - ); - } - ); + cy.get('[data-test-subj="rule_flyout_USB Device Plugged"]').within(() => { + cy.get('[data-test-subj="rule_flyout_rule_name"]').contains('USB Device Plugged'); + }); }); - after(() => { + it('...can delete detector', () => { // Visit Detectors page - cy.visit(`${Cypress.env('opensearch_dashboards')}/app/${PLUGIN_NAME}#/detectors`, { - timeout: 5000, + cy.visit(`${OPENSEARCH_DASHBOARDS_URL}/detectors`); + cy.waitForPageLoad('detectors', { + contains: 'Threat detectors', }); - // Confirm arrival at detectors page - cy.url().should('include', 'opensearch_security_analytics_dashboards#/detectors'); - - // Click on detector to be removed - cy.contains('sample_detector').click({ force: true }, { timeout: 2000 }); - - cy.url().should('include', 'opensearch_security_analytics_dashboards#/detector-details'); + // filter table to show only sample_detector findings + cy.triggerSearchField('Search threat detectors', 'sample_detector'); - // Click "Actions" button, the click "Delete" - cy.get('button') - .contains('Actions') - .click({ force: true }, { timeout: 2000 }) - .then(() => { - // Confirm arrival at detectors page - cy.contains('Delete').click({ force: true }); + // intercept detectors and rules requests + cy.intercept('detectors/_search').as('getDetector'); + cy.intercept('rules/_search?prePackaged=true').as('getPrePackagedRules'); + cy.intercept('rules/_search?prePackaged=false').as('getRules'); - // Search for sample_detector, presumably deleted - cy.get(`[placeholder="Search threat detectors"]`).type('sample_detector').trigger('search'); + // Click on detector to be removed + cy.contains('sample_detector').click({ force: true }); + cy.waitForPageLoad('detector-details', { + contains: sample_detector.name, + }); - // Confirm sample_detector no longer exists - cy.contains('There are no existing detectors.'); - }); + // wait for detector details to load before continuing + cy.wait(['@getDetector', '@getPrePackagedRules', '@getRules']).then(() => { + // Click "Actions" button, the click "Delete" + cy.get('button.euiButton') + .contains('Actions') + .click({ force: true }) + .then(() => { + // Confirm arrival at detectors page + cy.get('[data-test-subj="editButton"]').contains('Delete').click({ force: true }); + + // Search for sample_detector, presumably deleted + cy.triggerSearchField('Search threat detectors', 'sample_detector'); + + // Confirm sample_detector no longer exists + cy.contains('There are no existing detectors.'); + }); + }); }); + + after(() => cy.cleanUpTests()); }); diff --git a/cypress/integration/5_integrations.spec.js b/cypress/integration/5_integrations.spec.js index ee82d4ab9..a38ea3ecb 100644 --- a/cypress/integration/5_integrations.spec.js +++ b/cypress/integration/5_integrations.spec.js @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { PLUGIN_NAME } from '../support/constants'; +import { DETECTOR_TRIGGER_TIMEOUT, OPENSEARCH_DASHBOARDS_URL } from '../support/constants'; import sample_index_settings from '../fixtures/sample_index_settings.json'; import sample_dns_settings from '../fixtures/integration_tests/index/create_dns_settings.json'; import windows_usb_rule_data from '../fixtures/integration_tests/rule/create_windows_usb_rule.json'; @@ -19,14 +19,8 @@ describe('Integration tests', () => { const indexName = 'cypress-index-windows'; const dnsName = 'cypress-index-dns'; - const cleanUpTests = () => { - cy.deleteAllCustomRules(); - cy.deleteAllDetectors(); - cy.deleteAllIndices(); - }; - before(() => { - cleanUpTests(); + cy.cleanUpTests(); // Create custom rules cy.createRule(windows_usb_rule_data).then((response) => { @@ -66,25 +60,17 @@ describe('Integration tests', () => { cy.request('POST', `${Cypress.env('opensearch')}/${dnsName}/_doc/101`, add_dns_index_data); // Wait for detector interval to pass - cy.wait(60000); + cy.wait(DETECTOR_TRIGGER_TIMEOUT); }); beforeEach(() => { // Visit Detectors page - cy.visit(`${Cypress.env('opensearch_dashboards')}/app/${PLUGIN_NAME}#/detectors`); + cy.visit(`${OPENSEARCH_DASHBOARDS_URL}/detectors`); // Wait for page to load - cy.wait(7000); - - // Check that correct page is showing - cy.url().should( - 'eq', - 'http://localhost:5601/app/opensearch_security_analytics_dashboards#/detectors' - ); + cy.waitForPageLoad('detectors', 'Threat detectors'); }); - after(() => cleanUpTests()); - it('...can navigate to findings page', () => { cy.intercept({ method: 'GET', @@ -151,4 +137,6 @@ describe('Integration tests', () => { }); }); }); + + after(() => cy.cleanUpTests()); }); diff --git a/cypress/support/commands.js b/cypress/support/commands.js index 011ce6aae..cb5c8824d 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -3,8 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -const { NODE_API } = require('./constants'); - +const { NODE_API, OPENSEARCH_DASHBOARDS, OPENSEARCH_DASHBOARDS_URL } = require('./constants'); // *********************************************** // This example commands.js shows you how to // create various custom commands and overwrite @@ -84,6 +83,33 @@ Cypress.Commands.overwrite('request', (originalFn, ...args) => { return originalFn(Object.assign({}, defaults, options)); }); +Cypress.Commands.add('cleanUpTests', () => { + cy.deleteAllCustomRules(); + cy.deleteAllDetectors(); + cy.deleteAllIndices(); +}); + +Cypress.Commands.add('getTableFirstRow', (selector) => { + if (!selector) return cy.get('tbody > tr').first(); + return cy.get('tbody > tr:first').find(selector); +}); + +Cypress.Commands.add('triggerSearchField', (placeholder, text) => { + cy.get(`[placeholder="${placeholder}"]`).type(`{selectall}${text}`).trigger('search'); +}); + +Cypress.Commands.add('waitForPageLoad', (url, { timeout = 10000, contains = null }) => { + const fullUrl = `${OPENSEARCH_DASHBOARDS_URL}/${url}`; + Cypress.log({ + message: `Wait for url: ${fullUrl} to be loaded.`, + }); + cy.url({ timeout: timeout }) + .should('include', fullUrl) + .then(() => { + contains && cy.contains(contains); + }); +}); + Cypress.Commands.add('deleteAllIndices', () => { cy.request({ method: 'DELETE', @@ -170,9 +196,7 @@ Cypress.Commands.add( Cypress.Commands.add('createRule', (ruleJSON) => { return cy.request({ method: 'POST', - url: `${Cypress.env('opensearch_dashboards')}${NODE_API.RULES_BASE}?category=${ - ruleJSON.category - }`, + url: `${OPENSEARCH_DASHBOARDS}${NODE_API.RULES_BASE}?category=${ruleJSON.category}`, body: JSON.stringify(ruleJSON), }); }); diff --git a/cypress/support/constants.js b/cypress/support/constants.js index dd8156067..a5ca5beb4 100644 --- a/cypress/support/constants.js +++ b/cypress/support/constants.js @@ -5,9 +5,10 @@ import { API } from '../../server/utils/constants'; -export const NINETY_SECONDS = 90000; export const TWENTY_SECONDS_TIMEOUT = { timeout: 20000 }; +export const DETECTOR_TRIGGER_TIMEOUT = 65000; + export const FEATURE_SYSTEM_INDICES = { DETECTORS_INDEX: '.opensearch-detectors-config', DETECTOR_QUERIES_INDEX: '.opensearch-sap-windows-detectors-queries', @@ -23,3 +24,6 @@ export const NODE_API = { ...API, INDEX_TEMPLATE_BASE: '/_index_template', }; + +export const { opensearch_dashboards: OPENSEARCH_DASHBOARDS } = Cypress.env(); +export const OPENSEARCH_DASHBOARDS_URL = `${OPENSEARCH_DASHBOARDS}/app/${PLUGIN_NAME}#`; diff --git a/cypress/support/index.d.ts b/cypress/support/index.d.ts index 5dc4c2a8d..2af6cf0b6 100644 --- a/cypress/support/index.d.ts +++ b/cypress/support/index.d.ts @@ -3,10 +3,20 @@ * SPDX-License-Identifier: Apache-2.0 */ -/// +/// declare namespace Cypress { interface Chainable { + /** + * Wait for page to be loaded + * @param {string} url + * @param {number} timeout + * @example + * cy.waitForPageLoad('detectors') + * cy.waitForPageLoad('detectors', 20000) + */ + waitForPageLoad(url: string, timeout?: number): Chainable; + /** * Deletes all indices in cluster * @example @@ -14,6 +24,33 @@ declare namespace Cypress { */ deleteAllIndices(): Chainable; + /** + * Removes custom rules, detectors and indices + * @example + * cy.cleanUpTests() + */ + cleanUpTests(): Chainable; + + /** + * Returns table first row + * Can find elements deeper in a row with selector + * @param {string} selector + * @example + * cy.getTableFirstRow() + * cy.getTableFirstRow('td') + */ + getTableFirstRow(selector: string): Chainable; + + /** + * Returns table first row + * Can find elements deeper in a row with selector + * @param {string} placeholder + * @param {string} text + * @example + * cy.triggerSearchField('Search rules', 'USB Detection Rule') + */ + triggerSearchField(placeholder: string, text: string): Chainable; + /** * Deletes all custom rules in cluster * @example