Initial e2e tests for serverless plugins (#157166)

## Summary

This PR adds boilerplate code and a few initial end-to-end tests to
serverless plugins.

Note that the tests defined in this PR are not part of any CI run yet,
this will be done in a follow-up after this PR is merged.

### Details

The serverless test structure corresponds to what we have in
`x-pack/test` with API tests in `api_integration` and UI tests in
`functional`, each with their set of helper methods and sub-directories
for
- `common` functionality shared across serverless projects (core, shared
UX, ...)
- `observability` project specific functionality
- `search` project specific functionality
- `security` project specific functionality

The `shared` directory contains fixtures, services, ... that are shared
across `api_integration` abd `functional` tests.

```
x-pack/test_serverless/
├─ api_integration
│  ├─ services
│  ├─ test_suites
│  │  ├─ common
│  │  ├─ observability
│  │  ├─ search
│  │  ├─ security
├─ functional
│  ├─ page_objects
│  ├─ services
│  ├─ test_suites
│  │  ├─ common
│  │  ├─ observability
│  │  ├─ search
│  │  ├─ security
├─ shared
│  ├─ services
│  ├─ types
```

See also `x-pack/test_serverless/README.md`

### Run tests

Similar to how functional tests are run in `x-pack/test`, you can point
the functional tests server and test runner to config files in this
`x-pack/test_serverless` directory, e.g. from the `x-pack` directory
run:
```
node scripts/functional_tests_server.js --config test_serverless/api_integration/test_suites/common/config.ts
```
and 
```
node scripts/functional_test_runner.js --config test_serverless/api_integration/test_suites/common/config.ts
```

### Additional changes

- The stateful `common_page` page object used the existence of the
global nav to determine `isChromeVisible` and `isChromeHidden`, which is
not working when the global nav is disabled. To solve this, a
`data-test-subj` that indicates the chrome visible state is added to the
Kibana app wrapper and is used for the checks.
- Add a few `data-test-subj` entries to the Observability overview page.
- Add optional `dataTestSubj` to the `Navigation` component and use that
for the serverless search nav.
- Add optional `titleDataTestSubj` to the `SolutionNav` component and
use it for the serverless security nav.
- Add a data-test-subj entry to the Search overview page.
This commit is contained in:
Robert Oskamp 2023-05-22 12:57:38 +02:00 committed by GitHub
parent a3c940f0cd
commit 87be4cb678
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
59 changed files with 1084 additions and 11 deletions

View file

@ -79,6 +79,21 @@ disabled:
# Asset Manager configs, in tech preview, will move to enabled after more stability introduced
- x-pack/test/api_integration/apis/asset_manager/config.ts
# Serverless base config files
- x-pack/test_serverless/api_integration/config.base.ts
- x-pack/test_serverless/functional/config.base.ts
- x-pack/test_serverless/shared/config.base.ts
# Serverless configs, currently only for manual tests runs, CI integration planned
- x-pack/test_serverless/api_integration/test_suites/common/config.ts
- x-pack/test_serverless/api_integration/test_suites/observability/config.ts
- x-pack/test_serverless/api_integration/test_suites/search/config.ts
- x-pack/test_serverless/api_integration/test_suites/security/config.ts
- x-pack/test_serverless/functional/test_suites/common/config.ts
- x-pack/test_serverless/functional/test_suites/observability/config.ts
- x-pack/test_serverless/functional/test_suites/search/config.ts
- x-pack/test_serverless/functional/test_suites/security/config.ts
defaultQueue: 'n2-4-spot'
enabled:
- test/accessibility/config.ts

View file

@ -173,7 +173,7 @@ const DEV_PATTERNS = [
...DEV_FILE_PATTERNS.map((file) => `{packages,src,x-pack}/**/${file}`),
'packages/kbn-interpreter/tasks/**/*',
'src/dev/**/*',
'x-pack/{dev-tools,tasks,test,build_chromium}/**/*',
'x-pack/{dev-tools,tasks,test,test_serverless,build_chromium}/**/*',
'x-pack/performance/**/*',
'src/setup_node_env/index.js',
'src/cli/dev.js',
@ -602,6 +602,8 @@ module.exports = {
'x-pack/test/ui_capabilities/*/tests/**/*',
'x-pack/test/performance/**/*.ts',
'**/cypress.config.{js,ts}',
'x-pack/test_serverless/**/config*.ts',
'x-pack/test_serverless/*/test_suites/**/*',
],
rules: {
'import/no-default-export': 'off',

8
.gitignore vendored
View file

@ -42,6 +42,14 @@ __tmp__
/x-pack/test/*/screenshots/visual_regression_gallery.html
/x-pack/test/functional/apps/*/*/reporting/reports/failure
# Ignore the same artifacts in x-pack/test_serverless
/x-pack/test_serverless/*/failure_debug
/x-pack/test_serverless/*/screenshots/diff
/x-pack/test_serverless/*/screenshots/failure
/x-pack/test_serverless/*/screenshots/session
/x-pack/test_serverless/*/screenshots/visual_regression_gallery.html
/x-pack/test_serverless/functional/apps/*/*/reporting/reports/failure
/html_docs
.eslintcache
/plugins/

View file

@ -21,6 +21,7 @@ describe('AppWrapper', () => {
expect(component.getDOMNode()).toMatchInlineSnapshot(`
<div
class="kbnAppWrapper"
data-test-subj="kbnAppWrapper visibleChrome"
>
app-content
</div>
@ -31,6 +32,7 @@ describe('AppWrapper', () => {
expect(component.getDOMNode()).toMatchInlineSnapshot(`
<div
class="kbnAppWrapper kbnAppWrapper--hiddenChrome"
data-test-subj="kbnAppWrapper hiddenChrome"
>
app-content
</div>
@ -41,6 +43,7 @@ describe('AppWrapper', () => {
expect(component.getDOMNode()).toMatchInlineSnapshot(`
<div
class="kbnAppWrapper"
data-test-subj="kbnAppWrapper visibleChrome"
>
app-content
</div>

View file

@ -17,7 +17,10 @@ export const AppWrapper: React.FunctionComponent<{
}> = ({ chromeVisible$, children }) => {
const visible = useObservable(chromeVisible$);
return (
<div className={classNames(APP_WRAPPER_CLASS, { 'kbnAppWrapper--hiddenChrome': !visible })}>
<div
className={classNames(APP_WRAPPER_CLASS, { 'kbnAppWrapper--hiddenChrome': !visible })}
data-test-subj={`kbnAppWrapper ${visible ? 'visible' : 'hidden'}Chrome`}
>
{children}
</div>
);

View file

@ -61,6 +61,7 @@ describe('RenderingService#start', () => {
expect(targetDomElement.querySelector('div.kbnAppWrapper')).toMatchInlineSnapshot(`
<div
class="kbnAppWrapper kbnAppWrapper--hiddenChrome"
data-test-subj="kbnAppWrapper hiddenChrome"
>
<div
id="app-fixed-viewport"

View file

@ -34,6 +34,7 @@ interface Props extends ChromeNavigationViewModel {
* ID of sections to highlight
*/
activeNavItemId?: string;
dataTestSubj?: string; // optional test subject for the navigation
}
export const Navigation = ({
@ -163,7 +164,12 @@ export const Navigation = ({
);
return (
<EuiFlexGroup direction="column" gutterSize="none" style={{ overflowY: 'auto' }}>
<EuiFlexGroup
direction="column"
gutterSize="none"
style={{ overflowY: 'auto' }}
data-test-subj={props.dataTestSubj}
>
<EuiFlexItem grow={false}>
<EuiCollapsibleNavGroup css={{ background: euiTheme.colors.darkestShade, height: '50px' }}>
<NavHeader />

View file

@ -121,7 +121,7 @@ export const SolutionNav: FC<SolutionNavProps> = ({
const HeadingElement = headingProps?.element || 'h2';
const titleText = (
<EuiTitle size="xs" id={headingID}>
<EuiTitle size="xs" id={headingID} data-test-subj={headingProps?.['data-test-subj']}>
<HeadingElement>
{icon && (
<KibanaSolutionAvatar className="kbnSolutionNav__avatar" iconType={icon} name={name} />

View file

@ -26,7 +26,6 @@ export class CommonPageObject extends FtrService {
private readonly browser = this.ctx.getService('browser');
private readonly retry = this.ctx.getService('retry');
private readonly find = this.ctx.getService('find');
private readonly globalNav = this.ctx.getService('globalNav');
private readonly testSubjects = this.ctx.getService('testSubjects');
private readonly loginPage = this.ctx.getPageObject('login');
private readonly kibanaServer = this.ctx.getService('kibanaServer');
@ -394,13 +393,11 @@ export class CommonPageObject extends FtrService {
}
async isChromeVisible() {
const globalNavShown = await this.globalNav.exists();
return globalNavShown;
return await this.testSubjects.exists('kbnAppWrapper visibleChrome');
}
async isChromeHidden() {
const globalNavShown = await this.globalNav.exists();
return !globalNavShown;
return await this.testSubjects.exists('kbnAppWrapper hiddenChrome');
}
async waitForTopNavToBeVisible() {

View file

@ -168,6 +168,7 @@ export function OverviewPage() {
rightSideGroupProps: {
responsive: true,
},
'data-test-subj': 'obltOverviewPageHeader',
}}
>
<HeaderMenu />
@ -177,7 +178,7 @@ export function OverviewPage() {
onViewDetailsClick={() => setIsDataAssistantFlyoutVisible(true)}
/>
<EuiFlexGroup direction="column" gutterSize="s">
<EuiFlexGroup direction="column" gutterSize="s" data-test-subj="obltOverviewAlerts">
<EuiFlexItem>
<SectionContainer
title={i18n.translate('xpack.observability.overview.alerts.title', {

View file

@ -35,7 +35,7 @@ export const ElasticsearchOverview = () => {
const { http, userProfile } = useKibanaServices();
return (
<EuiPageTemplate offset={0} grow restrictWidth>
<EuiPageTemplate offset={0} grow restrictWidth data-test-subj="svlSearchOverviewPage">
<EuiPageTemplate.Section alignment="top" className="serverlessSearchHeaderSection">
<EuiText color="ghost">
<EuiFlexGroup justifyContent="spaceBetween">

View file

@ -145,6 +145,7 @@ export const createServerlessSearchSideNavComponent = (core: CoreStart) => () =>
activeNavItemId={activeNavItemId}
homeHref="/app/elasticsearch"
linkToCloud="projects"
dataTestSubj="svlSearchSideNav"
/>
</NavigationKibanaProvider>
);

View file

@ -39,6 +39,9 @@ export const SecuritySideNavigation: React.FC = () => {
/>
}
closeFlyoutButtonPosition={'inside'}
headingProps={{
'data-test-subj': 'securitySolutionNavHeading',
}}
/>
);
};

View file

@ -0,0 +1,82 @@
# Kibana Serverless Tests
The tests and helper methods (services, page objects) defined here in
`x-pack/test_serverless` cover the serverless functionality introduced by the
`serverless`, `serverless_observability`, `serverless_search` and
`serverless_security` plugins.
## Serverless testing structure and conventions
### Overview
The serverless test structure corresponds to what we have in `x-pack/test` with
API tests in `api_integration` and UI tests in `functional`, each with their
set of helper methods and sub-directories for
- `common` functionality shared across serverless projects (core, shared UX, ...)
- `observability` project specific functionality
- `search` project specific functionality
- `security` project specific functionality
The `shared` directory contains fixtures, services, ... that are shared across
`api_integration` abd `functional` tests.
```
x-pack/test_serverless/
├─ api_integration
│ ├─ services
│ ├─ test_suites
│ │ ├─ common
│ │ ├─ observability
│ │ ├─ search
│ │ ├─ security
├─ functional
│ ├─ page_objects
│ ├─ services
│ ├─ test_suites
│ │ ├─ common
│ │ ├─ observability
│ │ ├─ search
│ │ ├─ security
├─ shared
│ ├─ services
│ ├─ types
```
### Shared services and page objects
Test services and page objects from `x-pack/test/[api_integration|functional]`
are available for reuse.
Serverless specific services and page objects are implemented in
`x-pack/test_serverless/[api_integration|functional]` only and may not be added
to or make modifications in `x-pack/test`.
With this helper method reuse, we have to avoid name clashes and go with the
following namesapces:
| project | namespace for helper methods |
| ------------- | ---------------------------- |
| common | svlCommon |
| observability | svlOblt |
| search | svlSearch |
| security | svlSec |
### Adding Serverless Tests
As outlined above, serverless tests are separated from stateful tests (except
the reuse helper methods), which includes a separate base configuration. All
tests that should run in an serverless environment have to be added here in
`x-pack/test_serverless`.
Tests in this area should be clearly designed for the serverless environment,
particularly when it comes to timing for API requests and UI interaction.
## Run tests
Similar to how functional tests are run in `x-pack/test`, you can point the
functional tests server and test runner to config files in this `x-pack/test_serverless`
directory, e.g. from the `x-pack` directory run:
```
node scripts/functional_tests_server.js --config test_serverless/api_integration/test_suites/common/config.ts
node scripts/functional_test_runner.js --config test_serverless/api_integration/test_suites/common/config.ts
```

View file

@ -0,0 +1,31 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { FtrConfigProviderContext } from '@kbn/test';
import { services } from './services';
import type { CreateTestConfigOptions } from '../shared/types';
export function createTestConfig(options: CreateTestConfigOptions) {
return async ({ readConfigFile }: FtrConfigProviderContext) => {
const svlSharedConfig = await readConfigFile(require.resolve('../shared/config.base.ts'));
return {
...svlSharedConfig.getAll(),
services,
kbnTestServer: {
...svlSharedConfig.get('kbnTestServer'),
serverArgs: [
...svlSharedConfig.get('kbnTestServer.serverArgs'),
`--serverless${options.serverlessProject ? `=${options.serverlessProject}` : ''}`,
],
},
testFiles: options.testFiles,
};
};
}

View file

@ -0,0 +1,12 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { GenericFtrProviderContext } from '@kbn/test';
import { services } from './services';
export type FtrProviderContext = GenericFtrProviderContext<typeof services, {}>;

View file

@ -0,0 +1,19 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
// eslint-disable-next-line @kbn/imports/no_boundary_crossing
import { services as xpackApiIntegrationServices } from '../../../test/api_integration/services';
import { services as svlSharedServices } from '../../shared/services';
import { SvlCommonApiServiceProvider } from './svl_common_api';
export const services = {
...xpackApiIntegrationServices,
...svlSharedServices,
svlCommonApi: SvlCommonApiServiceProvider,
};

View file

@ -0,0 +1,31 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import expect from '@kbn/expect';
import { FtrProviderContext } from '../ftr_provider_context';
const COMMON_REQUEST_HEADERS = {
'kbn-xsrf': 'some-xsrf-token',
};
export function SvlCommonApiServiceProvider({}: FtrProviderContext) {
return {
getCommonRequestHeader() {
return COMMON_REQUEST_HEADERS;
},
assertResponseStatusCode(expectedStatus: number, actualStatus: number, responseBody: object) {
expect(actualStatus).to.eql(
expectedStatus,
`Expected status code ${expectedStatus}, got ${actualStatus} with body '${JSON.stringify(
responseBody
)}'`
);
},
};
}

View file

@ -0,0 +1,13 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { createTestConfig } from '../../config.base';
export default createTestConfig({
serverlessProject: undefined,
testFiles: [require.resolve('.')],
});

View file

@ -0,0 +1,15 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { FtrProviderContext } from '../../ftr_provider_context';
export default function ({ loadTestFile }: FtrProviderContext) {
describe('serverless common API', function () {
loadTestFile(require.resolve('./switch_project'));
loadTestFile(require.resolve('./security_users'));
});
}

View file

@ -0,0 +1,25 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { FtrProviderContext } from '../../ftr_provider_context';
export default function ({ getService }: FtrProviderContext) {
const svlCommonApi = getService('svlCommonApi');
const supertest = getService('supertest');
describe('security/users', function () {
it('rejects request to create user', async () => {
const { body, status } = await supertest
.post(`/internal/security/users/some_testuser`)
.set(svlCommonApi.getCommonRequestHeader())
.send({ username: 'some_testuser', password: 'testpassword', roles: [] });
// in a non-serverless environment this would succeed with a 200
svlCommonApi.assertResponseStatusCode(400, status, body);
});
});
}

View file

@ -0,0 +1,25 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { FtrProviderContext } from '../../ftr_provider_context';
export default function ({ getService }: FtrProviderContext) {
const svlCommonApi = getService('svlCommonApi');
const supertest = getService('supertest');
describe('switch_project', function () {
it('rejects request with invalid body', async () => {
const { body, status } = await supertest
.post(`/internal/serverless/switch_project`)
.set(svlCommonApi.getCommonRequestHeader())
.send({ invalid: 'body' });
// in a non-serverless environment this would return a 404
svlCommonApi.assertResponseStatusCode(400, status, body);
});
});
}

View file

@ -0,0 +1,13 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { createTestConfig } from '../../config.base';
export default createTestConfig({
serverlessProject: 'oblt',
testFiles: [require.resolve('.')],
});

View file

@ -0,0 +1,14 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { FtrProviderContext } from '../../ftr_provider_context';
export default function ({ loadTestFile }: FtrProviderContext) {
describe('serverless observability API', function () {
loadTestFile(require.resolve('./security_users'));
});
}

View file

@ -0,0 +1,31 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { FtrProviderContext } from '../../ftr_provider_context';
export default function ({ getService }: FtrProviderContext) {
const svlCommonApi = getService('svlCommonApi');
const supertest = getService('supertest');
/*
* This is a placeholder test to demonstrate usage.
* This test case is actually already covered in the `serverless` plugin tests
* and should be replaced with something specific to the observability project
* once it modifies / adds / disables Kibana APIs.
*/
describe('security/users', function () {
it('rejects request to create user', async () => {
const { body, status } = await supertest
.post(`/internal/security/users/some_testuser`)
.set(svlCommonApi.getCommonRequestHeader())
.send({ username: 'some_testuser', password: 'testpassword', roles: [] });
// in a non-serverless environment this would succeed with a 200
svlCommonApi.assertResponseStatusCode(400, status, body);
});
});
}

View file

@ -0,0 +1,13 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { createTestConfig } from '../../config.base';
export default createTestConfig({
serverlessProject: 'es',
testFiles: [require.resolve('.')],
});

View file

@ -0,0 +1,14 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { FtrProviderContext } from '../../ftr_provider_context';
export default function ({ loadTestFile }: FtrProviderContext) {
describe('serverless search API', function () {
loadTestFile(require.resolve('./security_users'));
});
}

View file

@ -0,0 +1,31 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { FtrProviderContext } from '../../ftr_provider_context';
export default function ({ getService }: FtrProviderContext) {
const svlCommonApi = getService('svlCommonApi');
const supertest = getService('supertest');
/*
* This is a placeholder test to demonstrate usage.
* This test case is actually already covered in the `serverless` plugin tests
* and should be replaced with something specific to the search project
* once it modifies / adds / disables Kibana APIs.
*/
describe('security/users', function () {
it('rejects request to create user', async () => {
const { body, status } = await supertest
.post(`/internal/security/users/some_testuser`)
.set(svlCommonApi.getCommonRequestHeader())
.send({ username: 'some_testuser', password: 'testpassword', roles: [] });
// in a non-serverless environment this would succeed with a 200
svlCommonApi.assertResponseStatusCode(400, status, body);
});
});
}

View file

@ -0,0 +1,13 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { createTestConfig } from '../../config.base';
export default createTestConfig({
serverlessProject: 'security',
testFiles: [require.resolve('.')],
});

View file

@ -0,0 +1,14 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { FtrProviderContext } from '../../ftr_provider_context';
export default function ({ loadTestFile }: FtrProviderContext) {
describe('serverless security API', function () {
loadTestFile(require.resolve('./security_users'));
});
}

View file

@ -0,0 +1,31 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { FtrProviderContext } from '../../ftr_provider_context';
export default function ({ getService }: FtrProviderContext) {
const svlCommonApi = getService('svlCommonApi');
const supertest = getService('supertest');
/*
* This is a placeholder test to demonstrate usage.
* This test case is actually already covered in the `serverless` plugin tests
* and should be replaced with something specific to the security project
* once it modifies / adds / disables Kibana APIs.
*/
describe('security/users', function () {
it('rejects request to create user', async () => {
const { body, status } = await supertest
.post(`/internal/security/users/some_testuser`)
.set(svlCommonApi.getCommonRequestHeader())
.send({ username: 'some_testuser', password: 'testpassword', roles: [] });
// in a non-serverless environment this would succeed with a 200
svlCommonApi.assertResponseStatusCode(400, status, body);
});
});
}

View file

@ -0,0 +1,62 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { resolve } from 'path';
import { FtrConfigProviderContext } from '@kbn/test';
import { pageObjects } from './page_objects';
import { services } from './services';
import type { CreateTestConfigOptions } from '../shared/types';
export function createTestConfig(options: CreateTestConfigOptions) {
return async ({ readConfigFile }: FtrConfigProviderContext) => {
const svlSharedConfig = await readConfigFile(require.resolve('../shared/config.base.ts'));
return {
...svlSharedConfig.getAll(),
pageObjects,
services,
kbnTestServer: {
...svlSharedConfig.get('kbnTestServer'),
serverArgs: [
...svlSharedConfig.get('kbnTestServer.serverArgs'),
`--serverless${options.serverlessProject ? `=${options.serverlessProject}` : ''}`,
],
},
testFiles: options.testFiles,
uiSettings: {
defaults: {
'accessibility:disableAnimations': true,
'dateFormat:tz': 'UTC',
},
},
// the apps section defines the urls that
// `PageObjects.common.navigateTo(appKey)` will use.
// Merge urls for your plugin with the urls defined in
// Kibana's config in order to use this helper
apps: {
home: {
pathname: '/app/home',
hash: '/',
},
landingPage: {
pathname: '/',
},
observability: {
pathname: '/app/observability',
},
},
// choose where screenshots should be saved
screenshots: {
directory: resolve(__dirname, 'screenshots'),
},
};
};
}

View file

@ -0,0 +1,13 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { GenericFtrProviderContext } from '@kbn/test';
import { pageObjects } from './page_objects';
import { services } from './services';
export type FtrProviderContext = GenericFtrProviderContext<typeof services, typeof pageObjects>;

View file

@ -0,0 +1,23 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
// eslint-disable-next-line @kbn/imports/no_boundary_crossing
import { pageObjects as xpackFunctionalPageObjects } from '../../../test/functional/page_objects';
import { SvlCommonPageProvider } from './svl_common_page';
import { SvlObltOverviewPageProvider } from './svl_oblt_overview_page';
import { SvlSearchLandingPageProvider } from './svl_search_landing_page';
import { SvlSecLandingPageProvider } from './svl_sec_landing_page';
export const pageObjects = {
...xpackFunctionalPageObjects,
svlCommonPage: SvlCommonPageProvider,
svlObltOverviewPage: SvlObltOverviewPageProvider,
svlSearchLandingPage: SvlSearchLandingPageProvider,
svlSecLandingPage: SvlSecLandingPageProvider,
};

View file

@ -0,0 +1,18 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { FtrProviderContext } from '../ftr_provider_context';
export function SvlCommonPageProvider({ getService }: FtrProviderContext) {
const testSubjects = getService('testSubjects');
return {
async assertProjectHeaderExists() {
await testSubjects.existOrFail('kibanaProjectHeader');
},
};
}

View file

@ -0,0 +1,18 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { FtrProviderContext } from '../ftr_provider_context';
export function SvlObltOverviewPageProvider({ getService }: FtrProviderContext) {
const testSubjects = getService('testSubjects');
return {
async assertAlertsSectionExists() {
await testSubjects.existOrFail('obltOverviewAlerts');
},
};
}

View file

@ -0,0 +1,18 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { FtrProviderContext } from '../ftr_provider_context';
export function SvlSearchLandingPageProvider({ getService }: FtrProviderContext) {
const testSubjects = getService('testSubjects');
return {
async assertSvlSearchSideNavExists() {
await testSubjects.existOrFail('svlSearchSideNav');
},
};
}

View file

@ -0,0 +1,18 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { FtrProviderContext } from '../ftr_provider_context';
export function SvlSecLandingPageProvider({ getService }: FtrProviderContext) {
const testSubjects = getService('testSubjects');
return {
async assertSvlSecSideNavExists() {
await testSubjects.existOrFail('securitySolutionNavHeading');
},
};
}

View file

@ -0,0 +1,25 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
// eslint-disable-next-line @kbn/imports/no_boundary_crossing
import { services as xpackFunctionalServices } from '../../../test/functional/services';
import { services as svlSharedServices } from '../../shared/services';
import { SvlCommonNavigationServiceProvider } from './svl_common_navigation';
import { SvlObltNavigationServiceProvider } from './svl_oblt_navigation';
import { SvlSearchNavigationServiceProvider } from './svl_search_navigation';
import { SvlSecNavigationServiceProvider } from './svl_sec_navigation';
export const services = {
...xpackFunctionalServices,
...svlSharedServices,
svlCommonNavigation: SvlCommonNavigationServiceProvider,
svlObltNavigation: SvlObltNavigationServiceProvider,
svlSearchNavigation: SvlSearchNavigationServiceProvider,
svlSecNavigation: SvlSecNavigationServiceProvider,
};

View file

@ -0,0 +1,26 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { FtrProviderContext } from '../ftr_provider_context';
export function SvlCommonNavigationServiceProvider({
getService,
getPageObjects,
}: FtrProviderContext) {
const retry = getService('retry');
const testSubjects = getService('testSubjects');
const PageObjects = getPageObjects(['common']);
return {
async navigateToKibanaHome() {
await retry.tryForTime(60 * 1000, async () => {
await PageObjects.common.navigateToApp('home');
await testSubjects.existOrFail('homeApp', { timeout: 2000 });
});
},
};
}

View file

@ -0,0 +1,26 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { FtrProviderContext } from '../ftr_provider_context';
export function SvlObltNavigationServiceProvider({
getService,
getPageObjects,
}: FtrProviderContext) {
const retry = getService('retry');
const testSubjects = getService('testSubjects');
const PageObjects = getPageObjects(['common']);
return {
async navigateToLandingPage() {
await retry.tryForTime(60 * 1000, async () => {
await PageObjects.common.navigateToApp('landingPage');
await testSubjects.existOrFail('obltOverviewPageHeader', { timeout: 2000 });
});
},
};
}

View file

@ -0,0 +1,26 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { FtrProviderContext } from '../ftr_provider_context';
export function SvlSearchNavigationServiceProvider({
getService,
getPageObjects,
}: FtrProviderContext) {
const retry = getService('retry');
const testSubjects = getService('testSubjects');
const PageObjects = getPageObjects(['common']);
return {
async navigateToLandingPage() {
await retry.tryForTime(60 * 1000, async () => {
await PageObjects.common.navigateToApp('landingPage');
await testSubjects.existOrFail('svlSearchOverviewPage', { timeout: 2000 });
});
},
};
}

View file

@ -0,0 +1,29 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { FtrProviderContext } from '../ftr_provider_context';
export function SvlSecNavigationServiceProvider({
getService,
getPageObjects,
}: FtrProviderContext) {
const retry = getService('retry');
const testSubjects = getService('testSubjects');
const PageObjects = getPageObjects(['common']);
return {
async navigateToLandingPage() {
await retry.tryForTime(60 * 1000, async () => {
await PageObjects.common.navigateToApp('landingPage');
// Currently, the security landing page app is not loading correctly.
// Replace '~kbnAppWrapper' with a proper test subject of the landing
// page once it loads successfully.
await testSubjects.existOrFail('~kbnAppWrapper', { timeout: 2000 });
});
},
};
}

View file

@ -0,0 +1,13 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { createTestConfig } from '../../config.base';
export default createTestConfig({
serverlessProject: undefined,
testFiles: [require.resolve('.')],
});

View file

@ -0,0 +1,20 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { FtrProviderContext } from '../../ftr_provider_context';
export default function ({ getPageObject, getService }: FtrProviderContext) {
const svlCommonPage = getPageObject('svlCommonPage');
const svlCommonNavigation = getService('svlCommonNavigation');
describe('home page', function () {
it('has project header', async () => {
await svlCommonNavigation.navigateToKibanaHome();
await svlCommonPage.assertProjectHeaderExists();
});
});
}

View file

@ -0,0 +1,14 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { FtrProviderContext } from '../../ftr_provider_context';
export default function ({ loadTestFile }: FtrProviderContext) {
describe('serverless common UI', function () {
loadTestFile(require.resolve('./home_page'));
});
}

View file

@ -0,0 +1,13 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { createTestConfig } from '../../config.base';
export default createTestConfig({
serverlessProject: 'oblt',
testFiles: [require.resolve('.')],
});

View file

@ -0,0 +1,14 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { FtrProviderContext } from '../../ftr_provider_context';
export default function ({ loadTestFile }: FtrProviderContext) {
describe('serverless observability UI', function () {
loadTestFile(require.resolve('./landing_page'));
});
}

View file

@ -0,0 +1,20 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { FtrProviderContext } from '../../ftr_provider_context';
export default function ({ getPageObject, getService }: FtrProviderContext) {
const svlObltOverviewPage = getPageObject('svlObltOverviewPage');
const svlObltNavigation = getService('svlObltNavigation');
describe('landing page', function () {
it('has alerts section', async () => {
await svlObltNavigation.navigateToLandingPage();
await svlObltOverviewPage.assertAlertsSectionExists();
});
});
}

View file

@ -0,0 +1,13 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { createTestConfig } from '../../config.base';
export default createTestConfig({
serverlessProject: 'es',
testFiles: [require.resolve('.')],
});

View file

@ -0,0 +1,14 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { FtrProviderContext } from '../../ftr_provider_context';
export default function ({ loadTestFile }: FtrProviderContext) {
describe('serverless search UI', function () {
loadTestFile(require.resolve('./landing_page'));
});
}

View file

@ -0,0 +1,20 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { FtrProviderContext } from '../../ftr_provider_context';
export default function ({ getPageObject, getService }: FtrProviderContext) {
const svlSearchLandingPage = getPageObject('svlSearchLandingPage');
const svlSearchNavigation = getService('svlSearchNavigation');
describe('landing page', function () {
it('has serverless side nav', async () => {
await svlSearchNavigation.navigateToLandingPage();
await svlSearchLandingPage.assertSvlSearchSideNavExists();
});
});
}

View file

@ -0,0 +1,13 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { createTestConfig } from '../../config.base';
export default createTestConfig({
serverlessProject: 'security',
testFiles: [require.resolve('.')],
});

View file

@ -0,0 +1,14 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { FtrProviderContext } from '../../ftr_provider_context';
export default function ({ loadTestFile }: FtrProviderContext) {
describe('serverless security UI', function () {
loadTestFile(require.resolve('./landing_page'));
});
}

View file

@ -0,0 +1,20 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { FtrProviderContext } from '../../ftr_provider_context';
export default function ({ getPageObject, getService }: FtrProviderContext) {
const svlSecLandingPage = getPageObject('svlSecLandingPage');
const svlSecNavigation = getService('svlSecNavigation');
describe('landing page', function () {
it('has serverless side nav', async () => {
await svlSecNavigation.navigateToLandingPage();
await svlSecLandingPage.assertSvlSecSideNavExists();
});
});
}

View file

@ -0,0 +1,73 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { format as formatUrl } from 'url';
import { esTestConfig, kbnTestConfig, kibanaServerTestUser } from '@kbn/test';
export default async () => {
const servers = {
kibana: kbnTestConfig.getUrlParts(),
elasticsearch: esTestConfig.getUrlParts(),
};
return {
servers,
esTestCluster: {
license: 'trial',
from: 'snapshot',
serverArgs: ['xpack.security.enabled=false'],
},
kbnTestServer: {
buildArgs: [],
sourceArgs: ['--no-base-path', '--env.name=development'],
serverArgs: [
`--server.port=${kbnTestConfig.getPort()}`,
'--status.allowAnonymous=true',
// We shouldn't embed credentials into the URL since Kibana requests to Elasticsearch should
// either include `kibanaServerTestUser` credentials, or credentials provided by the test
// user, or none at all in case anonymous access is used.
`--elasticsearch.hosts=${formatUrl(
Object.fromEntries(
Object.entries(servers.elasticsearch).filter(([key]) => key.toLowerCase() !== 'auth')
)
)}`,
`--elasticsearch.username=${kibanaServerTestUser.username}`,
`--elasticsearch.password=${kibanaServerTestUser.password}`,
'--telemetry.sendUsageTo=staging',
`--logging.appenders.deprecation=${JSON.stringify({
type: 'console',
layout: {
type: 'json',
},
})}`,
`--logging.loggers=${JSON.stringify([
{
name: 'elasticsearch.deprecation',
level: 'all',
appenders: ['deprecation'],
},
])}`,
],
},
// overriding default timeouts from packages/kbn-test/src/functional_test_runner/lib/config/schema.ts
// so we can easily adjust them for serverless where needed
timeouts: {
find: 10 * 1000,
try: 120 * 1000,
waitFor: 20 * 1000,
esRequestTimeout: 30 * 1000,
kibanaReportCompletion: 60 * 1000,
kibanaStabilize: 15 * 1000,
navigateStatusPageCheck: 250,
waitForExists: 2500,
},
};
};

View file

@ -0,0 +1,8 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export const services = {};

View file

@ -0,0 +1,11 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export interface CreateTestConfigOptions {
serverlessProject: 'es' | 'oblt' | 'security' | undefined;
testFiles: string[];
}

View file

@ -0,0 +1,24 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "target/types",
"rootDirs": [".", "../test"],
"types": ["node", "@kbn/ambient-ftr-types"],
},
"include": [
"**/*",
"../../typings/**/*",
"../../packages/kbn-test/types/ftr_globals/**/*",
],
"exclude": [
"target/**/*",
"*/plugins/**/*",
"*/packages/**/*",
"*/*/packages/**/*",
],
"kbn_references": [
{ "path": "../test/tsconfig.json" },
"@kbn/expect",
"@kbn/test",
]
}