[SIEM] Refactor to make Cypress tests more readable (#54907)

* extracts methods to tasks

* uses cypress api for assertions

* updates readme
This commit is contained in:
MadameSheema 2020-01-22 18:13:49 +01:00 committed by GitHub
parent 838d7ba213
commit 14269faa3a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 244 additions and 99 deletions

View file

@ -22,29 +22,29 @@ automatically when you submit a PR.
Smoke Tests are located in `siem/cypress/integration/smoke_tests`
## Test Helpers
## Structure
_Test helpers_ are functions that may be re-used across tests.
### Tasks
- Reusable code and CSS selectors should be added to
`siem/cypress/integration/lib`, as described below.
_Tasks_ are functions that my be re-used across tests. Inside the _tasks_ folder there are some other folders that represents
the page to which we will perform the actions. For each folder we are going to create a file for each one of the sections that
has the page.
### Reusable Test Helper Functions and CSS Selectors
i.e.
- tasks
- hosts
- events.ts
The `cypress/integration/lib` directory contains code intended to be re-used
across many different tests. Add reusable test helper functions and CSS
selectors to directories under `cypress/integration/lib`.
### Screens
- Files named `helpers.ts` (e.g. `siem/cypress/integration/lib/login/helpers.ts`)
contain functions (e.g. `login`) that may be imported and invoked from multiple tests.
In _screens_ folder we are going to find all the elements we want to interact in our tests. Inside _screens_ fonder there
are some other folders that represents the page that contains the elements the tests are going to interact with. For each
folder we are going to create a file for each one of the sections that the page has.
- Files named `selectors.ts` export CSS selectors for re-use. For example,
`siem/cypress/integration/lib/login/selectors.ts` exports the following selector
that matches the Username text area in the Kibana login page:
```sh
export const USERNAME = '[data-test-subj="loginUsername"]';
```
i.e.
- tasks
- hosts
- events.ts
## Mock Data

View file

@ -9,25 +9,31 @@ import {
FIELDS_BROWSER_CONTAINER,
FIELDS_BROWSER_SELECTED_CATEGORY_TITLE,
FIELDS_BROWSER_TITLE,
} from '../../lib/fields_browser/selectors';
FIELDS_BROWSER_CHECKBOX,
} from '../../../screens/hosts/fields_browser';
import { HOSTS_PAGE } from '../../lib/urls';
import { loginAndWaitForPage, DEFAULT_TIMEOUT } from '../../lib/util/helpers';
import { loginAndWaitForPage } from '../../../tasks/login';
import { openEventsViewerFieldsBrowser, filterSearchBar } from '../../lib/events_viewer/helpers';
import { closeFieldsBrowser } from '../../../tasks/hosts/fields_browsers';
import { openEvents } from '../../../tasks/hosts/main';
import {
clickOutsideFieldsBrowser,
openEventsViewerFieldsBrowser,
filterSearchBar,
} from '../../lib/events_viewer/helpers';
closeModal,
opensInspectQueryModal,
waitsForEventsToBeLoaded,
addsHostGeoCityNameToHeader,
addsHostGeoCountryNameToHeader,
resetFields,
} from '../../../tasks/hosts/events';
import {
CLOSE_MODAL,
EVENTS_VIEWER_PANEL,
HEADER_SUBTITLE,
INSPECT_MODAL,
INSPECT_QUERY,
LOAD_MORE,
LOCAL_EVENTS_COUNT,
} from '../../lib/events_viewer/selectors';
import { SERVER_SIDE_EVENT_COUNT } from '../../lib/timeline/selectors';
import { clickEventsTab } from '../../lib/hosts/helpers';
HOST_GEO_CITY_NAME_HEADER,
HOST_GEO_COUNTRY_NAME_HEADER,
} from '../../../screens/hosts/events';
import { DEFAULT_TIMEOUT } from '../../lib/util/helpers';
const defaultHeadersInDefaultEcsCategory = [
{ id: '@timestamp' },
@ -43,7 +49,7 @@ describe('Events Viewer', () => {
context('Fields rendering', () => {
before(() => {
loginAndWaitForPage(HOSTS_PAGE);
clickEventsTab();
openEvents();
});
beforeEach(() => {
@ -51,7 +57,7 @@ describe('Events Viewer', () => {
});
afterEach(() => {
clickOutsideFieldsBrowser();
closeFieldsBrowser();
cy.get(FIELDS_BROWSER_CONTAINER).should('not.exist');
});
@ -69,7 +75,7 @@ describe('Events Viewer', () => {
it('displays a checked checkbox for all of the default events viewer columns that are also in the default ECS category', () => {
defaultHeadersInDefaultEcsCategory.forEach(header =>
cy.get(`[data-test-subj="field-${header.id}-checkbox"]`).should('be.checked')
cy.get(FIELDS_BROWSER_CHECKBOX(header.id)).should('be.checked')
);
});
});
@ -77,26 +83,17 @@ describe('Events Viewer', () => {
context('Events viewer query modal', () => {
before(() => {
loginAndWaitForPage(HOSTS_PAGE);
clickEventsTab();
openEvents();
});
after(() => {
cy.get(CLOSE_MODAL).click();
closeModal();
cy.get(INSPECT_MODAL, { timeout: DEFAULT_TIMEOUT }).should('not.exist');
});
it('launches the inspect query modal when the inspect button is clicked', () => {
// wait for data to load
cy.get(SERVER_SIDE_EVENT_COUNT, { timeout: DEFAULT_TIMEOUT })
.should('exist')
.invoke('text', { timeout: DEFAULT_TIMEOUT })
.should('not.equal', '0');
cy.get(INSPECT_QUERY, { timeout: DEFAULT_TIMEOUT })
.should('exist')
.trigger('mousemove', { force: true })
.click({ force: true });
waitsForEventsToBeLoaded();
opensInspectQueryModal();
cy.get(INSPECT_MODAL, { timeout: DEFAULT_TIMEOUT }).should('exist');
});
});
@ -104,7 +101,7 @@ describe('Events Viewer', () => {
context('Events viewer fields behaviour', () => {
before(() => {
loginAndWaitForPage(HOSTS_PAGE);
clickEventsTab();
openEvents();
});
beforeEach(() => {
@ -113,89 +110,55 @@ describe('Events Viewer', () => {
it('adds a field to the events viewer when the user clicks the checkbox', () => {
const filterInput = 'host.geo.c';
const toggleField = 'host.geo.city_name';
filterFieldsBrowser(filterInput);
cy.get(`${EVENTS_VIEWER_PANEL} [data-test-subj="header-text-${toggleField}"]`).should(
'not.exist'
);
cy.get(`${EVENTS_VIEWER_PANEL} [data-test-subj="field-${toggleField}-checkbox"]`).check({
force: true,
});
clickOutsideFieldsBrowser();
cy.get(`${EVENTS_VIEWER_PANEL} [data-test-subj="header-text-${toggleField}"]`).should(
'exist'
);
cy.get(HOST_GEO_CITY_NAME_HEADER).should('not.exist');
addsHostGeoCityNameToHeader();
closeFieldsBrowser();
cy.get(HOST_GEO_CITY_NAME_HEADER).should('exist');
});
it('resets all fields in the events viewer when `Reset Fields` is clicked', () => {
const filterInput = 'host.geo.c';
const toggleField = 'host.geo.country_name';
filterFieldsBrowser(filterInput);
cy.get(`${EVENTS_VIEWER_PANEL} [data-test-subj="header-text-${toggleField}"]`).should(
'not.exist'
);
cy.get(`${EVENTS_VIEWER_PANEL} [data-test-subj="field-${toggleField}-checkbox"]`).check({
force: true,
});
cy.get(`${EVENTS_VIEWER_PANEL} [data-test-subj="reset-fields"]`).click({ force: true });
cy.get(`${EVENTS_VIEWER_PANEL} [data-test-subj="header-text-${toggleField}"]`).should(
'not.exist'
);
cy.get(HOST_GEO_COUNTRY_NAME_HEADER).should('not.exist');
addsHostGeoCountryNameToHeader();
resetFields();
cy.get(HOST_GEO_COUNTRY_NAME_HEADER).should('not.exist');
});
});
context('Events behaviour', () => {
before(() => {
loginAndWaitForPage(HOSTS_PAGE);
clickEventsTab();
openEvents();
});
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
waitsForEventsToBeLoaded();
cy.get(HEADER_SUBTITLE)
.invoke('text')
.then(text1 => {
cy.get(HEADER_SUBTITLE)
.invoke('text', { timeout: DEFAULT_TIMEOUT })
.should('not.equal', 'Showing: 0 events');
.then(initialNumberOfEvents => {
filterSearchBar(filterInput);
cy.get(HEADER_SUBTITLE)
.invoke('text')
.should(text2 => {
expect(text1).not.to.eq(text2);
});
.should('not.equal', initialNumberOfEvents);
});
});
it('loads more events when the load more button is clicked', () => {
cy.get(LOCAL_EVENTS_COUNT, { timeout: DEFAULT_TIMEOUT })
const defaultNumberOfLoadedEvents = '25';
cy.get(LOCAL_EVENTS_COUNT)
.invoke('text')
.then(text1 => {
cy.get(LOCAL_EVENTS_COUNT)
.invoke('text')
.should('equal', '25');
.should('equal', defaultNumberOfLoadedEvents);
cy.get(LOAD_MORE).click({ force: true });
cy.get(LOAD_MORE).click({ force: true });
cy.get(LOCAL_EVENTS_COUNT)
.invoke('text')
.should(text2 => {
expect(text1).not.to.eq(text2);
});
});
cy.get(LOCAL_EVENTS_COUNT)
.invoke('text')
.should('not.equal', defaultNumberOfLoadedEvents);
});
});
});

View file

@ -0,0 +1,36 @@
/*
* 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 const EVENTS_VIEWER_PANEL = '[data-test-subj="events-viewer-panel"]';
export const CLOSE_MODAL = '[data-test-subj="modal-inspect-close"]';
export const HEADER_SUBTITLE = `${EVENTS_VIEWER_PANEL} [data-test-subj="header-panel-subtitle"]`;
export const INSPECT_MODAL = '[data-test-subj="modal-inspect-euiModal"]';
export const INSPECT_QUERY = `${EVENTS_VIEWER_PANEL} [data-test-subj="inspect-icon-button"]`;
export const LOAD_MORE = `${EVENTS_VIEWER_PANEL} [data-test-subj="TimelineMoreButton"]`;
export const LOCAL_EVENTS_COUNT = `${EVENTS_VIEWER_PANEL} [data-test-subj="local-events-count"]`;
export const SERVER_SIDE_EVENT_COUNT = '[data-test-subj="server-side-event-count"]';
export const HOST_GEO_CITY_NAME_HEADER = '[data-test-subj="header-text-host.geo.city_name"]';
export const HOST_GEO_CITY_NAME_CHECKBOX = '[data-test-subj="field-host.geo.city_name-checkbox"]';
export const HOST_GEO_COUNTRY_NAME_HEADER = '[data-test-subj="header-text-host.geo.country_name"]';
export const HOST_GEO_COUNTRY_NAME_CHECKBOX =
'[data-test-subj="field-host.geo.country_name-checkbox"]';
export const FIELDS_BROWSER_CONTAINER = '[data-test-subj="fields-browser-container"]';
export const EVENTS_VIEWER_FIELDS_BUTTON = `${EVENTS_VIEWER_PANEL} [data-test-subj="show-field-browser-gear"]`;
export const RESET_FIELDS = `${EVENTS_VIEWER_PANEL} [data-test-subj="reset-fields"]`;

View file

@ -0,0 +1,22 @@
/*
* 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.
*/
/** Clicking this button in the timeline opens the Fields browser */
export const TIMELINE_FIELDS_BUTTON =
'[data-test-subj="timeline"] [data-test-subj="show-field-browser"]';
/** The title displayed in the fields browser (i.e. Customize Columns) */
export const FIELDS_BROWSER_TITLE = '[data-test-subj="field-browser-title"]';
/** Contains the body of the fields browser */
export const FIELDS_BROWSER_CONTAINER = '[data-test-subj="fields-browser-container"]';
/** The title of the selected category in the right-hand side of the fields browser */
export const FIELDS_BROWSER_SELECTED_CATEGORY_TITLE = '[data-test-subj="selected-category-title"]';
export const FIELDS_BROWSER_CHECKBOX = (id: string) => {
return `[data-test-subj="field-${id}-checkbox`;
};

View file

@ -0,0 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
export const EVENTS_TAB = '[data-test-subj="navigation-events"]';
export const KQL_SEARCH_BAR = '[data-test-subj="queryInput"]';

View file

@ -0,0 +1,66 @@
/*
* 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 { DEFAULT_TIMEOUT } from '../../integration/lib/util/helpers';
import {
EVENTS_VIEWER_FIELDS_BUTTON,
CLOSE_MODAL,
INSPECT_QUERY,
SERVER_SIDE_EVENT_COUNT,
HOST_GEO_CITY_NAME_CHECKBOX,
HOST_GEO_COUNTRY_NAME_CHECKBOX,
FIELDS_BROWSER_CONTAINER,
RESET_FIELDS,
LOAD_MORE,
} from '../../screens/hosts/events';
export const closeModal = () => {
cy.get(CLOSE_MODAL, { timeout: DEFAULT_TIMEOUT }).click();
};
export const opensInspectQueryModal = () => {
cy.get(INSPECT_QUERY, { timeout: DEFAULT_TIMEOUT })
.should('exist')
.trigger('mousemove', { force: true })
.click({ force: true });
};
export const waitsForEventsToBeLoaded = () => {
cy.get(SERVER_SIDE_EVENT_COUNT, { timeout: DEFAULT_TIMEOUT })
.should('exist')
.invoke('text', { timeout: DEFAULT_TIMEOUT })
.should('not.equal', '0');
};
export const addsHostGeoCityNameToHeader = () => {
cy.get(HOST_GEO_CITY_NAME_CHECKBOX).check({
force: true,
});
};
export const addsHostGeoCountryNameToHeader = () => {
cy.get(HOST_GEO_COUNTRY_NAME_CHECKBOX).check({
force: true,
});
};
export const resetFields = () => {
cy.get(RESET_FIELDS).click({ force: true });
};
export const openEventsViewerFieldsBrowser = () => {
cy.get(EVENTS_VIEWER_FIELDS_BUTTON, { timeout: DEFAULT_TIMEOUT }).click({ force: true });
cy.get(SERVER_SIDE_EVENT_COUNT, { timeout: DEFAULT_TIMEOUT })
.invoke('text')
.should('not.equal', '0');
cy.get(FIELDS_BROWSER_CONTAINER).should('exist');
};
export const loadMoreEvents = () => {
cy.get(LOAD_MORE).click({ force: true });
};

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;
* you may not use this file except in compliance with the Elastic License.
*/
import { DEFAULT_TIMEOUT } from '../../integration/lib/util/helpers';
import { KQL_SEARCH_BAR } from '../../screens/hosts/main';
export const closeFieldsBrowser = () => {
cy.get(KQL_SEARCH_BAR, { timeout: DEFAULT_TIMEOUT }).click();
};

View file

@ -0,0 +1,13 @@
/*
* 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 { DEFAULT_TIMEOUT } from '../../integration/lib/util/helpers';
import { EVENTS_TAB } from '../../screens/hosts/main';
/** Clicks the Events tab on the hosts page */
export const openEvents = () =>
cy.get(EVENTS_TAB, { timeout: DEFAULT_TIMEOUT }).click({ force: true });

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;
* you may not use this file except in compliance with the Elastic License.
*/
import { login } from '../integration/lib/login/helpers';
/** The default time in ms to wait for a Cypress command to complete */
export const DEFAULT_TIMEOUT = 30 * 1000;
/**
* Authenticates with Kibana, visits the specified `url`, and waits for the
* Kibana logo to be displayed before continuing
*/
export const loginAndWaitForPage = (url: string) => {
login();
cy.visit(`${Cypress.config().baseUrl}${url}`);
cy.viewport('macbook-15');
cy.contains('a', 'SIEM', { timeout: DEFAULT_TIMEOUT });
};