[Endpoint] Re-enable Functional test case for Endpoint related pages (#68445)

* Improve Policy test service provider
  - Added `getFullAgentConfig()` to Endpoint Policy Test data provider service
* enable Policy List functional tests
* Added Policy Details Tests
* Add test ids to policy detail Max and Linux forms
* Added page objects utilities and moved `clickOnEuiCheckbox` there
This commit is contained in:
Paul Tavares 2020-06-09 13:47:00 -04:00 committed by GitHub
parent 45c81ce1ab
commit 14410e0c79
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 524 additions and 286 deletions

View file

@ -68,11 +68,13 @@ export const PolicyDetails = React.memo(() => {
}
),
body: (
<FormattedMessage
id="xpack.securitySolution.endpoint.policy.details.updateSuccessMessage"
defaultMessage="Policy {name} has been updated."
values={{ name: policyName }}
/>
<span data-test-subj="policyDetailsSuccessMessage">
<FormattedMessage
id="xpack.securitySolution.endpoint.policy.details.updateSuccessMessage"
defaultMessage="Policy {name} has been updated."
values={{ name: policyName }}
/>
</span>
),
});
} else {
@ -116,7 +118,7 @@ export const PolicyDetails = React.memo(() => {
<EuiLoadingSpinner size="xl" />
) : policyApiError ? (
<EuiCallOut color="danger" title={policyApiError?.error}>
{policyApiError?.message}
<span data-test-subj="policyDetailsIdNotFoundMessage">{policyApiError?.message}</span>
</EuiCallOut>
) : null}
<SpyRoute />

View file

@ -5,23 +5,25 @@
*/
import React, { useCallback, useMemo } from 'react';
import { EuiCheckbox, htmlIdGenerator } from '@elastic/eui';
import { EuiCheckbox, EuiCheckboxProps, htmlIdGenerator } from '@elastic/eui';
import { useDispatch } from 'react-redux';
import { usePolicyDetailsSelector } from '../../policy_hooks';
import { policyConfig } from '../../../store/policy_details/selectors';
import { PolicyDetailsAction } from '../../../store/policy_details';
import { UIPolicyConfig } from '../../../../../../../common/endpoint/types';
type EventsCheckboxProps = Omit<EuiCheckboxProps, 'id' | 'label' | 'checked' | 'onChange'> & {
name: string;
setter: (config: UIPolicyConfig, checked: boolean) => UIPolicyConfig;
getter: (config: UIPolicyConfig) => boolean;
};
export const EventsCheckbox = React.memo(function ({
name,
setter,
getter,
}: {
name: string;
setter: (config: UIPolicyConfig, checked: boolean) => UIPolicyConfig;
getter: (config: UIPolicyConfig) => boolean;
}) {
...otherProps
}: EventsCheckboxProps) {
const policyDetailsConfig = usePolicyDetailsSelector(policyConfig);
const selected = getter(policyDetailsConfig);
const dispatch = useDispatch<(action: PolicyDetailsAction) => void>();
@ -44,6 +46,7 @@ export const EventsCheckbox = React.memo(function ({
label={name}
checked={selected}
onChange={handleCheckboxChange}
{...otherProps}
/>
);
});

View file

@ -73,6 +73,7 @@ export const LinuxEvents = React.memo(() => {
<EventsCheckbox
name={item.name}
key={index}
data-test-subj={`policyLinuxEvent_${item.protectionField}`}
setter={(config, checked) =>
setIn(config)(item.os)('events')(item.protectionField)(checked)
}

View file

@ -73,6 +73,7 @@ export const MacEvents = React.memo(() => {
<EventsCheckbox
name={item.name}
key={index}
data-test-subj={`policyMacEvent_${item.protectionField}`}
setter={(config, checked) =>
setIn(config)(item.os)('events')(item.protectionField)(checked)
}

View file

@ -113,6 +113,7 @@ export const WindowsEvents = React.memo(() => {
<EventsCheckbox
name={item.name}
key={index}
data-test-subj={`policyWindowsEvent_${item.protectionField}`}
setter={(config, checked) =>
setIn(config)(item.os)('events')(item.protectionField)(checked)
}

View file

@ -6,6 +6,7 @@
const alwaysImportedTests = [
require.resolve('../test/functional/config.js'),
require.resolve('../test/functional_endpoint/config.ts'),
require.resolve('../test/functional_with_es_ssl/config.ts'),
require.resolve('../test/functional/config_security_basic.ts'),
require.resolve('../test/functional/config_security_trial.ts'),

View file

@ -13,68 +13,71 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
const testSubjects = getService('testSubjects');
// FLAKY: https://github.com/elastic/kibana/issues/63621
describe.skip('host list', function () {
describe.skip('endpoint list', function () {
this.tags('ciGroup7');
const sleep = (ms = 100) => new Promise((resolve) => setTimeout(resolve, ms));
before(async () => {
await esArchiver.load('endpoint/metadata/api_feature');
await pageObjects.common.navigateToUrlWithBrowserHistory('endpoint', '/hosts');
await pageObjects.header.waitUntilLoadingHasFinished();
await pageObjects.endpoint.navigateToEndpointList();
});
it('finds title', async () => {
const title = await testSubjects.getVisibleText('hostListTitle');
expect(title).to.equal('Hosts');
const title = await testSubjects.getVisibleText('pageViewHeaderLeftTitle');
expect(title).to.equal('Endpoints');
});
it('displays table data', async () => {
const expectedData = [
[
'Hostname',
'Host Status',
'Policy',
'Policy Status',
'Alerts',
'Operating System',
'IP Address',
'Sensor Version',
'Version',
'Last Active',
],
[
'cadmann-4.example.com',
'Error',
'Policy Name',
'Policy Status',
'0',
'windows 10.0',
'10.192.213.130, 10.70.28.129',
'version',
'xxxx',
'6.6.1',
'Jan 24, 2020 @ 16:06:09.541',
],
[
'thurlow-9.example.com',
'Error',
'Policy Name',
'Policy Status',
'0',
'windows 10.0',
'10.46.229.234',
'version',
'xxxx',
'6.0.0',
'Jan 24, 2020 @ 16:06:09.541',
],
[
'rezzani-7.example.com',
'Error',
'Policy Name',
'Policy Status',
'0',
'windows 10.0',
'10.101.149.26, 2606:a000:ffc0:39:11ef:37b9:3371:578c',
'version',
'xxxx',
'6.8.0',
'Jan 24, 2020 @ 16:06:09.541',
],
];
const tableData = await pageObjects.endpoint.getEndpointAppTableData('hostListTable');
expect(tableData).to.eql(expectedData);
});
it('no details flyout when host page displayed', async () => {
it('no details flyout when endpoint page displayed', async () => {
await testSubjects.missingOrFail('hostDetailsFlyout');
});
@ -108,22 +111,21 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
await (await testSubjects.findAll('hostnameCellLink'))[1].click();
await sleep(500); // give page time to refresh and verify it did not change
const hostDetailTitleNew = await testSubjects.getVisibleText('hostDetailsFlyoutTitle');
expect(hostDetailTitleNew).to.eql(hostDetailTitleInitial);
expect(hostDetailTitleNew).to.equal(hostDetailTitleInitial);
});
describe('no data', () => {
before(async () => {
// clear out the data and reload the page
await esArchiver.unload('endpoint/metadata/api_feature');
await pageObjects.common.navigateToUrlWithBrowserHistory('endpoint', '/hosts');
await pageObjects.header.waitUntilLoadingHasFinished();
await pageObjects.endpoint.navigateToEndpointList();
});
after(async () => {
// reload the data so the other tests continue to pass
await esArchiver.load('endpoint/metadata/api_feature');
});
it('displays no items found when empty', async () => {
// get the host list table data and verify message
// get the endpoint list table data and verify message
const [, [noItemsFoundMessage]] = await pageObjects.endpoint.getEndpointAppTableData(
'hostListTable'
);
@ -133,12 +135,9 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
describe('has a url with a host id', () => {
before(async () => {
await pageObjects.common.navigateToUrlWithBrowserHistory(
'endpoint',
'/hosts',
await pageObjects.endpoint.navigateToEndpointList(
'selected_host=fc0ff548-feba-41b6-8367-65e8790d0eaf'
);
await pageObjects.header.waitUntilLoadingHasFinished();
});
it('shows a flyout', async () => {
@ -168,7 +167,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
'',
'0',
'00000000-0000-0000-0000-000000000000',
'Successful',
'Unknown',
'10.101.149.262606:a000:ffc0:39:11ef:37b9:3371:578c',
'rezzani-7.example.com',
'6.8.0',

View file

@ -1,76 +0,0 @@
/*
* 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 expect from '@kbn/expect';
import { FtrProviderContext } from '../../../ftr_provider_context';
export default function ({ getPageObjects, getService }: FtrProviderContext) {
const pageObjects = getPageObjects(['common']);
const spacesService = getService('spaces');
const testSubjects = getService('testSubjects');
const appsMenu = getService('appsMenu');
describe('spaces', () => {
describe('space with no features disabled', () => {
before(async () => {
await spacesService.create({
id: 'custom_space',
name: 'custom_space',
disabledFeatures: [],
});
});
after(async () => {
await spacesService.delete('custom_space');
});
it('shows endpoint navlink', async () => {
await pageObjects.common.navigateToApp('home', {
basePath: '/s/custom_space',
});
const navLinks = (await appsMenu.readLinks()).map((link) => link.text);
expect(navLinks).to.contain('Endpoint');
});
it(`endpoint app shows 'Hello World'`, async () => {
await pageObjects.common.navigateToApp('endpoint', {
basePath: '/s/custom_space',
});
await testSubjects.existOrFail('welcomeTitle');
});
it(`endpoint hosts shows hosts lists page`, async () => {
await pageObjects.common.navigateToUrlWithBrowserHistory('endpoint', '/hosts', undefined, {
basePath: '/s/custom_space',
ensureCurrentUrl: false,
shouldLoginIfPrompted: false,
});
await testSubjects.existOrFail('hostPage');
});
});
describe('space with endpoint disabled', () => {
before(async () => {
await spacesService.create({
id: 'custom_space',
name: 'custom_space',
disabledFeatures: ['endpoint'],
});
});
after(async () => {
await spacesService.delete('custom_space');
});
it(`doesn't show endpoint navlink`, async () => {
await pageObjects.common.navigateToApp('home', {
basePath: '/s/custom_space',
});
const navLinks = (await appsMenu.readLinks()).map((link) => link.text);
expect(navLinks).not.to.contain('Endpoint');
});
});
});
}

View file

@ -1,13 +0,0 @@
/*
* 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 { FtrProviderContext } from '../../../ftr_provider_context';
export default function ({ loadTestFile }: FtrProviderContext) {
describe('feature controls', function () {
this.tags('skipFirefox');
loadTestFile(require.resolve('./endpoint_spaces'));
});
}

View file

@ -1,55 +0,0 @@
/*
* 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 expect from '@kbn/expect';
import { FtrProviderContext } from '../../ftr_provider_context';
export default ({ getPageObjects, getService }: FtrProviderContext) => {
const pageObjects = getPageObjects(['common', 'endpoint']);
const testSubjects = getService('testSubjects');
describe('Header nav', function () {
this.tags('ciGroup7');
before(async () => {
await pageObjects.common.navigateToApp('endpoint');
});
it('renders the tabs when the app loads', async () => {
const homeTabText = await testSubjects.getVisibleText('homeEndpointTab');
const hostsTabText = await testSubjects.getVisibleText('hostsEndpointTab');
const alertsTabText = await testSubjects.getVisibleText('alertsEndpointTab');
const policiesTabText = await testSubjects.getVisibleText('policiesEndpointTab');
expect(homeTabText.trim()).to.be('Home');
expect(hostsTabText.trim()).to.be('Hosts');
expect(alertsTabText.trim()).to.be('Alerts');
expect(policiesTabText.trim()).to.be('Policies');
});
it('renders the hosts page when the Hosts tab is selected', async () => {
await (await testSubjects.find('hostsEndpointTab')).click();
await testSubjects.existOrFail('hostPage');
});
it('renders the alerts page when the Alerts tab is selected', async () => {
await (await testSubjects.find('alertsEndpointTab')).click();
await testSubjects.existOrFail('alertListPage');
});
it('renders the policy page when Policy tab is selected', async () => {
await (await testSubjects.find('policiesEndpointTab')).click();
await testSubjects.existOrFail('policyListPage');
});
it('renders the home page when Home tab is selected after selecting another tab', async () => {
await (await testSubjects.find('hostsEndpointTab')).click();
await testSubjects.existOrFail('hostPage');
await (await testSubjects.find('homeEndpointTab')).click();
await testSubjects.existOrFail('welcomeTitle');
});
});
};

View file

@ -9,12 +9,11 @@ export default function ({ loadTestFile }: FtrProviderContext) {
describe('endpoint', function () {
this.tags('ciGroup7');
loadTestFile(require.resolve('./feature_controls'));
loadTestFile(require.resolve('./landing_page'));
loadTestFile(require.resolve('./header_nav'));
loadTestFile(require.resolve('./host_list'));
loadTestFile(require.resolve('./endpoint_list'));
loadTestFile(require.resolve('./policy_list'));
loadTestFile(require.resolve('./alerts'));
loadTestFile(require.resolve('./resolver'));
loadTestFile(require.resolve('./policy_details'));
// loadTestFile(require.resolve('./alerts'));
// loadTestFile(require.resolve('./resolver'));
});
}

View file

@ -1,29 +0,0 @@
/*
* 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 expect from '@kbn/expect';
import { FtrProviderContext } from '../../ftr_provider_context';
export default ({ getPageObjects, getService }: FtrProviderContext) => {
const pageObjects = getPageObjects(['common', 'endpoint']);
const testSubjects = getService('testSubjects');
describe('Endpoint landing page', function () {
this.tags('ciGroup7');
before(async () => {
await pageObjects.common.navigateToApp('endpoint');
});
it('Loads the endpoint app', async () => {
const welcomeEndpointMessage = await pageObjects.endpoint.welcomeEndpointTitle();
expect(welcomeEndpointMessage).to.be('Hello World');
});
it('Does not display a toast indicating that the ingest manager failed to initialize', async () => {
await testSubjects.missingOrFail('euiToastHeader');
});
});
};

View file

@ -0,0 +1,225 @@
/*
* 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 expect from '@kbn/expect';
import { FtrProviderContext } from '../../ftr_provider_context';
import { PolicyTestResourceInfo } from '../../services/endpoint_policy';
export default function ({ getPageObjects, getService }: FtrProviderContext) {
const pageObjects = getPageObjects(['common', 'endpoint', 'policy', 'endpointPageUtils']);
const testSubjects = getService('testSubjects');
const policyTestResources = getService('policyTestResources');
describe('When on the Endpoint Policy Details Page', function () {
this.tags(['ciGroup7']);
describe('with an invalid policy id', () => {
it('should display an error', async () => {
await pageObjects.policy.navigateToPolicyDetails('invalid-id');
await testSubjects.existOrFail('policyDetailsIdNotFoundMessage');
expect(await testSubjects.getVisibleText('policyDetailsIdNotFoundMessage')).to.equal(
'Saved object [ingest-datasources/invalid-id] not found'
);
});
});
describe('with a valid policy id', () => {
let policyInfo: PolicyTestResourceInfo;
before(async () => {
policyInfo = await policyTestResources.createPolicy();
await pageObjects.policy.navigateToPolicyDetails(policyInfo.datasource.id);
});
after(async () => {
if (policyInfo) {
await policyInfo.cleanup();
}
});
it('should display policy view', async () => {
expect(await testSubjects.getVisibleText('pageViewHeaderLeftTitle')).to.equal(
policyInfo.datasource.name
);
});
});
describe('and the save button is clicked', () => {
let policyInfo: PolicyTestResourceInfo;
beforeEach(async () => {
policyInfo = await policyTestResources.createPolicy();
await pageObjects.policy.navigateToPolicyDetails(policyInfo.datasource.id);
});
afterEach(async () => {
if (policyInfo) {
await policyInfo.cleanup();
}
});
it('should display success toast on successful save', async () => {
await pageObjects.endpointPageUtils.clickOnEuiCheckbox('policyWindowsEvent_dns');
await pageObjects.policy.confirmAndSave();
await testSubjects.existOrFail('policyDetailsSuccessMessage');
expect(await testSubjects.getVisibleText('policyDetailsSuccessMessage')).to.equal(
`Policy ${policyInfo.datasource.name} has been updated.`
);
});
it('should persist update on the screen', async () => {
await pageObjects.endpointPageUtils.clickOnEuiCheckbox('policyWindowsEvent_process');
await pageObjects.policy.confirmAndSave();
await testSubjects.existOrFail('policyDetailsSuccessMessage');
await pageObjects.policy.navigateToPolicyList();
await pageObjects.policy.navigateToPolicyDetails(policyInfo.datasource.id);
expect(await (await testSubjects.find('policyWindowsEvent_process')).isSelected()).to.equal(
false
);
});
it('should have updated policy data in overall agent configuration', async () => {
// This test ensures that updates made to the Endpoint Policy are carried all the way through
// to the generated Agent Configuration that is dispatch down to the Elastic Agent.
await Promise.all([
pageObjects.endpointPageUtils.clickOnEuiCheckbox('policyWindowsEvent_file'),
pageObjects.endpointPageUtils.clickOnEuiCheckbox('policyLinuxEvent_file'),
pageObjects.endpointPageUtils.clickOnEuiCheckbox('policyMacEvent_file'),
]);
await pageObjects.policy.confirmAndSave();
await testSubjects.existOrFail('policyDetailsSuccessMessage');
const agentFullConfig = await policyTestResources.getFullAgentConfig(
policyInfo.agentConfig.id
);
expect(agentFullConfig).to.eql({
datasources: [
{
enabled: true,
id: policyInfo.datasource.id,
inputs: [
{
enabled: true,
policy: {
linux: {
advanced: {
elasticsearch: {
indices: {
control: 'control-index',
event: 'event-index',
logging: 'logging-index',
},
kernel: {
connect: true,
process: true,
},
},
},
events: {
file: false,
network: true,
process: true,
},
logging: {
file: 'info',
stdout: 'debug',
},
},
mac: {
advanced: {
elasticsearch: {
indices: {
control: 'control-index',
event: 'event-index',
logging: 'logging-index',
},
kernel: {
connect: true,
process: true,
},
},
},
events: {
file: false,
network: true,
process: true,
},
logging: {
file: 'info',
stdout: 'debug',
},
malware: {
mode: 'detect',
},
},
windows: {
advanced: {
elasticsearch: {
indices: {
control: 'control-index',
event: 'event-index',
logging: 'logging-index',
},
kernel: {
connect: true,
process: true,
},
},
},
events: {
dll_and_driver_load: true,
dns: true,
file: false,
network: true,
process: true,
registry: true,
security: true,
},
logging: {
file: 'info',
stdout: 'debug',
},
malware: {
mode: 'prevent',
},
},
},
streams: [],
type: 'endpoint',
},
],
name: 'Protect East Coast',
namespace: 'default',
package: {
name: 'endpoint',
version: policyInfo.packageInfo.version,
},
use_output: 'default',
},
],
id: policyInfo.agentConfig.id,
outputs: {
default: {
hosts: ['http://localhost:9200'],
type: 'elasticsearch',
},
},
revision: 3,
settings: {
monitoring: {
enabled: false,
logs: false,
metrics: false,
},
},
});
});
});
});
}

View file

@ -8,15 +8,15 @@ import { FtrProviderContext } from '../../ftr_provider_context';
import { PolicyTestResourceInfo } from '../../services/endpoint_policy';
export default function ({ getPageObjects, getService }: FtrProviderContext) {
const pageObjects = getPageObjects(['common', 'endpoint']);
const pageObjects = getPageObjects(['common', 'endpoint', 'policy']);
const testSubjects = getService('testSubjects');
const policyTestResources = getService('policyTestResources');
const RELATIVE_DATE_FORMAT = /\d (?:seconds|minutes) ago/i;
// FLAKY: https://github.com/elastic/kibana/issues/66579
describe.skip('When on the Endpoint Policy List', function () {
describe('When on the Endpoint Policy List', function () {
this.tags(['ciGroup7']);
before(async () => {
await pageObjects.common.navigateToUrlWithBrowserHistory('endpoint', '/policy');
await pageObjects.policy.navigateToPolicyList();
});
it('loads the Policy List Page', async () => {
@ -34,10 +34,12 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
const allHeaderCells = await pageObjects.endpoint.tableHeaderVisibleText('policyTable');
expect(allHeaderCells).to.eql([
'Policy Name',
'Revision',
'Created By',
'Created Date',
'Last Updated By',
'Last Updated',
'Version',
'Description',
'Agent Configuration',
'Actions',
]);
});
it('should show empty table results message', async () => {
@ -47,13 +49,13 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
expect(noItemsFoundMessage).to.equal('No items found');
});
xdescribe('and policies exists', () => {
describe('and policies exists', () => {
let policyInfo: PolicyTestResourceInfo;
before(async () => {
// load/create a policy and then navigate back to the policy view so that the list is refreshed
policyInfo = await policyTestResources.createPolicy();
await pageObjects.common.navigateToUrlWithBrowserHistory('endpoint', '/policy');
await pageObjects.policy.navigateToPolicyList();
await pageObjects.endpoint.waitForTableToHaveData('policyTable');
});
after(async () => {
@ -64,26 +66,24 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
it('should show policy on the list', async () => {
const [, policyRow] = await pageObjects.endpoint.getEndpointAppTableData('policyTable');
expect(policyRow).to.eql([
'Protect East Coast',
'1',
'Elastic Endpoint v1.0.0',
'Protect the worlds data - but in the East Coast',
policyInfo.agentConfig.id,
// Validate row data with the exception of the Date columns - since those are initially
// shown as relative.
expect([policyRow[0], policyRow[1], policyRow[3], policyRow[5], policyRow[6]]).to.eql([
'Protect East Coastrev. 1',
'elastic',
'elastic',
`${policyInfo.datasource.package?.title} v${policyInfo.datasource.package?.version}`,
'',
]);
[policyRow[2], policyRow[4]].forEach((relativeDate) => {
expect(relativeDate).to.match(RELATIVE_DATE_FORMAT);
});
});
it('should show policy name as link', async () => {
const policyNameLink = await testSubjects.find('policyNameLink');
expect(await policyNameLink.getTagName()).to.equal('a');
expect(await policyNameLink.getAttribute('href')).to.match(
new RegExp(`\/endpoint\/policy\/${policyInfo.datasource.id}$`)
);
});
it('should show agent configuration as link', async () => {
const agentConfigLink = await testSubjects.find('agentConfigLink');
expect(await agentConfigLink.getTagName()).to.equal('a');
expect(await agentConfigLink.getAttribute('href')).to.match(
new RegExp(`\/app\/ingestManager\#\/configs\/${policyInfo.datasource.config_id}$`)
new RegExp(`\/management\/policy\/${policyInfo.datasource.id}$`)
);
});
});

View file

@ -7,11 +7,22 @@
import { WebElementWrapper } from 'test/functional/services/lib/web_element_wrapper';
import { FtrProviderContext } from '../ftr_provider_context';
export function EndpointPageProvider({ getService }: FtrProviderContext) {
export function EndpointPageProvider({ getService, getPageObjects }: FtrProviderContext) {
const testSubjects = getService('testSubjects');
const pageObjects = getPageObjects(['common', 'header']);
const retry = getService('retry');
return {
/**
* Navigate to the Endpoints list page
*/
async navigateToEndpointList(searchParams?: string) {
await pageObjects.common.navigateToApp('securitySolution', {
hash: `/management/endpoints${searchParams ? `?${searchParams}` : ''}`,
});
await pageObjects.header.waitUntilLoadingHasFinished();
},
/**
* Finds the Table with the given `selector` (test subject) and returns
* back an array containing the table's header column text
@ -31,10 +42,6 @@ export function EndpointPageProvider({ getService }: FtrProviderContext) {
);
},
async welcomeEndpointTitle() {
return await testSubjects.getVisibleText('welcomeTitle');
},
/**
* 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.

View file

@ -7,9 +7,13 @@
import { pageObjects as xpackFunctionalPageObjects } from '../../functional/page_objects';
import { EndpointPageProvider } from './endpoint_page';
import { EndpointAlertsPageProvider } from './endpoint_alerts_page';
import { EndpointPolicyPageProvider } from './policy_page';
import { EndpointPageUtils } from './page_utils';
export const pageObjects = {
...xpackFunctionalPageObjects,
endpoint: EndpointPageProvider,
policy: EndpointPolicyPageProvider,
endpointPageUtils: EndpointPageUtils,
endpointAlerts: EndpointAlertsPageProvider,
};

View file

@ -0,0 +1,30 @@
/*
* 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 { FtrProviderContext } from '../ftr_provider_context';
export function EndpointPageUtils({ getService }: FtrProviderContext) {
const find = getService('find');
return {
/**
* Finds a given EuiCheckbox by test subject and clicks on it
*
* @param euiCheckBoxTestId
*/
async clickOnEuiCheckbox(euiCheckBoxTestId: string) {
// This utility is needed because EuiCheckbox forwards the test subject on to
// the actual `<input>` which is not actually visible/accessible on the page.
// In order to actually cause the state of the checkbox to change, the `<label>`
// must be clicked.
const euiCheckboxLabelElement = await find.byXPath(
`//input[@data-test-subj='${euiCheckBoxTestId}']/../label`
);
await euiCheckboxLabelElement.click();
},
};
}

View file

@ -0,0 +1,59 @@
/*
* 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 { FtrProviderContext } from '../ftr_provider_context';
export function EndpointPolicyPageProvider({ getService, getPageObjects }: FtrProviderContext) {
const pageObjects = getPageObjects(['common', 'header']);
const testSubjects = getService('testSubjects');
return {
/**
* Navigates to the Endpoint Policy List
*/
async navigateToPolicyList() {
await pageObjects.common.navigateToApp('securitySolution', { hash: '/management/policy' });
await pageObjects.header.waitUntilLoadingHasFinished();
},
/**
* Navigates to the Endpoint Policy Details page
*
* @param policyId
*/
async navigateToPolicyDetails(policyId: string) {
await pageObjects.common.navigateToApp('securitySolution', {
hash: `/management/policy/${policyId}`,
});
await pageObjects.header.waitUntilLoadingHasFinished();
},
/**
* Finds and returns the Policy Details Page Save button
*/
async findSaveButton() {
await this.ensureIsOnDetailsPage();
return await testSubjects.find('policyDetailsSaveButton');
},
/**
* ensures that the Details Page is the currently display view
*/
async ensureIsOnDetailsPage() {
await testSubjects.existOrFail('policyDetailsPage');
},
/**
* Clicks Save button and confirms update on the Policy Details page
*/
async confirmAndSave() {
await this.ensureIsOnDetailsPage();
await (await this.findSaveButton()).click();
await testSubjects.existOrFail('policyDetailsConfirmModal');
await pageObjects.common.clickConfirmOnModal();
},
};
}

View file

@ -6,17 +6,26 @@
import { FtrProviderContext } from '../ftr_provider_context';
import {
CreateAgentConfigRequest,
CreateAgentConfigResponse,
CreateDatasourceRequest,
CreateDatasourceResponse,
DeleteAgentConfigRequest,
DeleteDatasourcesRequest,
GetFullAgentConfigResponse,
GetPackagesResponse,
} from '../../../plugins/ingest_manager/common';
import { Immutable } from '../../../plugins/security_solution/common/endpoint/types';
import { factory as policyConfigFactory } from '../../../plugins/security_solution/common/endpoint/models/policy_config';
import { Immutable } from '../../../plugins/security_solution/common/endpoint/types';
const INGEST_API_ROOT = '/api/ingest_manager';
const INGEST_API_AGENT_CONFIGS = `${INGEST_API_ROOT}/agent_configs`;
const INGEST_API_AGENT_CONFIGS_DELETE = `${INGEST_API_AGENT_CONFIGS}/delete`;
const INGEST_API_DATASOURCES = `${INGEST_API_ROOT}/datasources`;
const INGEST_API_DATASOURCES_DELETE = `${INGEST_API_DATASOURCES}/delete`;
const INGEST_API_EPM_PACKAGES = `${INGEST_API_ROOT}/epm/packages`;
const SECURITY_PACKAGES_ROUTE = `${INGEST_API_EPM_PACKAGES}?category=security`;
/**
* Holds information about the test resources created to support an Endpoint Policy
@ -28,56 +37,103 @@ export interface PolicyTestResourceInfo {
* This is where Endpoint Policy is stored.
*/
datasource: Immutable<CreateDatasourceResponse['item']>;
/**
* Information about the endpoint package
*/
packageInfo: Immutable<GetPackagesResponse['response'][0]>;
/** will clean up (delete) the objects created (agent config + datasource) */
cleanup: () => Promise<void>;
}
export function EndpointPolicyTestResourcesProvider({ getService }: FtrProviderContext) {
const supertest = getService('supertest');
const log = getService('log');
const logSupertestApiErrorAndThrow = (message: string, error: any) => {
const responseBody = error?.response?.body;
const responseText = error?.response?.text;
log.error(JSON.stringify(responseBody || responseText, null, 2));
log.error(error);
throw new Error(message);
};
return {
/**
* Retrieves the full Agent configuration, which mirrors what the Elastic Agent would get
* once they checkin.
*/
async getFullAgentConfig(agentConfigId: string): Promise<GetFullAgentConfigResponse['item']> {
let fullAgentConfig: GetFullAgentConfigResponse['item'];
try {
const apiResponse: { body: GetFullAgentConfigResponse } = await supertest
.get(`${INGEST_API_AGENT_CONFIGS}/${agentConfigId}/full`)
.expect(200);
fullAgentConfig = apiResponse.body.item;
} catch (error) {
logSupertestApiErrorAndThrow('Unable to get full Agent Configuration', error);
}
return fullAgentConfig!;
},
/**
* Creates an Ingest Agent Configuration and adds to it the Endpoint Datasource that
* stores the Policy configuration data
*/
async createPolicy(): Promise<PolicyTestResourceInfo> {
// FIXME: Refactor after https://github.com/elastic/kibana/issues/64822 is fixed. `isInitialized` setup below should be deleted
// Due to an issue in Ingest API, we first create the Fleet user. This avoids the Agent Config api throwing a 500
const isFleetSetupResponse = await supertest
.get('/api/ingest_manager/fleet/setup')
// Retrieve information about the Endpoint security package
// EPM does not currently have an API to get the "lastest" information for a page given its name,
// so we'll retrieve a list of packages for a category of Security, and will then find the
// endpoint package info. in the list. The request is kicked off here, but handled below after
// agent config creation so that they can be executed concurrently
const secPackagesRequest = supertest
.get(SECURITY_PACKAGES_ROUTE)
.set('kbn-xsrf', 'xxx')
.expect(200);
if (!isFleetSetupResponse.body.isInitialized) {
await supertest
.post('/api/ingest_manager/fleet/setup')
.set('kbn-xsrf', 'xxx')
.send()
.expect(200);
}
// create agent config
const {
body: { item: agentConfig },
}: { body: CreateAgentConfigResponse } = await supertest
.post(INGEST_API_AGENT_CONFIGS)
.set('kbn-xsrf', 'xxx')
.send({ name: 'East Coast', description: 'East Coast call center', namespace: '' })
.expect(200);
let agentConfig: CreateAgentConfigResponse['item'];
try {
const newAgentconfigData: CreateAgentConfigRequest['body'] = {
name: 'East Coast',
description: 'East Coast call center',
namespace: 'default',
};
const { body: createResponse }: { body: CreateAgentConfigResponse } = await supertest
.post(INGEST_API_AGENT_CONFIGS)
.set('kbn-xsrf', 'xxx')
.send(newAgentconfigData)
.expect(200);
agentConfig = createResponse.item;
} catch (error) {
logSupertestApiErrorAndThrow(`Unable to create Agent Config via Ingest!`, error);
}
// Retrieve the Endpoint package information
let endpointPackageInfo: GetPackagesResponse['response'][0] | undefined;
try {
const { body: secPackages }: { body: GetPackagesResponse } = await secPackagesRequest;
endpointPackageInfo = secPackages.response.find(
(epmPackage) => epmPackage.name === 'endpoint'
);
if (!endpointPackageInfo) {
throw new Error(`Endpoint package was not found via ${SECURITY_PACKAGES_ROUTE}`);
}
} catch (error) {
logSupertestApiErrorAndThrow(`Unable to retrieve Endpoint package via Ingest!`, error);
}
// create datasource and associated it to agent config
const {
body: { item: datasource },
}: { body: CreateDatasourceResponse } = await supertest
.post(INGEST_API_DATASOURCES)
.set('kbn-xsrf', 'xxx')
.send({
let datasource: CreateDatasourceResponse['item'];
try {
const newDatasourceData: CreateDatasourceRequest['body'] = {
name: 'Protect East Coast',
description: 'Protect the worlds data - but in the East Coast',
config_id: agentConfig.id,
config_id: agentConfig!.id,
enabled: true,
output_id: '',
inputs: [
// TODO: should we retrieve the latest Endpoint Package and build the input (policy) from that?
{
type: 'endpoint',
enabled: true,
@ -89,32 +145,55 @@ export function EndpointPolicyTestResourcesProvider({ getService }: FtrProviderC
},
},
],
namespace: '',
namespace: 'default',
package: {
name: 'endpoint',
title: 'Elastic Endpoint',
version: '1.0.0',
title: endpointPackageInfo?.title ?? '',
version: endpointPackageInfo?.version ?? '',
},
})
.expect(200);
};
const { body: createResponse }: { body: CreateDatasourceResponse } = await supertest
.post(INGEST_API_DATASOURCES)
.set('kbn-xsrf', 'xxx')
.send(newDatasourceData)
.expect(200);
datasource = createResponse.item;
} catch (error) {
logSupertestApiErrorAndThrow(`Unable to create Datasource via Ingest!`, error);
}
return {
agentConfig,
datasource,
agentConfig: agentConfig!,
datasource: datasource!,
packageInfo: endpointPackageInfo!,
async cleanup() {
// Delete Datasource
await supertest
.post(INGEST_API_DATASOURCES_DELETE)
.set('kbn-xsrf', 'xxx')
.send({ datasourceIds: [datasource.id] })
.expect(200);
try {
const deleteDatasourceData: DeleteDatasourcesRequest['body'] = {
datasourceIds: [datasource.id],
};
await supertest
.post(INGEST_API_DATASOURCES_DELETE)
.set('kbn-xsrf', 'xxx')
.send(deleteDatasourceData)
.expect(200);
} catch (error) {
logSupertestApiErrorAndThrow('Unable to delete Datasource via Ingest!', error);
}
// Delete Agent config
await supertest
.post(INGEST_API_AGENT_CONFIGS_DELETE)
.set('kbn-xsrf', 'xxx')
.send({ agentConfigId: agentConfig.id })
.expect(200);
try {
const deleteAgentConfigData: DeleteAgentConfigRequest['body'] = {
agentConfigId: agentConfig.id,
};
await supertest
.post(INGEST_API_AGENT_CONFIGS_DELETE)
.set('kbn-xsrf', 'xxx')
.send(deleteAgentConfigData)
.expect(200);
} catch (error) {
logSupertestApiErrorAndThrow('Unable to delete Agent Config via Ingest!', error);
}
},
};
},