mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Defend Workflows] Init Cypress (#147822)
## Summary Add initial Cypress pipeline for Defend Workflows Team Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Ashokaditya <1849116+ashokaditya@users.noreply.github.com>
This commit is contained in:
parent
c3c168e93c
commit
c0d2a032f9
24 changed files with 895 additions and 1 deletions
|
@ -21,6 +21,9 @@ disabled:
|
|||
- x-pack/test/fleet_cypress/config.ts
|
||||
- x-pack/test/fleet_cypress/visual_config.ts
|
||||
- x-pack/test/functional_enterprise_search/cypress.config.ts
|
||||
- x-pack/test/defend_workflows_cypress/cli_config.ts
|
||||
- x-pack/test/defend_workflows_cypress/config.ts
|
||||
- x-pack/test/defend_workflows_cypress/visual_config.ts
|
||||
- x-pack/test/osquery_cypress/cli_config.ts
|
||||
- x-pack/test/osquery_cypress/config.ts
|
||||
- x-pack/test/osquery_cypress/visual_config.ts
|
||||
|
|
15
.buildkite/pipelines/pull_request/defend_workflows.yml
Normal file
15
.buildkite/pipelines/pull_request/defend_workflows.yml
Normal file
|
@ -0,0 +1,15 @@
|
|||
steps:
|
||||
- command: .buildkite/scripts/steps/functional/defend_workflows.sh
|
||||
label: 'Defend Workflows Cypress Tests'
|
||||
agents:
|
||||
queue: n2-4-spot
|
||||
depends_on: build
|
||||
timeout_in_minutes: 120
|
||||
retry:
|
||||
automatic:
|
||||
- exit_status: '-1'
|
||||
limit: 3
|
||||
- exit_status: '*'
|
||||
limit: 1
|
||||
artifact_paths:
|
||||
- "target/kibana-security-solution/**/*"
|
|
@ -66,12 +66,14 @@ const uploadPipeline = (pipelineContent: string | object) => {
|
|||
/^x-pack\/plugins\/timelines/,
|
||||
/^x-pack\/plugins\/triggers_actions_ui\/public\/application\/sections\/action_connector_form/,
|
||||
/^x-pack\/plugins\/triggers_actions_ui\/public\/application\/context\/actions_connectors_context\.tsx/,
|
||||
/^x-pack\/test\/defend_workflows_cypress/,
|
||||
/^x-pack\/test\/security_solution_cypress/,
|
||||
/^fleet_packages\.json/, // It contains reference to prebuilt detection rules, we want to run security solution tests if it changes
|
||||
])) ||
|
||||
GITHUB_PR_LABELS.includes('ci:all-cypress-suites')
|
||||
) {
|
||||
pipeline.push(getPipeline('.buildkite/pipelines/pull_request/security_solution.yml'));
|
||||
pipeline.push(getPipeline('.buildkite/pipelines/pull_request/defend_workflows.yml'));
|
||||
}
|
||||
|
||||
if (
|
||||
|
@ -118,6 +120,7 @@ const uploadPipeline = (pipelineContent: string | object) => {
|
|||
GITHUB_PR_LABELS.includes('ci:all-cypress-suites')
|
||||
) {
|
||||
pipeline.push(getPipeline('.buildkite/pipelines/pull_request/fleet_cypress.yml'));
|
||||
pipeline.push(getPipeline('.buildkite/pipelines/pull_request/defend_workflows.yml'));
|
||||
}
|
||||
|
||||
if (
|
||||
|
|
17
.buildkite/scripts/steps/functional/defend_workflows.sh
Executable file
17
.buildkite/scripts/steps/functional/defend_workflows.sh
Executable file
|
@ -0,0 +1,17 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
source .buildkite/scripts/common/util.sh
|
||||
|
||||
.buildkite/scripts/bootstrap.sh
|
||||
node scripts/build_kibana_platform_plugins.js
|
||||
|
||||
export JOB=kibana-defend-workflows-cypress
|
||||
|
||||
echo "--- Defend Workflows Cypress tests"
|
||||
|
||||
node scripts/functional_tests \
|
||||
--debug --bail \
|
||||
--config x-pack/test/defend_workflows_cypress/cli_config.ts
|
||||
|
14
test/scripts/jenkins_defend_workflows_cypress.sh
Executable file
14
test/scripts/jenkins_defend_workflows_cypress.sh
Executable file
|
@ -0,0 +1,14 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
source test/scripts/jenkins_test_setup_xpack.sh
|
||||
|
||||
echo " -> Running defend workflows cypress tests"
|
||||
cd "$XPACK_DIR"
|
||||
|
||||
node scripts/functional_tests \
|
||||
--debug --bail \
|
||||
--kibana-install-dir "$KIBANA_INSTALL_DIR" \
|
||||
--config test/defend_workflows_cypress/cli_config.ts
|
||||
|
||||
echo ""
|
||||
echo ""
|
|
@ -22,6 +22,10 @@
|
|||
"cypress:run-as-ci": "node --max-old-space-size=2048 ../../../scripts/functional_tests --config ../../test/security_solution_cypress/cli_config_parallel.ts",
|
||||
"cypress:run-as-ci:firefox": "node --max-old-space-size=2048 ../../../scripts/functional_tests --config ../../test/security_solution_cypress/config.firefox.ts",
|
||||
"cypress:run:upgrade": "yarn cypress:run:reporter --browser chrome --config specPattern=./cypress/upgrade_e2e/**/*.cy.ts",
|
||||
"cypress:dw:open": "yarn cypress open --config-file ./public/management/cypress.config.ts",
|
||||
"cypress:dw:open-as-ci": "node ../../../scripts/functional_tests --config ../../test/defend_workflows_cypress/visual_config.ts",
|
||||
"cypress:dw:run": "yarn cypress run --config-file ./public/management/cypress.config.ts",
|
||||
"cypress:dw:run-as-ci": "node ../../../scripts/functional_tests --config ../../test/defend_workflows_cypress/cli_config.ts",
|
||||
"junit:merge": "../../../node_modules/.bin/mochawesome-merge ../../../target/kibana-security-solution/cypress/results/mochawesome*.json > ../../../target/kibana-security-solution/cypress/results/output.json && ../../../node_modules/.bin/marge ../../../target/kibana-security-solution/cypress/results/output.json --reportDir ../../../target/kibana-security-solution/cypress/results && mkdir -p ../../../target/junit && cp ../../../target/kibana-security-solution/cypress/results/*.xml ../../../target/junit/",
|
||||
"test:generate": "node scripts/endpoint/resolver_generator"
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ const HomePageComponent: React.FC<HomePageProps> = ({ children, setHeaderActionM
|
|||
useUpgradeSecurityPackages();
|
||||
|
||||
return (
|
||||
<SecuritySolutionAppWrapper className="kbnAppWrapper">
|
||||
<SecuritySolutionAppWrapper id="security-solution-app" className="kbnAppWrapper">
|
||||
<ConsoleManager>
|
||||
<TourContextProvider>
|
||||
<>
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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 { defineCypressConfig } from '@kbn/cypress-config';
|
||||
|
||||
export default defineCypressConfig({
|
||||
defaultCommandTimeout: 60000,
|
||||
execTimeout: 120000,
|
||||
pageLoadTimeout: 12000,
|
||||
|
||||
retries: {
|
||||
runMode: 1,
|
||||
openMode: 0,
|
||||
},
|
||||
|
||||
screenshotsFolder:
|
||||
'../../../target/kibana-security-solution/public/management/cypress/screenshots',
|
||||
trashAssetsBeforeRuns: false,
|
||||
video: false,
|
||||
viewportHeight: 900,
|
||||
viewportWidth: 1440,
|
||||
experimentalStudio: true,
|
||||
|
||||
env: {
|
||||
'cypress-react-selector': {
|
||||
root: '#security-solution-app',
|
||||
},
|
||||
},
|
||||
|
||||
e2e: {
|
||||
baseUrl: 'http://localhost:5620',
|
||||
supportFile: 'public/management/cypress/support/e2e.ts',
|
||||
specPattern: 'public/management/cypress/e2e/**/*.cy.{js,jsx,ts,tsx}',
|
||||
experimentalSessionAndOrigin: true,
|
||||
testIsolation: 'on',
|
||||
experimentalRunAllSpecs: true,
|
||||
},
|
||||
});
|
|
@ -0,0 +1,115 @@
|
|||
# Cypress Tests
|
||||
|
||||
The `management/cypress` directory contains functional UI tests that execute using [Cypress](https://www.cypress.io/).
|
||||
|
||||
## Running the tests
|
||||
|
||||
There are currently three ways to run the tests, comprised of two execution modes and two target environments, which will be detailed below.
|
||||
|
||||
### Execution modes
|
||||
|
||||
#### Interactive mode
|
||||
|
||||
When you run Cypress in interactive mode, an interactive runner is displayed that allows you to see commands as they execute while also viewing the application under test. For more information, please see [cypress documentation](https://docs.cypress.io/guides/core-concepts/test-runner.html#Overview).
|
||||
|
||||
#### Headless mode
|
||||
|
||||
A headless browser is a browser simulation program that does not have a user interface. These programs operate like any other browser but do not display any UI. This is why meanwhile you are executing the tests on this mode you are not going to see the application under test. Just the output of the test is displayed on the terminal once the execution is finished.
|
||||
|
||||
### Target environments
|
||||
|
||||
#### FTR (CI)
|
||||
|
||||
This is the configuration used by CI. It uses the FTR to spawn both a Kibana instance (http://localhost:5620) and an Elasticsearch instance (http://localhost:9220) with a preloaded minimum set of data (see preceding "Test data" section), and then executes cypress against this stack. You can find this configuration in `x-pack/test/defend_workflows_cypress`
|
||||
|
||||
### Test Execution: Examples
|
||||
|
||||
#### FTR + Headless (Chrome)
|
||||
|
||||
Since this is how tests are run on CI, this will likely be the configuration you want to reproduce failures locally, etc.
|
||||
|
||||
```shell
|
||||
# bootstrap kibana from the project root
|
||||
yarn kbn bootstrap
|
||||
|
||||
# build the plugins/assets that cypress will execute against
|
||||
node scripts/build_kibana_platform_plugins
|
||||
|
||||
# launch the cypress test runner
|
||||
cd x-pack/plugins/security_solution
|
||||
yarn cypress:dw:run-as-ci
|
||||
```
|
||||
#### FTR + Interactive
|
||||
|
||||
This is the preferred mode for developing new tests.
|
||||
|
||||
```shell
|
||||
# bootstrap kibana from the project root
|
||||
yarn kbn bootstrap
|
||||
|
||||
# build the plugins/assets that cypress will execute against
|
||||
node scripts/build_kibana_platform_plugins
|
||||
|
||||
# launch the cypress test runner
|
||||
cd x-pack/plugins/security_solution
|
||||
yarn cypress:dw:open-as-ci
|
||||
```
|
||||
|
||||
Note that you can select the browser you want to use on the top right side of the interactive runner.
|
||||
|
||||
## Folder Structure
|
||||
|
||||
### integration/
|
||||
|
||||
Cypress convention. Contains the specs that are going to be executed.
|
||||
|
||||
### fixtures/
|
||||
|
||||
Cypress convention. Fixtures are used as external pieces of static data when we stub responses.
|
||||
|
||||
### plugins/
|
||||
|
||||
Cypress convention. As a convenience, by default Cypress will automatically include the plugins file cypress/plugins/index.js before every single spec file it runs.
|
||||
|
||||
### screens/
|
||||
|
||||
Contains the elements we want to interact with within our tests.
|
||||
|
||||
Each file inside the screens folder represents a screen in our application.
|
||||
|
||||
### tasks/
|
||||
|
||||
_Tasks_ are functions that may be reused across tests.
|
||||
|
||||
Each file inside the tasks folder represents a screen of our application.
|
||||
|
||||
## Test data
|
||||
|
||||
The data the tests need:
|
||||
|
||||
- Is generated on the fly using our application APIs (preferred way)
|
||||
|
||||
## Development Best Practices
|
||||
|
||||
### Clean up the state
|
||||
|
||||
Remember to clean up the state of the test after its execution. Be mindful of failure scenarios, as well: if your test fails, will it leave the environment in a recoverable state?
|
||||
|
||||
### Minimize the use of es_archive
|
||||
|
||||
When possible, create all the data that you need for executing the tests using the application APIS or the UI.
|
||||
|
||||
### Speed up test execution time
|
||||
|
||||
Loading the web page takes a big amount of time, in order to minimize that impact, the following points should be
|
||||
taken into consideration until another solution is implemented:
|
||||
|
||||
- Group the tests that are similar in different contexts.
|
||||
- For every context login only once, and clean the state between tests if needed without reloading the page.
|
||||
- All tests in a spec file must be order-independent.
|
||||
|
||||
Remember that by minimizing the number of times the web page is loaded, we minimize the execution time as well.
|
||||
|
||||
## Linting
|
||||
|
||||
Optional linting rules for Cypress and linting setup can be found [here](https://github.com/cypress-io/eslint-plugin-cypress#usage)
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* 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 { login } from '../tasks/login';
|
||||
import { runEndpointLoaderScript } from '../tasks/run_endpoint_loader';
|
||||
|
||||
describe('Endpoints page', () => {
|
||||
before(() => {
|
||||
runEndpointLoaderScript();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
login();
|
||||
});
|
||||
|
||||
it('Loads the endpoints page', () => {
|
||||
cy.visit('/app/security/administration/endpoints');
|
||||
cy.contains('Hosts running Elastic Defend').should('exist');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"name": "Using fixtures to represent data",
|
||||
"email": "hello@cypress.io",
|
||||
"body": "Fixtures are a great way to mock data for responses to routes"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export const TOGGLE_NAVIGATION_BTN = '[data-test-subj="toggleNavButton"]';
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// / <reference types="cypress" />
|
||||
|
||||
// ***********************************************************
|
||||
// This example support/index.js is processed and
|
||||
// loaded automatically before your test files.
|
||||
//
|
||||
// This is a great place to put global configuration and
|
||||
// behavior that modifies Cypress.
|
||||
//
|
||||
// You can change the location of this file or turn off
|
||||
// automatically serving support files with the
|
||||
// 'supportFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/configuration
|
||||
// ***********************************************************
|
||||
|
||||
// force ESM in this module
|
||||
export {};
|
||||
|
||||
import 'cypress-react-selector';
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
namespace Cypress {
|
||||
interface Chainable {
|
||||
getBySel(...args: Parameters<Cypress.Chainable['get']>): Chainable<JQuery<HTMLElement>>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Cypress.Commands.add('getBySel', (selector, ...args) =>
|
||||
cy.get(`[data-test-subj="${selector}"]`, ...args)
|
||||
);
|
||||
|
||||
Cypress.on('uncaught:exception', () => false);
|
|
@ -0,0 +1,365 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/* eslint-disable import/no-nodejs-modules */
|
||||
|
||||
import * as yaml from 'js-yaml';
|
||||
import type { UrlObject } from 'url';
|
||||
import Url from 'url';
|
||||
import type { Role } from '@kbn/security-plugin/common';
|
||||
import { getT1Analyst } from '../../../../scripts/endpoint/common/roles_users/t1_analyst';
|
||||
import { getT2Analyst } from '../../../../scripts/endpoint/common/roles_users/t2_analyst';
|
||||
import { getHunter } from '../../../../scripts/endpoint/common/roles_users/hunter';
|
||||
import { getThreadIntelligenceAnalyst } from '../../../../scripts/endpoint/common/roles_users/thread_intelligence_analyst';
|
||||
import { getSocManager } from '../../../../scripts/endpoint/common/roles_users/soc_manager';
|
||||
import { getPlatformEngineer } from '../../../../scripts/endpoint/common/roles_users/platform_engineer';
|
||||
import { getEndpointOperationsAnalyst } from '../../../../scripts/endpoint/common/roles_users/endpoint_operations_analyst';
|
||||
import { getEndpointSecurityPolicyManager } from '../../../../scripts/endpoint/common/roles_users/endpoint_security_policy_manager';
|
||||
|
||||
export enum ROLE {
|
||||
t1_analyst = 't1Analyst',
|
||||
t2_analyst = 't2Analyst',
|
||||
analyst_hunter = 'hunter',
|
||||
thread_intelligence_analyst = 'threadIntelligenceAnalyst',
|
||||
detections_engineer = 'detectionsEngineer',
|
||||
soc_manager = 'socManager',
|
||||
platform_engineer = 'platformEngineer',
|
||||
endpoint_operations_analyst = 'endpointOperationsAnalyst',
|
||||
endpoint_security_policy_manager = 'endpointSecurityPolicyManager',
|
||||
}
|
||||
|
||||
export const rolesMapping: { [id: string]: Omit<Role, 'name'> } = {
|
||||
t1Analyst: getT1Analyst(),
|
||||
t2Analyst: getT2Analyst(),
|
||||
hunter: getHunter(),
|
||||
threadIntelligenceAnalyst: getThreadIntelligenceAnalyst(),
|
||||
socManager: getSocManager(),
|
||||
platformEngineer: getPlatformEngineer(),
|
||||
endpointOperationsAnalyst: getEndpointOperationsAnalyst(),
|
||||
endpointSecurityPolicyManager: getEndpointSecurityPolicyManager(),
|
||||
};
|
||||
/**
|
||||
* Credentials in the `kibana.dev.yml` config file will be used to authenticate
|
||||
* with Kibana when credentials are not provided via environment variables
|
||||
*/
|
||||
const KIBANA_DEV_YML_PATH = '../../../config/kibana.dev.yml';
|
||||
|
||||
/**
|
||||
* The configuration path in `kibana.dev.yml` to the username to be used when
|
||||
* authenticating with Kibana.
|
||||
*/
|
||||
const ELASTICSEARCH_USERNAME_CONFIG_PATH = 'config.elasticsearch.username';
|
||||
|
||||
/**
|
||||
* The configuration path in `kibana.dev.yml` to the password to be used when
|
||||
* authenticating with Kibana.
|
||||
*/
|
||||
const ELASTICSEARCH_PASSWORD_CONFIG_PATH = 'config.elasticsearch.password';
|
||||
|
||||
/**
|
||||
* The `CYPRESS_ELASTICSEARCH_USERNAME` environment variable specifies the
|
||||
* username to be used when authenticating with Kibana
|
||||
*/
|
||||
const ELASTICSEARCH_USERNAME = 'ELASTICSEARCH_USERNAME';
|
||||
|
||||
/**
|
||||
* The `CYPRESS_ELASTICSEARCH_PASSWORD` environment variable specifies the
|
||||
* username to be used when authenticating with Kibana
|
||||
*/
|
||||
const ELASTICSEARCH_PASSWORD = 'ELASTICSEARCH_PASSWORD';
|
||||
|
||||
/**
|
||||
* The Kibana server endpoint used for authentication
|
||||
*/
|
||||
const LOGIN_API_ENDPOINT = '/internal/security/login';
|
||||
|
||||
/**
|
||||
* cy.visit will default to the baseUrl which uses the default kibana test user
|
||||
* This function will override that functionality in cy.visit by building the baseUrl
|
||||
* directly from the environment variables set up in x-pack/test/security_solution_cypress/runner.ts
|
||||
*
|
||||
* @param role string role/user to log in with
|
||||
* @param route string route to visit
|
||||
*/
|
||||
export const getUrlWithRoute = (role: ROLE, route: string) => {
|
||||
const url = Cypress.config().baseUrl;
|
||||
const kibana = new URL(String(url));
|
||||
const theUrl = `${Url.format({
|
||||
auth: `${role}:changeme`,
|
||||
username: role,
|
||||
password: Cypress.env(ELASTICSEARCH_PASSWORD),
|
||||
protocol: kibana.protocol.replace(':', ''),
|
||||
hostname: kibana.hostname,
|
||||
port: kibana.port,
|
||||
} as UrlObject)}${route.startsWith('/') ? '' : '/'}${route}`;
|
||||
cy.log(`origin: ${theUrl}`);
|
||||
|
||||
return theUrl;
|
||||
};
|
||||
|
||||
interface User {
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a URL with basic auth using the passed in user.
|
||||
*
|
||||
* @param user the user information to build the basic auth with
|
||||
* @param route string route to visit
|
||||
*/
|
||||
export const constructUrlWithUser = (user: User, route: string) => {
|
||||
const url = Cypress.config().baseUrl;
|
||||
const kibana = new URL(String(url));
|
||||
const hostname = kibana.hostname;
|
||||
const username = user.username;
|
||||
const password = user.password;
|
||||
const protocol = kibana.protocol.replace(':', '');
|
||||
const port = kibana.port;
|
||||
|
||||
const path = `${route.startsWith('/') ? '' : '/'}${route}`;
|
||||
const strUrl = `${protocol}://${username}:${password}@${hostname}:${port}${path}`;
|
||||
const builtUrl = new URL(strUrl);
|
||||
|
||||
cy.log(`origin: ${builtUrl.href}`);
|
||||
|
||||
return builtUrl.href;
|
||||
};
|
||||
|
||||
export const getCurlScriptEnvVars = () => ({
|
||||
ELASTICSEARCH_URL: Cypress.env('ELASTICSEARCH_URL'),
|
||||
ELASTICSEARCH_USERNAME: Cypress.env('ELASTICSEARCH_USERNAME'),
|
||||
ELASTICSEARCH_PASSWORD: Cypress.env('ELASTICSEARCH_PASSWORD'),
|
||||
KIBANA_URL: Cypress.config().baseUrl,
|
||||
});
|
||||
|
||||
export const createRoleAndUser = (role: ROLE) => {
|
||||
const env = getCurlScriptEnvVars();
|
||||
|
||||
// post the role
|
||||
cy.request({
|
||||
method: 'PUT',
|
||||
url: `${env.KIBANA_URL}/api/security/role/${role}`,
|
||||
body: rolesMapping[role],
|
||||
headers: {
|
||||
'kbn-xsrf': 'cypress',
|
||||
},
|
||||
auth: {
|
||||
user: Cypress.env(ELASTICSEARCH_USERNAME),
|
||||
pass: Cypress.env(ELASTICSEARCH_PASSWORD),
|
||||
},
|
||||
});
|
||||
|
||||
// post the user associated with the role to elasticsearch
|
||||
cy.request({
|
||||
method: 'POST',
|
||||
url: `${env.KIBANA_URL}/internal/security/users/${role}`,
|
||||
headers: {
|
||||
'kbn-xsrf': 'cypress',
|
||||
},
|
||||
body: {
|
||||
username: role,
|
||||
password: Cypress.env(ELASTICSEARCH_PASSWORD),
|
||||
roles: [role],
|
||||
},
|
||||
auth: {
|
||||
user: Cypress.env(ELASTICSEARCH_USERNAME),
|
||||
pass: Cypress.env(ELASTICSEARCH_PASSWORD),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const deleteRoleAndUser = (role: ROLE) => {
|
||||
const env = getCurlScriptEnvVars();
|
||||
|
||||
cy.request({
|
||||
method: 'DELETE',
|
||||
auth: {
|
||||
user: Cypress.env(ELASTICSEARCH_USERNAME),
|
||||
pass: Cypress.env(ELASTICSEARCH_PASSWORD),
|
||||
},
|
||||
headers: {
|
||||
'kbn-xsrf': 'cypress',
|
||||
},
|
||||
url: `${env.KIBANA_URL}/internal/security/users/${role}`,
|
||||
});
|
||||
cy.request({
|
||||
method: 'DELETE',
|
||||
auth: {
|
||||
user: Cypress.env(ELASTICSEARCH_USERNAME),
|
||||
pass: Cypress.env(ELASTICSEARCH_PASSWORD),
|
||||
},
|
||||
headers: {
|
||||
'kbn-xsrf': 'cypress',
|
||||
},
|
||||
url: `${env.KIBANA_URL}/api/security/role/${role}`,
|
||||
});
|
||||
};
|
||||
|
||||
export const loginWithUser = (user: User) => {
|
||||
const url = Cypress.config().baseUrl;
|
||||
|
||||
cy.request({
|
||||
body: {
|
||||
providerType: 'basic',
|
||||
providerName: url && !url.includes('localhost') ? 'cloud-basic' : 'basic',
|
||||
currentURL: '/',
|
||||
params: {
|
||||
username: user.username,
|
||||
password: user.password,
|
||||
},
|
||||
},
|
||||
headers: { 'kbn-xsrf': 'cypress-creds-via-config' },
|
||||
method: 'POST',
|
||||
url: constructUrlWithUser(user, LOGIN_API_ENDPOINT),
|
||||
});
|
||||
};
|
||||
|
||||
export const loginWithRole = async (role: ROLE) => {
|
||||
createRoleAndUser(role);
|
||||
const theUrl = Url.format({
|
||||
auth: `${role}:changeme`,
|
||||
username: role,
|
||||
password: Cypress.env(ELASTICSEARCH_PASSWORD),
|
||||
protocol: Cypress.env('protocol'),
|
||||
hostname: Cypress.env('hostname'),
|
||||
port: Cypress.env('configport'),
|
||||
} as UrlObject);
|
||||
cy.log(`origin: ${theUrl}`);
|
||||
cy.request({
|
||||
body: {
|
||||
providerType: 'basic',
|
||||
providerName: 'basic',
|
||||
currentURL: '/',
|
||||
params: {
|
||||
username: role,
|
||||
password: Cypress.env(ELASTICSEARCH_PASSWORD),
|
||||
},
|
||||
},
|
||||
headers: { 'kbn-xsrf': 'cypress-creds-via-config' },
|
||||
method: 'POST',
|
||||
url: getUrlWithRoute(role, LOGIN_API_ENDPOINT),
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Authenticates with Kibana using, if specified, credentials specified by
|
||||
* environment variables. The credentials in `kibana.dev.yml` will be used
|
||||
* for authentication when the environment variables are unset.
|
||||
*
|
||||
* To speed the execution of tests, prefer this non-interactive authentication,
|
||||
* which is faster than authentication via Kibana's interactive login page.
|
||||
*/
|
||||
export const login = (role?: ROLE) => {
|
||||
if (role != null) {
|
||||
loginWithRole(role);
|
||||
} else if (credentialsProvidedByEnvironment()) {
|
||||
loginViaEnvironmentCredentials();
|
||||
} else {
|
||||
loginViaConfig();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns `true` if the credentials used to login to Kibana are provided
|
||||
* via environment variables
|
||||
*/
|
||||
const credentialsProvidedByEnvironment = (): boolean =>
|
||||
Cypress.env(ELASTICSEARCH_USERNAME) != null && Cypress.env(ELASTICSEARCH_PASSWORD) != null;
|
||||
|
||||
/**
|
||||
* Authenticates with Kibana by reading credentials from the
|
||||
* `CYPRESS_ELASTICSEARCH_USERNAME` and `CYPRESS_ELASTICSEARCH_PASSWORD`
|
||||
* environment variables, and POSTing the username and password directly to
|
||||
* Kibana's `/internal/security/login` endpoint, bypassing the login page (for speed).
|
||||
*/
|
||||
const loginViaEnvironmentCredentials = () => {
|
||||
const url = Cypress.config().baseUrl;
|
||||
|
||||
cy.log(
|
||||
`Authenticating via environment credentials from the \`CYPRESS_${ELASTICSEARCH_USERNAME}\` and \`CYPRESS_${ELASTICSEARCH_PASSWORD}\` environment variables`
|
||||
);
|
||||
|
||||
// programmatically authenticate without interacting with the Kibana login page
|
||||
cy.request({
|
||||
body: {
|
||||
providerType: 'basic',
|
||||
providerName: url && !url.includes('localhost') ? 'cloud-basic' : 'basic',
|
||||
currentURL: '/',
|
||||
params: {
|
||||
username: Cypress.env(ELASTICSEARCH_USERNAME),
|
||||
password: Cypress.env(ELASTICSEARCH_PASSWORD),
|
||||
},
|
||||
},
|
||||
headers: { 'kbn-xsrf': 'cypress-creds-via-env' },
|
||||
method: 'POST',
|
||||
url: `${Cypress.config().baseUrl}${LOGIN_API_ENDPOINT}`,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Authenticates with Kibana by reading credentials from the
|
||||
* `kibana.dev.yml` file and POSTing the username and password directly to
|
||||
* Kibana's `/internal/security/login` endpoint, bypassing the login page (for speed).
|
||||
*/
|
||||
const loginViaConfig = () => {
|
||||
cy.log(
|
||||
`Authenticating via config credentials \`${ELASTICSEARCH_USERNAME_CONFIG_PATH}\` and \`${ELASTICSEARCH_PASSWORD_CONFIG_PATH}\` from \`${KIBANA_DEV_YML_PATH}\``
|
||||
);
|
||||
|
||||
// read the login details from `kibana.dev.yaml`
|
||||
cy.readFile(KIBANA_DEV_YML_PATH).then((kibanaDevYml) => {
|
||||
const config = yaml.safeLoad(kibanaDevYml);
|
||||
|
||||
// programmatically authenticate without interacting with the Kibana login page
|
||||
cy.request({
|
||||
body: {
|
||||
providerType: 'basic',
|
||||
providerName: 'basic',
|
||||
currentURL: '/',
|
||||
params: {
|
||||
username: Cypress.env(ELASTICSEARCH_USERNAME),
|
||||
password: config.elasticsearch.password,
|
||||
},
|
||||
},
|
||||
headers: { 'kbn-xsrf': 'cypress-creds-via-config' },
|
||||
method: 'POST',
|
||||
url: `${Cypress.config().baseUrl}${LOGIN_API_ENDPOINT}`,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the configured auth details that were used to spawn cypress
|
||||
*
|
||||
* @returns the default Elasticsearch username and password for this environment
|
||||
*/
|
||||
export const getEnvAuth = (): User => {
|
||||
if (credentialsProvidedByEnvironment()) {
|
||||
return {
|
||||
username: Cypress.env(ELASTICSEARCH_USERNAME),
|
||||
password: Cypress.env(ELASTICSEARCH_PASSWORD),
|
||||
};
|
||||
} else {
|
||||
let user: User = { username: '', password: '' };
|
||||
cy.readFile(KIBANA_DEV_YML_PATH).then((devYml) => {
|
||||
const config = yaml.safeLoad(devYml);
|
||||
user = { username: config.elasticsearch.username, password: config.elasticsearch.password };
|
||||
});
|
||||
|
||||
return user;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Authenticates with Kibana, visits the specified `url`, and waits for the
|
||||
* Kibana global nav to be displayed before continuing
|
||||
*/
|
||||
export const loginAndWaitForPage = (url: string) => {
|
||||
login();
|
||||
cy.visit(url);
|
||||
};
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* 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 { TOGGLE_NAVIGATION_BTN } from '../screens/navigation';
|
||||
|
||||
export const INTEGRATIONS = 'app/integrations#/';
|
||||
export const FLEET = 'app/fleet/';
|
||||
export const FLEET_AGENT_POLICIES = 'app/fleet/policies';
|
||||
export const navigateTo = (page: string, opts?: Partial<Cypress.VisitOptions>) => {
|
||||
cy.visit(page, opts);
|
||||
cy.contains('Loading Elastic').should('exist');
|
||||
cy.contains('Loading Elastic').should('not.exist');
|
||||
|
||||
// There's a security warning toast that seemingly makes ui elements in the bottom right unavailable, so we close it
|
||||
cy.get('[data-test-subj="toastCloseButton"]', { timeout: 30000 }).click();
|
||||
cy.waitForReact();
|
||||
};
|
||||
|
||||
export const openNavigationFlyout = () => {
|
||||
cy.get(TOGGLE_NAVIGATION_BTN).click();
|
||||
};
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export const runEndpointLoaderScript = () => {
|
||||
const {
|
||||
ELASTICSEARCH_USERNAME,
|
||||
ELASTICSEARCH_PASSWORD,
|
||||
hostname: HOSTNAME,
|
||||
configport: PORT,
|
||||
} = Cypress.env();
|
||||
const script = `node scripts/endpoint/resolver_generator.js --node="http://${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD}@${HOSTNAME}:9220" --kibana="http://${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD}@${HOSTNAME}:${PORT}" --delete --numHosts=1 --numDocs=1 --fleet --withNewUser=santaEndpoint:changeme`;
|
||||
|
||||
cy.exec(script, { env: { NODE_TLS_REJECT_UNAUTHORIZED: 1 } });
|
||||
};
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"extends": "../../../../../../tsconfig.base.json",
|
||||
"include": [
|
||||
"**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"outDir": "target/types",
|
||||
"types": [
|
||||
"cypress",
|
||||
"node",
|
||||
"cypress-react-selector"
|
||||
],
|
||||
},
|
||||
"kbn_references": [
|
||||
"@kbn/cypress-config",
|
||||
// this cypress project uses code from the parent ts project
|
||||
// in a way that can't be auto-matically deteceted at this time
|
||||
// so we have to force the inclusion of this reference
|
||||
"@kbn/test",
|
||||
{
|
||||
"path": "../../../tsconfig.json",
|
||||
"force": true
|
||||
}
|
||||
]
|
||||
}
|
|
@ -125,6 +125,7 @@
|
|||
"@kbn/core-status-common-internal",
|
||||
"@kbn/repo-info",
|
||||
"@kbn/storybook",
|
||||
"@kbn/cypress-config",
|
||||
"@kbn/controls-plugin",
|
||||
"@kbn/shared-ux-utility",
|
||||
],
|
||||
|
|
19
x-pack/test/defend_workflows_cypress/cli_config.ts
Normal file
19
x-pack/test/defend_workflows_cypress/cli_config.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* 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 { FtrConfigProviderContext } from '@kbn/test';
|
||||
|
||||
import { DefendWorkflowsCypressCliTestRunner } from './runner';
|
||||
|
||||
export default async function ({ readConfigFile }: FtrConfigProviderContext) {
|
||||
const defendWorkflowsCypressConfig = await readConfigFile(require.resolve('./config.ts'));
|
||||
return {
|
||||
...defendWorkflowsCypressConfig.getAll(),
|
||||
|
||||
testRunner: DefendWorkflowsCypressCliTestRunner,
|
||||
};
|
||||
}
|
49
x-pack/test/defend_workflows_cypress/config.ts
Normal file
49
x-pack/test/defend_workflows_cypress/config.ts
Normal file
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* 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 { FtrConfigProviderContext } from '@kbn/test';
|
||||
import { CA_CERT_PATH } from '@kbn/dev-utils';
|
||||
import { services } from './services';
|
||||
|
||||
export default async function ({ readConfigFile }: FtrConfigProviderContext) {
|
||||
const kibanaCommonTestsConfig = await readConfigFile(
|
||||
require.resolve('../../../test/common/config.js')
|
||||
);
|
||||
const xpackFunctionalTestsConfig = await readConfigFile(
|
||||
require.resolve('../functional/config.base.js')
|
||||
);
|
||||
|
||||
return {
|
||||
...kibanaCommonTestsConfig.getAll(),
|
||||
|
||||
services,
|
||||
|
||||
esTestCluster: {
|
||||
...xpackFunctionalTestsConfig.get('esTestCluster'),
|
||||
serverArgs: [
|
||||
...xpackFunctionalTestsConfig.get('esTestCluster.serverArgs'),
|
||||
// define custom es server here
|
||||
// API Keys is enabled at the top level
|
||||
'xpack.security.enabled=true',
|
||||
'http.host=0.0.0.0',
|
||||
],
|
||||
},
|
||||
|
||||
kbnTestServer: {
|
||||
...xpackFunctionalTestsConfig.get('kbnTestServer'),
|
||||
serverArgs: [
|
||||
...xpackFunctionalTestsConfig.get('kbnTestServer.serverArgs'),
|
||||
'--csp.strict=false',
|
||||
// define custom kibana server args here
|
||||
`--elasticsearch.ssl.certificateAuthorities=${CA_CERT_PATH}`,
|
||||
// always install Endpoint package by default when Fleet sets up
|
||||
`--xpack.fleet.packages.0.name=endpoint`,
|
||||
`--xpack.fleet.packages.0.version=latest`,
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
12
x-pack/test/defend_workflows_cypress/ftr_provider_context.d.ts
vendored
Normal file
12
x-pack/test/defend_workflows_cypress/ftr_provider_context.d.ts
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* 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 { GenericFtrProviderContext } from '@kbn/test';
|
||||
|
||||
import { services } from './services';
|
||||
|
||||
export type FtrProviderContext = GenericFtrProviderContext<typeof services, {}>;
|
57
x-pack/test/defend_workflows_cypress/runner.ts
Normal file
57
x-pack/test/defend_workflows_cypress/runner.ts
Normal file
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* 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 { resolve } from 'path';
|
||||
import Url from 'url';
|
||||
import { withProcRunner } from '@kbn/dev-proc-runner';
|
||||
|
||||
import { FtrProviderContext } from './ftr_provider_context';
|
||||
|
||||
export async function DefendWorkflowsCypressCliTestRunner(context: FtrProviderContext) {
|
||||
await startDefendWorkflowsCypress(context, 'dw:run');
|
||||
}
|
||||
|
||||
export async function DefendWorkflowsCypressVisualTestRunner(context: FtrProviderContext) {
|
||||
await startDefendWorkflowsCypress(context, 'dw:open');
|
||||
}
|
||||
|
||||
function startDefendWorkflowsCypress(context: FtrProviderContext, cypressCommand: string) {
|
||||
const log = context.getService('log');
|
||||
const config = context.getService('config');
|
||||
return withProcRunner(log, async (procs) => {
|
||||
await procs.run('cypress', {
|
||||
cmd: 'yarn',
|
||||
args: [`cypress:${cypressCommand}`],
|
||||
cwd: resolve(__dirname, '../../plugins/security_solution'),
|
||||
env: {
|
||||
FORCE_COLOR: '1',
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
CYPRESS_baseUrl: Url.format({
|
||||
protocol: config.get('servers.kibana.protocol'),
|
||||
hostname: config.get('servers.kibana.hostname'),
|
||||
port: config.get('servers.kibana.port'),
|
||||
}),
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
CYPRESS_protocol: config.get('servers.kibana.protocol'),
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
CYPRESS_hostname: config.get('servers.kibana.hostname'),
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
CYPRESS_configport: config.get('servers.kibana.port'),
|
||||
CYPRESS_ELASTICSEARCH_URL: Url.format(config.get('servers.elasticsearch')),
|
||||
CYPRESS_ELASTICSEARCH_USERNAME: config.get('servers.kibana.username'),
|
||||
CYPRESS_ELASTICSEARCH_PASSWORD: config.get('servers.kibana.password'),
|
||||
CYPRESS_KIBANA_URL: Url.format({
|
||||
protocol: config.get('servers.kibana.protocol'),
|
||||
hostname: config.get('servers.kibana.hostname'),
|
||||
port: config.get('servers.kibana.port'),
|
||||
}),
|
||||
...process.env,
|
||||
},
|
||||
wait: true,
|
||||
});
|
||||
});
|
||||
}
|
8
x-pack/test/defend_workflows_cypress/services.ts
Normal file
8
x-pack/test/defend_workflows_cypress/services.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export * from '../../../test/common/services';
|
19
x-pack/test/defend_workflows_cypress/visual_config.ts
Normal file
19
x-pack/test/defend_workflows_cypress/visual_config.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* 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 { FtrConfigProviderContext } from '@kbn/test';
|
||||
|
||||
import { DefendWorkflowsCypressVisualTestRunner } from './runner';
|
||||
|
||||
export default async function ({ readConfigFile }: FtrConfigProviderContext) {
|
||||
const defendWorkflowsCypressConfig = await readConfigFile(require.resolve('./config.ts'));
|
||||
return {
|
||||
...defendWorkflowsCypressConfig.getAll(),
|
||||
|
||||
testRunner: DefendWorkflowsCypressVisualTestRunner,
|
||||
};
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue