[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:
Patryk Kopyciński 2023-01-04 17:05:13 +01:00 committed by GitHub
parent c3c168e93c
commit c0d2a032f9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 895 additions and 1 deletions

View file

@ -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

View 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/**/*"

View file

@ -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 (

View 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

View 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 ""

View file

@ -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"
}

View file

@ -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>
<>

View file

@ -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,
},
});

View file

@ -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)

View file

@ -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');
});
});

View file

@ -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"
}

View 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 const TOGGLE_NAVIGATION_BTN = '[data-test-subj="toggleNavButton"]';

View file

@ -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);

View file

@ -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);
};

View file

@ -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();
};

View file

@ -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 } });
};

View file

@ -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
}
]
}

View file

@ -125,6 +125,7 @@
"@kbn/core-status-common-internal",
"@kbn/repo-info",
"@kbn/storybook",
"@kbn/cypress-config",
"@kbn/controls-plugin",
"@kbn/shared-ux-utility",
],

View 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,
};
}

View 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`,
],
},
};
}

View 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, {}>;

View 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,
});
});
}

View 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';

View 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,
};
}