From 7d2104c9bfec70c1ea9f1455cca227bc345d210e Mon Sep 17 00:00:00 2001 From: dkirchan Date: Wed, 18 Oct 2023 16:01:25 +0300 Subject: [PATCH 01/42] Fixed parallel script for cypress and buildkite --- .../pipeline.sh | 5 +- .../run_cypress/parallel_serverless.ts | 508 ++++++++++++++++++ .../start_cypress_parallel_serverless.js | 9 + .../cypress_ci_serverless_qa.config.ts | 3 - .../security_solution_cypress/package.json | 4 +- 5 files changed, 522 insertions(+), 7 deletions(-) create mode 100644 x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts create mode 100644 x-pack/plugins/security_solution/scripts/start_cypress_parallel_serverless.js diff --git a/.buildkite/scripts/pipelines/security_solution_quality_gate/pipeline.sh b/.buildkite/scripts/pipelines/security_solution_quality_gate/pipeline.sh index 3d61d70cf6828..2a3472c2d22f4 100755 --- a/.buildkite/scripts/pipelines/security_solution_quality_gate/pipeline.sh +++ b/.buildkite/scripts/pipelines/security_solution_quality_gate/pipeline.sh @@ -13,7 +13,6 @@ echo "--- Serverless Security Second Quality Gate" cd x-pack/test/security_solution_cypress set +e -VAULT_DEC_KEY=$(vault read -field=key secret/kibana-issues/dev/security-solution-qg-enc-key) -ENV_PWD=$(echo $TEST_ENV_PWD | openssl aes-256-cbc -d -a -pass pass:$VAULT_DEC_KEY) +QA_API_KEY=$(vault read -field=api-key secret/kibana-issues/dev/security-solution-qg-enc-key) -CYPRESS_ELASTICSEARCH_URL=$TEST_ENV_ES_URL CYPRESS_BASE_URL=$TEST_ENV_KB_URL CYPRESS_ELASTICSEARCH_USERNAME=$TEST_ENV_USERNAME CYPRESS_ELASTICSEARCH_PASSWORD=$ENV_PWD CYPRESS_KIBANA_URL=$CYPRESS_BASE_URL yarn cypress:run:qa:serverless; status=$?; yarn junit:merge || :; exit $status \ No newline at end of file +API_KEY=$QA_API_KEY yarn cypress:run:qa:serverless:parallel; status=$?; yarn junit:merge || :; exit $status \ No newline at end of file diff --git a/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts b/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts new file mode 100644 index 0000000000000..a9f0d2be5c1ed --- /dev/null +++ b/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts @@ -0,0 +1,508 @@ +/* + * 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 { run } from '@kbn/dev-cli-runner'; +import yargs from 'yargs'; +import _ from 'lodash'; +import globby from 'globby'; +import pMap from 'p-map'; +import { ToolingLog } from '@kbn/tooling-log'; +import { withProcRunner } from '@kbn/dev-proc-runner'; +import cypress from 'cypress'; +import { findChangedFiles } from 'find-cypress-specs'; +import grep from '@cypress/grep/src/plugin'; +import crypto from 'crypto'; +import fs from 'fs'; +import { createFailError } from '@kbn/dev-cli-errors'; +import axios from 'axios'; +import { renderSummaryTable } from './print_run'; +import { isSkipped } from './utils'; +import path from 'path'; + +type Environment = { + name: string; + id: string; + region: string; + es_url: string; + kb_url: string; + product: string; +}; + +type Credentials = { + username: string; + password: string; +}; + +/** + * Retrieve test files using a glob pattern. + * If process.env.RUN_ALL_TESTS is true, returns all matching files, otherwise, return files that should be run by this job based on process.env.BUILDKITE_PARALLEL_JOB_COUNT and process.env.BUILDKITE_PARALLEL_JOB + */ +const retrieveIntegrations = (integrationsPaths: string[]) => { + const nonSkippedSpecs = integrationsPaths.filter((filePath) => !isSkipped(filePath)); + + if (process.env.RUN_ALL_TESTS === 'true') { + return nonSkippedSpecs; + } else { + // The number of instances of this job were created + const chunksTotal: number = process.env.BUILDKITE_PARALLEL_JOB_COUNT + ? parseInt(process.env.BUILDKITE_PARALLEL_JOB_COUNT, 10) + : 1; + // An index which uniquely identifies this instance of the job + const chunkIndex: number = process.env.BUILDKITE_PARALLEL_JOB + ? parseInt(process.env.BUILDKITE_PARALLEL_JOB, 10) + : 0; + + const nonSkippedSpecsForChunk: string[] = []; + + for (let i = chunkIndex; i < nonSkippedSpecs.length; i += chunksTotal) { + nonSkippedSpecsForChunk.push(nonSkippedSpecs[i]); + } + + return nonSkippedSpecsForChunk; + } +}; + +const encode = (str: string): string => Buffer.from(str, 'binary').toString('base64'); + +const getApiKeyFromElasticCloudJsonFile = () => { + const userHomeDir = require('os').homedir(); + try { + const jsonString = fs.readFileSync(path.join(userHomeDir, '.elastic/cloud.json'), 'utf-8'); + const jsonData = JSON.parse(jsonString); + return jsonData.api_key.qa; + } catch (e) { + console.log('API KEY could not be found in '); + return null; + } +}; + +// Poller function that is polling every 20s, forever until function is resolved. +async function poll( + fn: () => Promise, + retries: number = Infinity, + interval: number = 20000 +): Promise { + return Promise.resolve() + .then(fn) + .catch(async function retry(err: any) { + if (retries-- > 0) + return new Promise((resolve) => setTimeout(resolve, interval)).then(fn).catch(retry); + throw err; + }); +} + +// Method to invoke the create environment API for serverless. +async function createEnvironment( + baseUrl: string, + projectName: string, + runnerId: string, + apiKey: string, + onEarlyExit: (msg: string) => void +): Promise { + console.log(`${runnerId}: Creating environment ${projectName}...`); + let environment = {} as Environment; + await axios + .post( + `${baseUrl}/api/v1/serverless/projects/security`, + { + name: `${projectName}`, + region_id: 'aws-eu-west-1', + }, + { + headers: { + Authorization: `ApiKey ${apiKey}`, + 'Content-Type': 'application/json', + }, + } + ) + .then((response) => { + environment.name = response.data.name; + environment.id = response.data.id; + environment.region = response.data.region_id; + environment.es_url = `${response.data.endpoints.elasticsearch}:443`; + environment.kb_url = `${response.data.endpoints.kibana}:443`; + environment.product = response.data.type; + }) + .catch((error) => { + onEarlyExit(error); + }); + return environment; +} + +// Method to invoke the delete environment API for serverless. +async function deleteEnvironment( + baseUrl: string, + projectId: string, + projectName: string, + runnerId: string, + apiKey: string, + onEarlyExit: (msg: string) => void +) { + await axios + .delete(`${baseUrl}/api/v1/serverless/projects/security/${projectId}`, { + headers: { + Authorization: `ApiKey ${apiKey}`, + }, + }) + .then((response) => { + console.log(`${runnerId} : Environment ${projectName} was successfully deleted...`); + }) + .catch((error) => { + onEarlyExit(error); + }); +} + +// Method to reset the credentials for the created environment. +async function resetCredentials( + baseUrl: string, + environmentId: string, + runnerId: string, + apiKey: string +): Promise { + console.log(`${runnerId} : Reseting credentials`); + let credentials = {} as Credentials; + + await poll(async () => { + return await axios + .post( + `${baseUrl}/api/v1/serverless/projects/security/${environmentId}/_reset-credentials`, + {}, + { + headers: { + Authorization: `ApiKey ${apiKey}`, + }, + } + ) + .then((response) => { + credentials.password = response.data.password; + credentials.username = response.data.username; + }) + .catch((error) => { + throw error; + }); + }); + return credentials; +} + +// Wait until elasticsearch status goes green +async function waitForEsStatusGreen(esUrl: string, auth: string, runnerId: string) { + await poll(async () => { + await axios + .get(`${esUrl}/_cluster/health?wait_for_status=green&timeout=50s`, { + headers: { + Authorization: `Basic ${auth}`, + }, + }) + .then((response) => { + console.log(`${runnerId}: Elasticsearch is ready with status ${response.data.status}.`); + }) + .catch((error) => { + if (error.code == 'ENOTFOUND') { + console.log( + `${runnerId}: The elasticsearch url is not yet reachable. Retrying in 20s...` + ); + } + throw error; + }); + }); +} + +// Wait until Kibana is available +async function waitForKibanaAvailable(kbUrl: string, auth: string, runnerId: string) { + await poll(async () => { + await axios + .get(`${kbUrl}/api/status`, { + headers: { + Authorization: `Basic ${auth}`, + }, + }) + .then((response) => { + if (response.data.status.overall.level != 'available') { + console.log(`${runnerId}: Kibana is not available. Retrying in 20s...`); + throw new Error(`${runnerId}: Kibana is not available. Retrying in 20s...`); + } + }) + .catch((error) => { + if (error.code == 'ENOTFOUND') { + console.log(`${runnerId}: The kibana url is not yet reachable. Retrying in 20s...`); + } + throw error; + }); + }); +} + +export const cli = () => { + run( + async () => { + const log = new ToolingLog({ + level: 'info', + writeTo: process.stdout, + }); + + const PROJECT_NAME_PREFIX = 'kibana-cypress-security-solution-ephemeral'; + const QA_BASE_ENV_URL = 'https://global.qa.cld.elstc.co'; + + // Checking if API key is either provided via env variable or in ~/.elastic.cloud.json + if (!process.env.API_KEY && !getApiKeyFromElasticCloudJsonFile()) { + log.error('The api key for the environment needs to be provided with the env var API_KEY.'); + log.error( + 'If running locally, ~/.elastic/cloud.json is attempted to be read which contains the api key.' + ); + return process.exit(0); + } + + const API_KEY = process.env.API_KEY + ? process.env.API_KEY + : getApiKeyFromElasticCloudJsonFile(); + + const PARALLEL_COUNT = process.env.PARALLEL_COUNT ? Number(process.env.PARALLEL_COUNT) : 1; + + let BASE_ENV_URL = QA_BASE_ENV_URL; + if (!process.env.CLOUD_ENV) { + log.warning( + 'The cloud environment to be provided with the env var CLOUD_ENV. Currently working only for QA so the script can proceed.' + ); + // Abort when more environments will be integrated + // eslint-disable-next-line no-process-exit + // return process.exit(0); + } + + const { argv } = yargs(process.argv.slice(2)) + .coerce('configFile', (arg) => (_.isArray(arg) ? _.last(arg) : arg)) + .coerce('spec', (arg) => (_.isArray(arg) ? _.last(arg) : arg)) + .coerce('env', (arg: string) => + arg.split(',').reduce((acc, curr) => { + const [key, value] = curr.split('='); + if (key === 'burn') { + acc[key] = parseInt(value, 10); + } else { + acc[key] = value; + } + return acc; + }, {} as Record) + ); + + log.info(` +---------------------------------------------- +Script arguments: +---------------------------------------------- + +${JSON.stringify(argv, null, 2)} + +---------------------------------------------- +`); + + const isOpen = argv._.includes('open'); + + const cypressConfigFilePath = require.resolve(`../../${argv.configFile}`) as string; + const cypressConfigFile = await import(cypressConfigFilePath); + + log.info(` +---------------------------------------------- +Cypress config for file: ${cypressConfigFilePath}: +---------------------------------------------- + +${JSON.stringify(cypressConfigFile, null, 2)} + +---------------------------------------------- +`); + + const specConfig = cypressConfigFile.e2e.specPattern; + const specArg = argv.spec; + const specPattern = specArg ?? specConfig; + + log.info('Config spec pattern:', specConfig); + log.info('Arguments spec pattern:', specArg); + log.info('Resulting spec pattern:', specPattern); + + // The grep function will filter Cypress specs by tags: it will include and exclude + // spec files according to the tags configuration. + const grepSpecPattern = grep({ + ...cypressConfigFile, + specPattern, + excludeSpecPattern: [], + }).specPattern; + + log.info('Resolved spec files or pattern after grep:', grepSpecPattern); + const isGrepReturnedFilePaths = _.isArray(grepSpecPattern); + const isGrepReturnedSpecPattern = !isGrepReturnedFilePaths && grepSpecPattern === specPattern; + const grepFilterSpecs = cypressConfigFile.env?.grepFilterSpecs; + + // IMPORTANT! + // When grep returns the same spec pattern as it gets in its arguments, we treat it as + // it couldn't find any concrete specs to execute (maybe because all of them are skipped). + // In this case, we do an early return - it's important to do that. + // If we don't return early, these specs will start executing, and Cypress will be skipping + // tests at runtime: those that should be excluded according to the tags passed in the config. + // This can take so much time that the job can fail by timeout in CI. + if (grepFilterSpecs && isGrepReturnedSpecPattern) { + log.info('No tests found - all tests could have been skipped via Cypress tags'); + // eslint-disable-next-line no-process-exit + return process.exit(0); + } + + const concreteFilePaths = isGrepReturnedFilePaths + ? grepSpecPattern // use the returned concrete file paths + : globby.sync(specPattern); // convert the glob pattern to concrete file paths + + let files = retrieveIntegrations(concreteFilePaths); + + log.info('Resolved spec files after retrieveIntegrations:', files); + + if (argv.changedSpecsOnly) { + files = (findChangedFiles('main', false) as string[]).reduce((acc, itemPath) => { + const existing = files.find((grepFilePath) => grepFilePath.includes(itemPath)); + if (existing) { + acc.push(existing); + } + return acc; + }, [] as string[]); + + // to avoid running too many tests, we limit the number of files to 3 + // we may extend this in the future + files = files.slice(0, 3); + } + + if (!files?.length) { + log.info('No tests found'); + // eslint-disable-next-line no-process-exit + return process.exit(0); + } + + await pMap( + files, + async (filePath) => { + let result: + | CypressCommandLine.CypressRunResult + | CypressCommandLine.CypressFailedRunResult + | undefined; + await withProcRunner(log, async (procs) => { + const abortCtrl = new AbortController(); + const onEarlyExit = (msg: string) => { + log.error(msg); + abortCtrl.abort(); + }; + const id = crypto.randomBytes(8).toString('hex'); + const PROJECT_NAME = `${PROJECT_NAME_PREFIX}-${id}`; + + // Creating environment for the test to run + const environment = await createEnvironment( + BASE_ENV_URL, + PROJECT_NAME, + id, + API_KEY, + onEarlyExit + ); + + // Reset credentials for elastic user + const credentials = await resetCredentials(BASE_ENV_URL, environment.id, id, API_KEY); + + // Base64 encode the credentials in order to invoke ES and KB APIs + const auth = encode(`${credentials.username}:${credentials.password}`); + + //Wait for elasticsearch status to go green. + await waitForEsStatusGreen(environment.es_url, auth, id); + + // Wait until Kibana is available + await waitForKibanaAvailable(environment.kb_url, auth, id); + + // Normalized the set of available env vars in cypress + const cyCustomEnv = { + CYPRESS_BASE_URL: environment.kb_url, + + ELASTICSEARCH_URL: environment.es_url, + ELASTICSEARCH_USERNAME: credentials.username, + ELASTICSEARCH_PASSWORD: credentials.password, + + KIBANA_URL: environment.kb_url, + KIBANA_USERNAME: credentials.username, + KIBANA_PASSWORD: credentials.password, + + CLOUD_SERVERLESS: true, + }; + + if (process.env.DEBUG) { + log.info(` + ---------------------------------------------- + Cypress run ENV for file: ${filePath}: + ---------------------------------------------- + + ${JSON.stringify(cyCustomEnv, null, 2)} + + ---------------------------------------------- + `); + } + + if (isOpen) { + await cypress.open({ + configFile: cypressConfigFilePath, + config: { + e2e: { + baseUrl: environment.kb_url, + }, + env: cyCustomEnv, + }, + }); + } else { + try { + result = await cypress.run({ + browser: 'electron', + spec: filePath, + configFile: cypressConfigFilePath, + reporter: argv.reporter as string, + reporterOptions: argv.reporterOptions, + headed: argv.headed as boolean, + config: { + e2e: { + baseUrl: environment.kb_url, + }, + numTestsKeptInMemory: 0, + env: cyCustomEnv, + }, + }); + } catch (error) { + result = error; + } + } + + // Delete serverless environment + await deleteEnvironment( + BASE_ENV_URL, + environment.id, + PROJECT_NAME, + id, + API_KEY, + onEarlyExit + ); + + return result; + }); + return result; + }, + { + concurrency: PARALLEL_COUNT, + } + ).then((results) => { + renderSummaryTable(results as CypressCommandLine.CypressRunResult[]); + const hasFailedTests = _.some( + results, + (result) => + (result as CypressCommandLine.CypressFailedRunResult)?.status === 'failed' || + (result as CypressCommandLine.CypressRunResult)?.totalFailed + ); + if (hasFailedTests) { + throw createFailError('Not all tests passed'); + } + }); + }, + { + flags: { + allowUnexpected: true, + }, + } + ); +}; diff --git a/x-pack/plugins/security_solution/scripts/start_cypress_parallel_serverless.js b/x-pack/plugins/security_solution/scripts/start_cypress_parallel_serverless.js new file mode 100644 index 0000000000000..ab8850323536d --- /dev/null +++ b/x-pack/plugins/security_solution/scripts/start_cypress_parallel_serverless.js @@ -0,0 +1,9 @@ +/* + * 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. + */ + +require('../../../../src/setup_node_env'); +require('./run_cypress/parallel_serverless').cli(); diff --git a/x-pack/test/security_solution_cypress/cypress/cypress_ci_serverless_qa.config.ts b/x-pack/test/security_solution_cypress/cypress/cypress_ci_serverless_qa.config.ts index e76893eceea36..cec99a0653a9f 100644 --- a/x-pack/test/security_solution_cypress/cypress/cypress_ci_serverless_qa.config.ts +++ b/x-pack/test/security_solution_cypress/cypress/cypress_ci_serverless_qa.config.ts @@ -19,9 +19,6 @@ export default defineCypressConfig({ grepFilterSpecs: true, grepOmitFiltered: true, grepTags: '@serverlessQA --@brokenInServerless --@skipInServerless', - // Grep plugin is working taking under consideration the directory where cypress lives. - // https://github.com/elastic/kibana/pull/167494#discussion_r1340567022 for more context. - grepIntegrationFolder: '../', }, execTimeout: 150000, pageLoadTimeout: 150000, diff --git a/x-pack/test/security_solution_cypress/package.json b/x-pack/test/security_solution_cypress/package.json index 988f504224b20..0d41f3777f4f5 100644 --- a/x-pack/test/security_solution_cypress/package.json +++ b/x-pack/test/security_solution_cypress/package.json @@ -1,4 +1,5 @@ { +<<<<<<< HEAD "author": "Elastic", "name": "@kbn/security-solution-plugin", "version": "1.0.0", @@ -27,6 +28,7 @@ "cypress:investigations:run:serverless": "yarn cypress:serverless --spec './cypress/e2e/investigations/**/*.cy.ts'", "cypress:explore:run:serverless": "yarn cypress:serverless --spec './cypress/e2e/explore/**/*.cy.ts'", "cypress:changed-specs-only:serverless": "yarn cypress:serverless --changed-specs-only --env burn=5", - "cypress:burn:serverless": "yarn cypress:serverless --env burn=5" + "cypress:burn:serverless": "yarn cypress:serverless --env burn=5", + "cypress:run:qa:serverless:parallel": "TZ=UTC NODE_OPTIONS=--openssl-legacy-provider node ../../plugins/security_solution/scripts/start_cypress_parallel_serverless --config-file ../../test/security_solution_cypress/cypress/cypress_ci_serverless_qa.config.ts" } } From 9e43643bc1a00023cf0d4d193437b5bc631093fc Mon Sep 17 00:00:00 2001 From: dkirchan Date: Thu, 19 Oct 2023 08:54:33 +0300 Subject: [PATCH 02/42] Fixed API_KEY target to be more accurate to which API_KEY it refers to --- .../pipelines/security_solution_quality_gate/pipeline.sh | 2 +- .../scripts/run_cypress/parallel_serverless.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.buildkite/scripts/pipelines/security_solution_quality_gate/pipeline.sh b/.buildkite/scripts/pipelines/security_solution_quality_gate/pipeline.sh index 2a3472c2d22f4..dcde730013cf5 100755 --- a/.buildkite/scripts/pipelines/security_solution_quality_gate/pipeline.sh +++ b/.buildkite/scripts/pipelines/security_solution_quality_gate/pipeline.sh @@ -15,4 +15,4 @@ set +e QA_API_KEY=$(vault read -field=api-key secret/kibana-issues/dev/security-solution-qg-enc-key) -API_KEY=$QA_API_KEY yarn cypress:run:qa:serverless:parallel; status=$?; yarn junit:merge || :; exit $status \ No newline at end of file +CLOUD_QA_API_KEY=$QA_API_KEY yarn cypress:run:qa:serverless:parallel; status=$?; yarn junit:merge || :; exit $status \ No newline at end of file diff --git a/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts b/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts index a9f0d2be5c1ed..89f45637ad6ca 100644 --- a/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts +++ b/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts @@ -247,7 +247,7 @@ export const cli = () => { const QA_BASE_ENV_URL = 'https://global.qa.cld.elstc.co'; // Checking if API key is either provided via env variable or in ~/.elastic.cloud.json - if (!process.env.API_KEY && !getApiKeyFromElasticCloudJsonFile()) { + if (!process.env.CLOUD_QA_API_KEY && !getApiKeyFromElasticCloudJsonFile()) { log.error('The api key for the environment needs to be provided with the env var API_KEY.'); log.error( 'If running locally, ~/.elastic/cloud.json is attempted to be read which contains the api key.' @@ -255,8 +255,8 @@ export const cli = () => { return process.exit(0); } - const API_KEY = process.env.API_KEY - ? process.env.API_KEY + const API_KEY = process.env.CLOUD_QA_API_KEY + ? process.env.CLOUD_QA_API_KEY : getApiKeyFromElasticCloudJsonFile(); const PARALLEL_COUNT = process.env.PARALLEL_COUNT ? Number(process.env.PARALLEL_COUNT) : 1; From bf05e41ba6fe5df701c7861fcad12d747824f505 Mon Sep 17 00:00:00 2001 From: dkirchan Date: Thu, 19 Oct 2023 09:12:35 +0300 Subject: [PATCH 03/42] Fixed error codes to avoid logging them. Logging status code and message now --- .../scripts/run_cypress/parallel_serverless.ts | 8 ++++---- x-pack/test/security_solution_cypress/package.json | 1 - 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts b/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts index 89f45637ad6ca..53cd26da01b14 100644 --- a/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts +++ b/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts @@ -128,7 +128,7 @@ async function createEnvironment( environment.product = response.data.type; }) .catch((error) => { - onEarlyExit(error); + onEarlyExit(`${error.code}:${error.data}`); }); return environment; } @@ -152,7 +152,7 @@ async function deleteEnvironment( console.log(`${runnerId} : Environment ${projectName} was successfully deleted...`); }) .catch((error) => { - onEarlyExit(error); + onEarlyExit(`${error.code}:${error.data}`); }); } @@ -182,7 +182,7 @@ async function resetCredentials( credentials.username = response.data.username; }) .catch((error) => { - throw error; + throw Error(`${error.code}:${error.data}`); }); }); return credentials; @@ -425,7 +425,7 @@ ${JSON.stringify(cypressConfigFile, null, 2)} CLOUD_SERVERLESS: true, }; - if (process.env.DEBUG) { + if (process.env.DEBUG && !process.env.CI) { log.info(` ---------------------------------------------- Cypress run ENV for file: ${filePath}: diff --git a/x-pack/test/security_solution_cypress/package.json b/x-pack/test/security_solution_cypress/package.json index 0d41f3777f4f5..031234598a62a 100644 --- a/x-pack/test/security_solution_cypress/package.json +++ b/x-pack/test/security_solution_cypress/package.json @@ -1,5 +1,4 @@ { -<<<<<<< HEAD "author": "Elastic", "name": "@kbn/security-solution-plugin", "version": "1.0.0", From 950cf062f5da85c556c2f11f26646c9c44cb886c Mon Sep 17 00:00:00 2001 From: dkirchan Date: Thu, 19 Oct 2023 09:16:42 +0300 Subject: [PATCH 04/42] Wrapped vault read to a retry --- .../pipeline.sh | 2 +- .../run_cypress/parallel_serverless.ts | 95 +++++++++++-------- 2 files changed, 59 insertions(+), 38 deletions(-) diff --git a/.buildkite/scripts/pipelines/security_solution_quality_gate/pipeline.sh b/.buildkite/scripts/pipelines/security_solution_quality_gate/pipeline.sh index dcde730013cf5..98822d33399d0 100755 --- a/.buildkite/scripts/pipelines/security_solution_quality_gate/pipeline.sh +++ b/.buildkite/scripts/pipelines/security_solution_quality_gate/pipeline.sh @@ -13,6 +13,6 @@ echo "--- Serverless Security Second Quality Gate" cd x-pack/test/security_solution_cypress set +e -QA_API_KEY=$(vault read -field=api-key secret/kibana-issues/dev/security-solution-qg-enc-key) +QA_API_KEY=$(retry 5 5 vault read -field=api-key secret/kibana-issues/dev/security-solution-qg-enc-key) CLOUD_QA_API_KEY=$QA_API_KEY yarn cypress:run:qa:serverless:parallel; status=$?; yarn junit:merge || :; exit $status \ No newline at end of file diff --git a/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts b/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts index 53cd26da01b14..6e4daa3bf0e46 100644 --- a/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts +++ b/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts @@ -20,8 +20,20 @@ import fs from 'fs'; import { createFailError } from '@kbn/dev-cli-errors'; import axios from 'axios'; import { renderSummaryTable } from './print_run'; -import { isSkipped } from './utils'; +import { isSkipped, parseTestFileConfig, SecuritySolutionDescribeBlockFtrConfig } from './utils'; import path from 'path'; +import os from 'os'; + +type ProductType = { + product_line: string; + product_tier: string; +}; + +type CreateEnvironmentRequestBody = { + name: string; + region_id: string; + product_types: ProductType[]; +}; type Environment = { name: string; @@ -37,6 +49,9 @@ type Credentials = { password: string; }; +const DEFAULT_REGION = 'aws-eu-west-1'; +let log: ToolingLog; + /** * Retrieve test files using a glob pattern. * If process.env.RUN_ALL_TESTS is true, returns all matching files, otherwise, return files that should be run by this job based on process.env.BUILDKITE_PARALLEL_JOB_COUNT and process.env.BUILDKITE_PARALLEL_JOB @@ -69,13 +84,13 @@ const retrieveIntegrations = (integrationsPaths: string[]) => { const encode = (str: string): string => Buffer.from(str, 'binary').toString('base64'); const getApiKeyFromElasticCloudJsonFile = () => { - const userHomeDir = require('os').homedir(); + const userHomeDir = os.homedir(); try { const jsonString = fs.readFileSync(path.join(userHomeDir, '.elastic/cloud.json'), 'utf-8'); const jsonData = JSON.parse(jsonString); return jsonData.api_key.qa; } catch (e) { - console.log('API KEY could not be found in '); + log.info('API KEY could not be found in '); return null; } }; @@ -83,14 +98,16 @@ const getApiKeyFromElasticCloudJsonFile = () => { // Poller function that is polling every 20s, forever until function is resolved. async function poll( fn: () => Promise, - retries: number = Infinity, + retries: number = 200, interval: number = 20000 ): Promise { return Promise.resolve() .then(fn) - .catch(async function retry(err: any) { - if (retries-- > 0) + .catch(async function retry(err: Error): Promise { + if (retries-- > 0) { + retries -= 1; return new Promise((resolve) => setTimeout(resolve, interval)).then(fn).catch(retry); + } throw err; }); } @@ -101,24 +118,29 @@ async function createEnvironment( projectName: string, runnerId: string, apiKey: string, + ftrConfig: SecuritySolutionDescribeBlockFtrConfig, onEarlyExit: (msg: string) => void ): Promise { - console.log(`${runnerId}: Creating environment ${projectName}...`); + log.info(`${runnerId}: Creating environment ${projectName}...`); let environment = {} as Environment; + const body = { + name: projectName, + region_id: DEFAULT_REGION, + } as CreateEnvironmentRequestBody; + + const productTypes: ProductType[] = []; + ftrConfig?.productTypes?.forEach((t) => { + productTypes.push(t as ProductType); + }); + if (productTypes.length > 0) body.product_types = productTypes; + await axios - .post( - `${baseUrl}/api/v1/serverless/projects/security`, - { - name: `${projectName}`, - region_id: 'aws-eu-west-1', + .post(`${baseUrl}/api/v1/serverless/projects/security`, body, { + headers: { + Authorization: `ApiKey ${apiKey}`, + 'Content-Type': 'application/json', }, - { - headers: { - Authorization: `ApiKey ${apiKey}`, - 'Content-Type': 'application/json', - }, - } - ) + }) .then((response) => { environment.name = response.data.name; environment.id = response.data.id; @@ -149,7 +171,7 @@ async function deleteEnvironment( }, }) .then((response) => { - console.log(`${runnerId} : Environment ${projectName} was successfully deleted...`); + log.info(`${runnerId} : Environment ${projectName} was successfully deleted...`); }) .catch((error) => { onEarlyExit(`${error.code}:${error.data}`); @@ -163,11 +185,11 @@ async function resetCredentials( runnerId: string, apiKey: string ): Promise { - console.log(`${runnerId} : Reseting credentials`); + log.info(`${runnerId} : Reseting credentials`); let credentials = {} as Credentials; await poll(async () => { - return await axios + return axios .post( `${baseUrl}/api/v1/serverless/projects/security/${environmentId}/_reset-credentials`, {}, @@ -182,7 +204,7 @@ async function resetCredentials( credentials.username = response.data.username; }) .catch((error) => { - throw Error(`${error.code}:${error.data}`); + throw new Error(`${error.code}:${error.data}`); }); }); return credentials; @@ -198,15 +220,13 @@ async function waitForEsStatusGreen(esUrl: string, auth: string, runnerId: strin }, }) .then((response) => { - console.log(`${runnerId}: Elasticsearch is ready with status ${response.data.status}.`); + log.info(`${runnerId}: Elasticsearch is ready with status ${response.data.status}.`); }) .catch((error) => { - if (error.code == 'ENOTFOUND') { - console.log( - `${runnerId}: The elasticsearch url is not yet reachable. Retrying in 20s...` - ); + if (error.code === 'ENOTFOUND') { + log.info(`${runnerId}: The elasticsearch url is not yet reachable. Retrying in 20s...`); } - throw error; + throw new Error(`${runnerId} - ${error.code}:${error.data}`); }); }); } @@ -221,16 +241,16 @@ async function waitForKibanaAvailable(kbUrl: string, auth: string, runnerId: str }, }) .then((response) => { - if (response.data.status.overall.level != 'available') { - console.log(`${runnerId}: Kibana is not available. Retrying in 20s...`); + if (response.data.status.overall.level !== 'available') { + log.info(`${runnerId}: Kibana is not available. Retrying in 20s...`); throw new Error(`${runnerId}: Kibana is not available. Retrying in 20s...`); } }) .catch((error) => { - if (error.code == 'ENOTFOUND') { - console.log(`${runnerId}: The kibana url is not yet reachable. Retrying in 20s...`); + if (error.code === 'ENOTFOUND') { + log.info(`${runnerId}: The kibana url is not yet reachable. Retrying in 20s...`); } - throw error; + throw new Error(`${runnerId} - ${error.code}:${error.data}`); }); }); } @@ -238,7 +258,7 @@ async function waitForKibanaAvailable(kbUrl: string, auth: string, runnerId: str export const cli = () => { run( async () => { - const log = new ToolingLog({ + log = new ToolingLog({ level: 'info', writeTo: process.stdout, }); @@ -252,6 +272,7 @@ export const cli = () => { log.error( 'If running locally, ~/.elastic/cloud.json is attempted to be read which contains the api key.' ); + // eslint-disable-next-line no-process-exit return process.exit(0); } @@ -388,6 +409,7 @@ ${JSON.stringify(cypressConfigFile, null, 2)} }; const id = crypto.randomBytes(8).toString('hex'); const PROJECT_NAME = `${PROJECT_NAME_PREFIX}-${id}`; + const specFileFTRConfig = parseTestFileConfig(filePath); // Creating environment for the test to run const environment = await createEnvironment( @@ -395,6 +417,7 @@ ${JSON.stringify(cypressConfigFile, null, 2)} PROJECT_NAME, id, API_KEY, + specFileFTRConfig, onEarlyExit ); @@ -430,9 +453,7 @@ ${JSON.stringify(cypressConfigFile, null, 2)} ---------------------------------------------- Cypress run ENV for file: ${filePath}: ---------------------------------------------- - ${JSON.stringify(cyCustomEnv, null, 2)} - ---------------------------------------------- `); } From 2d7e9bbfdeff58524b3d4b2df2cf64b6760b9e2f Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Thu, 19 Oct 2023 16:20:56 +0000 Subject: [PATCH 05/42] [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' --- .../run_cypress/parallel_serverless.ts | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts b/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts index 6e4daa3bf0e46..34d0250d307cd 100644 --- a/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts +++ b/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts @@ -19,35 +19,36 @@ import crypto from 'crypto'; import fs from 'fs'; import { createFailError } from '@kbn/dev-cli-errors'; import axios from 'axios'; -import { renderSummaryTable } from './print_run'; -import { isSkipped, parseTestFileConfig, SecuritySolutionDescribeBlockFtrConfig } from './utils'; import path from 'path'; import os from 'os'; +import { renderSummaryTable } from './print_run'; +import type { SecuritySolutionDescribeBlockFtrConfig } from './utils'; +import { isSkipped, parseTestFileConfig } from './utils'; -type ProductType = { +interface ProductType { product_line: string; product_tier: string; -}; +} -type CreateEnvironmentRequestBody = { +interface CreateEnvironmentRequestBody { name: string; region_id: string; product_types: ProductType[]; -}; +} -type Environment = { +interface Environment { name: string; id: string; region: string; es_url: string; kb_url: string; product: string; -}; +} -type Credentials = { +interface Credentials { username: string; password: string; -}; +} const DEFAULT_REGION = 'aws-eu-west-1'; let log: ToolingLog; @@ -122,7 +123,7 @@ async function createEnvironment( onEarlyExit: (msg: string) => void ): Promise { log.info(`${runnerId}: Creating environment ${projectName}...`); - let environment = {} as Environment; + const environment = {} as Environment; const body = { name: projectName, region_id: DEFAULT_REGION, @@ -186,7 +187,7 @@ async function resetCredentials( apiKey: string ): Promise { log.info(`${runnerId} : Reseting credentials`); - let credentials = {} as Credentials; + const credentials = {} as Credentials; await poll(async () => { return axios @@ -282,13 +283,13 @@ export const cli = () => { const PARALLEL_COUNT = process.env.PARALLEL_COUNT ? Number(process.env.PARALLEL_COUNT) : 1; - let BASE_ENV_URL = QA_BASE_ENV_URL; + const BASE_ENV_URL = QA_BASE_ENV_URL; if (!process.env.CLOUD_ENV) { log.warning( 'The cloud environment to be provided with the env var CLOUD_ENV. Currently working only for QA so the script can proceed.' ); // Abort when more environments will be integrated - // eslint-disable-next-line no-process-exit + // return process.exit(0); } @@ -427,7 +428,7 @@ ${JSON.stringify(cypressConfigFile, null, 2)} // Base64 encode the credentials in order to invoke ES and KB APIs const auth = encode(`${credentials.username}:${credentials.password}`); - //Wait for elasticsearch status to go green. + // Wait for elasticsearch status to go green. await waitForEsStatusGreen(environment.es_url, auth, id); // Wait until Kibana is available From 93bb326750cee76cb145f08ef975e2c2b0664daa Mon Sep 17 00:00:00 2001 From: dkirchan Date: Mon, 23 Oct 2023 12:08:11 +0300 Subject: [PATCH 06/42] Added parallelism - Disabled Essentials non-functioning test - Run all @serverless --- .../pipelines/security_solution_quality_gate/pipeline.sh | 4 ++-- .../cypress/cypress_ci_serverless_qa.config.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.buildkite/scripts/pipelines/security_solution_quality_gate/pipeline.sh b/.buildkite/scripts/pipelines/security_solution_quality_gate/pipeline.sh index 98822d33399d0..8d40f3fd544aa 100755 --- a/.buildkite/scripts/pipelines/security_solution_quality_gate/pipeline.sh +++ b/.buildkite/scripts/pipelines/security_solution_quality_gate/pipeline.sh @@ -13,6 +13,6 @@ echo "--- Serverless Security Second Quality Gate" cd x-pack/test/security_solution_cypress set +e -QA_API_KEY=$(retry 5 5 vault read -field=api-key secret/kibana-issues/dev/security-solution-qg-enc-key) +QA_API_KEY=$(retry 5 5 vault read -field=qa_api_key secret/kibana-issues/dev/security-solution-qg-enc-key) -CLOUD_QA_API_KEY=$QA_API_KEY yarn cypress:run:qa:serverless:parallel; status=$?; yarn junit:merge || :; exit $status \ No newline at end of file +PARALLEL_COUNT=4 CLOUD_QA_API_KEY=$QA_API_KEY yarn cypress:run:qa:serverless:parallel; status=$?; yarn junit:merge || :; exit $status \ No newline at end of file diff --git a/x-pack/test/security_solution_cypress/cypress/cypress_ci_serverless_qa.config.ts b/x-pack/test/security_solution_cypress/cypress/cypress_ci_serverless_qa.config.ts index cec99a0653a9f..3a1be3ed0221a 100644 --- a/x-pack/test/security_solution_cypress/cypress/cypress_ci_serverless_qa.config.ts +++ b/x-pack/test/security_solution_cypress/cypress/cypress_ci_serverless_qa.config.ts @@ -18,7 +18,7 @@ export default defineCypressConfig({ env: { grepFilterSpecs: true, grepOmitFiltered: true, - grepTags: '@serverlessQA --@brokenInServerless --@skipInServerless', + grepTags: '@serverless --@brokenInServerless --@skipInServerless', }, execTimeout: 150000, pageLoadTimeout: 150000, From 023b18f0a940a36871d8aa1d5a062ca37094fba6 Mon Sep 17 00:00:00 2001 From: Gloria Hornero Date: Thu, 26 Oct 2023 12:50:00 +0200 Subject: [PATCH 07/42] fixes linting issue --- .../scripts/run_cypress/parallel_serverless.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts b/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts index 34d0250d307cd..7365f4bfb4639 100644 --- a/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts +++ b/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts @@ -105,8 +105,9 @@ async function poll( return Promise.resolve() .then(fn) .catch(async function retry(err: Error): Promise { - if (retries-- > 0) { - retries -= 1; + let remainingRetries = retries; + if (remainingRetries-- > 0) { + remainingRetries -= 1; return new Promise((resolve) => setTimeout(resolve, interval)).then(fn).catch(retry); } throw err; From c22ec0e11275637cffd0b198ac84dabe6b73dc6b Mon Sep 17 00:00:00 2001 From: Gloria Hornero Date: Tue, 31 Oct 2023 13:40:14 +0100 Subject: [PATCH 08/42] reuses existing types --- .../scripts/run_cypress/parallel_serverless.ts | 9 ++------- .../scripts/run_cypress/utils.ts | 17 +++-------------- 2 files changed, 5 insertions(+), 21 deletions(-) diff --git a/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts b/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts index 7365f4bfb4639..c76b60926e206 100644 --- a/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts +++ b/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts @@ -22,14 +22,9 @@ import axios from 'axios'; import path from 'path'; import os from 'os'; import { renderSummaryTable } from './print_run'; -import type { SecuritySolutionDescribeBlockFtrConfig } from './utils'; +import type { SecuritySolutionDescribeBlockFtrConfig, ProductType } from './utils'; import { isSkipped, parseTestFileConfig } from './utils'; -interface ProductType { - product_line: string; - product_tier: string; -} - interface CreateEnvironmentRequestBody { name: string; region_id: string; @@ -132,7 +127,7 @@ async function createEnvironment( const productTypes: ProductType[] = []; ftrConfig?.productTypes?.forEach((t) => { - productTypes.push(t as ProductType); + productTypes.push(t); }); if (productTypes.length > 0) body.product_types = productTypes; diff --git a/x-pack/plugins/security_solution/scripts/run_cypress/utils.ts b/x-pack/plugins/security_solution/scripts/run_cypress/utils.ts index fdaf5aeac1288..0ccb526be6d14 100644 --- a/x-pack/plugins/security_solution/scripts/run_cypress/utils.ts +++ b/x-pack/plugins/security_solution/scripts/run_cypress/utils.ts @@ -11,6 +11,7 @@ import * as parser from '@babel/parser'; import generate from '@babel/generator'; import type { ExpressionStatement, ObjectExpression, ObjectProperty } from '@babel/types'; import { schema, type TypeOf } from '@kbn/config-schema'; +import { productType as ProductTypeSchema } from '@kbn/security-solution-serverless/common/config'; import { getExperimentalAllowedValues } from '../../common/experimental_features'; export const isSkipped = (filePath: string): boolean => { @@ -100,22 +101,10 @@ const TestFileFtrConfigSchema = schema.object( }) ) ), - productTypes: schema.maybe( - // TODO:PT write validate function to ensure that only the correct combinations are used - schema.arrayOf( - schema.object({ - product_line: schema.oneOf([ - schema.literal('security'), - schema.literal('endpoint'), - schema.literal('cloud'), - ]), - - product_tier: schema.oneOf([schema.literal('essentials'), schema.literal('complete')]), - }) - ) - ), + productTypes: schema.maybe(schema.arrayOf(ProductTypeSchema)), }, { defaultValue: {}, unknowns: 'forbid' } ); export type SecuritySolutionDescribeBlockFtrConfig = TypeOf; +export type ProductType = TypeOf; From fd3e26c96a0dc4580af8acab3e70020e1efd8107 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 31 Oct 2023 14:02:36 +0000 Subject: [PATCH 09/42] [CI] Auto-commit changed files from 'node scripts/lint_ts_projects --fix' --- x-pack/plugins/security_solution/tsconfig.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/tsconfig.json b/x-pack/plugins/security_solution/tsconfig.json index 16ad154a95d83..a3e04654d3793 100644 --- a/x-pack/plugins/security_solution/tsconfig.json +++ b/x-pack/plugins/security_solution/tsconfig.json @@ -177,6 +177,7 @@ "@kbn/react-kibana-mount", "@kbn/unified-doc-viewer-plugin", "@kbn/shared-ux-error-boundary", - "@kbn/zod-helpers" + "@kbn/zod-helpers", + "@kbn/security-solution-serverless" ] } From 60270f9e1fba1b48715f5d6627f6614ec0d53619 Mon Sep 17 00:00:00 2001 From: Gloria Hornero Date: Tue, 31 Oct 2023 15:17:13 +0100 Subject: [PATCH 10/42] Revert "reuses existing types" This reverts commit 2fd9d9fecad90b4f41455b1eaf0c8ca63186c770. --- .../scripts/run_cypress/parallel_serverless.ts | 9 +++++++-- .../scripts/run_cypress/utils.ts | 17 ++++++++++++++--- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts b/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts index c76b60926e206..7365f4bfb4639 100644 --- a/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts +++ b/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts @@ -22,9 +22,14 @@ import axios from 'axios'; import path from 'path'; import os from 'os'; import { renderSummaryTable } from './print_run'; -import type { SecuritySolutionDescribeBlockFtrConfig, ProductType } from './utils'; +import type { SecuritySolutionDescribeBlockFtrConfig } from './utils'; import { isSkipped, parseTestFileConfig } from './utils'; +interface ProductType { + product_line: string; + product_tier: string; +} + interface CreateEnvironmentRequestBody { name: string; region_id: string; @@ -127,7 +132,7 @@ async function createEnvironment( const productTypes: ProductType[] = []; ftrConfig?.productTypes?.forEach((t) => { - productTypes.push(t); + productTypes.push(t as ProductType); }); if (productTypes.length > 0) body.product_types = productTypes; diff --git a/x-pack/plugins/security_solution/scripts/run_cypress/utils.ts b/x-pack/plugins/security_solution/scripts/run_cypress/utils.ts index 0ccb526be6d14..fdaf5aeac1288 100644 --- a/x-pack/plugins/security_solution/scripts/run_cypress/utils.ts +++ b/x-pack/plugins/security_solution/scripts/run_cypress/utils.ts @@ -11,7 +11,6 @@ import * as parser from '@babel/parser'; import generate from '@babel/generator'; import type { ExpressionStatement, ObjectExpression, ObjectProperty } from '@babel/types'; import { schema, type TypeOf } from '@kbn/config-schema'; -import { productType as ProductTypeSchema } from '@kbn/security-solution-serverless/common/config'; import { getExperimentalAllowedValues } from '../../common/experimental_features'; export const isSkipped = (filePath: string): boolean => { @@ -101,10 +100,22 @@ const TestFileFtrConfigSchema = schema.object( }) ) ), - productTypes: schema.maybe(schema.arrayOf(ProductTypeSchema)), + productTypes: schema.maybe( + // TODO:PT write validate function to ensure that only the correct combinations are used + schema.arrayOf( + schema.object({ + product_line: schema.oneOf([ + schema.literal('security'), + schema.literal('endpoint'), + schema.literal('cloud'), + ]), + + product_tier: schema.oneOf([schema.literal('essentials'), schema.literal('complete')]), + }) + ) + ), }, { defaultValue: {}, unknowns: 'forbid' } ); export type SecuritySolutionDescribeBlockFtrConfig = TypeOf; -export type ProductType = TypeOf; From 56c4e9cb01357fefdc00ebe07bec40233549d3df Mon Sep 17 00:00:00 2001 From: Gloria Hornero Date: Tue, 31 Oct 2023 15:28:17 +0100 Subject: [PATCH 11/42] reusing existing types --- .../run_cypress/parallel_serverless.ts | 9 ++------ .../scripts/run_cypress/utils.ts | 23 ++++++++++--------- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts b/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts index 7365f4bfb4639..c76b60926e206 100644 --- a/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts +++ b/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts @@ -22,14 +22,9 @@ import axios from 'axios'; import path from 'path'; import os from 'os'; import { renderSummaryTable } from './print_run'; -import type { SecuritySolutionDescribeBlockFtrConfig } from './utils'; +import type { SecuritySolutionDescribeBlockFtrConfig, ProductType } from './utils'; import { isSkipped, parseTestFileConfig } from './utils'; -interface ProductType { - product_line: string; - product_tier: string; -} - interface CreateEnvironmentRequestBody { name: string; region_id: string; @@ -132,7 +127,7 @@ async function createEnvironment( const productTypes: ProductType[] = []; ftrConfig?.productTypes?.forEach((t) => { - productTypes.push(t as ProductType); + productTypes.push(t); }); if (productTypes.length > 0) body.product_types = productTypes; diff --git a/x-pack/plugins/security_solution/scripts/run_cypress/utils.ts b/x-pack/plugins/security_solution/scripts/run_cypress/utils.ts index fdaf5aeac1288..c03f2697e425b 100644 --- a/x-pack/plugins/security_solution/scripts/run_cypress/utils.ts +++ b/x-pack/plugins/security_solution/scripts/run_cypress/utils.ts @@ -82,6 +82,16 @@ export const parseTestFileConfig = (filePath: string): SecuritySolutionDescribeB return {}; }; +const ProducTypeSchema = schema.object({ + product_line: schema.oneOf([ + schema.literal('security'), + schema.literal('endpoint'), + schema.literal('cloud'), + ]), + + product_tier: schema.oneOf([schema.literal('essentials'), schema.literal('complete')]), +}); + const TestFileFtrConfigSchema = schema.object( { license: schema.maybe(schema.string()), @@ -102,20 +112,11 @@ const TestFileFtrConfigSchema = schema.object( ), productTypes: schema.maybe( // TODO:PT write validate function to ensure that only the correct combinations are used - schema.arrayOf( - schema.object({ - product_line: schema.oneOf([ - schema.literal('security'), - schema.literal('endpoint'), - schema.literal('cloud'), - ]), - - product_tier: schema.oneOf([schema.literal('essentials'), schema.literal('complete')]), - }) - ) + schema.arrayOf(ProducTypeSchema) ), }, { defaultValue: {}, unknowns: 'forbid' } ); export type SecuritySolutionDescribeBlockFtrConfig = TypeOf; +export type ProductType = TypeOf; From dcd01d840900633ff028375f66948f71c6b57f66 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 31 Oct 2023 14:36:34 +0000 Subject: [PATCH 12/42] [CI] Auto-commit changed files from 'node scripts/lint_ts_projects --fix' --- x-pack/plugins/security_solution/tsconfig.json | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugins/security_solution/tsconfig.json b/x-pack/plugins/security_solution/tsconfig.json index a3e04654d3793..1766051325f5f 100644 --- a/x-pack/plugins/security_solution/tsconfig.json +++ b/x-pack/plugins/security_solution/tsconfig.json @@ -178,6 +178,5 @@ "@kbn/unified-doc-viewer-plugin", "@kbn/shared-ux-error-boundary", "@kbn/zod-helpers", - "@kbn/security-solution-serverless" ] } From fb9010706f9a7248fedf149b85571041a84835fd Mon Sep 17 00:00:00 2001 From: Gloria Hornero Date: Tue, 31 Oct 2023 16:25:12 +0100 Subject: [PATCH 13/42] Revert "reusing existing types" This reverts commit ac7b53fb83e82d5bee7e212d430934493201c767. --- .../run_cypress/parallel_serverless.ts | 9 ++++++-- .../scripts/run_cypress/utils.ts | 23 +++++++++---------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts b/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts index c76b60926e206..7365f4bfb4639 100644 --- a/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts +++ b/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts @@ -22,9 +22,14 @@ import axios from 'axios'; import path from 'path'; import os from 'os'; import { renderSummaryTable } from './print_run'; -import type { SecuritySolutionDescribeBlockFtrConfig, ProductType } from './utils'; +import type { SecuritySolutionDescribeBlockFtrConfig } from './utils'; import { isSkipped, parseTestFileConfig } from './utils'; +interface ProductType { + product_line: string; + product_tier: string; +} + interface CreateEnvironmentRequestBody { name: string; region_id: string; @@ -127,7 +132,7 @@ async function createEnvironment( const productTypes: ProductType[] = []; ftrConfig?.productTypes?.forEach((t) => { - productTypes.push(t); + productTypes.push(t as ProductType); }); if (productTypes.length > 0) body.product_types = productTypes; diff --git a/x-pack/plugins/security_solution/scripts/run_cypress/utils.ts b/x-pack/plugins/security_solution/scripts/run_cypress/utils.ts index c03f2697e425b..fdaf5aeac1288 100644 --- a/x-pack/plugins/security_solution/scripts/run_cypress/utils.ts +++ b/x-pack/plugins/security_solution/scripts/run_cypress/utils.ts @@ -82,16 +82,6 @@ export const parseTestFileConfig = (filePath: string): SecuritySolutionDescribeB return {}; }; -const ProducTypeSchema = schema.object({ - product_line: schema.oneOf([ - schema.literal('security'), - schema.literal('endpoint'), - schema.literal('cloud'), - ]), - - product_tier: schema.oneOf([schema.literal('essentials'), schema.literal('complete')]), -}); - const TestFileFtrConfigSchema = schema.object( { license: schema.maybe(schema.string()), @@ -112,11 +102,20 @@ const TestFileFtrConfigSchema = schema.object( ), productTypes: schema.maybe( // TODO:PT write validate function to ensure that only the correct combinations are used - schema.arrayOf(ProducTypeSchema) + schema.arrayOf( + schema.object({ + product_line: schema.oneOf([ + schema.literal('security'), + schema.literal('endpoint'), + schema.literal('cloud'), + ]), + + product_tier: schema.oneOf([schema.literal('essentials'), schema.literal('complete')]), + }) + ) ), }, { defaultValue: {}, unknowns: 'forbid' } ); export type SecuritySolutionDescribeBlockFtrConfig = TypeOf; -export type ProductType = TypeOf; From f68e7ade7640493e6ae67cc10392d62fa67ffc7d Mon Sep 17 00:00:00 2001 From: Gloria Hornero Date: Tue, 31 Oct 2023 20:18:33 +0100 Subject: [PATCH 14/42] extracts retrieve integrations to utils --- .../scripts/run_cypress/parallel.ts | 31 +------------------ .../run_cypress/parallel_serverless.ts | 31 +------------------ .../scripts/run_cypress/utils.ts | 29 +++++++++++++++++ 3 files changed, 31 insertions(+), 60 deletions(-) diff --git a/x-pack/plugins/security_solution/scripts/run_cypress/parallel.ts b/x-pack/plugins/security_solution/scripts/run_cypress/parallel.ts index 1882fba6e3884..b1fd9d7391264 100644 --- a/x-pack/plugins/security_solution/scripts/run_cypress/parallel.ts +++ b/x-pack/plugins/security_solution/scripts/run_cypress/parallel.ts @@ -28,38 +28,9 @@ import { import { createFailError } from '@kbn/dev-cli-errors'; import pRetry from 'p-retry'; import { renderSummaryTable } from './print_run'; -import { isSkipped, parseTestFileConfig } from './utils'; +import { parseTestFileConfig, retrieveIntegrations } from './utils'; import { getFTRConfig } from './get_ftr_config'; -/** - * Retrieve test files using a glob pattern. - * If process.env.RUN_ALL_TESTS is true, returns all matching files, otherwise, return files that should be run by this job based on process.env.BUILDKITE_PARALLEL_JOB_COUNT and process.env.BUILDKITE_PARALLEL_JOB - */ -const retrieveIntegrations = (integrationsPaths: string[]) => { - const nonSkippedSpecs = integrationsPaths.filter((filePath) => !isSkipped(filePath)); - - if (process.env.RUN_ALL_TESTS === 'true') { - return nonSkippedSpecs; - } else { - // The number of instances of this job were created - const chunksTotal: number = process.env.BUILDKITE_PARALLEL_JOB_COUNT - ? parseInt(process.env.BUILDKITE_PARALLEL_JOB_COUNT, 10) - : 1; - // An index which uniquely identifies this instance of the job - const chunkIndex: number = process.env.BUILDKITE_PARALLEL_JOB - ? parseInt(process.env.BUILDKITE_PARALLEL_JOB, 10) - : 0; - - const nonSkippedSpecsForChunk: string[] = []; - - for (let i = chunkIndex; i < nonSkippedSpecs.length; i += chunksTotal) { - nonSkippedSpecsForChunk.push(nonSkippedSpecs[i]); - } - - return nonSkippedSpecsForChunk; - } -}; - export const cli = () => { run( async () => { diff --git a/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts b/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts index 7365f4bfb4639..f23335f9daf78 100644 --- a/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts +++ b/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts @@ -23,7 +23,7 @@ import path from 'path'; import os from 'os'; import { renderSummaryTable } from './print_run'; import type { SecuritySolutionDescribeBlockFtrConfig } from './utils'; -import { isSkipped, parseTestFileConfig } from './utils'; +import { parseTestFileConfig, retrieveIntegrations } from './utils'; interface ProductType { product_line: string; @@ -53,35 +53,6 @@ interface Credentials { const DEFAULT_REGION = 'aws-eu-west-1'; let log: ToolingLog; -/** - * Retrieve test files using a glob pattern. - * If process.env.RUN_ALL_TESTS is true, returns all matching files, otherwise, return files that should be run by this job based on process.env.BUILDKITE_PARALLEL_JOB_COUNT and process.env.BUILDKITE_PARALLEL_JOB - */ -const retrieveIntegrations = (integrationsPaths: string[]) => { - const nonSkippedSpecs = integrationsPaths.filter((filePath) => !isSkipped(filePath)); - - if (process.env.RUN_ALL_TESTS === 'true') { - return nonSkippedSpecs; - } else { - // The number of instances of this job were created - const chunksTotal: number = process.env.BUILDKITE_PARALLEL_JOB_COUNT - ? parseInt(process.env.BUILDKITE_PARALLEL_JOB_COUNT, 10) - : 1; - // An index which uniquely identifies this instance of the job - const chunkIndex: number = process.env.BUILDKITE_PARALLEL_JOB - ? parseInt(process.env.BUILDKITE_PARALLEL_JOB, 10) - : 0; - - const nonSkippedSpecsForChunk: string[] = []; - - for (let i = chunkIndex; i < nonSkippedSpecs.length; i += chunksTotal) { - nonSkippedSpecsForChunk.push(nonSkippedSpecs[i]); - } - - return nonSkippedSpecsForChunk; - } -}; - const encode = (str: string): string => Buffer.from(str, 'binary').toString('base64'); const getApiKeyFromElasticCloudJsonFile = () => { diff --git a/x-pack/plugins/security_solution/scripts/run_cypress/utils.ts b/x-pack/plugins/security_solution/scripts/run_cypress/utils.ts index fdaf5aeac1288..37ef1d58baf42 100644 --- a/x-pack/plugins/security_solution/scripts/run_cypress/utils.ts +++ b/x-pack/plugins/security_solution/scripts/run_cypress/utils.ts @@ -13,6 +13,35 @@ import type { ExpressionStatement, ObjectExpression, ObjectProperty } from '@bab import { schema, type TypeOf } from '@kbn/config-schema'; import { getExperimentalAllowedValues } from '../../common/experimental_features'; +/** + * Retrieve test files using a glob pattern. + * If process.env.RUN_ALL_TESTS is true, returns all matching files, otherwise, return files that should be run by this job based on process.env.BUILDKITE_PARALLEL_JOB_COUNT and process.env.BUILDKITE_PARALLEL_JOB + */ +export const retrieveIntegrations = (integrationsPaths: string[]) => { + const nonSkippedSpecs = integrationsPaths.filter((filePath) => !isSkipped(filePath)); + + if (process.env.RUN_ALL_TESTS === 'true') { + return nonSkippedSpecs; + } else { + // The number of instances of this job were created + const chunksTotal: number = process.env.BUILDKITE_PARALLEL_JOB_COUNT + ? parseInt(process.env.BUILDKITE_PARALLEL_JOB_COUNT, 10) + : 1; + // An index which uniquely identifies this instance of the job + const chunkIndex: number = process.env.BUILDKITE_PARALLEL_JOB + ? parseInt(process.env.BUILDKITE_PARALLEL_JOB, 10) + : 0; + + const nonSkippedSpecsForChunk: string[] = []; + + for (let i = chunkIndex; i < nonSkippedSpecs.length; i += chunksTotal) { + nonSkippedSpecsForChunk.push(nonSkippedSpecs[i]); + } + + return nonSkippedSpecsForChunk; + } +}; + export const isSkipped = (filePath: string): boolean => { const testFile = fs.readFileSync(filePath, { encoding: 'utf8' }); From e4c169ed630ccc0db7cbee4fd817d02b0caa8044 Mon Sep 17 00:00:00 2001 From: Gloria Hornero Date: Thu, 2 Nov 2023 10:36:45 +0100 Subject: [PATCH 15/42] removing unnecessary methods --- .../cypress/e2e/explore/cases/connector_options.cy.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/explore/cases/connector_options.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/explore/cases/connector_options.cy.ts index 8bc8d67d9e1be..9bff9b341c68a 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/explore/cases/connector_options.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/explore/cases/connector_options.cy.ts @@ -27,15 +27,9 @@ import { import { goToCreateNewCase } from '../../../tasks/all_cases'; import { CASES_URL } from '../../../urls/navigation'; import { CONNECTOR_CARD_DETAILS, CONNECTOR_TITLE } from '../../../screens/case_details'; -import { cleanKibana } from '../../../tasks/common'; // FLAKY: https://github.com/elastic/kibana/issues/165712 describe('Cases connector incident fields', { tags: ['@ess', '@serverless'] }, () => { - before(() => { - cleanKibana(); - login(); - }); - beforeEach(() => { login(); cy.intercept('GET', '/api/cases/configure/connectors/_find', getMockConnectorsResponse()); From 9e33e810a94dc815058b54ffad109a431ce9f458 Mon Sep 17 00:00:00 2001 From: dkirchan Date: Thu, 2 Nov 2023 12:53:11 +0200 Subject: [PATCH 16/42] Fixed comment with product type to get an already defined one --- .../scripts/run_cypress/parallel_serverless.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts b/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts index f23335f9daf78..86b9a922d32cb 100644 --- a/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts +++ b/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts @@ -24,16 +24,13 @@ import os from 'os'; import { renderSummaryTable } from './print_run'; import type { SecuritySolutionDescribeBlockFtrConfig } from './utils'; import { parseTestFileConfig, retrieveIntegrations } from './utils'; +import type { SecurityProductTypes, SecurityProductType } from '../../../security_solution_serverless/common/config'; -interface ProductType { - product_line: string; - product_tier: string; -} interface CreateEnvironmentRequestBody { name: string; region_id: string; - product_types: ProductType[]; + product_types: SecurityProductTypes; } interface Environment { @@ -101,9 +98,9 @@ async function createEnvironment( region_id: DEFAULT_REGION, } as CreateEnvironmentRequestBody; - const productTypes: ProductType[] = []; + const productTypes: SecurityProductTypes = []; ftrConfig?.productTypes?.forEach((t) => { - productTypes.push(t as ProductType); + productTypes.push(t as SecurityProductType); }); if (productTypes.length > 0) body.product_types = productTypes; From 47653cb2efe83792161587bb8e5dcbce6f00a636 Mon Sep 17 00:00:00 2001 From: dkirchan Date: Thu, 2 Nov 2023 13:04:35 +0200 Subject: [PATCH 17/42] Changed the encode method to the default btoa() --- .../scripts/run_cypress/parallel_serverless.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts b/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts index 86b9a922d32cb..059bc6425605c 100644 --- a/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts +++ b/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts @@ -50,8 +50,6 @@ interface Credentials { const DEFAULT_REGION = 'aws-eu-west-1'; let log: ToolingLog; -const encode = (str: string): string => Buffer.from(str, 'binary').toString('base64'); - const getApiKeyFromElasticCloudJsonFile = () => { const userHomeDir = os.homedir(); try { @@ -395,7 +393,7 @@ ${JSON.stringify(cypressConfigFile, null, 2)} const credentials = await resetCredentials(BASE_ENV_URL, environment.id, id, API_KEY); // Base64 encode the credentials in order to invoke ES and KB APIs - const auth = encode(`${credentials.username}:${credentials.password}`); + const auth = btoa(`${credentials.username}:${credentials.password}`); // Wait for elasticsearch status to go green. await waitForEsStatusGreen(environment.es_url, auth, id); From 1934c672301d2806bb23d7fdb926c8495923878d Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Thu, 2 Nov 2023 11:39:11 +0000 Subject: [PATCH 18/42] [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' --- .../scripts/run_cypress/parallel_serverless.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts b/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts index 059bc6425605c..91c7af6a46fa9 100644 --- a/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts +++ b/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts @@ -21,11 +21,13 @@ import { createFailError } from '@kbn/dev-cli-errors'; import axios from 'axios'; import path from 'path'; import os from 'os'; +import type { + SecurityProductTypes, + SecurityProductType, +} from '@kbn/security-solution-serverless/common/config'; import { renderSummaryTable } from './print_run'; import type { SecuritySolutionDescribeBlockFtrConfig } from './utils'; import { parseTestFileConfig, retrieveIntegrations } from './utils'; -import type { SecurityProductTypes, SecurityProductType } from '../../../security_solution_serverless/common/config'; - interface CreateEnvironmentRequestBody { name: string; From 5535a0e3585f2ba0e2c338efbc6ef29affe6b4dc Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Thu, 2 Nov 2023 11:45:07 +0000 Subject: [PATCH 19/42] [CI] Auto-commit changed files from 'node scripts/lint_ts_projects --fix' --- x-pack/plugins/security_solution/tsconfig.json | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/security_solution/tsconfig.json b/x-pack/plugins/security_solution/tsconfig.json index 1766051325f5f..2464ad018aee3 100644 --- a/x-pack/plugins/security_solution/tsconfig.json +++ b/x-pack/plugins/security_solution/tsconfig.json @@ -178,5 +178,6 @@ "@kbn/unified-doc-viewer-plugin", "@kbn/shared-ux-error-boundary", "@kbn/zod-helpers", + "@kbn/security-solution-serverless", ] } From 597d8c197258b5b365016979f23fdbb568abea4e Mon Sep 17 00:00:00 2001 From: dkirchan Date: Thu, 2 Nov 2023 14:01:56 +0200 Subject: [PATCH 20/42] Fixing return types and API_KEY check --- .../run_cypress/parallel_serverless.ts | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts b/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts index 059bc6425605c..00d3e49942e46 100644 --- a/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts +++ b/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts @@ -24,8 +24,10 @@ import os from 'os'; import { renderSummaryTable } from './print_run'; import type { SecuritySolutionDescribeBlockFtrConfig } from './utils'; import { parseTestFileConfig, retrieveIntegrations } from './utils'; -import type { SecurityProductTypes, SecurityProductType } from '../../../security_solution_serverless/common/config'; - +import type { + SecurityProductTypes, + SecurityProductType, +} from '../../../security_solution_serverless/common/config'; interface CreateEnvironmentRequestBody { name: string; @@ -50,15 +52,15 @@ interface Credentials { const DEFAULT_REGION = 'aws-eu-west-1'; let log: ToolingLog; -const getApiKeyFromElasticCloudJsonFile = () => { +const getApiKeyFromElasticCloudJsonFile = (): string | undefined => { const userHomeDir = os.homedir(); try { const jsonString = fs.readFileSync(path.join(userHomeDir, '.elastic/cloud.json'), 'utf-8'); const jsonData = JSON.parse(jsonString); return jsonData.api_key.qa; } catch (e) { - log.info('API KEY could not be found in '); - return null; + log.info('API KEY could not be found in .elastic/cloud.json'); + return undefined; } }; @@ -131,7 +133,7 @@ async function deleteEnvironment( runnerId: string, apiKey: string, onEarlyExit: (msg: string) => void -) { +): Promise { await axios .delete(`${baseUrl}/api/v1/serverless/projects/security/${projectId}`, { headers: { @@ -179,7 +181,7 @@ async function resetCredentials( } // Wait until elasticsearch status goes green -async function waitForEsStatusGreen(esUrl: string, auth: string, runnerId: string) { +async function waitForEsStatusGreen(esUrl: string, auth: string, runnerId: string): Promise { await poll(async () => { await axios .get(`${esUrl}/_cluster/health?wait_for_status=green&timeout=50s`, { @@ -200,7 +202,11 @@ async function waitForEsStatusGreen(esUrl: string, auth: string, runnerId: strin } // Wait until Kibana is available -async function waitForKibanaAvailable(kbUrl: string, auth: string, runnerId: string) { +async function waitForKibanaAvailable( + kbUrl: string, + auth: string, + runnerId: string +): Promise { await poll(async () => { await axios .get(`${kbUrl}/api/status`, { @@ -379,6 +385,12 @@ ${JSON.stringify(cypressConfigFile, null, 2)} const PROJECT_NAME = `${PROJECT_NAME_PREFIX}-${id}`; const specFileFTRConfig = parseTestFileConfig(filePath); + if (!API_KEY) { + log.info('API KEY to create environment could not be retrieved.'); + // eslint-disable-next-line no-process-exit + return process.exit(0); + } + // Creating environment for the test to run const environment = await createEnvironment( BASE_ENV_URL, From 9d646cac5450363acefd17c4e59d8e28348a53cb Mon Sep 17 00:00:00 2001 From: dkirchan Date: Thu, 2 Nov 2023 14:16:56 +0200 Subject: [PATCH 21/42] Reverted Product type change --- .../scripts/run_cypress/parallel_serverless.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts b/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts index 685f8640a8a54..0af34343c2df8 100644 --- a/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts +++ b/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts @@ -21,19 +21,19 @@ import { createFailError } from '@kbn/dev-cli-errors'; import axios from 'axios'; import path from 'path'; import os from 'os'; -import type { - SecurityProductTypes, - SecurityProductType, -} from '@kbn/security-solution-serverless/common/config'; import { renderSummaryTable } from './print_run'; import type { SecuritySolutionDescribeBlockFtrConfig } from './utils'; import { parseTestFileConfig, retrieveIntegrations } from './utils'; +interface ProductType { + product_line: string; + product_tier: string; +} interface CreateEnvironmentRequestBody { name: string; region_id: string; - product_types: SecurityProductTypes; + product_types: ProductType[]; } interface Environment { @@ -99,9 +99,9 @@ async function createEnvironment( region_id: DEFAULT_REGION, } as CreateEnvironmentRequestBody; - const productTypes: SecurityProductTypes = []; + const productTypes: ProductType[] = []; ftrConfig?.productTypes?.forEach((t) => { - productTypes.push(t as SecurityProductType); + productTypes.push(t as ProductType); }); if (productTypes.length > 0) body.product_types = productTypes; From 57d39bab44fefb60625ea58962012f7a4d6673be Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Thu, 2 Nov 2023 12:23:01 +0000 Subject: [PATCH 22/42] [CI] Auto-commit changed files from 'node scripts/lint_ts_projects --fix' --- x-pack/plugins/security_solution/tsconfig.json | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugins/security_solution/tsconfig.json b/x-pack/plugins/security_solution/tsconfig.json index 2464ad018aee3..1766051325f5f 100644 --- a/x-pack/plugins/security_solution/tsconfig.json +++ b/x-pack/plugins/security_solution/tsconfig.json @@ -178,6 +178,5 @@ "@kbn/unified-doc-viewer-plugin", "@kbn/shared-ux-error-boundary", "@kbn/zod-helpers", - "@kbn/security-solution-serverless", ] } From 596f4d333ae18585c0e9de50ec389005909a99b5 Mon Sep 17 00:00:00 2001 From: dkirchan Date: Thu, 2 Nov 2023 14:24:06 +0200 Subject: [PATCH 23/42] Removed return undefined --- .../security_solution/scripts/run_cypress/parallel_serverless.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts b/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts index 0af34343c2df8..7de4bdc740895 100644 --- a/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts +++ b/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts @@ -61,7 +61,6 @@ const getApiKeyFromElasticCloudJsonFile = (): string | undefined => { return jsonData.api_key.qa; } catch (e) { log.info('API KEY could not be found in .elastic/cloud.json'); - return undefined; } }; From 267c0b9952563303f1d6d38e0a2f9b472756bfff Mon Sep 17 00:00:00 2001 From: Gloria Hornero Date: Thu, 2 Nov 2023 14:12:42 +0100 Subject: [PATCH 24/42] stabilizing tests in production --- .../cypress_ci_serverless_qa.config.ts | 2 +- .../missing_privileges_callout.cy.ts | 148 +++++++------- .../install_update_authorization.cy.ts | 2 +- .../install_update_error_handling.cy.ts | 3 +- .../prebuilt_rules/management.cy.ts | 2 +- .../prebuilt_rules/notifications.cy.ts | 180 ++++++++++-------- .../prebuilt_rules/update_workflow.cy.ts | 2 +- .../upgrade_of_prebuilt_rules.cy.ts | 2 +- .../event_correlation_rule.cy.ts | 6 +- .../rule_creation/machine_learning_rule.cy.ts | 126 ++++++------ .../related_integrations.cy.ts | 2 +- .../bulk_actions/bulk_edit_rules.cy.ts | 74 +++---- .../import_export/export_rule.cy.ts | 26 +-- .../rule_actions/snoozing/rule_snoozing.cy.ts | 2 +- .../rules_table/rules_table_selection.cy.ts | 98 +++++----- .../investigations/alerts/alerts_charts.cy.ts | 76 ++++---- .../alerts/alerts_details.cy.ts | 140 +++++++------- .../e2e/investigations/timelines/export.cy.ts | 6 +- .../timelines/row_renderers.cy.ts | 6 +- .../cypress/objects/timeline.ts | 8 +- .../cypress/tasks/api_calls/timelines.ts | 6 +- 21 files changed, 478 insertions(+), 439 deletions(-) diff --git a/x-pack/test/security_solution_cypress/cypress/cypress_ci_serverless_qa.config.ts b/x-pack/test/security_solution_cypress/cypress/cypress_ci_serverless_qa.config.ts index 3a1be3ed0221a..d80f13071a681 100644 --- a/x-pack/test/security_solution_cypress/cypress/cypress_ci_serverless_qa.config.ts +++ b/x-pack/test/security_solution_cypress/cypress/cypress_ci_serverless_qa.config.ts @@ -18,7 +18,7 @@ export default defineCypressConfig({ env: { grepFilterSpecs: true, grepOmitFiltered: true, - grepTags: '@serverless --@brokenInServerless --@skipInServerless', + grepTags: '@serverless --@brokenInServerless --@skipInServerless --@brokenInServerlessQA', }, execTimeout: 150000, pageLoadTimeout: 150000, diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/missing_privileges_callout.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/missing_privileges_callout.cy.ts index d6b4aec5bf3ea..3e75f8b4a28bb 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/missing_privileges_callout.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/missing_privileges_callout.cy.ts @@ -44,101 +44,105 @@ const waitForPageTitleToBeShown = () => { cy.get(PAGE_TITLE).should('be.visible'); }; -describe('Detections > Callouts', { tags: ['@ess', '@serverless'] }, () => { - before(() => { - // First, we have to open the app on behalf of a privileged user in order to initialize it. - // Otherwise the app will be disabled and show a "welcome"-like page. - login(); - visit(ALERTS_URL); - waitForPageTitleToBeShown(); - }); - - context('indicating read-only access to resources', () => { - context('On Detections home page', () => { - beforeEach(() => { - loadPageAsReadOnlyUser(ALERTS_URL); - }); +describe( + 'Detections > Callouts', + { tags: ['@ess', '@serverless', '@brokenInServerlessQA'] }, + () => { + before(() => { + // First, we have to open the app on behalf of a privileged user in order to initialize it. + // Otherwise the app will be disabled and show a "welcome"-like page. + login(); + visit(ALERTS_URL); + waitForPageTitleToBeShown(); + }); - it('We show one primary callout', () => { - waitForCallOutToBeShown(MISSING_PRIVILEGES_CALLOUT, 'primary'); - }); + context('indicating read-only access to resources', () => { + context('On Detections home page', () => { + beforeEach(() => { + loadPageAsReadOnlyUser(ALERTS_URL); + }); - context('When a user clicks Dismiss on the callout', () => { - it('We hide it and persist the dismissal', () => { + it('We show one primary callout', () => { waitForCallOutToBeShown(MISSING_PRIVILEGES_CALLOUT, 'primary'); - dismissCallOut(MISSING_PRIVILEGES_CALLOUT); - reloadPage(); - getCallOut(MISSING_PRIVILEGES_CALLOUT).should('not.exist'); }); - }); - }); - // FYI: Rules Management check moved to ../detection_rules/all_rules_read_only.spec.ts - - context('On Rule Details page', () => { - beforeEach(() => { - createRule(getNewRule()).then((rule) => - loadPageAsReadOnlyUser(ruleDetailsUrl(rule.body.id)) - ); + context('When a user clicks Dismiss on the callout', () => { + it('We hide it and persist the dismissal', () => { + waitForCallOutToBeShown(MISSING_PRIVILEGES_CALLOUT, 'primary'); + dismissCallOut(MISSING_PRIVILEGES_CALLOUT); + reloadPage(); + getCallOut(MISSING_PRIVILEGES_CALLOUT).should('not.exist'); + }); + }); }); - afterEach(() => { - deleteCustomRule(); - }); + // FYI: Rules Management check moved to ../detection_rules/all_rules_read_only.spec.ts - it('We show one primary callout', () => { - waitForCallOutToBeShown(MISSING_PRIVILEGES_CALLOUT, 'primary'); - }); + context('On Rule Details page', () => { + beforeEach(() => { + createRule(getNewRule()).then((rule) => + loadPageAsReadOnlyUser(ruleDetailsUrl(rule.body.id)) + ); + }); + + afterEach(() => { + deleteCustomRule(); + }); - context('When a user clicks Dismiss on the callouts', () => { - it('We hide them and persist the dismissal', () => { + it('We show one primary callout', () => { waitForCallOutToBeShown(MISSING_PRIVILEGES_CALLOUT, 'primary'); + }); - dismissCallOut(MISSING_PRIVILEGES_CALLOUT); - reloadPage(); + context('When a user clicks Dismiss on the callouts', () => { + it('We hide them and persist the dismissal', () => { + waitForCallOutToBeShown(MISSING_PRIVILEGES_CALLOUT, 'primary'); - getCallOut(MISSING_PRIVILEGES_CALLOUT).should('not.exist'); + dismissCallOut(MISSING_PRIVILEGES_CALLOUT); + reloadPage(); + + getCallOut(MISSING_PRIVILEGES_CALLOUT).should('not.exist'); + }); }); }); }); - }); - context('indicating read-write access to resources', () => { - context('On Detections home page', () => { - beforeEach(() => { - loadPageAsPlatformEngineer(ALERTS_URL); - }); + context('indicating read-write access to resources', () => { + context('On Detections home page', () => { + beforeEach(() => { + loadPageAsPlatformEngineer(ALERTS_URL); + }); - it('We show no callout', () => { - getCallOut(MISSING_PRIVILEGES_CALLOUT).should('not.exist'); + it('We show no callout', () => { + getCallOut(MISSING_PRIVILEGES_CALLOUT).should('not.exist'); + }); }); - }); - context('On Rules Management page', () => { - beforeEach(() => { - login(ROLES.platform_engineer); - loadPageAsPlatformEngineer(RULES_MANAGEMENT_URL); - }); + context('On Rules Management page', () => { + beforeEach(() => { + login(ROLES.platform_engineer); + loadPageAsPlatformEngineer(RULES_MANAGEMENT_URL); + }); - it('We show no callout', () => { - getCallOut(MISSING_PRIVILEGES_CALLOUT).should('not.exist'); + it('We show no callout', () => { + getCallOut(MISSING_PRIVILEGES_CALLOUT).should('not.exist'); + }); }); - }); - context('On Rule Details page', () => { - beforeEach(() => { - createRule(getNewRule()).then((rule) => - loadPageAsPlatformEngineer(ruleDetailsUrl(rule.body.id)) - ); - }); + context('On Rule Details page', () => { + beforeEach(() => { + createRule(getNewRule()).then((rule) => + loadPageAsPlatformEngineer(ruleDetailsUrl(rule.body.id)) + ); + }); - afterEach(() => { - deleteCustomRule(); - }); + afterEach(() => { + deleteCustomRule(); + }); - it('We show no callouts', () => { - getCallOut(MISSING_PRIVILEGES_CALLOUT).should('not.exist'); + it('We show no callouts', () => { + getCallOut(MISSING_PRIVILEGES_CALLOUT).should('not.exist'); + }); }); }); - }); -}); + } +); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/install_update_authorization.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/install_update_authorization.cy.ts index 052306817a87d..d40aca2054372 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/install_update_authorization.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/install_update_authorization.cy.ts @@ -68,7 +68,7 @@ const loginPageAsWriteAuthorizedUser = (url: string) => { describe( 'Detection rules, Prebuilt Rules Installation and Update - Authorization/RBAC', - { tags: ['@ess', '@serverless'] }, + { tags: ['@ess', '@serverless', '@brokenInServerlessQA'] }, () => { beforeEach(() => { preventPrebuiltRulesPackageInstallation(); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/install_update_error_handling.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/install_update_error_handling.cy.ts index effb732489849..09d278ddee716 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/install_update_error_handling.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/install_update_error_handling.cy.ts @@ -37,13 +37,12 @@ import { visitRulesManagementTable } from '../../../tasks/rules_management'; describe( 'Detection rules, Prebuilt Rules Installation and Update - Error handling', - { tags: ['@ess', '@serverless'] }, + { tags: ['@ess', '@serverless', '@brokenInServerlessQA'] }, () => { beforeEach(() => { preventPrebuiltRulesPackageInstallation(); cleanKibana(); login(); - visitRulesManagementTable(); }); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/management.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/management.cy.ts index 7fef8e4abbea2..b0ad9b9da100a 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/management.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/management.cy.ts @@ -51,7 +51,7 @@ const rules = Array.from(Array(5)).map((_, i) => { }); }); -describe('Prebuilt rules', { tags: ['@ess', '@serverless'] }, () => { +describe('Prebuilt rules', { tags: ['@ess', '@serverless', '@brokenInServerlessQA'] }, () => { before(() => { cleanKibana(); }); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/notifications.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/notifications.cy.ts index eef35c79d3624..180c435c10213 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/notifications.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/notifications.cy.ts @@ -54,16 +54,20 @@ describe( cy.get(RULES_UPDATES_TAB).should('not.exist'); }); - it('should NOT display install or update notifications when latest rules are installed', () => { - visitRulesManagementTable(); - createAndInstallMockedPrebuiltRules([RULE_1]); + it( + 'should NOT display install or update notifications when latest rules are installed', + { tags: ['@brokenInServerlessQA'] }, + () => { + visitRulesManagementTable(); + createAndInstallMockedPrebuiltRules([RULE_1]); - /* Assert that there are no installation or update notifications */ - /* Add Elastic Rules button should not contain a number badge */ - /* and Rule Upgrade tab should not be displayed */ - cy.get(ADD_ELASTIC_RULES_BTN).should('have.text', 'Add Elastic rules'); - cy.get(RULES_UPDATES_TAB).should('not.exist'); - }); + /* Assert that there are no installation or update notifications */ + /* Add Elastic Rules button should not contain a number badge */ + /* and Rule Upgrade tab should not be displayed */ + cy.get(ADD_ELASTIC_RULES_BTN).should('have.text', 'Add Elastic rules'); + cy.get(RULES_UPDATES_TAB).should('not.exist'); + } + ); }); describe('Notifications', () => { @@ -71,60 +75,68 @@ describe( installPrebuiltRuleAssets([RULE_1]); }); - describe('Rules installation notification when no rules have been installed', () => { - beforeEach(() => { - visitRulesManagementTable(); - }); - - it('should notify user about prebuilt rules available for installation', () => { - cy.get(ADD_ELASTIC_RULES_BTN).should('be.visible'); - cy.get(ADD_ELASTIC_RULES_BTN).should('have.text', `Add Elastic rules${1}`); - cy.get(RULES_UPDATES_TAB).should('not.exist'); - }); - }); - - describe('Rule installation notification when at least one rule already installed', () => { - beforeEach(() => { - installAllPrebuiltRulesRequest().then(() => { - /* Create new rule assets with a different rule_id as the one that was */ - /* installed before in order to trigger the installation notification */ - const RULE_2 = createRuleAssetSavedObject({ - name: 'Test rule 2', - rule_id: 'rule_2', - }); - const RULE_3 = createRuleAssetSavedObject({ - name: 'Test rule 3', - rule_id: 'rule_3', - }); - + describe( + 'Rules installation notification when no rules have been installed', + { tags: ['@brokenInServerlessQA'] }, + () => { + beforeEach(() => { visitRulesManagementTable(); - installPrebuiltRuleAssets([RULE_2, RULE_3]); }); - }); - it('should notify user about prebuilt rules available for installation', () => { - const numberOfAvailableRules = 2; - cy.get(ADD_ELASTIC_RULES_BTN).should('be.visible'); - cy.get(ADD_ELASTIC_RULES_BTN).should( - 'have.text', - `Add Elastic rules${numberOfAvailableRules}` - ); - cy.get(RULES_UPDATES_TAB).should('not.exist'); - }); - - it('should notify user a rule is again available for installation if it is deleted', () => { - /* Install available rules, assert that the notification is gone */ - /* then delete one rule and assert that the notification is back */ - installAllPrebuiltRulesRequest().then(() => { - cy.reload(); - deleteFirstRule(); + it('should notify user about prebuilt rules available for installation', () => { cy.get(ADD_ELASTIC_RULES_BTN).should('be.visible'); cy.get(ADD_ELASTIC_RULES_BTN).should('have.text', `Add Elastic rules${1}`); + cy.get(RULES_UPDATES_TAB).should('not.exist'); + }); + } + ); + + describe( + 'Rule installation notification when at least one rule already installed', + { tags: ['@brokenInServerlessQA'] }, + () => { + beforeEach(() => { + installAllPrebuiltRulesRequest().then(() => { + /* Create new rule assets with a different rule_id as the one that was */ + /* installed before in order to trigger the installation notification */ + const RULE_2 = createRuleAssetSavedObject({ + name: 'Test rule 2', + rule_id: 'rule_2', + }); + const RULE_3 = createRuleAssetSavedObject({ + name: 'Test rule 3', + rule_id: 'rule_3', + }); + + visitRulesManagementTable(); + installPrebuiltRuleAssets([RULE_2, RULE_3]); + }); }); - }); - }); - describe('Rule update notification', () => { + it('should notify user about prebuilt rules available for installation', () => { + const numberOfAvailableRules = 2; + cy.get(ADD_ELASTIC_RULES_BTN).should('be.visible'); + cy.get(ADD_ELASTIC_RULES_BTN).should( + 'have.text', + `Add Elastic rules${numberOfAvailableRules}` + ); + cy.get(RULES_UPDATES_TAB).should('not.exist'); + }); + + it('should notify user a rule is again available for installation if it is deleted', () => { + /* Install available rules, assert that the notification is gone */ + /* then delete one rule and assert that the notification is back */ + installAllPrebuiltRulesRequest().then(() => { + cy.reload(); + deleteFirstRule(); + cy.get(ADD_ELASTIC_RULES_BTN).should('be.visible'); + cy.get(ADD_ELASTIC_RULES_BTN).should('have.text', `Add Elastic rules${1}`); + }); + }); + } + ); + + describe('Rule update notification', { tags: ['@brokenInServerlessQA'] }, () => { beforeEach(() => { installAllPrebuiltRulesRequest().then(() => { /* Create new rule asset with the same rule_id as the one that was installed */ @@ -148,35 +160,39 @@ describe( }); }); - describe('Rule installation available and rule update available notifications', () => { - beforeEach(() => { - installAllPrebuiltRulesRequest().then(() => { - /* Create new rule assets with a different rule_id as the one that was */ - /* installed before in order to trigger the installation notification */ - const RULE_2 = createRuleAssetSavedObject({ - name: 'Test rule 2', - rule_id: 'rule_2', + describe( + 'Rule installation available and rule update available notifications', + { tags: ['@brokenInServerlessQA'] }, + () => { + beforeEach(() => { + installAllPrebuiltRulesRequest().then(() => { + /* Create new rule assets with a different rule_id as the one that was */ + /* installed before in order to trigger the installation notification */ + const RULE_2 = createRuleAssetSavedObject({ + name: 'Test rule 2', + rule_id: 'rule_2', + }); + /* Create new rule asset with the same rule_id as the one that was installed */ + /* but with a higher version, in order to trigger the update notification */ + const UPDATED_RULE = createRuleAssetSavedObject({ + name: 'Test rule 1.1 (updated)', + rule_id: 'rule_1', + version: 2, + }); + installPrebuiltRuleAssets([RULE_2, UPDATED_RULE]); + visitRulesManagementTable(); }); - /* Create new rule asset with the same rule_id as the one that was installed */ - /* but with a higher version, in order to trigger the update notification */ - const UPDATED_RULE = createRuleAssetSavedObject({ - name: 'Test rule 1.1 (updated)', - rule_id: 'rule_1', - version: 2, - }); - installPrebuiltRuleAssets([RULE_2, UPDATED_RULE]); - visitRulesManagementTable(); }); - }); - it('should notify user about prebuilt rules available for installation and for upgrade', () => { - // 1 rule available for installation - cy.get(ADD_ELASTIC_RULES_BTN).should('have.text', `Add Elastic rules${1}`); - // 1 rule available for update - cy.get(RULES_UPDATES_TAB).should('be.visible'); - cy.get(RULES_UPDATES_TAB).should('have.text', `Rule Updates${1}`); - }); - }); + it('should notify user about prebuilt rules available for installation and for upgrade', () => { + // 1 rule available for installation + cy.get(ADD_ELASTIC_RULES_BTN).should('have.text', `Add Elastic rules${1}`); + // 1 rule available for update + cy.get(RULES_UPDATES_TAB).should('be.visible'); + cy.get(RULES_UPDATES_TAB).should('have.text', `Rule Updates${1}`); + }); + } + ); }); } ); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/update_workflow.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/update_workflow.cy.ts index 443d046ae4715..9586194f421a5 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/update_workflow.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/update_workflow.cy.ts @@ -29,7 +29,7 @@ import { visitRulesManagementTable } from '../../../tasks/rules_management'; describe( 'Detection rules, Prebuilt Rules Installation and Update workflow', - { tags: ['@ess', '@serverless'] }, + { tags: ['@ess', '@serverless', '@brokenInServerlessQA'] }, () => { describe('Installation of prebuilt rules', () => { const RULE_1 = createRuleAssetSavedObject({ diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/upgrade_of_prebuilt_rules.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/upgrade_of_prebuilt_rules.cy.ts index d3eab8a75fab8..36b203bea6e6a 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/upgrade_of_prebuilt_rules.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/upgrade_of_prebuilt_rules.cy.ts @@ -30,7 +30,7 @@ import { visitRulesManagementTable } from '../../../tasks/rules_management'; describe( 'Detection rules, Prebuilt Rules Installation and Update workflow', - { tags: ['@ess', '@serverless'] }, + { tags: ['@ess', '@serverless', '@brokenInServerlessQA'] }, () => { describe('Upgrade of prebuilt rules', () => { const RULE_1_ID = 'rule_1'; diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/event_correlation_rule.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/event_correlation_rule.cy.ts index 36fa6cd484154..eeca0d06bcc57 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/event_correlation_rule.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/event_correlation_rule.cy.ts @@ -43,7 +43,7 @@ import { import { getDetails, waitForTheRuleToBeExecuted } from '../../../tasks/rule_details'; import { expectNumberOfRules, goToRuleDetailsOf } from '../../../tasks/alerts_detection_rules'; -import { cleanKibana, deleteAlertsAndRules } from '../../../tasks/common'; +import { deleteAlertsAndRules } from '../../../tasks/common'; import { createAndEnableRule, fillAboutRuleAndContinue, @@ -58,10 +58,6 @@ import { openRuleManagementPageViaBreadcrumbs } from '../../../tasks/rules_manag import { CREATE_RULE_URL } from '../../../urls/navigation'; describe('EQL rules', { tags: ['@ess', '@serverless'] }, () => { - before(() => { - cleanKibana(); - }); - beforeEach(() => { login(); deleteAlertsAndRules(); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/machine_learning_rule.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/machine_learning_rule.cy.ts index 684a44d37d214..3929f57d0b6f5 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/machine_learning_rule.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/machine_learning_rule.cy.ts @@ -41,7 +41,6 @@ import { import { getDetails } from '../../../tasks/rule_details'; import { expectNumberOfRules, goToRuleDetailsOf } from '../../../tasks/alerts_detection_rules'; -import { cleanKibana } from '../../../tasks/common'; import { createAndEnableRule, fillAboutRuleAndContinue, @@ -54,74 +53,77 @@ import { visit } from '../../../tasks/navigation'; import { openRuleManagementPageViaBreadcrumbs } from '../../../tasks/rules_management'; import { CREATE_RULE_URL } from '../../../urls/navigation'; -describe('Machine Learning rules', { tags: ['@ess', '@serverless'] }, () => { - const expectedUrls = (getMachineLearningRule().references ?? []).join(''); - const expectedFalsePositives = (getMachineLearningRule().false_positives ?? []).join(''); - const expectedTags = (getMachineLearningRule().tags ?? []).join(''); - const expectedMitre = formatMitreAttackDescription(getMachineLearningRule().threat ?? []); - const expectedNumberOfRules = 1; +describe( + 'Machine Learning rules', + { tags: ['@ess', '@serverless', '@brokenInServerlessQA'] }, + () => { + const expectedUrls = (getMachineLearningRule().references ?? []).join(''); + const expectedFalsePositives = (getMachineLearningRule().false_positives ?? []).join(''); + const expectedTags = (getMachineLearningRule().tags ?? []).join(''); + const expectedMitre = formatMitreAttackDescription(getMachineLearningRule().threat ?? []); + const expectedNumberOfRules = 1; - beforeEach(() => { - cleanKibana(); - login(); - visit(CREATE_RULE_URL); - }); + beforeEach(() => { + login(); + visit(CREATE_RULE_URL); + }); - it('Creates and enables a new ml rule', () => { - const mlRule = getMachineLearningRule(); - selectMachineLearningRuleType(); - fillDefineMachineLearningRuleAndContinue(mlRule); - fillAboutRuleAndContinue(mlRule); - fillScheduleRuleAndContinue(mlRule); - createAndEnableRule(); - openRuleManagementPageViaBreadcrumbs(); + it('Creates and enables a new ml rule', () => { + const mlRule = getMachineLearningRule(); + selectMachineLearningRuleType(); + fillDefineMachineLearningRuleAndContinue(mlRule); + fillAboutRuleAndContinue(mlRule); + fillScheduleRuleAndContinue(mlRule); + createAndEnableRule(); + openRuleManagementPageViaBreadcrumbs(); - cy.get(CUSTOM_RULES_BTN).should('have.text', 'Custom rules (1)'); + cy.get(CUSTOM_RULES_BTN).should('have.text', 'Custom rules (1)'); - expectNumberOfRules(RULES_MANAGEMENT_TABLE, expectedNumberOfRules); + expectNumberOfRules(RULES_MANAGEMENT_TABLE, expectedNumberOfRules); - cy.get(RULE_NAME).should('have.text', mlRule.name); - cy.get(RISK_SCORE).should('have.text', mlRule.risk_score); - cy.get(SEVERITY).should('have.text', 'Critical'); - cy.get(RULE_SWITCH).should('have.attr', 'aria-checked', 'true'); + cy.get(RULE_NAME).should('have.text', mlRule.name); + cy.get(RISK_SCORE).should('have.text', mlRule.risk_score); + cy.get(SEVERITY).should('have.text', 'Critical'); + cy.get(RULE_SWITCH).should('have.attr', 'aria-checked', 'true'); - goToRuleDetailsOf(mlRule.name); + goToRuleDetailsOf(mlRule.name); - cy.get(RULE_NAME_HEADER).should('contain', `${mlRule.name}`); - cy.get(ABOUT_RULE_DESCRIPTION).should('have.text', mlRule.description); - cy.get(ABOUT_DETAILS).within(() => { - getDetails(SEVERITY_DETAILS).should('have.text', 'Critical'); - getDetails(RISK_SCORE_DETAILS).should('have.text', mlRule.risk_score); - getDetails(REFERENCE_URLS_DETAILS).should((details) => { - expect(removeExternalLinkText(details.text())).equal(expectedUrls); + cy.get(RULE_NAME_HEADER).should('contain', `${mlRule.name}`); + cy.get(ABOUT_RULE_DESCRIPTION).should('have.text', mlRule.description); + cy.get(ABOUT_DETAILS).within(() => { + getDetails(SEVERITY_DETAILS).should('have.text', 'Critical'); + getDetails(RISK_SCORE_DETAILS).should('have.text', mlRule.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); }); - getDetails(FALSE_POSITIVES_DETAILS).should('have.text', expectedFalsePositives); - getDetails(MITRE_ATTACK_DETAILS).should((mitre) => { - expect(removeExternalLinkText(mitre.text())).equal(expectedMitre); + cy.get(DEFINITION_DETAILS).within(() => { + getDetails(ANOMALY_SCORE_DETAILS).should('have.text', mlRule.anomaly_threshold); + getDetails(RULE_TYPE_DETAILS).should('have.text', 'Machine Learning'); + getDetails(TIMELINE_TEMPLATE_DETAILS).should('have.text', 'None'); + const machineLearningJobsArray = isArray(mlRule.machine_learning_job_id) + ? mlRule.machine_learning_job_id + : [mlRule.machine_learning_job_id]; + // With the #1912 ML rule improvement changes we enable jobs on rule creation. + // Though, in cypress jobs enabling does not work reliably and job can be started or stopped. + // Thus, we disable next check till we fix the issue with enabling jobs in cypress. + // Relevant ticket: https://github.com/elastic/security-team/issues/5389 + // cy.get(MACHINE_LEARNING_JOB_STATUS).should('have.text', 'StoppedStopped'); + cy.get(MACHINE_LEARNING_JOB_ID).should('have.text', machineLearningJobsArray.join('')); + }); + cy.get(SCHEDULE_DETAILS).within(() => { + getDetails(RUNS_EVERY_DETAILS).should('have.text', `${mlRule.interval}`); + const humanizedDuration = getHumanizedDuration( + mlRule.from ?? 'now-6m', + mlRule.interval ?? '5m' + ); + getDetails(ADDITIONAL_LOOK_BACK_DETAILS).should('have.text', `${humanizedDuration}`); }); - getDetails(TAGS_DETAILS).should('have.text', expectedTags); - }); - cy.get(DEFINITION_DETAILS).within(() => { - getDetails(ANOMALY_SCORE_DETAILS).should('have.text', mlRule.anomaly_threshold); - getDetails(RULE_TYPE_DETAILS).should('have.text', 'Machine Learning'); - getDetails(TIMELINE_TEMPLATE_DETAILS).should('have.text', 'None'); - const machineLearningJobsArray = isArray(mlRule.machine_learning_job_id) - ? mlRule.machine_learning_job_id - : [mlRule.machine_learning_job_id]; - // With the #1912 ML rule improvement changes we enable jobs on rule creation. - // Though, in cypress jobs enabling does not work reliably and job can be started or stopped. - // Thus, we disable next check till we fix the issue with enabling jobs in cypress. - // Relevant ticket: https://github.com/elastic/security-team/issues/5389 - // cy.get(MACHINE_LEARNING_JOB_STATUS).should('have.text', 'StoppedStopped'); - cy.get(MACHINE_LEARNING_JOB_ID).should('have.text', machineLearningJobsArray.join('')); - }); - cy.get(SCHEDULE_DETAILS).within(() => { - getDetails(RUNS_EVERY_DETAILS).should('have.text', `${mlRule.interval}`); - const humanizedDuration = getHumanizedDuration( - mlRule.from ?? 'now-6m', - mlRule.interval ?? '5m' - ); - getDetails(ADDITIONAL_LOOK_BACK_DETAILS).should('have.text', `${humanizedDuration}`); }); - }); -}); + } +); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/related_integrations/related_integrations.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/related_integrations/related_integrations.cy.ts index 6f5434772e69a..1376e486790b7 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/related_integrations/related_integrations.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/related_integrations/related_integrations.cy.ts @@ -45,7 +45,7 @@ import { waitForPageToBeLoaded, } from '../../../../tasks/rule_details'; -describe('Related integrations', { tags: ['@ess', '@serverless'] }, () => { +describe('Related integrations', { tags: ['@ess', '@serverless', '@brokenInServerlessQA'] }, () => { const DATA_STREAM_NAME = 'logs-related-integrations-test'; const PREBUILT_RULE_NAME = 'Prebuilt rule with related integrations'; const RULE_RELATED_INTEGRATIONS: IntegrationDefinition[] = [ diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_edit_rules.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_edit_rules.cy.ts index 9a7eb4547cee0..ef3754c898ecf 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_edit_rules.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_edit_rules.cy.ts @@ -185,7 +185,7 @@ describe('Detection rules, bulk edit', { tags: ['@ess', '@serverless'] }, () => cy.get(APPLY_TIMELINE_RULE_BULK_MENU_ITEM).should('be.disabled'); }); - it('Only prebuilt rules selected', () => { + it('Only prebuilt rules selected', { tags: ['@brokenInServerlessQA'] }, () => { createAndInstallMockedPrebuiltRules(PREBUILT_RULES); // select Elastic(prebuilt) rules, check if we can't proceed further, as Elastic rules are not editable @@ -203,47 +203,55 @@ describe('Detection rules, bulk edit', { tags: ['@ess', '@serverless'] }, () => }); }); - it('Prebuilt and custom rules selected: user proceeds with custom rules editing', () => { - getRulesManagementTableRows().then((existedRulesRows) => { - createAndInstallMockedPrebuiltRules(PREBUILT_RULES); + it( + 'Prebuilt and custom rules selected: user proceeds with custom rules editing', + { tags: ['@brokenInServerlessQA'] }, + () => { + getRulesManagementTableRows().then((existedRulesRows) => { + createAndInstallMockedPrebuiltRules(PREBUILT_RULES); - // modal window should show how many rules can be edit, how many not - selectAllRules(); - clickAddTagsMenuItem(); + // modal window should show how many rules can be edit, how many not + selectAllRules(); + clickAddTagsMenuItem(); - waitForMixedRulesBulkEditModal(existedRulesRows.length); + waitForMixedRulesBulkEditModal(existedRulesRows.length); - getAvailablePrebuiltRulesCount().then((availablePrebuiltRulesCount) => { - checkPrebuiltRulesCannotBeModified(availablePrebuiltRulesCount); - }); + getAvailablePrebuiltRulesCount().then((availablePrebuiltRulesCount) => { + checkPrebuiltRulesCannotBeModified(availablePrebuiltRulesCount); + }); - // user can proceed with custom rule editing - cy.get(MODAL_CONFIRMATION_BTN) - .should('have.text', `Edit ${existedRulesRows.length} custom rules`) - .click(); + // user can proceed with custom rule editing + cy.get(MODAL_CONFIRMATION_BTN) + .should('have.text', `Edit ${existedRulesRows.length} custom rules`) + .click(); - // action should finish - typeTags(['test-tag']); - submitBulkEditForm(); - waitForBulkEditActionToFinish({ updatedCount: existedRulesRows.length }); - }); - }); + // action should finish + typeTags(['test-tag']); + submitBulkEditForm(); + waitForBulkEditActionToFinish({ updatedCount: existedRulesRows.length }); + }); + } + ); - it('Prebuilt and custom rules selected: user cancels action', () => { - createAndInstallMockedPrebuiltRules(PREBUILT_RULES); + it( + 'Prebuilt and custom rules selected: user cancels action', + { tags: ['@brokenInServerlessQA'] }, + () => { + createAndInstallMockedPrebuiltRules(PREBUILT_RULES); - getRulesManagementTableRows().then((rows) => { - // modal window should show how many rules can be edit, how many not - selectAllRules(); - clickAddTagsMenuItem(); - waitForMixedRulesBulkEditModal(rows.length); + getRulesManagementTableRows().then((rows) => { + // modal window should show how many rules can be edit, how many not + selectAllRules(); + clickAddTagsMenuItem(); + waitForMixedRulesBulkEditModal(rows.length); - checkPrebuiltRulesCannotBeModified(PREBUILT_RULES.length); + checkPrebuiltRulesCannotBeModified(PREBUILT_RULES.length); - // user cancels action and modal disappears - cancelConfirmationModal(); - }); - }); + // user cancels action and modal disappears + cancelConfirmationModal(); + }); + } + ); it('should not lose rules selection after edit action', () => { const rulesToUpdate = [RULE_NAME, 'New EQL Rule', 'New Terms Rule'] as const; diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/import_export/export_rule.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/import_export/export_rule.cy.ts index 275160d1f04a0..3983b5e8f534e 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/import_export/export_rule.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/import_export/export_rule.cy.ts @@ -100,19 +100,23 @@ describe('Export rules', { tags: ['@ess', '@serverless'] }, () => { expectManagementTableRules(['Enabled rule to export']); }); - it('shows a modal saying that no rules can be exported if all the selected rules are prebuilt', function () { - createAndInstallMockedPrebuiltRules(prebuiltRules); + it( + 'shows a modal saying that no rules can be exported if all the selected rules are prebuilt', + { tags: ['@brokenInServerlessQA'] }, + function () { + createAndInstallMockedPrebuiltRules(prebuiltRules); - filterByElasticRules(); - selectAllRules(); - bulkExportRules(); + filterByElasticRules(); + selectAllRules(); + bulkExportRules(); - cy.get(MODAL_CONFIRMATION_BODY).contains( - `${prebuiltRules.length} prebuilt Elastic rules (exporting prebuilt rules is not supported)` - ); - }); + cy.get(MODAL_CONFIRMATION_BODY).contains( + `${prebuiltRules.length} prebuilt Elastic rules (exporting prebuilt rules is not supported)` + ); + } + ); - it('exports only custom rules', function () { + it('exports only custom rules', { tags: ['@brokenInServerlessQA'] }, function () { const expectedNumberCustomRulesToBeExported = 1; createAndInstallMockedPrebuiltRules(prebuiltRules); @@ -141,7 +145,7 @@ describe('Export rules', { tags: ['@ess', '@serverless'] }, () => { }); }); - context('rules with exceptions', () => { + context('rules with exceptions', { tags: ['@brokenInServerlessQA'] }, () => { beforeEach(() => { deleteExceptionList(exceptionList.list_id, exceptionList.namespace_type); // create rule with exceptions diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/snoozing/rule_snoozing.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/snoozing/rule_snoozing.cy.ts index cbac9328fb4aa..fd300c04bfe4f 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/snoozing/rule_snoozing.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/snoozing/rule_snoozing.cy.ts @@ -181,7 +181,7 @@ describe('rule snoozing', { tags: ['@ess', '@serverless'] }, () => { deleteConnectors(); }); - it('adds an action to a snoozed rule', () => { + it('adds an action to a snoozed rule', { tags: ['@brokenInServerlessQA'] }, () => { createSnoozedRule(getNewRule({ name: 'Snoozed rule' })).then(({ body: rule }) => { visitEditRulePage(rule.id); goToActionsStepTab(); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rules_table/rules_table_selection.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rules_table/rules_table_selection.cy.ts index d07186c8252f9..ab957879885e8 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rules_table/rules_table_selection.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rules_table/rules_table_selection.cy.ts @@ -34,68 +34,72 @@ const RULE_2 = createRuleAssetSavedObject({ rule_id: 'rule_2', }); -describe('Rules table: selection', { tags: ['@ess', '@serverless'] }, () => { - before(() => { - cleanKibana(); - }); +describe( + 'Rules table: selection', + { tags: ['@ess', '@serverless', '@brokenInServerlessQA'] }, + () => { + before(() => { + cleanKibana(); + }); - beforeEach(() => { - login(); - /* Create and install two mock rules */ - createAndInstallMockedPrebuiltRules([RULE_1, RULE_2]); - visit(RULES_MANAGEMENT_URL); - waitForPrebuiltDetectionRulesToBeLoaded(); - }); + beforeEach(() => { + login(); + /* Create and install two mock rules */ + createAndInstallMockedPrebuiltRules([RULE_1, RULE_2]); + visit(RULES_MANAGEMENT_URL); + waitForPrebuiltDetectionRulesToBeLoaded(); + }); - it('should correctly update the selection label when rules are individually selected and unselected', () => { - waitForPrebuiltDetectionRulesToBeLoaded(); + it('should correctly update the selection label when rules are individually selected and unselected', () => { + waitForPrebuiltDetectionRulesToBeLoaded(); - selectRulesByName(['Test rule 1', 'Test rule 2']); + selectRulesByName(['Test rule 1', 'Test rule 2']); - cy.get(SELECTED_RULES_NUMBER_LABEL).should('contain.text', '2'); + cy.get(SELECTED_RULES_NUMBER_LABEL).should('contain.text', '2'); - unselectRulesByName(['Test rule 1', 'Test rule 2']); + unselectRulesByName(['Test rule 1', 'Test rule 2']); - cy.get(SELECTED_RULES_NUMBER_LABEL).should('contain.text', '0'); - }); + cy.get(SELECTED_RULES_NUMBER_LABEL).should('contain.text', '0'); + }); - it('should correctly update the selection label when rules are bulk selected and then bulk un-selected', () => { - waitForPrebuiltDetectionRulesToBeLoaded(); + it('should correctly update the selection label when rules are bulk selected and then bulk un-selected', () => { + waitForPrebuiltDetectionRulesToBeLoaded(); - cy.get(SELECT_ALL_RULES_BTN).click(); + cy.get(SELECT_ALL_RULES_BTN).click(); - getAvailablePrebuiltRulesCount().then((availablePrebuiltRulesCount) => { - cy.get(SELECTED_RULES_NUMBER_LABEL).should('contain.text', availablePrebuiltRulesCount); - }); + getAvailablePrebuiltRulesCount().then((availablePrebuiltRulesCount) => { + cy.get(SELECTED_RULES_NUMBER_LABEL).should('contain.text', availablePrebuiltRulesCount); + }); - // Un-select all rules via the Bulk Selection button from the Utility bar - cy.get(SELECT_ALL_RULES_BTN).click(); + // Un-select all rules via the Bulk Selection button from the Utility bar + cy.get(SELECT_ALL_RULES_BTN).click(); - // Current selection should be 0 rules - cy.get(SELECTED_RULES_NUMBER_LABEL).should('contain.text', '0'); - // Bulk selection button should be back to displaying all rules - getAvailablePrebuiltRulesCount().then((availablePrebuiltRulesCount) => { - cy.get(SELECT_ALL_RULES_BTN).should('contain.text', availablePrebuiltRulesCount); + // Current selection should be 0 rules + cy.get(SELECTED_RULES_NUMBER_LABEL).should('contain.text', '0'); + // Bulk selection button should be back to displaying all rules + getAvailablePrebuiltRulesCount().then((availablePrebuiltRulesCount) => { + cy.get(SELECT_ALL_RULES_BTN).should('contain.text', availablePrebuiltRulesCount); + }); }); - }); - it('should correctly update the selection label when rules are bulk selected and then unselected via the table select all checkbox', () => { - waitForPrebuiltDetectionRulesToBeLoaded(); + it('should correctly update the selection label when rules are bulk selected and then unselected via the table select all checkbox', () => { + waitForPrebuiltDetectionRulesToBeLoaded(); - cy.get(SELECT_ALL_RULES_BTN).click(); + cy.get(SELECT_ALL_RULES_BTN).click(); - getAvailablePrebuiltRulesCount().then((availablePrebuiltRulesCount) => { - cy.get(SELECTED_RULES_NUMBER_LABEL).should('contain.text', availablePrebuiltRulesCount); - }); + getAvailablePrebuiltRulesCount().then((availablePrebuiltRulesCount) => { + cy.get(SELECTED_RULES_NUMBER_LABEL).should('contain.text', availablePrebuiltRulesCount); + }); - // Un-select all rules via the Un-select All checkbox from the table - cy.get(SELECT_ALL_RULES_ON_PAGE_CHECKBOX).click(); + // Un-select all rules via the Un-select All checkbox from the table + cy.get(SELECT_ALL_RULES_ON_PAGE_CHECKBOX).click(); - // Current selection should be 0 rules - cy.get(SELECTED_RULES_NUMBER_LABEL).should('contain.text', '0'); - // Bulk selection button should be back to displaying all rules - getAvailablePrebuiltRulesCount().then((availablePrebuiltRulesCount) => { - cy.get(SELECT_ALL_RULES_BTN).should('contain.text', availablePrebuiltRulesCount); + // Current selection should be 0 rules + cy.get(SELECTED_RULES_NUMBER_LABEL).should('contain.text', '0'); + // Bulk selection button should be back to displaying all rules + getAvailablePrebuiltRulesCount().then((availablePrebuiltRulesCount) => { + cy.get(SELECT_ALL_RULES_BTN).should('contain.text', availablePrebuiltRulesCount); + }); }); - }); -}); + } +); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/alerts_charts.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/alerts_charts.cy.ts index 91e68ac4d5c8b..45eccfc68fa5c 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/alerts_charts.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/alerts_charts.cy.ts @@ -25,46 +25,50 @@ import { } from '../../../screens/search_bar'; import { TOASTER } from '../../../screens/alerts_detection_rules'; -describe('Histogram legend hover actions', { tags: ['@ess', '@serverless'] }, () => { - const ruleConfigs = getNewRule(); +describe( + 'Histogram legend hover actions', + { tags: ['@ess', '@serverless', '@brokenInServerlessQA'] }, + () => { + const ruleConfigs = getNewRule(); - before(() => { - cleanKibana(); - }); + before(() => { + cleanKibana(); + }); - beforeEach(() => { - login(); - createRule(getNewRule({ rule_id: 'new custom rule' })); - visitWithTimeRange(ALERTS_URL); - selectAlertsHistogram(); - }); + beforeEach(() => { + login(); + createRule(getNewRule({ rule_id: 'new custom rule' })); + visitWithTimeRange(ALERTS_URL); + selectAlertsHistogram(); + }); - it('Filter in/out should add a filter to KQL bar', function () { - const expectedNumberOfAlerts = 2; - clickAlertsHistogramLegend(); - clickAlertsHistogramLegendFilterFor(ruleConfigs.name); - cy.get(GLOBAL_SEARCH_BAR_FILTER_ITEM).should( - 'have.text', - `kibana.alert.rule.name: ${ruleConfigs.name}` - ); - cy.get(ALERTS_COUNT).should('have.text', `${expectedNumberOfAlerts} alerts`); + it('Filter in/out should add a filter to KQL bar', function () { + const expectedNumberOfAlerts = 2; + clickAlertsHistogramLegend(); + clickAlertsHistogramLegendFilterFor(ruleConfigs.name); + cy.get(GLOBAL_SEARCH_BAR_FILTER_ITEM).should( + 'have.text', + `kibana.alert.rule.name: ${ruleConfigs.name}` + ); + cy.get(ALERTS_COUNT).should('have.text', `${expectedNumberOfAlerts} alerts`); - clickAlertsHistogramLegend(); - clickAlertsHistogramLegendFilterOut(ruleConfigs.name); - cy.get(GLOBAL_SEARCH_BAR_FILTER_ITEM).should( - 'have.text', - `NOT kibana.alert.rule.name: ${ruleConfigs.name}` - ); - cy.get(ALERTS_COUNT).should('not.exist'); + clickAlertsHistogramLegend(); + clickAlertsHistogramLegendFilterOut(ruleConfigs.name); + cy.get(GLOBAL_SEARCH_BAR_FILTER_ITEM).should( + 'have.text', + `NOT kibana.alert.rule.name: ${ruleConfigs.name}` + ); + cy.get(ALERTS_COUNT).should('not.exist'); - cy.get(GLOBAL_SEARCH_BAR_FILTER_ITEM_DELETE).click(); - cy.get(GLOBAL_SEARCH_BAR_FILTER_ITEM).should('not.exist'); - }); + cy.get(GLOBAL_SEARCH_BAR_FILTER_ITEM_DELETE).click(); + cy.get(GLOBAL_SEARCH_BAR_FILTER_ITEM).should('not.exist'); + }); - it('Add To Timeline', function () { - clickAlertsHistogramLegend(); - clickAlertsHistogramLegendAddToTimeline(ruleConfigs.name); + it('Add To Timeline', function () { + clickAlertsHistogramLegend(); + clickAlertsHistogramLegendAddToTimeline(ruleConfigs.name); - cy.get(TOASTER).should('have.text', `Added ${ruleConfigs.name} to timeline`); - }); -}); + cy.get(TOASTER).should('have.text', `Added ${ruleConfigs.name} to timeline`); + }); + } +); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/alerts_details.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/alerts_details.cy.ts index bfa5a98921be0..5aa1701392144 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/alerts_details.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/alerts_details.cy.ts @@ -187,84 +187,88 @@ describe('Alert details flyout', () => { }); }); - describe('Localstorage management', { tags: ['@ess', '@serverless'] }, () => { - const ARCHIVED_RULE_ID = '7015a3e2-e4ea-11ed-8c11-49608884878f'; - const ARCHIVED_RULE_NAME = 'Endpoint Security'; - - before(() => { - cleanKibana(); - - // It just imports an alert without a rule but rule details page should work anyway - cy.task('esArchiverLoad', { archiveName: 'query_alert', useCreate: true, docsOnly: true }); - }); - - beforeEach(() => { - login(); - disableExpandableFlyout(); - visitWithTimeRange(ALERTS_URL); - waitForAlertsToPopulate(); - expandFirstAlert(); - }); - - const alertTableKey = 'alerts-page'; - const getFlyoutConfig = (dataTable: { [alertTableKey]: DataTableModel }) => - dataTable?.[alertTableKey]?.expandedDetail?.query; + describe( + 'Localstorage management', + { tags: ['@ess', '@serverless', '@brokenInServerlessQA'] }, + () => { + const ARCHIVED_RULE_ID = '7015a3e2-e4ea-11ed-8c11-49608884878f'; + const ARCHIVED_RULE_NAME = 'Endpoint Security'; + + before(() => { + cleanKibana(); + + // It just imports an alert without a rule but rule details page should work anyway + cy.task('esArchiverLoad', { archiveName: 'query_alert', useCreate: true, docsOnly: true }); + }); - /** - * Localstorage is updated after a delay here x-pack/plugins/security_solution/public/common/store/data_table/epic_local_storage.ts - * We create this config to re-check localStorage 3 times, every 500ms to avoid any potential flakyness from that delay - */ - const storageCheckRetryConfig = { - timeout: 1500, - interval: 500, - }; + beforeEach(() => { + login(); + disableExpandableFlyout(); + visitWithTimeRange(ALERTS_URL); + waitForAlertsToPopulate(); + expandFirstAlert(); + }); - it('should store the flyout state in localstorage', () => { - cy.get(OVERVIEW_RULE).should('be.visible'); - const localStorageCheck = () => - cy.getAllLocalStorage().then((storage) => { - const securityDataTable = getLocalstorageEntryAsObject(storage, 'securityDataTable'); - return getFlyoutConfig(securityDataTable)?.panelView === 'eventDetail'; - }); + const alertTableKey = 'alerts-page'; + const getFlyoutConfig = (dataTable: { [alertTableKey]: DataTableModel }) => + dataTable?.[alertTableKey]?.expandedDetail?.query; + + /** + * Localstorage is updated after a delay here x-pack/plugins/security_solution/public/common/store/data_table/epic_local_storage.ts + * We create this config to re-check localStorage 3 times, every 500ms to avoid any potential flakyness from that delay + */ + const storageCheckRetryConfig = { + timeout: 1500, + interval: 500, + }; - cy.waitUntil(localStorageCheck, storageCheckRetryConfig); - }); + it('should store the flyout state in localstorage', () => { + cy.get(OVERVIEW_RULE).should('be.visible'); + const localStorageCheck = () => + cy.getAllLocalStorage().then((storage) => { + const securityDataTable = getLocalstorageEntryAsObject(storage, 'securityDataTable'); + return getFlyoutConfig(securityDataTable)?.panelView === 'eventDetail'; + }); - it('should remove the flyout details from local storage when closed', () => { - cy.get(OVERVIEW_RULE).should('be.visible'); - closeAlertFlyout(); - const localStorageCheck = () => - cy.getAllLocalStorage().then((storage) => { - const securityDataTable = getLocalstorageEntryAsObject(storage, 'securityDataTable'); - return getFlyoutConfig(securityDataTable)?.panelView === undefined; - }); + cy.waitUntil(localStorageCheck, storageCheckRetryConfig); + }); - cy.waitUntil(localStorageCheck, storageCheckRetryConfig); - }); + it('should remove the flyout details from local storage when closed', () => { + cy.get(OVERVIEW_RULE).should('be.visible'); + closeAlertFlyout(); + const localStorageCheck = () => + cy.getAllLocalStorage().then((storage) => { + const securityDataTable = getLocalstorageEntryAsObject(storage, 'securityDataTable'); + return getFlyoutConfig(securityDataTable)?.panelView === undefined; + }); + + cy.waitUntil(localStorageCheck, storageCheckRetryConfig); + }); - it('should remove the flyout state from localstorage when navigating away without closing the flyout', () => { - cy.get(OVERVIEW_RULE).should('be.visible'); + it('should remove the flyout state from localstorage when navigating away without closing the flyout', () => { + cy.get(OVERVIEW_RULE).should('be.visible'); - visitRuleDetailsPage(ARCHIVED_RULE_ID); - waitForRuleDetailsPageToBeLoaded(ARCHIVED_RULE_NAME); + visitRuleDetailsPage(ARCHIVED_RULE_ID); + waitForRuleDetailsPageToBeLoaded(ARCHIVED_RULE_NAME); - const localStorageCheck = () => - cy.getAllLocalStorage().then((storage) => { - const securityDataTable = getLocalstorageEntryAsObject(storage, 'securityDataTable'); - return getFlyoutConfig(securityDataTable)?.panelView === undefined; - }); + const localStorageCheck = () => + cy.getAllLocalStorage().then((storage) => { + const securityDataTable = getLocalstorageEntryAsObject(storage, 'securityDataTable'); + return getFlyoutConfig(securityDataTable)?.panelView === undefined; + }); - cy.waitUntil(localStorageCheck, storageCheckRetryConfig); - }); + cy.waitUntil(localStorageCheck, storageCheckRetryConfig); + }); - it('should not reopen the flyout when navigating away from the alerts page and returning to it', () => { - cy.get(OVERVIEW_RULE).should('be.visible'); + it('should not reopen the flyout when navigating away from the alerts page and returning to it', () => { + cy.get(OVERVIEW_RULE).should('be.visible'); - visitRuleDetailsPage(ARCHIVED_RULE_ID); - waitForRuleDetailsPageToBeLoaded(ARCHIVED_RULE_NAME); + visitRuleDetailsPage(ARCHIVED_RULE_ID); + waitForRuleDetailsPageToBeLoaded(ARCHIVED_RULE_NAME); - visit(ALERTS_URL); - cy.get(OVERVIEW_RULE).should('not.exist'); - }); - }); + visit(ALERTS_URL); + cy.get(OVERVIEW_RULE).should('not.exist'); + }); + } + ); }); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/export.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/export.cy.ts index ca80b427f9c16..294ecf5c9127f 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/export.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/export.cy.ts @@ -19,13 +19,12 @@ import { TOASTER } from '../../../screens/alerts_detection_rules'; import { TIMELINE_CHECKBOX } from '../../../screens/timelines'; import { createTimeline } from '../../../tasks/api_calls/timelines'; import { expectedExportedTimeline, getTimeline } from '../../../objects/timeline'; -import { cleanKibana } from '../../../tasks/common'; +import { deleteTimelines } from '../../../tasks/common'; // FLAKY: https://github.com/elastic/kibana/issues/165744 describe('Export timelines', { tags: ['@ess', '@serverless'] }, () => { before(() => { - cleanKibana(); - login(); + deleteTimelines(); cy.intercept({ method: 'POST', path: '/api/timeline/_export?file_name=timelines_export.ndjson', @@ -38,7 +37,6 @@ describe('Export timelines', { tags: ['@ess', '@serverless'] }, () => { cy.wrap(response).as('timelineResponse2'); cy.wrap(response.body.data.persistTimeline.timeline.savedObjectId).as('timelineId2'); }); - visit(TIMELINES_URL); }); beforeEach(() => { diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/row_renderers.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/row_renderers.cy.ts index 60af66a577b23..960989b848977 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/row_renderers.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/row_renderers.cy.ts @@ -15,7 +15,7 @@ import { TIMELINE_ROW_RENDERERS_SURICATA_SIGNATURE_TOOLTIP, TIMELINE_ROW_RENDERERS_SURICATA_LINK_TOOLTIP, } from '../../../screens/timeline'; -import { cleanKibana, deleteTimelines, waitForWelcomePanelToBeLoaded } from '../../../tasks/common'; +import { deleteTimelines, waitForWelcomePanelToBeLoaded } from '../../../tasks/common'; import { waitForAllHostsToBeLoaded } from '../../../tasks/hosts/all_hosts'; import { login } from '../../../tasks/login'; @@ -26,10 +26,6 @@ import { populateTimeline } from '../../../tasks/timeline'; import { hostsUrl } from '../../../urls/navigation'; describe('Row renderers', { tags: ['@ess', '@serverless'] }, () => { - before(() => { - cleanKibana(); - }); - beforeEach(() => { deleteTimelines(); login(); diff --git a/x-pack/test/security_solution_cypress/cypress/objects/timeline.ts b/x-pack/test/security_solution_cypress/cypress/objects/timeline.ts index 5ad542ac49530..d57f389a04fc4 100644 --- a/x-pack/test/security_solution_cypress/cypress/objects/timeline.ts +++ b/x-pack/test/security_solution_cypress/cypress/objects/timeline.ts @@ -108,9 +108,9 @@ export const expectedExportedTimelineTemplate = ( templateTimelineVersion: 1, timelineType: 'template', created: timelineTemplateBody.created, - createdBy: 'system_indices_superuser', + createdBy: Cypress.env('ELASTICSEARCH_USERNAME'), updated: timelineTemplateBody.updated, - updatedBy: 'system_indices_superuser', + updatedBy: Cypress.env('ELASTICSEARCH_USERNAME'), sort: [], eventNotes: [], globalNotes: [], @@ -143,9 +143,9 @@ export const expectedExportedTimeline = (timelineResponse: Cypress.Response