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

View file

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

View file

@ -304,7 +304,11 @@ export const ColumnHeadersComponent = ({
} }
className={fullScreen ? FULL_SCREEN_TOGGLED_CLASS_NAME : ''} className={fullScreen ? FULL_SCREEN_TOGGLED_CLASS_NAME : ''}
color={fullScreen ? 'ghost' : 'primary'} 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" iconType="fullScreen"
onClick={toggleFullScreen} onClick={toggleFullScreen}
/> />

View file

@ -12,10 +12,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
const testSubjects = getService('testSubjects'); const testSubjects = getService('testSubjects');
const esArchiver = getService('esArchiver'); const esArchiver = getService('esArchiver');
const browser = getService('browser'); const browser = getService('browser');
const queryBar = getService('queryBar');
// FLAKY: https://github.com/elastic/kibana/issues/85085 describe('Endpoint Event Resolver', function () {
describe.skip('Endpoint Event Resolver', function () {
before(async () => { before(async () => {
await pageObjects.hosts.navigateToSecurityHostsPage(); await pageObjects.hosts.navigateToSecurityHostsPage();
await pageObjects.common.dismissBanner(); await pageObjects.common.dismissBanner();
@ -28,7 +26,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
before(async () => { before(async () => {
await esArchiver.load('empty_kibana'); await esArchiver.load('empty_kibana');
await esArchiver.load('endpoint/resolver_tree/functions', { useCreate: true }); await esArchiver.load('endpoint/resolver_tree/functions', { useCreate: true });
await pageObjects.hosts.navigateToEventsPanel();
await pageObjects.hosts.executeQueryAndOpenResolver('event.dataset : endpoint.events.file'); await pageObjects.hosts.executeQueryAndOpenResolver('event.dataset : endpoint.events.file');
}); });
after(async () => { after(async () => {
@ -194,114 +191,74 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
} }
await (await testSubjects.find('resolver:graph-controls:zoom-in')).click(); 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 () { describe('node related event pills', function () {
const expectedData = [ /**
'17 authentication', * Verifies that the pills of a node have the correct text.
'1 registry', *
'17 session', * @param id the node ID to verify the pills for.
'80 registry', * @param expectedPills a map of expected pills for all nodes
'8 network', */
'60 registry', 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 () => { before(async () => {
await esArchiver.load('empty_kibana'); await esArchiver.load('empty_kibana');
await esArchiver.load('endpoint/resolver_tree/events', { useCreate: true }); await esArchiver.load('endpoint/resolver_tree/alert_events', { useCreate: true });
await queryBar.setQuery('');
await queryBar.submitQuery();
}); });
after(async () => { after(async () => {
await pageObjects.hosts.deleteDataStreams(); await pageObjects.hosts.deleteDataStreams();
}); });
it('Check Related Events for event.process Node', async () => { describe('endpoint.alerts filter', () => {
await pageObjects.hosts.navigateToEventsPanel(); before(async () => {
await pageObjects.hosts.executeQueryAndOpenResolver( await pageObjects.hosts.executeQueryAndOpenResolver('event.dataset : endpoint.alerts');
'event.dataset : endpoint.events.process' await pageObjects.hosts.clickZoomOut();
); await browser.setWindowSize(2100, 1500);
await pageObjects.hosts.runNodeEvents(expectedData); });
});
it('Check Related Events for event.security Node', async () => { it('has the correct pill text', async () => {
await pageObjects.hosts.navigateToEventsPanel(); const expectedData: Map<string, Set<string>> = new Map([
await pageObjects.hosts.executeQueryAndOpenResolver( [
'event.dataset : endpoint.events.security' 'MTk0YzBmOTgtNjA4My1jNWE4LTYzNjYtZjVkNzI2YWU2YmIyLTc2MzYtMTMyNDc2MTQ0NDIuOTU5MTE2NjAw',
); new Set(['1 library']),
await pageObjects.hosts.runNodeEvents(expectedData); ],
}); [
'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 () => { for (const [id, expectedPills] of expectedData.entries()) {
await pageObjects.hosts.navigateToEventsPanel(); // center the node in the view
await pageObjects.hosts.executeQueryAndOpenResolver( await pageObjects.hosts.clickNodeLinkInPanel(id);
'event.dataset : endpoint.events.registry' await verifyPills(id, expectedPills);
); }
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);
}); });
}); });
}); });

View file

@ -4,13 +4,17 @@
* you may not use this file except in compliance with the Elastic License. * 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 { FtrProviderContext } from '../ftr_provider_context';
import { deleteEventsStream } from '../../security_solution_endpoint_api_int/apis/data_stream_helper'; import {
import { deleteAlertsStream } from '../../security_solution_endpoint_api_int/apis/data_stream_helper'; deleteEventsStream,
import { deleteMetadataStream } from '../../security_solution_endpoint_api_int/apis/data_stream_helper'; deleteAlertsStream,
import { deletePolicyStream } from '../../security_solution_endpoint_api_int/apis/data_stream_helper'; deleteMetadataStream,
import { deleteTelemetryStream } from '../../security_solution_endpoint_api_int/apis/data_stream_helper'; deletePolicyStream,
deleteTelemetryStream,
} from '../../security_solution_endpoint_api_int/apis/data_stream_helper';
export interface DataStyle { export interface DataStyle {
left: string; left: string;
top: string; top: string;
@ -22,6 +26,109 @@ export function SecurityHostsPageProvider({ getService, getPageObjects }: FtrPro
const pageObjects = getPageObjects(['common', 'header']); const pageObjects = getPageObjects(['common', 'header']);
const testSubjects = getService('testSubjects'); const testSubjects = getService('testSubjects');
const queryBar = getService('queryBar'); 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 * @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. * Navigate to the Security Hosts page
* @returns Promise<string[][]> */
*/ const navigateToSecurityHostsPage = async () => {
async parseStyles() { await pageObjects.common.navigateToUrlWithBrowserHistory('security', '/hosts/AllHosts');
const tableData = await this.getEndpointEventResolverNodeData('resolver:node', 'style'); await pageObjects.header.waitUntilLoadingHasFinished();
const styles: DataStyle[] = []; };
for (let i = 1; i < tableData.length; i++) {
const eachStyle = parseStyle(tableData[i]); /**
styles.push({ * Finds a table and returns the data in a nested array with row 0 is the headers if they exist.
top: eachStyle.top ?? '', * It uses euiTableCellContent to avoid polluting the array data with the euiTableRowCell__mobileHeader data.
height: eachStyle.height ?? '', * @param dataTestSubj
left: eachStyle.left ?? '', * @param element
width: eachStyle.width ?? '', * @returns Promise<string[][]>
}); */
} const getEndpointEventResolverNodeData = async (dataTestSubj: string, element: string) => {
return styles; await testSubjects.exists(dataTestSubj);
}, const Elements = await testSubjects.findAll(dataTestSubj);
/** const $ = [];
* Deletes DataStreams from Index Management. for (const value of Elements) {
*/ $.push(await value.getAttribute(element));
async deleteDataStreams() { }
await deleteEventsStream(getService); return $;
await deleteAlertsStream(getService); };
await deletePolicyStream(getService);
await deleteMetadataStream(getService); /**
await deleteTelemetryStream(getService); * Gets a array of not parsed styles and returns the Array of parsed styles.
}, * @returns Promise<string[][]>
/** */
* Runs Nodes Events const parseStyles = async () => {
*/ const tableData = await getEndpointEventResolverNodeData('resolver:node', 'style');
async runNodeEvents(expectedData: string[]) { const styles: DataStyle[] = [];
await testSubjects.exists('resolver:submenu:button', { timeout: 400 }); for (let i = 1; i < tableData.length; i++) {
const NodeSubmenuButtons = await testSubjects.findAll('resolver:submenu:button'); const eachStyle = parseStyle(tableData[i]);
for (let b = 0; b < NodeSubmenuButtons.length; b++) { styles.push({
await (await testSubjects.findAll('resolver:submenu:button'))[b].click(); top: eachStyle.top ?? '',
} height: eachStyle.height ?? '',
await testSubjects.exists('resolver:map:node-submenu-item', { timeout: 400 }); left: eachStyle.left ?? '',
const NodeSubmenuItems = await testSubjects.findAll('resolver:map:node-submenu-item'); width: eachStyle.width ?? '',
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'); return styles;
// this sleep is for the AMP enabled run };
await pageObjects.common.sleep(300); /**
const EventName = await Events[i]._webElement.getText(); * Deletes DataStreams from Index Management.
const LinkText = await testSubjects.find('resolver:breadcrumbs:last'); */
const linkText = await LinkText._webElement.getText(); const deleteDataStreams = async () => {
expect(EventName).to.equal(linkText); await deleteEventsStream(getService);
expect(EventName).to.equal(expectedData[i]); await deleteAlertsStream(getService);
} await deletePolicyStream(getService);
await testSubjects.click('full-screen'); await deleteMetadataStream(getService);
}, await deleteTelemetryStream(getService);
/** };
* Navigate to Events Panel
*/ /**
async navigateToEventsPanel() { * execute Query And Open Resolver
if (!(await testSubjects.exists('investigate-in-resolver-button', { timeout: 400 }))) { */
await (await testSubjects.find('navigation-hosts')).click(); const executeQueryAndOpenResolver = async (query: string) => {
await testSubjects.click('navigation-events'); await navigateToEventsPanel();
await testSubjects.existOrFail('event'); await queryBar.setQuery(query);
} await queryBar.submitQuery();
}, await testSubjects.click('full-screen');
/** await testSubjects.click('investigate-in-resolver-button');
* execute Query And Open Resolver };
*/
async executeQueryAndOpenResolver(query: string) { return {
await queryBar.setQuery(query); navigateToProcessListInPanel,
await queryBar.submitQuery(); findNodePills,
await testSubjects.click('full-screen'); clickNodeLinkInPanel,
await testSubjects.click('investigate-in-resolver-button'); findVisibleNodeIDs,
}, clickZoomOut,
navigateToEventsPanel,
navigateToSecurityHostsPage,
parseStyles,
deleteDataStreams,
executeQueryAndOpenResolver,
}; };
} }