[SIEM] Cypress preparation for Jenkins (#59013) (#59179)

* updates events viewer test

* updates login tasks

* updates ml conditional links

* updates url state

* updates timeline screen

* updates timeline tasks

* updates test files

* adds jenkins needed files

* ignoring isAttached lines due to a known error in Cypress (https://github.com/cypress-io/cypress/issues/4408)

* updates loop script

* updates readme with new cypress command explanation

* removes skip

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
MadameSheema 2020-03-04 21:45:29 +01:00 committed by GitHub
parent 0918c85ad8
commit 30d763accf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 4210 additions and 64 deletions

View file

@ -0,0 +1,21 @@
#!/usr/bin/env bash
source test/scripts/jenkins_test_setup.sh
installDir="$PARENT_DIR/install/kibana"
destDir="${installDir}-${CI_WORKER_NUMBER}"
cp -R "$installDir" "$destDir"
export KIBANA_INSTALL_DIR="$destDir"
echo " -> Running SIEM cypress tests"
cd "$XPACK_DIR"
checks-reporter-with-killswitch "SIEM Cypress Tests" \
node scripts/functional_tests \
--debug --bail \
--kibana-install-dir "$KIBANA_INSTALL_DIR" \
--config test/siem_cypress/config.ts
echo ""
echo ""

View file

@ -210,6 +210,30 @@ cd x-pack/legacy/plugins/siem
CYPRESS_baseUrl=http://localhost:5601 CYPRESS_ELASTICSEARCH_USERNAME=elastic CYPRESS_ELASTICSEARCH_PASSWORD=<password> yarn cypress:run
```
## Running (Headless) Tests on the Command Line as a Jenkins execution
To run (headless) tests as a Jenkins execution.
1. First bootstrap kibana changes from the Kibana root directory:
```sh
yarn kbn bootstrap
```
2. Launch Cypress command line test runner:
```sh
cd x-pack/legacy/plugins/siem
yarn cypress:run-as-ci
```
Note that with this type of execution you don't need to have running a kibana and elasticsearch instance. This is because
the command, as it would happen in the CI, will launch the instances. The elasticsearch instance will be fed with the data
placed in: `x-pack/test/siem_cypress/es_archives`.
As in this case we want to mimic a CI execution we want to execute the tests with the same set of data, this is why
in this case does not make sense to override Cypress environment variables.
## Reporting
When Cypress tests are run on the command line via `yarn cypress:run`,

View file

@ -134,7 +134,7 @@ describe('Events Viewer', () => {
});
it('filters the events by applying filter criteria from the search bar at the top of the page', () => {
const filterInput = '4bf34c1c-eaa9-46de-8921-67a4ccc49829'; // this will never match real data
const filterInput = 'aa7ca589f1b8220002f2fc61c64cfbf1'; // this will never match real data
cy.get(HEADER_SUBTITLE)
.invoke('text')
.then(initialNumberOfEvents => {

View file

@ -6,7 +6,7 @@
import { KQL_INPUT } from '../screens/siem_header';
import { loginAndWaitForPage } from '../tasks/login';
import { loginAndWaitForPageWithoutDateRange } from '../tasks/login';
import {
mlHostMultiHostKqlQuery,
@ -26,7 +26,7 @@ import {
describe('ml conditional links', () => {
it('sets the KQL from a single IP with a value for the query', () => {
loginAndWaitForPage(mlNetworkSingleIpKqlQuery);
loginAndWaitForPageWithoutDateRange(mlNetworkSingleIpKqlQuery);
cy.get(KQL_INPUT).should(
'have.attr',
'value',
@ -35,7 +35,7 @@ describe('ml conditional links', () => {
});
it('sets the KQL from a multiple IPs with a null for the query', () => {
loginAndWaitForPage(mlNetworkMultipleIpNullKqlQuery);
loginAndWaitForPageWithoutDateRange(mlNetworkMultipleIpNullKqlQuery);
cy.get(KQL_INPUT).should(
'have.attr',
'value',
@ -44,7 +44,7 @@ describe('ml conditional links', () => {
});
it('sets the KQL from a multiple IPs with a value for the query', () => {
loginAndWaitForPage(mlNetworkMultipleIpKqlQuery);
loginAndWaitForPageWithoutDateRange(mlNetworkMultipleIpKqlQuery);
cy.get(KQL_INPUT).should(
'have.attr',
'value',
@ -53,7 +53,7 @@ describe('ml conditional links', () => {
});
it('sets the KQL from a $ip$ with a value for the query', () => {
loginAndWaitForPage(mlNetworkKqlQuery);
loginAndWaitForPageWithoutDateRange(mlNetworkKqlQuery);
cy.get(KQL_INPUT).should(
'have.attr',
'value',
@ -62,7 +62,7 @@ describe('ml conditional links', () => {
});
it('sets the KQL from a single host name with a value for query', () => {
loginAndWaitForPage(mlHostSingleHostKqlQuery);
loginAndWaitForPageWithoutDateRange(mlHostSingleHostKqlQuery);
cy.get(KQL_INPUT).should(
'have.attr',
'value',
@ -71,7 +71,7 @@ describe('ml conditional links', () => {
});
it('sets the KQL from a multiple host names with null for query', () => {
loginAndWaitForPage(mlHostMultiHostNullKqlQuery);
loginAndWaitForPageWithoutDateRange(mlHostMultiHostNullKqlQuery);
cy.get(KQL_INPUT).should(
'have.attr',
'value',
@ -80,7 +80,7 @@ describe('ml conditional links', () => {
});
it('sets the KQL from a multiple host names with a value for query', () => {
loginAndWaitForPage(mlHostMultiHostKqlQuery);
loginAndWaitForPageWithoutDateRange(mlHostMultiHostKqlQuery);
cy.get(KQL_INPUT).should(
'have.attr',
'value',
@ -89,7 +89,7 @@ describe('ml conditional links', () => {
});
it('sets the KQL from a undefined/null host name but with a value for query', () => {
loginAndWaitForPage(mlHostVariableHostKqlQuery);
loginAndWaitForPageWithoutDateRange(mlHostVariableHostKqlQuery);
cy.get(KQL_INPUT).should(
'have.attr',
'value',
@ -98,7 +98,7 @@ describe('ml conditional links', () => {
});
it('redirects from a single IP with a null for the query', () => {
loginAndWaitForPage(mlNetworkSingleIpNullKqlQuery);
loginAndWaitForPageWithoutDateRange(mlNetworkSingleIpNullKqlQuery);
cy.url().should(
'include',
'/app/siem#/network/ip/127.0.0.1/source?timerange=(global:(linkTo:!(timeline),timerange:(from:1566990000000,kind:absolute,to:1567000799999)),timeline:(linkTo:!(global),timerange:(from:1566990000000,kind:absolute,to:1567000799999)))'
@ -106,7 +106,7 @@ describe('ml conditional links', () => {
});
it('redirects from a single IP with a value for the query', () => {
loginAndWaitForPage(mlNetworkSingleIpKqlQuery);
loginAndWaitForPageWithoutDateRange(mlNetworkSingleIpKqlQuery);
cy.url().should(
'include',
"/app/siem#/network/ip/127.0.0.1/source?query=(language:kuery,query:'(process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22)')&timerange=(global:(linkTo:!(timeline),timerange:(from:1566990000000,kind:absolute,to:1567000799999)),timeline:(linkTo:!(global),timerange:(from:1566990000000,kind:absolute,to:1567000799999)))"
@ -114,7 +114,7 @@ describe('ml conditional links', () => {
});
it('redirects from a multiple IPs with a null for the query', () => {
loginAndWaitForPage(mlNetworkMultipleIpNullKqlQuery);
loginAndWaitForPageWithoutDateRange(mlNetworkMultipleIpNullKqlQuery);
cy.url().should(
'include',
"app/siem#/network/flows?query=(language:kuery,query:'((source.ip:%20%22127.0.0.1%22%20or%20destination.ip:%20%22127.0.0.1%22)%20or%20(source.ip:%20%22127.0.0.2%22%20or%20destination.ip:%20%22127.0.0.2%22))')&timerange=(global:(linkTo:!(timeline),timerange:(from:1566990000000,kind:absolute,to:1567000799999)),timeline:(linkTo:!(global),timerange:(from:1566990000000,kind:absolute,to:1567000799999))"
@ -122,7 +122,7 @@ describe('ml conditional links', () => {
});
it('redirects from a multiple IPs with a value for the query', () => {
loginAndWaitForPage(mlNetworkMultipleIpKqlQuery);
loginAndWaitForPageWithoutDateRange(mlNetworkMultipleIpKqlQuery);
cy.url().should(
'include',
"/app/siem#/network/flows?query=(language:kuery,query:'((source.ip:%20%22127.0.0.1%22%20or%20destination.ip:%20%22127.0.0.1%22)%20or%20(source.ip:%20%22127.0.0.2%22%20or%20destination.ip:%20%22127.0.0.2%22))%20and%20((process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22))')&timerange=(global:(linkTo:!(timeline),timerange:(from:1566990000000,kind:absolute,to:1567000799999)),timeline:(linkTo:!(global),timerange:(from:1566990000000,kind:absolute,to:1567000799999)))"
@ -130,7 +130,7 @@ describe('ml conditional links', () => {
});
it('redirects from a $ip$ with a null query', () => {
loginAndWaitForPage(mlNetworkNullKqlQuery);
loginAndWaitForPageWithoutDateRange(mlNetworkNullKqlQuery);
cy.url().should(
'include',
'/app/siem#/network/flows?timerange=(global:(linkTo:!(timeline),timerange:(from:1566990000000,kind:absolute,to:1567000799999)),timeline:(linkTo:!(global),timerange:(from:1566990000000,kind:absolute,to:1567000799999)))'
@ -138,7 +138,7 @@ describe('ml conditional links', () => {
});
it('redirects from a $ip$ with a value for the query', () => {
loginAndWaitForPage(mlNetworkKqlQuery);
loginAndWaitForPageWithoutDateRange(mlNetworkKqlQuery);
cy.url().should(
'include',
"/app/siem#/network/flows?query=(language:kuery,query:'(process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22)')&timerange=(global:(linkTo:!(timeline),timerange:(from:1566990000000,kind:absolute,to:1567000799999)),timeline:(linkTo:!(global),timerange:(from:1566990000000,kind:absolute,to:1567000799999)))"
@ -146,7 +146,7 @@ describe('ml conditional links', () => {
});
it('redirects from a single host name with a null for the query', () => {
loginAndWaitForPage(mlHostSingleHostNullKqlQuery);
loginAndWaitForPageWithoutDateRange(mlHostSingleHostNullKqlQuery);
cy.url().should(
'include',
'/app/siem#/hosts/siem-windows/anomalies?timerange=(global:(linkTo:!(timeline),timerange:(from:1559800800000,kind:absolute,to:1559887199999)),timeline:(linkTo:!(global),timerange:(from:1559800800000,kind:absolute,to:1559887199999)))'
@ -154,7 +154,7 @@ describe('ml conditional links', () => {
});
it('redirects from a host name with a variable in the query', () => {
loginAndWaitForPage(mlHostSingleHostKqlQueryVariable);
loginAndWaitForPageWithoutDateRange(mlHostSingleHostKqlQueryVariable);
cy.url().should(
'include',
'/app/siem#/hosts/siem-windows/anomalies?timerange=(global:(linkTo:!(timeline),timerange:(from:1559800800000,kind:absolute,to:1559887199999)),timeline:(linkTo:!(global),timerange:(from:1559800800000,kind:absolute,to:1559887199999)))'
@ -162,7 +162,7 @@ describe('ml conditional links', () => {
});
it('redirects from a single host name with a value for query', () => {
loginAndWaitForPage(mlHostSingleHostKqlQuery);
loginAndWaitForPageWithoutDateRange(mlHostSingleHostKqlQuery);
cy.url().should(
'include',
"/app/siem#/hosts/siem-windows/anomalies?query=(language:kuery,query:'(process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22)')&timerange=(global:(linkTo:!(timeline),timerange:(from:1559800800000,kind:absolute,to:1559887199999)),timeline:(linkTo:!(global),timerange:(from:1559800800000,kind:absolute,to:1559887199999)))"
@ -170,7 +170,7 @@ describe('ml conditional links', () => {
});
it('redirects from a multiple host names with null for query', () => {
loginAndWaitForPage(mlHostMultiHostNullKqlQuery);
loginAndWaitForPageWithoutDateRange(mlHostMultiHostNullKqlQuery);
cy.url().should(
'include',
"/app/siem#/hosts/anomalies?query=(language:kuery,query:'(host.name:%20%22siem-windows%22%20or%20host.name:%20%22siem-suricata%22)')&timerange=(global:(linkTo:!(timeline),timerange:(from:1559800800000,kind:absolute,to:1559887199999)),timeline:(linkTo:!(global),timerange:(from:1559800800000,kind:absolute,to:1559887199999)))"
@ -178,7 +178,7 @@ describe('ml conditional links', () => {
});
it('redirects from a multiple host names with a value for query', () => {
loginAndWaitForPage(mlHostMultiHostKqlQuery);
loginAndWaitForPageWithoutDateRange(mlHostMultiHostKqlQuery);
cy.url().should(
'include',
"/app/siem#/hosts/anomalies?query=(language:kuery,query:'(host.name:%20%22siem-windows%22%20or%20host.name:%20%22siem-suricata%22)%20and%20((process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22))')&timerange=(global:(linkTo:!(timeline),timerange:(from:1559800800000,kind:absolute,to:1559887199999)),timeline:(linkTo:!(global),timerange:(from:1559800800000,kind:absolute,to:1559887199999)))"
@ -186,7 +186,7 @@ describe('ml conditional links', () => {
});
it('redirects from a undefined/null host name with a null for the KQL', () => {
loginAndWaitForPage(mlHostVariableHostNullKqlQuery);
loginAndWaitForPageWithoutDateRange(mlHostVariableHostNullKqlQuery);
cy.url().should(
'include',
'/app/siem#/hosts/anomalies?timerange=(global:(linkTo:!(timeline),timerange:(from:1559800800000,kind:absolute,to:1559887199999)),timeline:(linkTo:!(global),timerange:(from:1559800800000,kind:absolute,to:1559887199999)))'
@ -194,7 +194,7 @@ describe('ml conditional links', () => {
});
it('redirects from a undefined/null host name but with a value for query', () => {
loginAndWaitForPage(mlHostVariableHostKqlQuery);
loginAndWaitForPageWithoutDateRange(mlHostVariableHostKqlQuery);
cy.url().should(
'include',
"/app/siem#/hosts/anomalies?query=(language:kuery,query:'(process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22)')&timerange=(global:(linkTo:!(timeline),timerange:(from:1559800800000,kind:absolute,to:1559887199999)),timeline:(linkTo:!(global),timerange:(from:1559800800000,kind:absolute,to:1559887199999)))"

View file

@ -5,6 +5,7 @@
*/
import {
DATE_PICKER_APPLY_BUTTON_TIMELINE,
DATE_PICKER_END_DATE_POPOVER_BUTTON,
DATE_PICKER_END_DATE_POPOVER_BUTTON_TIMELINE,
DATE_PICKER_START_DATE_POPOVER_BUTTON,
@ -15,7 +16,7 @@ import { ANOMALIES_TAB } from '../screens/hosts/main';
import { BREADCRUMBS, HOSTS, KQL_INPUT, NETWORK } from '../screens/siem_header';
import { SERVER_SIDE_EVENT_COUNT, TIMELINE_TITLE } from '../screens/timeline';
import { loginAndWaitForPage } from '../tasks/login';
import { loginAndWaitForPage, loginAndWaitForPageWithoutDateRange } from '../tasks/login';
import {
setStartDate,
setEndDate,
@ -30,7 +31,12 @@ import { openAllHosts } from '../tasks/hosts/main';
import { waitForIpsTableToBeLoaded } from '../tasks/network/flows';
import { clearSearchBar, kqlSearch, navigateFromHeaderTo } from '../tasks/siem_header';
import { openTimeline } from '../tasks/siem_main';
import { addNameToTimeline, executeTimelineKQL } from '../tasks/timeline';
import {
addDescriptionToTimeline,
addNameToTimeline,
closeTimeline,
executeTimelineKQL,
} from '../tasks/timeline';
import { HOSTS_PAGE } from '../urls/navigation';
import { ABSOLUTE_DATE_RANGE } from '../urls/state';
@ -58,7 +64,7 @@ const ABSOLUTE_DATE = {
describe('url state', () => {
it('sets the global start and end dates from the url', () => {
loginAndWaitForPage(ABSOLUTE_DATE_RANGE.url);
loginAndWaitForPageWithoutDateRange(ABSOLUTE_DATE_RANGE.url);
cy.get(DATE_PICKER_START_DATE_POPOVER_BUTTON).should(
'have.attr',
'title',
@ -72,7 +78,7 @@ describe('url state', () => {
});
it('sets the url state when start and end date are set', () => {
loginAndWaitForPage(ABSOLUTE_DATE_RANGE.url);
loginAndWaitForPageWithoutDateRange(ABSOLUTE_DATE_RANGE.url);
setStartDate(ABSOLUTE_DATE.newStartTimeTyped);
updateDates();
waitForIpsTableToBeLoaded();
@ -88,7 +94,7 @@ describe('url state', () => {
});
it('sets the timeline start and end dates from the url when locked to global time', () => {
loginAndWaitForPage(ABSOLUTE_DATE_RANGE.url);
loginAndWaitForPageWithoutDateRange(ABSOLUTE_DATE_RANGE.url);
openTimeline();
cy.get(DATE_PICKER_START_DATE_POPOVER_BUTTON_TIMELINE).should(
@ -104,8 +110,7 @@ describe('url state', () => {
});
it('sets the timeline start and end dates independently of the global start and end dates when times are unlocked', () => {
loginAndWaitForPage(ABSOLUTE_DATE_RANGE.urlUnlinked);
loginAndWaitForPageWithoutDateRange(ABSOLUTE_DATE_RANGE.urlUnlinked);
cy.get(DATE_PICKER_START_DATE_POPOVER_BUTTON).should(
'have.attr',
'title',
@ -132,7 +137,7 @@ describe('url state', () => {
});
it('sets the url state when timeline/global date pickers are unlinked and timeline start and end date are set', () => {
loginAndWaitForPage(ABSOLUTE_DATE_RANGE.urlUnlinked);
loginAndWaitForPageWithoutDateRange(ABSOLUTE_DATE_RANGE.urlUnlinked);
openTimeline();
setTimelineStartDate(ABSOLUTE_DATE.newStartTimeTyped);
updateTimelineDates();
@ -148,24 +153,24 @@ describe('url state', () => {
});
it('sets kql on network page', () => {
loginAndWaitForPage(ABSOLUTE_DATE_RANGE.urlKqlNetworkNetwork);
loginAndWaitForPageWithoutDateRange(ABSOLUTE_DATE_RANGE.urlKqlNetworkNetwork);
cy.get(KQL_INPUT).should('have.attr', 'value', 'source.ip: "10.142.0.9"');
});
it('sets kql on hosts page', () => {
loginAndWaitForPage(ABSOLUTE_DATE_RANGE.urlKqlHostsHosts);
loginAndWaitForPageWithoutDateRange(ABSOLUTE_DATE_RANGE.urlKqlHostsHosts);
cy.get(KQL_INPUT).should('have.attr', 'value', 'source.ip: "10.142.0.9"');
});
it('sets the url state when kql is set', () => {
loginAndWaitForPage(ABSOLUTE_DATE_RANGE.url);
loginAndWaitForPageWithoutDateRange(ABSOLUTE_DATE_RANGE.url);
kqlSearch('source.ip: "10.142.0.9" {enter}');
cy.url().should('include', `query=(language:kuery,query:'source.ip:%20%2210.142.0.9%22%20')`);
});
it('sets the url state when kql is set and check if href reflect this change', () => {
loginAndWaitForPage(ABSOLUTE_DATE_RANGE.url);
loginAndWaitForPageWithoutDateRange(ABSOLUTE_DATE_RANGE.url);
kqlSearch('source.ip: "10.142.0.9" {enter}');
navigateFromHeaderTo(HOSTS);
@ -177,7 +182,7 @@ describe('url state', () => {
});
it('sets KQL in host page and detail page and check if href match on breadcrumb, tabs and subTabs', () => {
loginAndWaitForPage(ABSOLUTE_DATE_RANGE.urlHostNew);
loginAndWaitForPageWithoutDateRange(ABSOLUTE_DATE_RANGE.urlHostNew);
kqlSearch('host.name: "siem-kibana" {enter}');
openAllHosts();
waitForAllHostsToBeLoaded();
@ -223,7 +228,7 @@ describe('url state', () => {
});
it('Do not clears kql when navigating to a new page', () => {
loginAndWaitForPage(ABSOLUTE_DATE_RANGE.urlKqlHostsHosts);
loginAndWaitForPageWithoutDateRange(ABSOLUTE_DATE_RANGE.urlKqlHostsHosts);
navigateFromHeaderTo(NETWORK);
cy.get(KQL_INPUT).should('have.attr', 'value', 'source.ip: "10.142.0.9"');
@ -241,12 +246,23 @@ describe('url state', () => {
cy.wrap(intCount).should('be.above', 0);
});
const bestTimelineName = 'The Best Timeline';
addNameToTimeline(bestTimelineName);
const timelineName = 'SIEM';
addNameToTimeline(timelineName);
addDescriptionToTimeline('This is the best timeline of the world');
cy.url().should('include', 'timeline=');
cy.visit(
`/app/siem#/timelines?timerange=(global:(linkTo:!(),timerange:(from:1565274377369,kind:absolute,to:1565360777369)),timeline:(linkTo:!(),timerange:(from:1565274377369,kind:absolute,to:1565360777369)))`
).then(() => cy.get(TIMELINE_TITLE).should('have.attr', 'value', bestTimelineName));
cy.url({ timeout: 30000 }).should('match', /\w*-\w*-\w*-\w*-\w*/);
cy.url().then(url => {
const matched = url.match(/\w*-\w*-\w*-\w*-\w*/);
const newTimelineId = matched && matched.length > 0 ? matched[0] : 'null';
expect(matched).to.have.lengthOf(1);
closeTimeline();
cy.visit('/app/kibana');
cy.visit(`/app/siem#/overview?timeline\=(id:'${newTimelineId}',isOpen:!t)`);
cy.contains('a', 'SIEM');
cy.get(DATE_PICKER_APPLY_BUTTON_TIMELINE)
.invoke('text')
.should('not.equal', 'Updating');
cy.get(TIMELINE_TITLE).should('have.attr', 'value', timelineName);
});
});
});

View file

@ -24,8 +24,9 @@ export const TIMELINE_DATA_PROVIDERS = '[data-test-subj="dataProviders"]';
export const TIMELINE_DATA_PROVIDERS_EMPTY =
'[data-test-subj="dataProviders"] [data-test-subj="empty"]';
export const TIMELINE_DROPPED_DATA_PROVIDERS =
'[data-test-subj="dataProviders"] [data-test-subj="providerContainer"]';
export const TIMELINE_DESCRIPTION = '[data-test-subj="timeline-description"]';
export const TIMELINE_DROPPED_DATA_PROVIDERS = '[data-test-subj="providerContainer"]';
export const TIMELINE_FIELDS_BUTTON =
'[data-test-subj="timeline"] [data-test-subj="show-field-browser"]';
@ -43,8 +44,6 @@ export const TIMELINE_TITLE = '[data-test-subj="timeline-title"]';
export const TIMESTAMP_HEADER_FIELD = '[data-test-subj="header-text-@timestamp"]';
export const TIMELINE_TOGGLE_BUTTON = '[data-test-subj="flyoutOverlay"]';
export const TIMESTAMP_TOGGLE_FIELD = '[data-test-subj="toggle-field-@timestamp"]';
export const TOGGLE_TIMELINE_EXPAND_EVENT = '[data-test-subj="expand-event"]';

View file

@ -23,14 +23,14 @@ export const drag = (subject: JQuery<HTMLElement>) => {
clientY: subjectLocation.top,
force: true,
})
.wait(1)
.wait(5)
.trigger('mousemove', {
button: primaryButton,
clientX: subjectLocation.left + dndSloppyClickDetectionThreshold,
clientY: subjectLocation.top,
force: true,
})
.wait(1);
.wait(5);
};
/** Drags the subject being dragged on the specified drop target, but does not drop it */
@ -44,7 +44,7 @@ export const dragWithoutDrop = (dropTarget: JQuery<HTMLElement>) => {
export const drop = (dropTarget: JQuery<HTMLElement>) => {
cy.wrap(dropTarget)
.trigger('mousemove', { button: primaryButton, force: true })
.wait(1)
.wait(5)
.trigger('mouseup', { force: true })
.wait(1);
.wait(5);
};

View file

@ -46,7 +46,14 @@ export const setTimelineEndDate = (date: string) => {
.first()
.click({ force: true });
cy.get(DATE_PICKER_ABSOLUTE_INPUT).type(`{selectall}{backspace}${date}{enter}`);
cy.get(DATE_PICKER_ABSOLUTE_INPUT).click({ force: true });
cy.get(DATE_PICKER_ABSOLUTE_INPUT).then($el => {
// @ts-ignore
if (Cypress.dom.isAttached($el)) {
cy.wrap($el).click({ force: true });
}
cy.wrap($el).type(`{selectall}{backspace}${date}{enter}`);
});
};
export const setTimelineStartDate = (date: string) => {
@ -58,7 +65,14 @@ export const setTimelineStartDate = (date: string) => {
.first()
.click({ force: true });
cy.get(DATE_PICKER_ABSOLUTE_INPUT).type(`{selectall}{backspace}${date}{enter}`);
cy.get(DATE_PICKER_ABSOLUTE_INPUT).click({ force: true });
cy.get(DATE_PICKER_ABSOLUTE_INPUT).then($el => {
// @ts-ignore
if (Cypress.dom.isAttached($el)) {
cy.wrap($el).click({ force: true });
}
cy.wrap($el).type(`{selectall}{backspace}${date}{enter}`);
});
};
export const updateDates = () => {
@ -69,5 +83,8 @@ export const updateDates = () => {
};
export const updateTimelineDates = () => {
cy.get(DATE_PICKER_APPLY_BUTTON_TIMELINE).click({ force: true });
cy.get(DATE_PICKER_APPLY_BUTTON_TIMELINE)
.click({ force: true })
.invoke('text')
.should('not.equal', 'Updating');
};

View file

@ -120,10 +120,16 @@ const loginViaConfig = () => {
*/
export const loginAndWaitForPage = (url: string) => {
login();
cy.visit(`${Cypress.config().baseUrl}${url}`);
cy.viewport('macbook-15');
cy.visit(
`${url}?timerange=(global:(linkTo:!(timeline),timerange:(from:1547914976217,fromStr:'2019-01-19T16:22:56.217Z',kind:relative,to:1579537385745,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1547914976217,fromStr:'2019-01-19T16:22:56.217Z',kind:relative,to:1579537385745,toStr:now)))`
);
cy.contains('a', 'SIEM');
};
export const loginAndWaitForPageWithoutDateRange = (url: string) => {
login();
cy.viewport('macbook-15');
cy.visit(url);
cy.contains('a', 'SIEM');
};

View file

@ -4,6 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { DATE_PICKER_APPLY_BUTTON_TIMELINE } from '../screens/date_picker';
import {
CLOSE_TIMELINE_BTN,
CREATE_NEW_TIMELINE,
@ -12,6 +14,7 @@ import {
ID_TOGGLE_FIELD,
SEARCH_OR_FILTER_CONTAINER,
SERVER_SIDE_EVENT_COUNT,
TIMELINE_DESCRIPTION,
TIMELINE_FIELDS_BUTTON,
TIMELINE_INSPECT_BUTTON,
TIMELINE_SETTINGS_ICON,
@ -24,14 +27,31 @@ import { drag, drop } from '../tasks/common';
export const hostExistsQuery = 'host.name: *';
export const addDescriptionToTimeline = (description: string) => {
cy.get(TIMELINE_DESCRIPTION).type(`${description}{enter}`);
cy.get(DATE_PICKER_APPLY_BUTTON_TIMELINE)
.click()
.invoke('text')
.should('not.equal', 'Updating');
};
export const addNameToTimeline = (name: string) => {
cy.get(TIMELINE_TITLE).type(`${name}{enter}`);
cy.get(TIMELINE_TITLE).should('have.attr', 'value', name);
};
export const checkIdToggleField = () => {
cy.get(ID_TOGGLE_FIELD).should('not.exist');
cy.get(ID_HEADER_FIELD).should('not.exist');
cy.get(ID_TOGGLE_FIELD).check({
force: true,
});
};
export const closeTimeline = () => {
cy.get(CLOSE_TIMELINE_BTN).click({ force: true });
};
export const createNewTimeline = () => {
cy.get(TIMELINE_SETTINGS_ICON).click({ force: true });
cy.get(CREATE_NEW_TIMELINE).click();
@ -86,7 +106,3 @@ export const dragAndDropIdToggleFieldToTimeline = () => {
drop(headersDropArea)
);
};
export const addNameToTimeline = (name: string) => {
cy.get(TIMELINE_TITLE).type(name);
};

View file

@ -8,7 +8,8 @@
"extract-mitre-attacks": "node scripts/extract_tactics_techniques_mitre.js & node ../../../../scripts/eslint ./public/pages/detection_engine/mitre/mitre_tactics_techniques.ts --fix",
"build-graphql-types": "node scripts/generate_types_from_graphql.js",
"cypress:open": "../../../node_modules/.bin/cypress open",
"cypress:run": "../../../node_modules/.bin/cypress run --spec ./cypress/integration/**/*.spec.ts --reporter ../../../node_modules/cypress-multi-reporters --reporter-options configFile=./reporter_config.json; status=$?; ../../../node_modules/.bin/mochawesome-merge --reportDir ../../../../target/kibana-siem/cypress/results > ../../../../target/kibana-siem/cypress/results/output.json; ../../../../node_modules/.bin/marge ../../../../target/kibana-siem/cypress/results/output.json --reportDir ../../../../target/kibana-siem/cypress/results; mkdir -p ../../../../target/junit && cp ../../../../target/kibana-siem/cypress/results/*.xml ../../../../target/junit/ && exit $status;"
"cypress:run": "../../../node_modules/.bin/cypress run --spec ./cypress/integration/**/*.spec.ts --reporter ../../../node_modules/cypress-multi-reporters --reporter-options configFile=./reporter_config.json; status=$?; ../../../node_modules/.bin/mochawesome-merge --reportDir ../../../../target/kibana-siem/cypress/results > ../../../../target/kibana-siem/cypress/results/output.json; ../../../../node_modules/.bin/marge ../../../../target/kibana-siem/cypress/results/output.json --reportDir ../../../../target/kibana-siem/cypress/results; mkdir -p ../../../../target/junit && cp ../../../../target/kibana-siem/cypress/results/*.xml ../../../../target/junit/ && exit $status;",
"cypress:run-as-ci": "node ../../../../scripts/functional_tests --config ../../../test/siem_cypress/config.ts"
},
"devDependencies": {
"@types/lodash": "^4.14.110",

View file

@ -0,0 +1,83 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
const fs = require('fs');
const os = require('os');
const process = require('process');
const spawn = require('child_process').spawn;
/* eslint-disable no-process-exit */
const MUST_RUN_FROM_DIR = 'kibana';
const OUTPUT_DIR = 'target';
const OUTPUT_FILE = `${OUTPUT_DIR}/loop-cypress-tests.txt`;
const createOutputDir = () => {
fs.mkdir(OUTPUT_DIR, { recursive: true }, err => {
if (err) throw err;
});
};
const showUsage = () => {
const scriptName = process.argv[1].slice(process.argv[1].lastIndexOf('/') + 1);
console.log(`\nUsage: ${scriptName} <times-to-run>`, `\nExample: ${scriptName} 5`);
};
const exitIfIncorrectWorkingDir = () => {
if (!process.cwd().endsWith(`/${MUST_RUN_FROM_DIR}`)) {
console.error(
`\nERROR: This script must be run from the '${MUST_RUN_FROM_DIR}' directory, but it was ran from '${process.cwd()}' instead.`
);
showUsage();
process.exit(1);
}
};
const exitIfTimesToRunIsInvalid = timesToRun => {
if (!timesToRun > 0) {
console.error(
'\nERROR: You must specify a valid number of times to run the SIEM Cypress tests.'
);
showUsage();
process.exit(1);
}
};
const spawnChild = async () => {
const child = spawn('node', [
'scripts/functional_tests',
'--config',
'x-pack/test/siem_cypress/config.ts',
]);
for await (const chunk of child.stdout) {
console.log(chunk.toString());
fs.appendFileSync(OUTPUT_FILE, chunk.toString());
}
for await (const chunk of child.stderr) {
console.log(chunk.toString());
fs.appendFileSync(OUTPUT_FILE, chunk.toString());
}
const exitCode = await new Promise(resolve => {
child.on('close', resolve);
});
return exitCode;
};
const runNTimes = async timesToRun => {
for (let i = 0; i < timesToRun; i++) {
const startingRun = `\n\n*** Starting test run ${i +
1} of ${timesToRun} on host ${os.hostname()} at ${new Date()} ***\n\n`;
console.log(startingRun);
fs.appendFileSync(OUTPUT_FILE, startingRun);
const exitCode = await spawnChild();
const testRunCompleted = `\n\n*** Test run ${i +
1} of ${timesToRun} on host ${os.hostname()} exited with code ${exitCode} at ${new Date()} ***`;
console.log(testRunCompleted);
fs.appendFileSync(OUTPUT_FILE, testRunCompleted);
}
};
const timesToRun = Number(process.argv[2]) || 0;
exitIfIncorrectWorkingDir();
exitIfTimesToRunIsInvalid(timesToRun);
console.log(`\nCypress tests will be run ${timesToRun} times`);
console.log(`\nTest output will be appended to '${OUTPUT_FILE}'`);
createOutputDir();
runNTimes(timesToRun);

View file

@ -0,0 +1,47 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { resolve } from 'path';
import { FtrConfigProviderContext } from '@kbn/test/types/ftr';
import { SiemCypressTestRunner } from './runner';
export default async function({ readConfigFile }: FtrConfigProviderContext) {
const kibanaCommonTestsConfig = await readConfigFile(
require.resolve('../../../test/common/config.js')
);
const xpackFunctionalTestsConfig = await readConfigFile(
require.resolve('../functional/config.js')
);
return {
...kibanaCommonTestsConfig.getAll(),
testRunner: SiemCypressTestRunner,
esArchiver: {
directory: resolve(__dirname, 'es_archives'),
},
esTestCluster: {
...xpackFunctionalTestsConfig.get('esTestCluster'),
serverArgs: [
...xpackFunctionalTestsConfig.get('esTestCluster.serverArgs'),
// define custom es server here
],
},
kbnTestServer: {
...xpackFunctionalTestsConfig.get('kbnTestServer'),
serverArgs: [
...xpackFunctionalTestsConfig.get('kbnTestServer.serverArgs'),
'--csp.strict=false',
// define custom kibana server args here
],
},
};
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,284 @@
{
"type": "index",
"value": {
"index": ".kibana",
"mappings": {
"properties": {
"config": {
"dynamic": "true",
"properties": {
"buildNum": {
"type": "keyword"
},
"dateFormat:tz": {
"fields": {
"keyword": {
"ignore_above": 256,
"type": "keyword"
}
},
"type": "text"
}
}
},
"dashboard": {
"dynamic": "strict",
"properties": {
"description": {
"type": "text"
},
"hits": {
"type": "integer"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"optionsJSON": {
"type": "text"
},
"panelsJSON": {
"type": "text"
},
"refreshInterval": {
"properties": {
"display": {
"type": "keyword"
},
"pause": {
"type": "boolean"
},
"section": {
"type": "integer"
},
"value": {
"type": "integer"
}
}
},
"timeFrom": {
"type": "keyword"
},
"timeRestore": {
"type": "boolean"
},
"timeTo": {
"type": "keyword"
},
"title": {
"type": "text"
},
"uiStateJSON": {
"type": "text"
},
"version": {
"type": "integer"
}
}
},
"index-pattern": {
"dynamic": "strict",
"properties": {
"fieldFormatMap": {
"type": "text"
},
"fields": {
"type": "text"
},
"intervalName": {
"type": "keyword"
},
"notExpandable": {
"type": "boolean"
},
"sourceFilters": {
"type": "text"
},
"timeFieldName": {
"type": "keyword"
},
"title": {
"type": "text"
}
}
},
"search": {
"dynamic": "strict",
"properties": {
"columns": {
"type": "keyword"
},
"description": {
"type": "text"
},
"hits": {
"type": "integer"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"sort": {
"type": "keyword"
},
"title": {
"type": "text"
},
"version": {
"type": "integer"
}
}
},
"server": {
"dynamic": "strict",
"properties": {
"uuid": {
"type": "keyword"
}
}
},
"space": {
"properties": {
"_reserved": {
"type": "boolean"
},
"color": {
"type": "keyword"
},
"description": {
"type": "text"
},
"disabledFeatures": {
"type": "keyword"
},
"initials": {
"type": "keyword"
},
"name": {
"fields": {
"keyword": {
"ignore_above": 2048,
"type": "keyword"
}
},
"type": "text"
}
}
},
"spaceId": {
"type": "keyword"
},
"timelion-sheet": {
"dynamic": "strict",
"properties": {
"description": {
"type": "text"
},
"hits": {
"type": "integer"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"timelion_chart_height": {
"type": "integer"
},
"timelion_columns": {
"type": "integer"
},
"timelion_interval": {
"type": "keyword"
},
"timelion_other_interval": {
"type": "keyword"
},
"timelion_rows": {
"type": "integer"
},
"timelion_sheet": {
"type": "text"
},
"title": {
"type": "text"
},
"version": {
"type": "integer"
}
}
},
"type": {
"type": "keyword"
},
"url": {
"dynamic": "strict",
"properties": {
"accessCount": {
"type": "long"
},
"accessDate": {
"type": "date"
},
"createDate": {
"type": "date"
},
"url": {
"fields": {
"keyword": {
"ignore_above": 2048,
"type": "keyword"
}
},
"type": "text"
}
}
},
"visualization": {
"dynamic": "strict",
"properties": {
"description": {
"type": "text"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"savedSearchId": {
"type": "keyword"
},
"title": {
"type": "text"
},
"uiStateJSON": {
"type": "text"
},
"version": {
"type": "integer"
},
"visState": {
"type": "text"
}
}
}
}
},
"settings": {
"index": {
"number_of_replicas": "1",
"number_of_shards": "1"
}
}
}
}

View file

@ -0,0 +1,11 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { GenericFtrProviderContext } from '@kbn/test/types/ftr';
import { services } from './services';
export type FtrProviderContext = GenericFtrProviderContext<typeof services, {}>;

View file

@ -0,0 +1,37 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { resolve } from 'path';
import Url from 'url';
import { withProcRunner } from '@kbn/dev-utils';
import { FtrProviderContext } from './ftr_provider_context';
export async function SiemCypressTestRunner({ getService }: FtrProviderContext) {
const log = getService('log');
const config = getService('config');
const esArchiver = getService('esArchiver');
await esArchiver.load('empty_kibana');
await esArchiver.load('auditbeat');
await withProcRunner(log, async procs => {
await procs.run('cypress', {
cmd: 'yarn',
args: ['cypress:run'],
cwd: resolve(__dirname, '../../legacy/plugins/siem'),
env: {
FORCE_COLOR: '1',
CYPRESS_baseUrl: Url.format(config.get('servers.kibana')),
CYPRESS_ELASTICSEARCH_USERNAME: config.get('servers.elasticsearch.username'),
CYPRESS_ELASTICSEARCH_PASSWORD: config.get('servers.elasticsearch.password'),
...process.env,
},
wait: true,
});
});
}

View file

@ -0,0 +1,7 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
export * from '../../../test/common/services';