[Security Solution][Resolver] Fixing resolver functional tests (#85647)

* Fixing resolver functional tests

* Import the animation constant

* Only check specific nodes instead of all the ones in view

* Removing check for link text

* updating test description

* Adding comments
This commit is contained in:
Jonathan Buttner 2020-12-15 08:31:59 -05:00 committed by GitHub
parent 20638a64e2
commit d4a631cf8e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 261 additions and 205 deletions

View file

@ -8,7 +8,7 @@
/* eslint-disable react/display-name */
import React, { memo, useMemo, Fragment } from 'react';
import React, { memo, useMemo, Fragment, HTMLAttributes } from 'react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiSpacer, EuiText, EuiDescriptionList, EuiTextColor, EuiTitle } from '@elastic/eui';
@ -246,7 +246,12 @@ function EventDetailBreadcrumbs({
panelParameters: { nodeID, eventCategory: breadcrumbEventCategory },
});
const breadcrumbs = useMemo(() => {
const crumbs = [
const crumbs: Array<
{
text: JSX.Element | string;
'data-test-subj'?: string;
} & HTMLAttributes<HTMLAnchorElement>
> = [
{
text: i18n.translate(
'xpack.securitySolution.endpoint.resolver.panel.relatedEventDetail.events',
@ -254,6 +259,7 @@ function EventDetailBreadcrumbs({
defaultMessage: 'Events',
}
),
'data-test-subj': 'resolver:event-detail:breadcrumbs:node-list-link',
...nodesLinkNavProps,
},
{

View file

@ -262,6 +262,7 @@ const NodeEventsInCategoryBreadcrumbs = memo(function ({
defaultMessage: 'Events',
}
),
'data-test-subj': 'resolver:node-events-in-category:breadcrumbs:node-list-link',
...nodesLinkNavProps,
},
{

View file

@ -304,7 +304,11 @@ export const ColumnHeadersComponent = ({
}
className={fullScreen ? FULL_SCREEN_TOGGLED_CLASS_NAME : ''}
color={fullScreen ? 'ghost' : 'primary'}
data-test-subj="full-screen"
data-test-subj={
// a full screen button gets created for timeline and for the host page
// this sets the data-test-subj for each case so that tests can differentiate between them
timelineId === TimelineId.active ? 'full-screen-active' : 'full-screen'
}
iconType="fullScreen"
onClick={toggleFullScreen}
/>

View file

@ -12,10 +12,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
const testSubjects = getService('testSubjects');
const esArchiver = getService('esArchiver');
const browser = getService('browser');
const queryBar = getService('queryBar');
// FLAKY: https://github.com/elastic/kibana/issues/85085
describe.skip('Endpoint Event Resolver', function () {
describe('Endpoint Event Resolver', function () {
before(async () => {
await pageObjects.hosts.navigateToSecurityHostsPage();
await pageObjects.common.dismissBanner();
@ -28,7 +26,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
before(async () => {
await esArchiver.load('empty_kibana');
await esArchiver.load('endpoint/resolver_tree/functions', { useCreate: true });
await pageObjects.hosts.navigateToEventsPanel();
await pageObjects.hosts.executeQueryAndOpenResolver('event.dataset : endpoint.events.file');
});
after(async () => {
@ -194,114 +191,74 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
}
await (await testSubjects.find('resolver:graph-controls:zoom-in')).click();
});
it('Check Related Events for event.file Node', async () => {
const expectedData = [
'17 authentication',
'1 registry',
'17 session',
'8 file',
'1 registry',
];
await pageObjects.hosts.runNodeEvents(expectedData);
});
});
describe('Resolver Tree events', function () {
const expectedData = [
'17 authentication',
'1 registry',
'17 session',
'80 registry',
'8 network',
'60 registry',
];
describe('node related event pills', function () {
/**
* Verifies that the pills of a node have the correct text.
*
* @param id the node ID to verify the pills for.
* @param expectedPills a map of expected pills for all nodes
*/
const verifyPills = async (id: string, expectedPills: Set<string>) => {
const relatedEventPills = await pageObjects.hosts.findNodePills(id);
expect(relatedEventPills.length).to.equal(expectedPills.size);
for (const pill of relatedEventPills) {
const pillText = await pill._webElement.getText();
// check that we have the pill text in our expected map
expect(expectedPills.has(pillText)).to.equal(true);
}
};
before(async () => {
await esArchiver.load('empty_kibana');
await esArchiver.load('endpoint/resolver_tree/events', { useCreate: true });
await queryBar.setQuery('');
await queryBar.submitQuery();
await esArchiver.load('endpoint/resolver_tree/alert_events', { useCreate: true });
});
after(async () => {
await pageObjects.hosts.deleteDataStreams();
});
it('Check Related Events for event.process Node', async () => {
await pageObjects.hosts.navigateToEventsPanel();
await pageObjects.hosts.executeQueryAndOpenResolver(
'event.dataset : endpoint.events.process'
);
await pageObjects.hosts.runNodeEvents(expectedData);
});
describe('endpoint.alerts filter', () => {
before(async () => {
await pageObjects.hosts.executeQueryAndOpenResolver('event.dataset : endpoint.alerts');
await pageObjects.hosts.clickZoomOut();
await browser.setWindowSize(2100, 1500);
});
it('Check Related Events for event.security Node', async () => {
await pageObjects.hosts.navigateToEventsPanel();
await pageObjects.hosts.executeQueryAndOpenResolver(
'event.dataset : endpoint.events.security'
);
await pageObjects.hosts.runNodeEvents(expectedData);
});
it('has the correct pill text', async () => {
const expectedData: Map<string, Set<string>> = new Map([
[
'MTk0YzBmOTgtNjA4My1jNWE4LTYzNjYtZjVkNzI2YWU2YmIyLTc2MzYtMTMyNDc2MTQ0NDIuOTU5MTE2NjAw',
new Set(['1 library']),
],
[
'MTk0YzBmOTgtNjA4My1jNWE4LTYzNjYtZjVkNzI2YWU2YmIyLTMxMTYtMTMyNDcyNDk0MjQuOTg4ODI4NjAw',
new Set(['157 file', '520 registry']),
],
[
'MTk0YzBmOTgtNjA4My1jNWE4LTYzNjYtZjVkNzI2YWU2YmIyLTUwODQtMTMyNDc2MTQ0NDIuOTcyODQ3MjAw',
new Set(),
],
[
'MTk0YzBmOTgtNjA4My1jNWE4LTYzNjYtZjVkNzI2YWU2YmIyLTg2OTYtMTMyNDc2MTQ0MjEuNjc1MzY0OTAw',
new Set(['3 file']),
],
[
'MTk0YzBmOTgtNjA4My1jNWE4LTYzNjYtZjVkNzI2YWU2YmIyLTcyNjAtMTMyNDc2MTQ0MjIuMjQwNDI2MTAw',
new Set(),
],
[
'MTk0YzBmOTgtNjA4My1jNWE4LTYzNjYtZjVkNzI2YWU2YmIyLTczMDAtMTMyNDc2MTQ0MjEuNjg2NzI4NTAw',
new Set(),
],
]);
it('Check Related Events for event.registry Node', async () => {
await pageObjects.hosts.navigateToEventsPanel();
await pageObjects.hosts.executeQueryAndOpenResolver(
'event.dataset : endpoint.events.registry'
);
await pageObjects.hosts.runNodeEvents(expectedData);
});
it('Check Related Events for event.network Node', async () => {
await pageObjects.hosts.navigateToEventsPanel();
await pageObjects.hosts.executeQueryAndOpenResolver(
'event.dataset : endpoint.events.network'
);
await pageObjects.hosts.runNodeEvents(expectedData);
});
it('Check Related Events for event.library Node', async () => {
await esArchiver.load('empty_kibana');
await esArchiver.load('endpoint/resolver_tree/library_events', { useCreate: true });
await queryBar.setQuery('');
await queryBar.submitQuery();
const expectedLibraryData = [
'1 authentication',
'1 session',
'329 network',
'1 library',
'1 library',
];
await pageObjects.hosts.navigateToEventsPanel();
await pageObjects.hosts.executeQueryAndOpenResolver(
'event.dataset : endpoint.events.library'
);
// This lines will move the resolver view for clear visibility of the related events.
for (let i = 0; i < 7; i++) {
await (await testSubjects.find('resolver:graph-controls:west-button')).click();
}
await pageObjects.hosts.runNodeEvents(expectedLibraryData);
});
it('Check Related Events for event.alert Node', async () => {
await esArchiver.load('empty_kibana');
await esArchiver.load('endpoint/resolver_tree/alert_events', { useCreate: true });
await queryBar.setQuery('');
await queryBar.submitQuery();
const expectedAlertData = [
'1 library',
'157 file',
'520 registry',
'3 file',
'5 library',
'5 library',
];
await pageObjects.hosts.navigateToEventsPanel();
await pageObjects.hosts.executeQueryAndOpenResolver('event.dataset : endpoint.alerts');
await (await testSubjects.find('resolver:graph-controls:zoom-out')).click();
await browser.setWindowSize(2100, 1500);
for (let i = 0; i < 2; i++) {
await (await testSubjects.find('resolver:graph-controls:east-button')).click();
}
await pageObjects.hosts.runNodeEvents(expectedAlertData);
for (const [id, expectedPills] of expectedData.entries()) {
// center the node in the view
await pageObjects.hosts.clickNodeLinkInPanel(id);
await verifyPills(id, expectedPills);
}
});
});
});
});

View file

@ -4,13 +4,17 @@
* you may not use this file except in compliance with the Elastic License.
*/
import expect from '@kbn/expect';
import { WebElementWrapper } from 'test/functional/services/lib/web_element_wrapper';
import { nudgeAnimationDuration } from '../../../plugins/security_solution/public/resolver/store/camera/scaling_constants';
import { FtrProviderContext } from '../ftr_provider_context';
import { deleteEventsStream } from '../../security_solution_endpoint_api_int/apis/data_stream_helper';
import { deleteAlertsStream } from '../../security_solution_endpoint_api_int/apis/data_stream_helper';
import { deleteMetadataStream } from '../../security_solution_endpoint_api_int/apis/data_stream_helper';
import { deletePolicyStream } from '../../security_solution_endpoint_api_int/apis/data_stream_helper';
import { deleteTelemetryStream } from '../../security_solution_endpoint_api_int/apis/data_stream_helper';
import {
deleteEventsStream,
deleteAlertsStream,
deleteMetadataStream,
deletePolicyStream,
deleteTelemetryStream,
} from '../../security_solution_endpoint_api_int/apis/data_stream_helper';
export interface DataStyle {
left: string;
top: string;
@ -22,6 +26,109 @@ export function SecurityHostsPageProvider({ getService, getPageObjects }: FtrPro
const pageObjects = getPageObjects(['common', 'header']);
const testSubjects = getService('testSubjects');
const queryBar = getService('queryBar');
const find = getService('find');
/**
* Returns the node IDs for the visible nodes in the resolver graph.
*/
const findVisibleNodeIDs = async (): Promise<string[]> => {
const visibleNodes = await testSubjects.findAll('resolver:node');
return Promise.all(
visibleNodes.map(async (node: WebElementWrapper) => {
return node.getAttribute('data-test-resolver-node-id');
})
);
};
/**
* This assumes you are on the process list in the panel and will find and click the node
* with the given ID to bring it into view in the graph.
*
* @param id the ID of the node to find and click.
*/
const clickNodeLinkInPanel = async (id: string): Promise<void> => {
await navigateToProcessListInPanel();
const panelNodeButton = await find.byCssSelector(
`[data-test-subj='resolver:node-list:node-link'][data-test-node-id='${id}']`
);
await panelNodeButton?.click();
// ensure that we wait longer than the animation time
await pageObjects.common.sleep(nudgeAnimationDuration * 2);
};
/**
* Finds all the pills for a particular node.
*
* @param id the ID of the node
*/
const findNodePills = async (id: string): Promise<WebElementWrapper[]> => {
return testSubjects.findAllDescendant(
'resolver:map:node-submenu-item',
await find.byCssSelector(
`[data-test-subj='resolver:node'][data-test-resolver-node-id='${id}']`
)
);
};
/**
* Navigate back to the process list view in the panel.
*/
const navigateToProcessListInPanel = async () => {
const [
isOnNodeListPage,
isOnCategoryPage,
isOnNodeDetailsPage,
isOnRelatedEventDetailsPage,
] = await Promise.all([
testSubjects.exists('resolver:node-list', { timeout: 1 }),
testSubjects.exists('resolver:node-events-in-category:breadcrumbs:node-list-link', {
timeout: 1,
}),
testSubjects.exists('resolver:node-detail:breadcrumbs:node-list-link', { timeout: 1 }),
testSubjects.exists('resolver:event-detail:breadcrumbs:node-list-link', { timeout: 1 }),
]);
if (isOnNodeListPage) {
return;
} else if (isOnCategoryPage) {
await (
await testSubjects.find('resolver:node-events-in-category:breadcrumbs:node-list-link')
).click();
} else if (isOnNodeDetailsPage) {
await (await testSubjects.find('resolver:node-detail:breadcrumbs:node-list-link')).click();
} else if (isOnRelatedEventDetailsPage) {
await (await testSubjects.find('resolver:event-detail:breadcrumbs:node-list-link')).click();
} else {
// unknown page
return;
}
await pageObjects.common.sleep(100);
};
/**
* Click the zoom out control.
*/
const clickZoomOut = async () => {
await (await testSubjects.find('resolver:graph-controls:zoom-out')).click();
};
/**
* Navigate to Events Panel
*/
const navigateToEventsPanel = async () => {
const isFullScreen = await testSubjects.exists('exit-full-screen', { timeout: 400 });
if (isFullScreen) {
await (await testSubjects.find('exit-full-screen')).click();
}
if (!(await testSubjects.exists('investigate-in-resolver-button', { timeout: 400 }))) {
await (await testSubjects.find('navigation-hosts')).click();
await testSubjects.click('navigation-events');
await testSubjects.existOrFail('event');
}
};
/**
* @function parseStyles
@ -54,101 +161,82 @@ export function SecurityHostsPageProvider({ getService, getPageObjects }: FtrPro
}),
{}
);
return {
/**
* Navigate to the Security Hosts page
*/
async navigateToSecurityHostsPage() {
await pageObjects.common.navigateToUrlWithBrowserHistory('security', '/hosts/AllHosts');
await pageObjects.header.waitUntilLoadingHasFinished();
},
/**
* Finds a table and returns the data in a nested array with row 0 is the headers if they exist.
* It uses euiTableCellContent to avoid poluting the array data with the euiTableRowCell__mobileHeader data.
* @param dataTestSubj
* @param element
* @returns Promise<string[][]>
*/
async getEndpointEventResolverNodeData(dataTestSubj: string, element: string) {
await testSubjects.exists(dataTestSubj);
const Elements = await testSubjects.findAll(dataTestSubj);
const $ = [];
for (const value of Elements) {
$.push(await value.getAttribute(element));
}
return $;
},
/**
* Gets a array of not parsed styles and returns the Array of parsed styles.
* @returns Promise<string[][]>
*/
async parseStyles() {
const tableData = await this.getEndpointEventResolverNodeData('resolver:node', 'style');
const styles: DataStyle[] = [];
for (let i = 1; i < tableData.length; i++) {
const eachStyle = parseStyle(tableData[i]);
styles.push({
top: eachStyle.top ?? '',
height: eachStyle.height ?? '',
left: eachStyle.left ?? '',
width: eachStyle.width ?? '',
});
}
return styles;
},
/**
* Deletes DataStreams from Index Management.
*/
async deleteDataStreams() {
await deleteEventsStream(getService);
await deleteAlertsStream(getService);
await deletePolicyStream(getService);
await deleteMetadataStream(getService);
await deleteTelemetryStream(getService);
},
/**
* Runs Nodes Events
*/
async runNodeEvents(expectedData: string[]) {
await testSubjects.exists('resolver:submenu:button', { timeout: 400 });
const NodeSubmenuButtons = await testSubjects.findAll('resolver:submenu:button');
for (let b = 0; b < NodeSubmenuButtons.length; b++) {
await (await testSubjects.findAll('resolver:submenu:button'))[b].click();
}
await testSubjects.exists('resolver:map:node-submenu-item', { timeout: 400 });
const NodeSubmenuItems = await testSubjects.findAll('resolver:map:node-submenu-item');
for (let i = 0; i < NodeSubmenuItems.length; i++) {
await (await testSubjects.findAll('resolver:map:node-submenu-item'))[i].click();
const Events = await testSubjects.findAll('resolver:map:node-submenu-item');
// this sleep is for the AMP enabled run
await pageObjects.common.sleep(300);
const EventName = await Events[i]._webElement.getText();
const LinkText = await testSubjects.find('resolver:breadcrumbs:last');
const linkText = await LinkText._webElement.getText();
expect(EventName).to.equal(linkText);
expect(EventName).to.equal(expectedData[i]);
}
await testSubjects.click('full-screen');
},
/**
* Navigate to Events Panel
*/
async navigateToEventsPanel() {
if (!(await testSubjects.exists('investigate-in-resolver-button', { timeout: 400 }))) {
await (await testSubjects.find('navigation-hosts')).click();
await testSubjects.click('navigation-events');
await testSubjects.existOrFail('event');
}
},
/**
* execute Query And Open Resolver
*/
async executeQueryAndOpenResolver(query: string) {
await queryBar.setQuery(query);
await queryBar.submitQuery();
await testSubjects.click('full-screen');
await testSubjects.click('investigate-in-resolver-button');
},
/**
* Navigate to the Security Hosts page
*/
const navigateToSecurityHostsPage = async () => {
await pageObjects.common.navigateToUrlWithBrowserHistory('security', '/hosts/AllHosts');
await pageObjects.header.waitUntilLoadingHasFinished();
};
/**
* Finds a table and returns the data in a nested array with row 0 is the headers if they exist.
* It uses euiTableCellContent to avoid polluting the array data with the euiTableRowCell__mobileHeader data.
* @param dataTestSubj
* @param element
* @returns Promise<string[][]>
*/
const getEndpointEventResolverNodeData = async (dataTestSubj: string, element: string) => {
await testSubjects.exists(dataTestSubj);
const Elements = await testSubjects.findAll(dataTestSubj);
const $ = [];
for (const value of Elements) {
$.push(await value.getAttribute(element));
}
return $;
};
/**
* Gets a array of not parsed styles and returns the Array of parsed styles.
* @returns Promise<string[][]>
*/
const parseStyles = async () => {
const tableData = await getEndpointEventResolverNodeData('resolver:node', 'style');
const styles: DataStyle[] = [];
for (let i = 1; i < tableData.length; i++) {
const eachStyle = parseStyle(tableData[i]);
styles.push({
top: eachStyle.top ?? '',
height: eachStyle.height ?? '',
left: eachStyle.left ?? '',
width: eachStyle.width ?? '',
});
}
return styles;
};
/**
* Deletes DataStreams from Index Management.
*/
const deleteDataStreams = async () => {
await deleteEventsStream(getService);
await deleteAlertsStream(getService);
await deletePolicyStream(getService);
await deleteMetadataStream(getService);
await deleteTelemetryStream(getService);
};
/**
* execute Query And Open Resolver
*/
const executeQueryAndOpenResolver = async (query: string) => {
await navigateToEventsPanel();
await queryBar.setQuery(query);
await queryBar.submitQuery();
await testSubjects.click('full-screen');
await testSubjects.click('investigate-in-resolver-button');
};
return {
navigateToProcessListInPanel,
findNodePills,
clickNodeLinkInPanel,
findVisibleNodeIDs,
clickZoomOut,
navigateToEventsPanel,
navigateToSecurityHostsPage,
parseStyles,
deleteDataStreams,
executeQueryAndOpenResolver,
};
}