[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:
parent
20638a64e2
commit
d4a631cf8e
|
@ -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,
|
||||
},
|
||||
{
|
||||
|
|
|
@ -262,6 +262,7 @@ const NodeEventsInCategoryBreadcrumbs = memo(function ({
|
|||
defaultMessage: 'Events',
|
||||
}
|
||||
),
|
||||
'data-test-subj': 'resolver:node-events-in-category:breadcrumbs:node-list-link',
|
||||
...nodesLinkNavProps,
|
||||
},
|
||||
{
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
||||
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('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();
|
||||
describe('endpoint.alerts filter', () => {
|
||||
before(async () => {
|
||||
await pageObjects.hosts.executeQueryAndOpenResolver('event.dataset : endpoint.alerts');
|
||||
await (await testSubjects.find('resolver:graph-controls:zoom-out')).click();
|
||||
await pageObjects.hosts.clickZoomOut();
|
||||
await browser.setWindowSize(2100, 1500);
|
||||
for (let i = 0; i < 2; i++) {
|
||||
await (await testSubjects.find('resolver:graph-controls:east-button')).click();
|
||||
});
|
||||
|
||||
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(),
|
||||
],
|
||||
]);
|
||||
|
||||
for (const [id, expectedPills] of expectedData.entries()) {
|
||||
// center the node in the view
|
||||
await pageObjects.hosts.clickNodeLinkInPanel(id);
|
||||
await verifyPills(id, expectedPills);
|
||||
}
|
||||
await pageObjects.hosts.runNodeEvents(expectedAlertData);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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,22 +161,23 @@ export function SecurityHostsPageProvider({ getService, getPageObjects }: FtrPro
|
|||
}),
|
||||
{}
|
||||
);
|
||||
return {
|
||||
|
||||
/**
|
||||
* Navigate to the Security Hosts page
|
||||
*/
|
||||
async navigateToSecurityHostsPage() {
|
||||
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 poluting the array data with the euiTableRowCell__mobileHeader data.
|
||||
* It uses euiTableCellContent to avoid polluting the array data with the euiTableRowCell__mobileHeader data.
|
||||
* @param dataTestSubj
|
||||
* @param element
|
||||
* @returns Promise<string[][]>
|
||||
*/
|
||||
async getEndpointEventResolverNodeData(dataTestSubj: string, element: string) {
|
||||
const getEndpointEventResolverNodeData = async (dataTestSubj: string, element: string) => {
|
||||
await testSubjects.exists(dataTestSubj);
|
||||
const Elements = await testSubjects.findAll(dataTestSubj);
|
||||
const $ = [];
|
||||
|
@ -77,14 +185,14 @@ export function SecurityHostsPageProvider({ getService, getPageObjects }: FtrPro
|
|||
$.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 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]);
|
||||
|
@ -96,59 +204,39 @@ export function SecurityHostsPageProvider({ getService, getPageObjects }: FtrPro
|
|||
});
|
||||
}
|
||||
return styles;
|
||||
},
|
||||
};
|
||||
/**
|
||||
* Deletes DataStreams from Index Management.
|
||||
*/
|
||||
async deleteDataStreams() {
|
||||
const deleteDataStreams = async () => {
|
||||
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) {
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue