mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[kbn-scout] initial package implementation (playwright-test POC) (#194006)
## Summary This PR introduces a new package, **kbn-scout**, designed to streamline the setup and execution of Playwright tests for Kibana. The `kbn-scout` package consolidates server management and testing capabilities by wrapping both the Kibana/Elasticsearch server launcher and the Playwright test runner. It includes: - Test and worker-scoped fixtures for reliable setup across test suites - Page objects combined into the fixture for Kibana UI interactions - Configurations for seamless test execution in both local and CI environments (`Cloud` execution is out of scope) - This package aims to simplify test setup and enhance modularity, making it easier to create, run, and maintain deployment-agnostic tests, that are located in the plugin they actually test. Tests example is available in `x-pack/plugins/discover_enhanced` plugin under `ui_tests` folder How to run: 1) As a single script (servers + tests): ``` node scripts/scout_test.js --config=x-pack/plugins/discover_enhanced/ui_tests/playwright.config.ts --serverless=es ``` 2) Start servers first ``` node scripts/scout_start_servers.js --serverless=es ``` then run tests: ``` npx playwright test --config=x-pack/plugins/discover_enhanced/ui_tests/playwright.config.ts ``` if you have Playwright plugin in IDEA, just use it to run tests files --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
a92103b2a9
commit
310d922f13
86 changed files with 3081 additions and 3 deletions
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
|
@ -466,6 +466,7 @@ packages/kbn-rrule @elastic/response-ops
|
|||
packages/kbn-rule-data-utils @elastic/security-detections-response @elastic/response-ops @elastic/obs-ux-management-team
|
||||
packages/kbn-safer-lodash-set @elastic/kibana-security
|
||||
packages/kbn-saved-objects-settings @elastic/appex-sharedux
|
||||
packages/kbn-scout @elastic/appex-qa
|
||||
packages/kbn-screenshotting-server @elastic/appex-sharedux
|
||||
packages/kbn-search-api-keys-components @elastic/search-kibana
|
||||
packages/kbn-search-api-keys-server @elastic/search-kibana
|
||||
|
@ -1552,6 +1553,7 @@ packages/kbn-monaco/src/esql @elastic/kibana-esql
|
|||
/.eslintignore @elastic/kibana-operations
|
||||
|
||||
# QA - Appex QA
|
||||
/x-pack/plugins/discover_enhanced/ui_tests/ @elastic/appex-qa # temporarily
|
||||
/x-pack/test/functional/fixtures/package_registry_config.yml @elastic/appex-qa # No usages found
|
||||
/x-pack/test/functional/fixtures/kbn_archiver/packaging.json @elastic/appex-qa # No usages found
|
||||
/x-pack/test/functional/es_archives/filebeat @elastic/appex-qa
|
||||
|
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -143,6 +143,8 @@ x-pack/test/security_api_integration/plugins/audit_log/audit.log
|
|||
.ftr
|
||||
role_users.json
|
||||
|
||||
# ignore Scout temp directory
|
||||
.scout
|
||||
|
||||
.devcontainer/.env
|
||||
|
||||
|
|
|
@ -1484,6 +1484,7 @@
|
|||
"@kbn/repo-path": "link:packages/kbn-repo-path",
|
||||
"@kbn/repo-source-classifier": "link:packages/kbn-repo-source-classifier",
|
||||
"@kbn/repo-source-classifier-cli": "link:packages/kbn-repo-source-classifier-cli",
|
||||
"@kbn/scout": "link:packages/kbn-scout",
|
||||
"@kbn/security-api-integration-helpers": "link:x-pack/test/security_api_integration/packages/helpers",
|
||||
"@kbn/serverless-storybook-config": "link:packages/serverless/storybook/config",
|
||||
"@kbn/some-dev-log": "link:packages/kbn-some-dev-log",
|
||||
|
|
|
@ -58,5 +58,6 @@ export const TEST_DIR = new Set([
|
|||
'storybook',
|
||||
'.storybook',
|
||||
'integration_tests',
|
||||
'ui_tests',
|
||||
...RANDOM_TEST_FILE_NAMES,
|
||||
]);
|
||||
|
|
9
packages/kbn-scout/README.md
Normal file
9
packages/kbn-scout/README.md
Normal file
|
@ -0,0 +1,9 @@
|
|||
# @kbn/scout
|
||||
|
||||
The package is designed to streamline the setup and execution of Playwright tests for Kibana. It consolidates server management and testing capabilities by wrapping both the Kibana/Elasticsearch server launcher and the Playwright test runner. It includes:
|
||||
|
||||
- core test and worker-scoped fixtures for reliable setup across test suites
|
||||
- page objects combined into the fixture for for core Kibana apps UI interactions
|
||||
- configurations for seamless test execution in both local and CI environments
|
||||
|
||||
This package aims to simplify test setup and enhance modularity, making it easier to create, run, and maintain deployment-agnostic tests, that are located in the plugin they actually test.
|
19
packages/kbn-scout/index.ts
Normal file
19
packages/kbn-scout/index.ts
Normal 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
export { startServersCli, runTestsCli } from './src/cli';
|
||||
export { expect, test, createPlaywrightConfig, createLazyPageObject } from './src/playwright';
|
||||
export type {
|
||||
ScoutPage,
|
||||
ScoutPlaywrightOptions,
|
||||
ScoutTestOptions,
|
||||
PageObjects,
|
||||
ScoutTestFixtures,
|
||||
ScoutWorkerFixtures,
|
||||
} from './src/playwright';
|
14
packages/kbn-scout/jest.config.js
Normal file
14
packages/kbn-scout/jest.config.js
Normal 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
preset: '@kbn/test/jest_node',
|
||||
rootDir: '../..',
|
||||
roots: ['<rootDir>/packages/kbn-scout'],
|
||||
};
|
6
packages/kbn-scout/kibana.jsonc
Normal file
6
packages/kbn-scout/kibana.jsonc
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"type": "test-helper",
|
||||
"id": "@kbn/scout",
|
||||
"owner": "@elastic/appex-qa",
|
||||
"devOnly": true
|
||||
}
|
6
packages/kbn-scout/package.json
Normal file
6
packages/kbn-scout/package.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "@kbn/scout",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"license": "Elastic License 2.0 OR AGPL-3.0-only OR SSPL-1.0"
|
||||
}
|
11
packages/kbn-scout/src/cli/index.ts
Normal file
11
packages/kbn-scout/src/cli/index.ts
Normal 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
export { runTestsCli } from './run_tests_cli';
|
||||
export { startServersCli } from './start_servers_cli';
|
39
packages/kbn-scout/src/cli/run_tests_cli.ts
Normal file
39
packages/kbn-scout/src/cli/run_tests_cli.ts
Normal file
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { run } from '@kbn/dev-cli-runner';
|
||||
import { initLogsDir } from '@kbn/test';
|
||||
import { TEST_FLAG_OPTIONS, parseTestFlags, runTests } from '../playwright/runner';
|
||||
|
||||
/**
|
||||
* Start servers and run the tests
|
||||
*/
|
||||
export function runTestsCli() {
|
||||
run(
|
||||
async ({ flagsReader, log }) => {
|
||||
const options = await parseTestFlags(flagsReader);
|
||||
|
||||
if (options.logsDir) {
|
||||
initLogsDir(log, options.logsDir);
|
||||
}
|
||||
|
||||
await runTests(log, options);
|
||||
},
|
||||
{
|
||||
description: `Run Scout UI Tests`,
|
||||
usage: `
|
||||
Usage:
|
||||
node scripts/scout_test --help
|
||||
node scripts/scout_test --stateful --config <playwright_config_path>
|
||||
node scripts/scout_test --serverless=es --headed --config <playwright_config_path>
|
||||
`,
|
||||
flags: TEST_FLAG_OPTIONS,
|
||||
}
|
||||
);
|
||||
}
|
34
packages/kbn-scout/src/cli/start_servers_cli.ts
Normal file
34
packages/kbn-scout/src/cli/start_servers_cli.ts
Normal file
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { run } from '@kbn/dev-cli-runner';
|
||||
|
||||
import { initLogsDir } from '@kbn/test';
|
||||
|
||||
import { startServers, parseServerFlags, SERVER_FLAG_OPTIONS } from '../servers';
|
||||
|
||||
/**
|
||||
* Start servers
|
||||
*/
|
||||
export function startServersCli() {
|
||||
run(
|
||||
async ({ flagsReader: flags, log }) => {
|
||||
const options = parseServerFlags(flags);
|
||||
|
||||
if (options.logsDir) {
|
||||
initLogsDir(log, options.logsDir);
|
||||
}
|
||||
|
||||
await startServers(log, options);
|
||||
},
|
||||
{
|
||||
flags: SERVER_FLAG_OPTIONS,
|
||||
}
|
||||
);
|
||||
}
|
16
packages/kbn-scout/src/common/constants.ts
Normal file
16
packages/kbn-scout/src/common/constants.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { Role } from '@kbn/test/src/auth/types';
|
||||
|
||||
export const PROJECT_DEFAULT_ROLES = new Map<string, Role>([
|
||||
['es', 'developer'],
|
||||
['security', 'editor'],
|
||||
['oblt', 'editor'],
|
||||
]);
|
12
packages/kbn-scout/src/common/index.ts
Normal file
12
packages/kbn-scout/src/common/index.ts
Normal 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
export * from './services';
|
||||
export * from './constants';
|
||||
export * from './utils';
|
58
packages/kbn-scout/src/common/services/clients.ts
Normal file
58
packages/kbn-scout/src/common/services/clients.ts
Normal file
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { KbnClient, createEsClientForTesting } from '@kbn/test';
|
||||
import type { ToolingLog } from '@kbn/tooling-log';
|
||||
import { ScoutServerConfig } from '../../types';
|
||||
import { serviceLoadedMsg } from '../../playwright/utils';
|
||||
|
||||
interface ClientOptions {
|
||||
serviceName: string;
|
||||
url: string;
|
||||
username: string;
|
||||
password: string;
|
||||
log: ToolingLog;
|
||||
}
|
||||
|
||||
function createClientUrlWithAuth({ serviceName, url, username, password, log }: ClientOptions) {
|
||||
const clientUrl = new URL(url);
|
||||
clientUrl.username = username;
|
||||
clientUrl.password = password;
|
||||
|
||||
log.debug(serviceLoadedMsg(`${serviceName}client`));
|
||||
return clientUrl.toString();
|
||||
}
|
||||
|
||||
export function createEsClient(config: ScoutServerConfig, log: ToolingLog) {
|
||||
const { username, password } = config.auth;
|
||||
const elasticsearchUrl = createClientUrlWithAuth({
|
||||
serviceName: 'Es',
|
||||
url: config.hosts.elasticsearch,
|
||||
username,
|
||||
password,
|
||||
log,
|
||||
});
|
||||
|
||||
return createEsClientForTesting({
|
||||
esUrl: elasticsearchUrl,
|
||||
authOverride: { username, password },
|
||||
});
|
||||
}
|
||||
|
||||
export function createKbnClient(config: ScoutServerConfig, log: ToolingLog) {
|
||||
const kibanaUrl = createClientUrlWithAuth({
|
||||
serviceName: 'Kbn',
|
||||
url: config.hosts.kibana,
|
||||
username: config.auth.username,
|
||||
password: config.auth.password,
|
||||
log,
|
||||
});
|
||||
|
||||
return new KbnClient({ log, url: kibanaUrl });
|
||||
}
|
29
packages/kbn-scout/src/common/services/config.ts
Normal file
29
packages/kbn-scout/src/common/services/config.ts
Normal 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import { ToolingLog } from '@kbn/tooling-log';
|
||||
import { ScoutServerConfig } from '../../types';
|
||||
import { serviceLoadedMsg } from '../../playwright/utils';
|
||||
|
||||
export function createScoutConfig(configDir: string, configName: string, log: ToolingLog) {
|
||||
if (!configDir || !fs.existsSync(configDir)) {
|
||||
throw new Error(`Directory with servers configuration is missing`);
|
||||
}
|
||||
|
||||
const configPath = path.join(configDir, `${configName}.json`);
|
||||
log.info(`Reading test servers confiuration from file: ${configPath}`);
|
||||
|
||||
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8')) as ScoutServerConfig;
|
||||
|
||||
log.debug(serviceLoadedMsg('config'));
|
||||
|
||||
return config;
|
||||
}
|
28
packages/kbn-scout/src/common/services/es_archiver.ts
Normal file
28
packages/kbn-scout/src/common/services/es_archiver.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { Client } from '@elastic/elasticsearch';
|
||||
import { EsArchiver } from '@kbn/es-archiver';
|
||||
import { REPO_ROOT } from '@kbn/repo-info';
|
||||
import type { KbnClient } from '@kbn/test';
|
||||
import type { ToolingLog } from '@kbn/tooling-log';
|
||||
import { serviceLoadedMsg } from '../../playwright/utils';
|
||||
|
||||
export function createEsArchiver(esClient: Client, kbnClient: KbnClient, log: ToolingLog) {
|
||||
const esArchiver = new EsArchiver({
|
||||
log,
|
||||
client: esClient,
|
||||
kbnClient,
|
||||
baseDir: REPO_ROOT,
|
||||
});
|
||||
|
||||
log.debug(serviceLoadedMsg('esArchiver'));
|
||||
|
||||
return esArchiver;
|
||||
}
|
17
packages/kbn-scout/src/common/services/index.ts
Normal file
17
packages/kbn-scout/src/common/services/index.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
export { createEsClient, createKbnClient } from './clients';
|
||||
export { createScoutConfig } from './config';
|
||||
export { createEsArchiver } from './es_archiver';
|
||||
export { createKbnUrl } from './kibana_url';
|
||||
export { createSamlSessionManager } from './saml_auth';
|
||||
export { createLogger } from './logger';
|
||||
|
||||
export type { KibanaUrl } from './kibana_url';
|
73
packages/kbn-scout/src/common/services/kibana_url.ts
Normal file
73
packages/kbn-scout/src/common/services/kibana_url.ts
Normal 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import type { ToolingLog } from '@kbn/tooling-log';
|
||||
import { ScoutServerConfig } from '../../types';
|
||||
import { serviceLoadedMsg } from '../../playwright/utils';
|
||||
|
||||
export interface PathOptions {
|
||||
/**
|
||||
* Query string parameters
|
||||
*/
|
||||
params?: Record<string, string>;
|
||||
/**
|
||||
* The hash value of the URL
|
||||
*/
|
||||
hash?: string;
|
||||
}
|
||||
|
||||
export class KibanaUrl {
|
||||
#baseUrl: URL;
|
||||
|
||||
constructor(baseUrl: URL) {
|
||||
this.#baseUrl = baseUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an absolute URL based on Kibana's URL
|
||||
* @param rel relative url, resolved relative to Kibana's url
|
||||
* @param options optional modifications to apply to the URL
|
||||
*/
|
||||
get(rel?: string, options?: PathOptions) {
|
||||
const url = new URL(rel ?? '/', this.#baseUrl);
|
||||
|
||||
if (options?.params) {
|
||||
for (const [key, value] of Object.entries(options.params)) {
|
||||
url.searchParams.set(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
if (options?.hash !== undefined) {
|
||||
url.hash = options.hash;
|
||||
}
|
||||
|
||||
return url.href;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL for an app
|
||||
* @param appName name of the app to get the URL for
|
||||
* @param options optional modifications to apply to the URL
|
||||
*/
|
||||
app(appName: string, options?: PathOptions) {
|
||||
return this.get(`/app/${appName}`, options);
|
||||
}
|
||||
|
||||
toString() {
|
||||
return this.#baseUrl.href;
|
||||
}
|
||||
}
|
||||
|
||||
export function createKbnUrl(scoutConfig: ScoutServerConfig, log: ToolingLog) {
|
||||
const kbnUrl = new KibanaUrl(new URL(scoutConfig.hosts.kibana));
|
||||
|
||||
log.debug(serviceLoadedMsg('kbnUrl'));
|
||||
|
||||
return kbnUrl;
|
||||
}
|
19
packages/kbn-scout/src/common/services/logger.ts
Normal file
19
packages/kbn-scout/src/common/services/logger.ts
Normal 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { ToolingLog } from '@kbn/tooling-log';
|
||||
import { serviceLoadedMsg } from '../../playwright/utils';
|
||||
|
||||
export function createLogger() {
|
||||
const log = new ToolingLog({ level: 'verbose', writeTo: process.stdout });
|
||||
|
||||
log.debug(serviceLoadedMsg('logger'));
|
||||
|
||||
return log;
|
||||
}
|
71
packages/kbn-scout/src/common/services/saml_auth.ts
Normal file
71
packages/kbn-scout/src/common/services/saml_auth.ts
Normal file
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
import { URL } from 'url';
|
||||
import {
|
||||
SERVERLESS_ROLES_ROOT_PATH,
|
||||
STATEFUL_ROLES_ROOT_PATH,
|
||||
readRolesDescriptorsFromResource,
|
||||
} from '@kbn/es';
|
||||
import { REPO_ROOT } from '@kbn/repo-info';
|
||||
import { HostOptions, SamlSessionManager } from '@kbn/test';
|
||||
import { ToolingLog } from '@kbn/tooling-log';
|
||||
import { ScoutServerConfig } from '../../types';
|
||||
import { Protocol } from '../../playwright/types';
|
||||
import { serviceLoadedMsg } from '../../playwright/utils';
|
||||
|
||||
const getResourceDirPath = (config: ScoutServerConfig) => {
|
||||
return config.serverless
|
||||
? path.resolve(SERVERLESS_ROLES_ROOT_PATH, config.projectType!)
|
||||
: path.resolve(REPO_ROOT, STATEFUL_ROLES_ROOT_PATH);
|
||||
};
|
||||
|
||||
const createKibanaHostOptions = (config: ScoutServerConfig): HostOptions => {
|
||||
const kibanaUrl = new URL(config.hosts.kibana);
|
||||
kibanaUrl.username = config.auth.username;
|
||||
kibanaUrl.password = config.auth.password;
|
||||
|
||||
return {
|
||||
protocol: kibanaUrl.protocol.replace(':', '') as Protocol,
|
||||
hostname: kibanaUrl.hostname,
|
||||
port: Number(kibanaUrl.port),
|
||||
username: kibanaUrl.username,
|
||||
password: kibanaUrl.password,
|
||||
};
|
||||
};
|
||||
|
||||
export const createSamlSessionManager = (
|
||||
config: ScoutServerConfig,
|
||||
log: ToolingLog
|
||||
): SamlSessionManager => {
|
||||
const resourceDirPath = getResourceDirPath(config);
|
||||
const rolesDefinitionPath = path.resolve(resourceDirPath, 'roles.yml');
|
||||
|
||||
const supportedRoleDescriptors = readRolesDescriptorsFromResource(rolesDefinitionPath) as Record<
|
||||
string,
|
||||
unknown
|
||||
>;
|
||||
const supportedRoles = Object.keys(supportedRoleDescriptors);
|
||||
|
||||
const sessionManager = new SamlSessionManager({
|
||||
hostOptions: createKibanaHostOptions(config),
|
||||
log,
|
||||
isCloud: config.isCloud,
|
||||
supportedRoles: {
|
||||
roles: supportedRoles,
|
||||
sourcePath: rolesDefinitionPath,
|
||||
},
|
||||
cloudUsersFilePath: config.cloudUsersFilePath,
|
||||
});
|
||||
|
||||
log.debug(serviceLoadedMsg('samlAuth'));
|
||||
|
||||
return sessionManager;
|
||||
};
|
20
packages/kbn-scout/src/common/utils/index.ts
Normal file
20
packages/kbn-scout/src/common/utils/index.ts
Normal 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { ToolingLog } from '@kbn/tooling-log';
|
||||
import * as Rx from 'rxjs';
|
||||
|
||||
export async function silence(log: ToolingLog, milliseconds: number) {
|
||||
await Rx.firstValueFrom(
|
||||
log.getWritten$().pipe(
|
||||
Rx.startWith(null),
|
||||
Rx.switchMap(() => Rx.timer(milliseconds))
|
||||
)
|
||||
);
|
||||
}
|
138
packages/kbn-scout/src/config/config.ts
Normal file
138
packages/kbn-scout/src/config/config.ts
Normal file
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { Schema } from 'joi';
|
||||
import * as Url from 'url';
|
||||
import Path from 'path';
|
||||
import { cloneDeepWith, get, has, toPath } from 'lodash';
|
||||
import { REPO_ROOT } from '@kbn/repo-info';
|
||||
import { schema } from './schema';
|
||||
import { ScoutServerConfig } from '../types';
|
||||
import { formatCurrentDate, getProjectType } from './utils';
|
||||
|
||||
const $values = Symbol('values');
|
||||
|
||||
export class Config {
|
||||
private [$values]: Record<string, any>;
|
||||
|
||||
constructor(data: Record<string, any>) {
|
||||
const { error, value } = schema.validate(data, {
|
||||
abortEarly: false,
|
||||
});
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
this[$values] = value;
|
||||
}
|
||||
|
||||
public has(key: string | string[]) {
|
||||
function recursiveHasCheck(
|
||||
remainingPath: string[],
|
||||
values: Record<string, any>,
|
||||
childSchema: any
|
||||
): boolean {
|
||||
if (!childSchema.$_terms.keys && !childSchema.$_terms.patterns) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// normalize child and pattern checks so we can iterate the checks in a single loop
|
||||
const checks: Array<{ test: (k: string) => boolean; schema: Schema }> = [
|
||||
// match children first, they have priority
|
||||
...(childSchema.$_terms.keys || []).map((child: { key: string; schema: Schema }) => ({
|
||||
test: (k: string) => child.key === k,
|
||||
schema: child.schema,
|
||||
})),
|
||||
|
||||
// match patterns on any key that doesn't match an explicit child
|
||||
...(childSchema.$_terms.patterns || []).map((pattern: { regex: RegExp; rule: Schema }) => ({
|
||||
test: (k: string) => pattern.regex.test(k) && has(values, k),
|
||||
schema: pattern.rule,
|
||||
})),
|
||||
];
|
||||
|
||||
for (const check of checks) {
|
||||
if (!check.test(remainingPath[0])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (remainingPath.length > 1) {
|
||||
return recursiveHasCheck(
|
||||
remainingPath.slice(1),
|
||||
get(values, remainingPath[0]),
|
||||
check.schema
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const path = toPath(key);
|
||||
if (!path.length) {
|
||||
return true;
|
||||
}
|
||||
return recursiveHasCheck(path, this[$values], schema);
|
||||
}
|
||||
|
||||
public get(key: string | string[], defaultValue?: any) {
|
||||
if (!this.has(key)) {
|
||||
throw new Error(`Unknown config key "${key}"`);
|
||||
}
|
||||
|
||||
return cloneDeepWith(get(this[$values], key, defaultValue), (v) => {
|
||||
if (typeof v === 'function') {
|
||||
return v;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public getAll() {
|
||||
return cloneDeepWith(this[$values], (v) => {
|
||||
if (typeof v === 'function') {
|
||||
return v;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public getTestServersConfig(): ScoutServerConfig {
|
||||
return {
|
||||
serverless: this.get('serverless'),
|
||||
projectType: this.get('serverless')
|
||||
? getProjectType(this.get('kbnTestServer.serverArgs'))
|
||||
: undefined,
|
||||
isCloud: false,
|
||||
cloudUsersFilePath: Path.resolve(REPO_ROOT, '.ftr', 'role_users.json'),
|
||||
hosts: {
|
||||
kibana: Url.format({
|
||||
protocol: this.get('servers.kibana.protocol'),
|
||||
hostname: this.get('servers.kibana.hostname'),
|
||||
port: this.get('servers.kibana.port'),
|
||||
}),
|
||||
elasticsearch: Url.format({
|
||||
protocol: this.get('servers.elasticsearch.protocol'),
|
||||
hostname: this.get('servers.elasticsearch.hostname'),
|
||||
port: this.get('servers.elasticsearch.port'),
|
||||
}),
|
||||
},
|
||||
auth: {
|
||||
username: this.get('servers.kibana.username'),
|
||||
password: this.get('servers.kibana.password'),
|
||||
},
|
||||
|
||||
metadata: {
|
||||
generatedOn: formatCurrentDate(),
|
||||
config: this.getAll(),
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
22
packages/kbn-scout/src/config/constants.ts
Normal file
22
packages/kbn-scout/src/config/constants.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { resolve } from 'path';
|
||||
import { REPO_ROOT } from '@kbn/repo-info';
|
||||
|
||||
const SECURITY_TEST_PATH = resolve(REPO_ROOT, 'x-pack/test/security_api_integration');
|
||||
|
||||
export const SAML_IDP_PLUGIN_PATH = resolve(SECURITY_TEST_PATH, 'plugins/saml_provider');
|
||||
|
||||
export const STATEFUL_IDP_METADATA_PATH = resolve(
|
||||
SECURITY_TEST_PATH,
|
||||
'packages/helpers/saml/idp_metadata_mock_idp.xml'
|
||||
);
|
||||
export const SERVERLESS_IDP_METADATA_PATH = resolve(SAML_IDP_PLUGIN_PATH, 'metadata.xml');
|
||||
export const JWKS_PATH = resolve(SECURITY_TEST_PATH, 'packages/helpers/oidc/jwks.json');
|
26
packages/kbn-scout/src/config/get_config_file.ts
Normal file
26
packages/kbn-scout/src/config/get_config_file.ts
Normal 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
import { CliSupportedServerModes } from '../types';
|
||||
|
||||
export const getConfigFilePath = (config: CliSupportedServerModes): string => {
|
||||
if (config === 'stateful') {
|
||||
return path.join(__dirname, 'stateful', 'stateful.config.ts');
|
||||
}
|
||||
|
||||
const [mode, type] = config.split('=');
|
||||
if (mode !== 'serverless' || !type) {
|
||||
throw new Error(
|
||||
`Invalid config format: ${config}. Expected "stateful" or "serverless=<type>".`
|
||||
);
|
||||
}
|
||||
|
||||
return path.join(__dirname, 'serverless', `${type}.serverless.config.ts`);
|
||||
};
|
13
packages/kbn-scout/src/config/index.ts
Normal file
13
packages/kbn-scout/src/config/index.ts
Normal 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
export { loadConfig } from './loader/config_load';
|
||||
export { getConfigFilePath } from './get_config_file';
|
||||
export { loadServersConfig } from './utils';
|
||||
export type { Config } from './config';
|
27
packages/kbn-scout/src/config/loader/config_load.ts
Normal file
27
packages/kbn-scout/src/config/loader/config_load.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
import { ToolingLog } from '@kbn/tooling-log';
|
||||
import { Config } from '../config';
|
||||
|
||||
export const loadConfig = async (configPath: string, log: ToolingLog): Promise<Config> => {
|
||||
try {
|
||||
const absolutePath = path.resolve(configPath);
|
||||
const configModule = await import(absolutePath);
|
||||
|
||||
if (configModule.servers) {
|
||||
return new Config(configModule.servers);
|
||||
} else {
|
||||
throw new Error(`No 'servers' found in the config file at path: ${absolutePath}`);
|
||||
}
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to load config from ${configPath}: ${error.message}`);
|
||||
}
|
||||
};
|
10
packages/kbn-scout/src/config/schema/index.ts
Normal file
10
packages/kbn-scout/src/config/schema/index.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
export { schema } from './schema';
|
139
packages/kbn-scout/src/config/schema/schema.ts
Normal file
139
packages/kbn-scout/src/config/schema/schema.ts
Normal file
|
@ -0,0 +1,139 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import Joi from 'joi';
|
||||
|
||||
const maybeRequireKeys = (keys: string[], schemas: Record<string, Joi.Schema>) => {
|
||||
if (!keys.length) {
|
||||
return schemas;
|
||||
}
|
||||
|
||||
const withRequires: Record<string, Joi.Schema> = {};
|
||||
for (const [key, schema] of Object.entries(schemas)) {
|
||||
withRequires[key] = keys.includes(key) ? schema.required() : schema;
|
||||
}
|
||||
return withRequires;
|
||||
};
|
||||
|
||||
const urlPartsSchema = ({ requiredKeys }: { requiredKeys?: string[] } = {}) =>
|
||||
Joi.object()
|
||||
.keys(
|
||||
maybeRequireKeys(requiredKeys ?? [], {
|
||||
protocol: Joi.string().valid('http', 'https').default('http'),
|
||||
hostname: Joi.string().hostname().default('localhost'),
|
||||
port: Joi.number(),
|
||||
auth: Joi.string().regex(/^[^:]+:.+$/, 'username and password separated by a colon'),
|
||||
username: Joi.string(),
|
||||
password: Joi.string(),
|
||||
pathname: Joi.string().regex(/^\//, 'start with a /'),
|
||||
hash: Joi.string().regex(/^\//, 'start with a /'),
|
||||
certificateAuthorities: Joi.array().items(Joi.binary()).optional(),
|
||||
})
|
||||
)
|
||||
.default();
|
||||
|
||||
const requiredWhenEnabled = (schema: Joi.Schema) => {
|
||||
return Joi.when('enabled', {
|
||||
is: true,
|
||||
then: schema.required(),
|
||||
otherwise: schema.optional(),
|
||||
});
|
||||
};
|
||||
|
||||
const dockerServerSchema = () =>
|
||||
Joi.object()
|
||||
.keys({
|
||||
enabled: Joi.boolean().required(),
|
||||
image: requiredWhenEnabled(Joi.string()),
|
||||
port: requiredWhenEnabled(Joi.number()),
|
||||
portInContainer: requiredWhenEnabled(Joi.number()),
|
||||
waitForLogLine: Joi.alternatives(Joi.object().instance(RegExp), Joi.string()).optional(),
|
||||
waitForLogLineTimeoutMs: Joi.number().integer().optional(),
|
||||
waitFor: Joi.func().optional(),
|
||||
args: Joi.array().items(Joi.string()).optional(),
|
||||
})
|
||||
.default();
|
||||
|
||||
export const schema = Joi.object()
|
||||
.keys({
|
||||
serverless: Joi.boolean().default(false),
|
||||
servers: Joi.object()
|
||||
.keys({
|
||||
kibana: urlPartsSchema(),
|
||||
elasticsearch: urlPartsSchema({
|
||||
requiredKeys: ['port'],
|
||||
}),
|
||||
fleetserver: urlPartsSchema(),
|
||||
})
|
||||
.default(),
|
||||
|
||||
esTestCluster: Joi.object()
|
||||
.keys({
|
||||
license: Joi.valid('basic', 'trial', 'gold').default('basic'),
|
||||
from: Joi.string().default('snapshot'),
|
||||
serverArgs: Joi.array().items(Joi.string()).default([]),
|
||||
esJavaOpts: Joi.string(),
|
||||
dataArchive: Joi.string(),
|
||||
ssl: Joi.boolean().default(false),
|
||||
ccs: Joi.object().keys({
|
||||
remoteClusterUrl: Joi.string().uri({
|
||||
scheme: /https?/,
|
||||
}),
|
||||
}),
|
||||
files: Joi.array().items(Joi.string()),
|
||||
})
|
||||
.default(),
|
||||
|
||||
esServerlessOptions: Joi.object()
|
||||
.keys({
|
||||
host: Joi.string().ip(),
|
||||
resources: Joi.array().items(Joi.string()).default([]),
|
||||
})
|
||||
.default(),
|
||||
|
||||
kbnTestServer: Joi.object()
|
||||
.keys({
|
||||
buildArgs: Joi.array(),
|
||||
sourceArgs: Joi.array(),
|
||||
serverArgs: Joi.array(),
|
||||
installDir: Joi.string(),
|
||||
useDedicatedTaskRunner: Joi.boolean().default(false),
|
||||
/** Options for how FTR should execute and interact with Kibana */
|
||||
runOptions: Joi.object()
|
||||
.keys({
|
||||
/**
|
||||
* Log message to wait for before initiating tests, defaults to waiting for Kibana status to be `available`.
|
||||
* Note that this log message must not be filtered out by the current logging config, for example by the
|
||||
* log level. If needed, you can adjust the logging level via `kbnTestServer.serverArgs`.
|
||||
*/
|
||||
wait: Joi.object()
|
||||
.regex()
|
||||
.default(/Kibana is now available/),
|
||||
|
||||
/**
|
||||
* Does this test config only work when run against source?
|
||||
*/
|
||||
alwaysUseSource: Joi.boolean().default(false),
|
||||
})
|
||||
.default(),
|
||||
env: Joi.object().unknown().default(),
|
||||
delayShutdown: Joi.number(),
|
||||
})
|
||||
.default(),
|
||||
|
||||
// settings for the kibanaServer.uiSettings module
|
||||
uiSettings: Joi.object()
|
||||
.keys({
|
||||
defaults: Joi.object().unknown(true),
|
||||
})
|
||||
.default(),
|
||||
|
||||
dockerServers: Joi.object().pattern(Joi.string(), dockerServerSchema()).default(),
|
||||
})
|
||||
.default();
|
|
@ -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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { ScoutLoaderConfig } from '../../types';
|
||||
import { defaultConfig } from './serverless.base.config';
|
||||
|
||||
export const servers: ScoutLoaderConfig = {
|
||||
...defaultConfig,
|
||||
esTestCluster: {
|
||||
...defaultConfig.esTestCluster,
|
||||
serverArgs: [...defaultConfig.esTestCluster.serverArgs],
|
||||
},
|
||||
kbnTestServer: {
|
||||
serverArgs: [
|
||||
...defaultConfig.kbnTestServer.serverArgs,
|
||||
'--serverless=es',
|
||||
'--coreApp.allowDynamicConfigOverrides=true',
|
||||
],
|
||||
},
|
||||
};
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { defaultConfig } from './serverless.base.config';
|
||||
import { ScoutLoaderConfig } from '../../types';
|
||||
|
||||
export const servers: ScoutLoaderConfig = {
|
||||
...defaultConfig,
|
||||
esTestCluster: {
|
||||
...defaultConfig.esTestCluster,
|
||||
serverArgs: [
|
||||
...defaultConfig.esTestCluster.serverArgs,
|
||||
'xpack.apm_data.enabled=true',
|
||||
// for ML, data frame analytics are not part of this project type
|
||||
'xpack.ml.dfa.enabled=false',
|
||||
],
|
||||
},
|
||||
kbnTestServer: {
|
||||
serverArgs: [
|
||||
...defaultConfig.kbnTestServer.serverArgs,
|
||||
'--serverless=oblt',
|
||||
'--coreApp.allowDynamicConfigOverrides=true',
|
||||
'--xpack.uptime.service.manifestUrl=mockDevUrl',
|
||||
],
|
||||
},
|
||||
};
|
|
@ -0,0 +1,2 @@
|
|||
package_paths:
|
||||
- /packages/package-storage
|
|
@ -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
|
||||
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { ScoutLoaderConfig } from '../../types';
|
||||
import { defaultConfig } from './serverless.base.config';
|
||||
|
||||
export const servers: ScoutLoaderConfig = {
|
||||
...defaultConfig,
|
||||
esTestCluster: {
|
||||
...defaultConfig.esTestCluster,
|
||||
serverArgs: [
|
||||
...defaultConfig.esTestCluster.serverArgs,
|
||||
'xpack.security.authc.api_key.cache.max_keys=70000',
|
||||
],
|
||||
},
|
||||
kbnTestServer: {
|
||||
serverArgs: [
|
||||
...defaultConfig.kbnTestServer.serverArgs,
|
||||
'--serverless=security',
|
||||
'--coreApp.allowDynamicConfigOverrides=true',
|
||||
`--xpack.task_manager.unsafe.exclude_task_types=${JSON.stringify(['Fleet-Metrics-Task'])}`,
|
||||
],
|
||||
},
|
||||
};
|
|
@ -0,0 +1,157 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { resolve, join } from 'path';
|
||||
import { format as formatUrl } from 'url';
|
||||
import Fs from 'fs';
|
||||
|
||||
import { CA_CERT_PATH, kibanaDevServiceAccount } from '@kbn/dev-utils';
|
||||
import { defineDockerServersConfig, getDockerFileMountPath } from '@kbn/test';
|
||||
import { MOCK_IDP_REALM_NAME } from '@kbn/mock-idp-utils';
|
||||
|
||||
import { dockerImage } from '@kbn/test-suites-xpack/fleet_api_integration/config.base';
|
||||
import { REPO_ROOT } from '@kbn/repo-info';
|
||||
import { ScoutLoaderConfig } from '../../types';
|
||||
import { SAML_IDP_PLUGIN_PATH, SERVERLESS_IDP_METADATA_PATH, JWKS_PATH } from '../constants';
|
||||
|
||||
const packageRegistryConfig = join(__dirname, './package_registry_config.yml');
|
||||
const dockerArgs: string[] = ['-v', `${packageRegistryConfig}:/package-registry/config.yml`];
|
||||
|
||||
/**
|
||||
* This is used by CI to set the docker registry port
|
||||
* you can also define this environment variable locally when running tests which
|
||||
* will spin up a local docker package registry locally for you
|
||||
* if this is defined it takes precedence over the `packageRegistryOverride` variable
|
||||
*/
|
||||
const dockerRegistryPort: string | undefined = process.env.FLEET_PACKAGE_REGISTRY_PORT;
|
||||
|
||||
const servers = {
|
||||
elasticsearch: {
|
||||
protocol: 'https',
|
||||
hostname: 'localhost',
|
||||
port: 9220,
|
||||
username: 'elastic_serverless',
|
||||
password: 'changeme',
|
||||
certificateAuthorities: [Fs.readFileSync(CA_CERT_PATH)],
|
||||
},
|
||||
kibana: {
|
||||
protocol: 'http',
|
||||
hostname: 'localhost',
|
||||
port: 5620,
|
||||
username: 'elastic_serverless',
|
||||
password: 'changeme',
|
||||
},
|
||||
};
|
||||
|
||||
export const defaultConfig: ScoutLoaderConfig = {
|
||||
serverless: true,
|
||||
servers,
|
||||
dockerServers: defineDockerServersConfig({
|
||||
registry: {
|
||||
enabled: !!dockerRegistryPort,
|
||||
image: dockerImage,
|
||||
portInContainer: 8080,
|
||||
port: dockerRegistryPort,
|
||||
args: dockerArgs,
|
||||
waitForLogLine: 'package manifests loaded',
|
||||
waitForLogLineTimeoutMs: 60 * 2 * 1000, // 2 minutes
|
||||
},
|
||||
}),
|
||||
esTestCluster: {
|
||||
from: 'serverless',
|
||||
files: [SERVERLESS_IDP_METADATA_PATH, JWKS_PATH],
|
||||
serverArgs: [
|
||||
'xpack.security.authc.realms.file.file1.order=-100',
|
||||
`xpack.security.authc.realms.native.native1.enabled=false`,
|
||||
`xpack.security.authc.realms.native.native1.order=-97`,
|
||||
|
||||
'xpack.security.authc.realms.jwt.jwt1.allowed_audiences=elasticsearch',
|
||||
`xpack.security.authc.realms.jwt.jwt1.allowed_issuer=https://kibana.elastic.co/jwt/`,
|
||||
`xpack.security.authc.realms.jwt.jwt1.allowed_signature_algorithms=[RS256]`,
|
||||
`xpack.security.authc.realms.jwt.jwt1.allowed_subjects=elastic-agent`,
|
||||
`xpack.security.authc.realms.jwt.jwt1.claims.principal=sub`,
|
||||
'xpack.security.authc.realms.jwt.jwt1.client_authentication.type=shared_secret',
|
||||
'xpack.security.authc.realms.jwt.jwt1.order=-98',
|
||||
`xpack.security.authc.realms.jwt.jwt1.pkc_jwkset_path=${getDockerFileMountPath(JWKS_PATH)}`,
|
||||
`xpack.security.authc.realms.jwt.jwt1.token_type=access_token`,
|
||||
],
|
||||
ssl: true, // SSL is required for SAML realm
|
||||
},
|
||||
kbnTestServer: {
|
||||
buildArgs: [],
|
||||
env: {
|
||||
KBN_PATH_CONF: resolve(REPO_ROOT, 'config'),
|
||||
},
|
||||
sourceArgs: ['--no-base-path', '--env.name=development'],
|
||||
serverArgs: [
|
||||
`--server.restrictInternalApis=true`,
|
||||
`--server.port=${servers.kibana.port}`,
|
||||
'--status.allowAnonymous=true',
|
||||
`--migrations.zdt.runOnRoles=${JSON.stringify(['ui'])}`,
|
||||
// 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.serviceAccountToken=${kibanaDevServiceAccount.token}`,
|
||||
`--elasticsearch.ssl.certificateAuthorities=${CA_CERT_PATH}`,
|
||||
'--telemetry.sendUsageTo=staging',
|
||||
`--logging.appenders.deprecation=${JSON.stringify({
|
||||
type: 'console',
|
||||
layout: {
|
||||
type: 'json',
|
||||
},
|
||||
})}`,
|
||||
`--logging.loggers=${JSON.stringify([
|
||||
{
|
||||
name: 'elasticsearch.deprecation',
|
||||
level: 'all',
|
||||
appenders: ['deprecation'],
|
||||
},
|
||||
])}`,
|
||||
// Add meta info to the logs so FTR logs are more actionable
|
||||
`--logging.appenders.default=${JSON.stringify({
|
||||
type: 'console',
|
||||
layout: {
|
||||
type: 'pattern',
|
||||
pattern: '[%date][%level][%logger] %message %meta',
|
||||
},
|
||||
})}`,
|
||||
`--logging.appenders.console=${JSON.stringify({
|
||||
type: 'console',
|
||||
layout: {
|
||||
type: 'pattern',
|
||||
pattern: '[%date][%level][%logger] %message %meta',
|
||||
},
|
||||
})}`,
|
||||
// This ensures that we register the Security SAML API endpoints.
|
||||
// In the real world the SAML config is injected by control plane.
|
||||
`--plugin-path=${SAML_IDP_PLUGIN_PATH}`,
|
||||
'--xpack.cloud.id=ftr_fake_cloud_id',
|
||||
// Ensure that SAML is used as the default authentication method whenever a user navigates to Kibana. In other
|
||||
// words, Kibana should attempt to authenticate the user using the provider with the lowest order if the Login
|
||||
// Selector is disabled (which is how Serverless Kibana is configured). By declaring `cloud-basic` with a higher
|
||||
// order, we indicate that basic authentication can still be used, but only if explicitly requested when the
|
||||
// user navigates to `/login` page directly and enters username and password in the login form.
|
||||
'--xpack.security.authc.selector.enabled=false',
|
||||
`--xpack.security.authc.providers=${JSON.stringify({
|
||||
saml: { 'cloud-saml-kibana': { order: 0, realm: MOCK_IDP_REALM_NAME } },
|
||||
basic: { 'cloud-basic': { order: 1 } },
|
||||
})}`,
|
||||
'--xpack.encryptedSavedObjects.encryptionKey="wuGNaIhoMpk5sO4UBxgr3NyW1sFcLgIf"',
|
||||
`--server.publicBaseUrl=${servers.kibana.protocol}://${servers.kibana.hostname}:${servers.kibana.port}`,
|
||||
// configure security reponse header report-to settings to mimic MKI configuration
|
||||
`--csp.report_to=${JSON.stringify(['violations-endpoint'])}`,
|
||||
`--permissionsPolicy.report_to=${JSON.stringify(['violations-endpoint'])}`,
|
||||
],
|
||||
},
|
||||
};
|
204
packages/kbn-scout/src/config/stateful/base.config.ts
Normal file
204
packages/kbn-scout/src/config/stateful/base.config.ts
Normal file
|
@ -0,0 +1,204 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { join } from 'path';
|
||||
import { format as formatUrl } from 'url';
|
||||
|
||||
import {
|
||||
MOCK_IDP_ENTITY_ID,
|
||||
MOCK_IDP_ATTRIBUTE_PRINCIPAL,
|
||||
MOCK_IDP_ATTRIBUTE_ROLES,
|
||||
MOCK_IDP_ATTRIBUTE_EMAIL,
|
||||
MOCK_IDP_ATTRIBUTE_NAME,
|
||||
} from '@kbn/mock-idp-utils';
|
||||
import { defineDockerServersConfig } from '@kbn/test';
|
||||
import path from 'path';
|
||||
|
||||
import { MOCK_IDP_REALM_NAME } from '@kbn/mock-idp-utils';
|
||||
|
||||
import { dockerImage } from '@kbn/test-suites-xpack/fleet_api_integration/config.base';
|
||||
import { REPO_ROOT } from '@kbn/repo-info';
|
||||
import { STATEFUL_ROLES_ROOT_PATH } from '@kbn/es';
|
||||
import type { ScoutLoaderConfig } from '../../types';
|
||||
import { SAML_IDP_PLUGIN_PATH, STATEFUL_IDP_METADATA_PATH } from '../constants';
|
||||
|
||||
const packageRegistryConfig = join(__dirname, './package_registry_config.yml');
|
||||
const dockerArgs: string[] = ['-v', `${packageRegistryConfig}:/package-registry/config.yml`];
|
||||
|
||||
/**
|
||||
* This is used by CI to set the docker registry port
|
||||
* you can also define this environment variable locally when running tests which
|
||||
* will spin up a local docker package registry locally for you
|
||||
* if this is defined it takes precedence over the `packageRegistryOverride` variable
|
||||
*/
|
||||
const dockerRegistryPort: string | undefined = process.env.FLEET_PACKAGE_REGISTRY_PORT;
|
||||
|
||||
// if config is executed on CI or locally
|
||||
const isRunOnCI = process.env.CI;
|
||||
|
||||
const servers = {
|
||||
elasticsearch: {
|
||||
protocol: 'http',
|
||||
hostname: 'localhost',
|
||||
port: 9220,
|
||||
username: 'kibana_system',
|
||||
password: 'changeme',
|
||||
},
|
||||
kibana: {
|
||||
protocol: 'http',
|
||||
hostname: 'localhost',
|
||||
port: 5620,
|
||||
username: 'elastic',
|
||||
password: 'changeme',
|
||||
},
|
||||
};
|
||||
|
||||
const kbnUrl = `${servers.kibana.protocol}://${servers.kibana.hostname}:${servers.kibana.port}`;
|
||||
|
||||
export const defaultConfig: ScoutLoaderConfig = {
|
||||
servers,
|
||||
dockerServers: defineDockerServersConfig({
|
||||
registry: {
|
||||
enabled: !!dockerRegistryPort,
|
||||
image: dockerImage,
|
||||
portInContainer: 8080,
|
||||
port: dockerRegistryPort,
|
||||
args: dockerArgs,
|
||||
waitForLogLine: 'package manifests loaded',
|
||||
waitForLogLineTimeoutMs: 60 * 2 * 1000, // 2 minutes
|
||||
},
|
||||
}),
|
||||
esTestCluster: {
|
||||
from: 'snapshot',
|
||||
license: 'trial',
|
||||
files: [
|
||||
// Passing the roles that are equivalent to the ones we have in serverless
|
||||
path.resolve(REPO_ROOT, STATEFUL_ROLES_ROOT_PATH, 'roles.yml'),
|
||||
],
|
||||
serverArgs: [
|
||||
'path.repo=/tmp/',
|
||||
'path.repo=/tmp/repo,/tmp/repo_1,/tmp/repo_2,/tmp/cloud-snapshots/',
|
||||
'node.attr.name=apiIntegrationTestNode',
|
||||
'xpack.security.authc.api_key.enabled=true',
|
||||
'xpack.security.authc.token.enabled=true',
|
||||
`xpack.security.authc.realms.saml.${MOCK_IDP_REALM_NAME}.order=0`,
|
||||
`xpack.security.authc.realms.saml.${MOCK_IDP_REALM_NAME}.idp.metadata.path=${STATEFUL_IDP_METADATA_PATH}`,
|
||||
`xpack.security.authc.realms.saml.${MOCK_IDP_REALM_NAME}.idp.entity_id=${MOCK_IDP_ENTITY_ID}`,
|
||||
`xpack.security.authc.realms.saml.${MOCK_IDP_REALM_NAME}.sp.entity_id=${kbnUrl}`,
|
||||
`xpack.security.authc.realms.saml.${MOCK_IDP_REALM_NAME}.sp.acs=${kbnUrl}/api/security/saml/callback`,
|
||||
`xpack.security.authc.realms.saml.${MOCK_IDP_REALM_NAME}.sp.logout=${kbnUrl}/logout`,
|
||||
`xpack.security.authc.realms.saml.${MOCK_IDP_REALM_NAME}.attributes.principal=${MOCK_IDP_ATTRIBUTE_PRINCIPAL}`,
|
||||
`xpack.security.authc.realms.saml.${MOCK_IDP_REALM_NAME}.attributes.groups=${MOCK_IDP_ATTRIBUTE_ROLES}`,
|
||||
`xpack.security.authc.realms.saml.${MOCK_IDP_REALM_NAME}.attributes.name=${MOCK_IDP_ATTRIBUTE_NAME}`,
|
||||
`xpack.security.authc.realms.saml.${MOCK_IDP_REALM_NAME}.attributes.mail=${MOCK_IDP_ATTRIBUTE_EMAIL}`,
|
||||
],
|
||||
ssl: false,
|
||||
},
|
||||
kbnTestServer: {
|
||||
buildArgs: [],
|
||||
env: {},
|
||||
sourceArgs: ['--no-base-path', '--env.name=development'],
|
||||
serverArgs: [
|
||||
`--server.port=${servers.kibana.port}`,
|
||||
'--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=${servers.elasticsearch.username}`,
|
||||
`--elasticsearch.password=${servers.elasticsearch.password}`,
|
||||
// Needed for async search functional tests to introduce a delay
|
||||
`--data.search.aggs.shardDelay.enabled=true`,
|
||||
`--data.query.timefilter.minRefreshInterval=1000`,
|
||||
`--security.showInsecureClusterWarning=false`,
|
||||
'--telemetry.banner=false',
|
||||
'--telemetry.optIn=false',
|
||||
// These are *very* important to have them pointing to staging
|
||||
'--telemetry.sendUsageTo=staging',
|
||||
`--server.maxPayload=1679958`,
|
||||
// newsfeed mock service
|
||||
`--plugin-path=${path.join(REPO_ROOT, 'test', 'common', 'plugins', 'newsfeed')}`,
|
||||
// otel mock service
|
||||
`--plugin-path=${path.join(REPO_ROOT, 'test', 'common', 'plugins', 'otel_metrics')}`,
|
||||
`--newsfeed.service.urlRoot=${kbnUrl}`,
|
||||
`--newsfeed.service.pathTemplate=/api/_newsfeed-FTS-external-service-simulators/kibana/v{VERSION}.json`,
|
||||
`--logging.appenders.deprecation=${JSON.stringify({
|
||||
type: 'console',
|
||||
layout: {
|
||||
type: 'json',
|
||||
},
|
||||
})}`,
|
||||
`--logging.loggers=${JSON.stringify([
|
||||
{
|
||||
name: 'elasticsearch.deprecation',
|
||||
level: 'all',
|
||||
appenders: ['deprecation'],
|
||||
},
|
||||
])}`,
|
||||
// Add meta info to the logs so FTR logs are more actionable
|
||||
`--logging.appenders.default=${JSON.stringify({
|
||||
type: 'console',
|
||||
layout: {
|
||||
type: 'pattern',
|
||||
pattern: '[%date][%level][%logger] %message %meta',
|
||||
},
|
||||
})}`,
|
||||
`--logging.appenders.console=${JSON.stringify({
|
||||
type: 'console',
|
||||
layout: {
|
||||
type: 'pattern',
|
||||
pattern: '[%date][%level][%logger] %message %meta',
|
||||
},
|
||||
})}`,
|
||||
// x-pack/test/functional/config.base.js
|
||||
'--status.allowAnonymous=true',
|
||||
'--server.uuid=5b2de169-2785-441b-ae8c-186a1936b17d',
|
||||
'--xpack.maps.showMapsInspectorAdapter=true',
|
||||
'--xpack.maps.preserveDrawingBuffer=true',
|
||||
'--xpack.security.encryptionKey="wuGNaIhoMpk5sO4UBxgr3NyW1sFcLgIf"', // server restarts should not invalidate active sessions
|
||||
'--xpack.encryptedSavedObjects.encryptionKey="DkdXazszSCYexXqz4YktBGHCRkV6hyNK"',
|
||||
'--xpack.discoverEnhanced.actions.exploreDataInContextMenu.enabled=true',
|
||||
'--savedObjects.maxImportPayloadBytes=10485760', // for OSS test management/_import_objects,
|
||||
'--savedObjects.allowHttpApiAccess=false', // override default to not allow hiddenFromHttpApis saved objects access to the http APIs see https://github.com/elastic/dev/issues/2200
|
||||
// explicitly disable internal API restriction. See https://github.com/elastic/kibana/issues/163654
|
||||
'--server.restrictInternalApis=false',
|
||||
// disable fleet task that writes to metrics.fleet_server.* data streams, impacting functional tests
|
||||
`--xpack.task_manager.unsafe.exclude_task_types=${JSON.stringify(['Fleet-Metrics-Task'])}`,
|
||||
// x-pack/test/api_integration/config.ts
|
||||
'--xpack.security.session.idleTimeout=3600000', // 1 hour
|
||||
'--telemetry.optIn=true',
|
||||
'--xpack.fleet.agents.pollingRequestTimeout=5000', // 5 seconds
|
||||
'--xpack.ruleRegistry.write.enabled=true',
|
||||
'--xpack.ruleRegistry.write.enabled=true',
|
||||
'--xpack.ruleRegistry.write.cache.enabled=false',
|
||||
'--monitoring_collection.opentelemetry.metrics.prometheus.enabled=true',
|
||||
// SAML configuration
|
||||
...(isRunOnCI ? [] : ['--mock_idp_plugin.enabled=true']),
|
||||
// This ensures that we register the Security SAML API endpoints.
|
||||
// In the real world the SAML config is injected by control plane.
|
||||
`--plugin-path=${SAML_IDP_PLUGIN_PATH}`,
|
||||
'--xpack.cloud.id=ftr_fake_cloud_id',
|
||||
// Ensure that SAML is used as the default authentication method whenever a user navigates to Kibana. In other
|
||||
// words, Kibana should attempt to authenticate the user using the provider with the lowest order if the Login
|
||||
// Selector is disabled (replicating Serverless configuration). By declaring `cloud-basic` with a higher
|
||||
// order, we indicate that basic authentication can still be used, but only if explicitly requested when the
|
||||
// user navigates to `/login` page directly and enters username and password in the login form.
|
||||
'--xpack.security.authc.selector.enabled=false',
|
||||
`--xpack.security.authc.providers=${JSON.stringify({
|
||||
saml: { 'cloud-saml-kibana': { order: 0, realm: MOCK_IDP_REALM_NAME } },
|
||||
basic: { 'cloud-basic': { order: 1 } },
|
||||
})}`,
|
||||
`--server.publicBaseUrl=${kbnUrl}`,
|
||||
],
|
||||
},
|
||||
};
|
13
packages/kbn-scout/src/config/stateful/stateful.config.ts
Normal file
13
packages/kbn-scout/src/config/stateful/stateful.config.ts
Normal 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { ScoutLoaderConfig } from '../../types';
|
||||
import { defaultConfig } from './base.config';
|
||||
|
||||
export const servers: ScoutLoaderConfig = defaultConfig;
|
68
packages/kbn-scout/src/config/utils.ts
Normal file
68
packages/kbn-scout/src/config/utils.ts
Normal file
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import * as Fs from 'fs';
|
||||
import getopts from 'getopts';
|
||||
import path from 'path';
|
||||
import { ToolingLog } from '@kbn/tooling-log';
|
||||
import { ServerlessProjectType } from '@kbn/es';
|
||||
import { REPO_ROOT } from '@kbn/repo-info';
|
||||
import { CliSupportedServerModes, ScoutServerConfig } from '../types';
|
||||
import { getConfigFilePath } from './get_config_file';
|
||||
import { loadConfig } from './loader/config_load';
|
||||
|
||||
export const formatCurrentDate = () => {
|
||||
const now = new Date();
|
||||
|
||||
const format = (num: number, length: number) => String(num).padStart(length, '0');
|
||||
|
||||
return (
|
||||
`${format(now.getDate(), 2)}/${format(now.getMonth() + 1, 2)}/${now.getFullYear()} ` +
|
||||
`${format(now.getHours(), 2)}:${format(now.getMinutes(), 2)}:${format(now.getSeconds(), 2)}.` +
|
||||
`${format(now.getMilliseconds(), 3)}`
|
||||
);
|
||||
};
|
||||
|
||||
const saveTestServersConfigOnDisk = (testServersConfig: ScoutServerConfig, log: ToolingLog) => {
|
||||
const configDirPath = path.resolve(REPO_ROOT, '.scout', 'servers');
|
||||
const configFilePath = path.join(configDirPath, `local.json`);
|
||||
|
||||
try {
|
||||
const jsonData = JSON.stringify(testServersConfig, null, 2);
|
||||
|
||||
if (!Fs.existsSync(configDirPath)) {
|
||||
log.debug(`scout: creating configuration directory: ${configDirPath}`);
|
||||
Fs.mkdirSync(configDirPath, { recursive: true });
|
||||
}
|
||||
|
||||
Fs.writeFileSync(configFilePath, jsonData, 'utf-8');
|
||||
log.info(`scout: Test server configuration saved at ${configFilePath}`);
|
||||
} catch (error) {
|
||||
log.error(`scout: Failed to save test server configuration - ${error.message}`);
|
||||
throw new Error(`Failed to save test server configuration at ${configFilePath}`);
|
||||
}
|
||||
};
|
||||
|
||||
export async function loadServersConfig(mode: CliSupportedServerModes, log: ToolingLog) {
|
||||
// get path to one of the predefined config files
|
||||
const configPath = getConfigFilePath(mode);
|
||||
// load config that is compatible with kbn-test input format
|
||||
const config = await loadConfig(configPath, log);
|
||||
// construct config for Playwright Test
|
||||
const scoutServerConfig = config.getTestServersConfig();
|
||||
// save test config to the file
|
||||
saveTestServersConfigOnDisk(scoutServerConfig, log);
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
export const getProjectType = (kbnServerArgs: string[]) => {
|
||||
const options = getopts(kbnServerArgs);
|
||||
return options.serverless as ServerlessProjectType;
|
||||
};
|
75
packages/kbn-scout/src/playwright/config/index.ts
Normal file
75
packages/kbn-scout/src/playwright/config/index.ts
Normal file
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { defineConfig, PlaywrightTestConfig, devices } from '@playwright/test';
|
||||
import * as Path from 'path';
|
||||
import { REPO_ROOT } from '@kbn/repo-info';
|
||||
import { ScoutPlaywrightOptions, ScoutTestOptions, VALID_CONFIG_MARKER } from '../types';
|
||||
|
||||
export function createPlaywrightConfig(options: ScoutPlaywrightOptions): PlaywrightTestConfig {
|
||||
return defineConfig<ScoutTestOptions>({
|
||||
testDir: options.testDir,
|
||||
/* Run tests in files in parallel */
|
||||
fullyParallel: false,
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
forbidOnly: !!process.env.CI,
|
||||
/* Retry on CI only */
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
/* Opt out of parallel tests on CI. */
|
||||
workers: options.workers ?? 1,
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: [
|
||||
['html', { outputFolder: './output/reports', open: 'never' }], // HTML report configuration
|
||||
['json', { outputFile: './output/reports/test-results.json' }], // JSON report
|
||||
],
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
use: {
|
||||
serversConfigDir: Path.resolve(REPO_ROOT, '.scout', 'servers'),
|
||||
[VALID_CONFIG_MARKER]: true,
|
||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||
// baseURL: 'http://127.0.0.1:3000',
|
||||
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
trace: 'on-first-retry',
|
||||
screenshot: 'only-on-failure',
|
||||
// video: 'retain-on-failure',
|
||||
// storageState: './output/reports/state.json', // Store session state (like cookies)
|
||||
},
|
||||
|
||||
// Timeout for each test, includes test, hooks and fixtures
|
||||
timeout: 60000,
|
||||
|
||||
// Timeout for each assertion
|
||||
expect: {
|
||||
timeout: 10000,
|
||||
},
|
||||
|
||||
outputDir: './output/test-artifacts', // For other test artifacts (screenshots, videos, traces)
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
projects: [
|
||||
{
|
||||
name: 'chromium',
|
||||
use: { ...devices['Desktop Chrome'] },
|
||||
},
|
||||
|
||||
// {
|
||||
// name: 'firefox',
|
||||
// use: { ...devices['Desktop Firefox'] },
|
||||
// },
|
||||
],
|
||||
|
||||
/* Run your local dev server before starting the tests */
|
||||
// webServer: {
|
||||
// command: 'npm run start',
|
||||
// url: 'http://127.0.0.1:3000',
|
||||
// reuseExistingServer: !process.env.CI,
|
||||
// },
|
||||
});
|
||||
}
|
13
packages/kbn-scout/src/playwright/expect.ts
Normal file
13
packages/kbn-scout/src/playwright/expect.ts
Normal 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { test } from '@playwright/test';
|
||||
|
||||
// Export `expect` to avoid importing from Playwright directly
|
||||
export const expect = test.expect;
|
25
packages/kbn-scout/src/playwright/fixtures/index.ts
Normal file
25
packages/kbn-scout/src/playwright/fixtures/index.ts
Normal 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { mergeTests } from '@playwright/test';
|
||||
|
||||
import { scoutWorkerFixtures } from './worker';
|
||||
import { scoutTestFixtures } from './test';
|
||||
|
||||
export const scoutCoreFixtures = mergeTests(scoutWorkerFixtures, scoutTestFixtures);
|
||||
|
||||
export type {
|
||||
ScoutTestFixtures,
|
||||
ScoutWorkerFixtures,
|
||||
ScoutPage,
|
||||
Client,
|
||||
KbnClient,
|
||||
KibanaUrl,
|
||||
ToolingLog,
|
||||
} from './types';
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { test as base } from '@playwright/test';
|
||||
import { PROJECT_DEFAULT_ROLES } from '../../../common';
|
||||
import { LoginFixture, ScoutWorkerFixtures } from '../types';
|
||||
import { serviceLoadedMsg } from '../../utils';
|
||||
|
||||
type LoginFunction = (role: string) => Promise<void>;
|
||||
|
||||
export const browserAuthFixture = base.extend<{ browserAuth: LoginFixture }, ScoutWorkerFixtures>({
|
||||
browserAuth: async ({ log, context, samlAuth, config }, use) => {
|
||||
const setSessionCookie = async (cookieValue: string) => {
|
||||
await context.clearCookies();
|
||||
await context.addCookies([
|
||||
{
|
||||
name: 'sid',
|
||||
value: cookieValue,
|
||||
path: '/',
|
||||
domain: 'localhost',
|
||||
},
|
||||
]);
|
||||
};
|
||||
|
||||
const loginAs: LoginFunction = async (role) => {
|
||||
const cookie = await samlAuth.getInteractiveUserSessionCookieWithRoleScope(role);
|
||||
await setSessionCookie(cookie);
|
||||
};
|
||||
|
||||
const loginAsAdmin = () => loginAs('admin');
|
||||
const loginAsViewer = () => loginAs('viewer');
|
||||
const loginAsPrivilegedUser = () => {
|
||||
const roleName = config.serverless
|
||||
? PROJECT_DEFAULT_ROLES.get(config.projectType!)!
|
||||
: 'editor';
|
||||
return loginAs(roleName);
|
||||
};
|
||||
|
||||
log.debug(serviceLoadedMsg('browserAuth'));
|
||||
await use({ loginAsAdmin, loginAsViewer, loginAsPrivilegedUser });
|
||||
},
|
||||
});
|
19
packages/kbn-scout/src/playwright/fixtures/test/index.ts
Normal file
19
packages/kbn-scout/src/playwright/fixtures/test/index.ts
Normal 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { mergeTests } from '@playwright/test';
|
||||
import { browserAuthFixture } from './browser_auth';
|
||||
import { scoutPageFixture } from './page';
|
||||
import { pageObjectsFixture } from './page_objects';
|
||||
|
||||
export const scoutTestFixtures = mergeTests(
|
||||
browserAuthFixture,
|
||||
scoutPageFixture,
|
||||
pageObjectsFixture
|
||||
);
|
83
packages/kbn-scout/src/playwright/fixtures/test/page.ts
Normal file
83
packages/kbn-scout/src/playwright/fixtures/test/page.ts
Normal file
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { Page, test as base } from '@playwright/test';
|
||||
import { subj } from '@kbn/test-subj-selector';
|
||||
import { ScoutPage, KibanaUrl } from '../types';
|
||||
|
||||
/**
|
||||
* Instead of defining each method individually, we use a list of method names and loop through them, creating methods dynamically.
|
||||
* All methods must have 'selector: string' as the first argument
|
||||
*/
|
||||
function extendPageWithTestSubject(page: Page) {
|
||||
const methods: Array<keyof Page> = [
|
||||
'check',
|
||||
'click',
|
||||
'dblclick',
|
||||
'fill',
|
||||
'focus',
|
||||
'getAttribute',
|
||||
'hover',
|
||||
'isEnabled',
|
||||
'innerText',
|
||||
'isChecked',
|
||||
'isHidden',
|
||||
'locator',
|
||||
];
|
||||
|
||||
const extendedMethods: Partial<Record<keyof Page, Function>> = {};
|
||||
|
||||
for (const method of methods) {
|
||||
extendedMethods[method] = (...args: any[]) => {
|
||||
const selector = args[0];
|
||||
const testSubjSelector = subj(selector);
|
||||
return (page[method] as Function)(testSubjSelector, ...args.slice(1));
|
||||
};
|
||||
}
|
||||
|
||||
return extendedMethods as Record<keyof Page, any>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extends the 'page' fixture with Kibana-specific functionality
|
||||
*
|
||||
* 1. Allow calling methods with simplified 'data-test-subj' selectors.
|
||||
* Instead of manually constructing 'data-test-subj' selectors, this extension provides a `testSubj` object on the page
|
||||
* Supported methods include `click`, `check`, `fill`, and others that interact with `data-test-subj`.
|
||||
*
|
||||
* Example Usage:
|
||||
*
|
||||
* ```typescript
|
||||
* // Without `testSubj` extension:
|
||||
* await page.locator('[data-test-subj="foo"][data-test-subj="bar"]').click();
|
||||
*
|
||||
* // With `testSubj` extension:
|
||||
* await page.testSubj.click('foo & bar');
|
||||
* ```
|
||||
*
|
||||
* 2. Navigate to Kibana apps by using 'kbnUrl' fixture
|
||||
*
|
||||
* Example Usage:
|
||||
*
|
||||
* ```typescript
|
||||
* // Navigate to '/app/discover'
|
||||
* await page.gotoApp('discover);
|
||||
* ```
|
||||
*/
|
||||
export const scoutPageFixture = base.extend<{ page: ScoutPage; kbnUrl: KibanaUrl }>({
|
||||
page: async ({ page, kbnUrl }, use) => {
|
||||
// Extend page with '@kbn/test-subj-selector' support
|
||||
page.testSubj = extendPageWithTestSubject(page);
|
||||
|
||||
// Method to navigate to specific Kibana apps
|
||||
page.gotoApp = (appName: string) => page.goto(kbnUrl.app(appName));
|
||||
|
||||
await use(page);
|
||||
},
|
||||
});
|
|
@ -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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { test as base } from '@playwright/test';
|
||||
import { ScoutTestFixtures, ScoutWorkerFixtures } from '../types';
|
||||
import { createCorePageObjects } from '../../page_objects';
|
||||
|
||||
export const pageObjectsFixture = base.extend<ScoutTestFixtures, ScoutWorkerFixtures>({
|
||||
pageObjects: async ({ page }, use) => {
|
||||
const corePageObjects = createCorePageObjects(page);
|
||||
|
||||
await use(corePageObjects);
|
||||
},
|
||||
});
|
11
packages/kbn-scout/src/playwright/fixtures/types/index.ts
Normal file
11
packages/kbn-scout/src/playwright/fixtures/types/index.ts
Normal 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
export * from './test_scope';
|
||||
export * from './worker_scope';
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { Page } from 'playwright/test';
|
||||
import { PageObjects } from '../../page_objects';
|
||||
|
||||
export interface ScoutTestFixtures {
|
||||
browserAuth: LoginFixture;
|
||||
page: ScoutPage;
|
||||
pageObjects: PageObjects;
|
||||
}
|
||||
|
||||
export interface LoginFixture {
|
||||
loginAsViewer: () => Promise<void>;
|
||||
loginAsAdmin: () => Promise<void>;
|
||||
loginAsPrivilegedUser: () => Promise<void>;
|
||||
}
|
||||
|
||||
export type ScoutPage = Page & {
|
||||
gotoApp: (appName: string, options?: Parameters<Page['goto']>[1]) => ReturnType<Page['goto']>;
|
||||
testSubj: {
|
||||
check: (selector: string, options?: Parameters<Page['check']>[1]) => ReturnType<Page['check']>;
|
||||
click: (selector: string, options?: Parameters<Page['click']>[1]) => ReturnType<Page['click']>;
|
||||
dblclick: (
|
||||
selector: string,
|
||||
options?: Parameters<Page['dblclick']>[1]
|
||||
) => ReturnType<Page['dblclick']>;
|
||||
fill: (
|
||||
selector: string,
|
||||
value: string,
|
||||
options?: Parameters<Page['fill']>[2]
|
||||
) => ReturnType<Page['fill']>;
|
||||
focus: (selector: string, options?: Parameters<Page['focus']>[1]) => ReturnType<Page['focus']>;
|
||||
getAttribute: (
|
||||
selector: string,
|
||||
name: string,
|
||||
options?: Parameters<Page['getAttribute']>[2]
|
||||
) => ReturnType<Page['getAttribute']>;
|
||||
hover: (selector: string, options?: Parameters<Page['hover']>[1]) => ReturnType<Page['hover']>;
|
||||
innerText: (
|
||||
selector: string,
|
||||
options?: Parameters<Page['innerText']>[1]
|
||||
) => ReturnType<Page['innerText']>;
|
||||
isEnabled: (
|
||||
selector: string,
|
||||
options?: Parameters<Page['isEnabled']>[1]
|
||||
) => ReturnType<Page['isEnabled']>;
|
||||
isChecked: (
|
||||
selector: string,
|
||||
options?: Parameters<Page['isChecked']>[1]
|
||||
) => ReturnType<Page['isChecked']>;
|
||||
isHidden: (
|
||||
selector: string,
|
||||
options?: Parameters<Page['isHidden']>[1]
|
||||
) => ReturnType<Page['isHidden']>;
|
||||
locator: (
|
||||
selector: string,
|
||||
options?: Parameters<Page['locator']>[1]
|
||||
) => ReturnType<Page['locator']>;
|
||||
};
|
||||
};
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import type { KbnClient, SamlSessionManager } from '@kbn/test';
|
||||
import type { ToolingLog } from '@kbn/tooling-log';
|
||||
import type { Client } from '@elastic/elasticsearch';
|
||||
import { LoadActionPerfOptions } from '@kbn/es-archiver';
|
||||
import { IndexStats } from '@kbn/es-archiver/src/lib/stats';
|
||||
|
||||
import { ScoutServerConfig } from '../../../types';
|
||||
import { KibanaUrl } from '../../../common/services/kibana_url';
|
||||
|
||||
interface EsArchiverFixture {
|
||||
loadIfNeeded: (
|
||||
name: string,
|
||||
performance?: LoadActionPerfOptions | undefined
|
||||
) => Promise<Record<string, IndexStats>>;
|
||||
}
|
||||
|
||||
export interface ScoutWorkerFixtures {
|
||||
log: ToolingLog;
|
||||
config: ScoutServerConfig;
|
||||
kbnUrl: KibanaUrl;
|
||||
esClient: Client;
|
||||
kbnClient: KbnClient;
|
||||
esArchiver: EsArchiverFixture;
|
||||
samlAuth: SamlSessionManager;
|
||||
}
|
||||
|
||||
// re-export to import types from '@kbn-scout'
|
||||
export type { KbnClient, SamlSessionManager } from '@kbn/test';
|
||||
export type { ToolingLog } from '@kbn/tooling-log';
|
||||
export type { Client } from '@elastic/elasticsearch';
|
||||
export type { KibanaUrl } from '../../../common/services/kibana_url';
|
84
packages/kbn-scout/src/playwright/fixtures/worker/index.ts
Normal file
84
packages/kbn-scout/src/playwright/fixtures/worker/index.ts
Normal file
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { test as base } from '@playwright/test';
|
||||
|
||||
import { LoadActionPerfOptions } from '@kbn/es-archiver';
|
||||
import {
|
||||
createKbnUrl,
|
||||
createEsArchiver,
|
||||
createEsClient,
|
||||
createKbnClient,
|
||||
createLogger,
|
||||
createSamlSessionManager,
|
||||
createScoutConfig,
|
||||
} from '../../../common/services';
|
||||
import { ScoutWorkerFixtures } from '../types/worker_scope';
|
||||
import { ScoutTestOptions } from '../../types';
|
||||
|
||||
export const scoutWorkerFixtures = base.extend<{}, ScoutWorkerFixtures>({
|
||||
log: [
|
||||
({}, use) => {
|
||||
use(createLogger());
|
||||
},
|
||||
{ scope: 'worker' },
|
||||
],
|
||||
|
||||
config: [
|
||||
({ log }, use, testInfo) => {
|
||||
const configName = 'local';
|
||||
const projectUse = testInfo.project.use as ScoutTestOptions;
|
||||
const serversConfigDir = projectUse.serversConfigDir;
|
||||
const configInstance = createScoutConfig(serversConfigDir, configName, log);
|
||||
|
||||
use(configInstance);
|
||||
},
|
||||
{ scope: 'worker' },
|
||||
],
|
||||
|
||||
kbnUrl: [
|
||||
({ config, log }, use) => {
|
||||
use(createKbnUrl(config, log));
|
||||
},
|
||||
{ scope: 'worker' },
|
||||
],
|
||||
|
||||
esClient: [
|
||||
({ config, log }, use) => {
|
||||
use(createEsClient(config, log));
|
||||
},
|
||||
{ scope: 'worker' },
|
||||
],
|
||||
|
||||
kbnClient: [
|
||||
({ log, config }, use) => {
|
||||
use(createKbnClient(config, log));
|
||||
},
|
||||
{ scope: 'worker' },
|
||||
],
|
||||
|
||||
esArchiver: [
|
||||
({ log, esClient, kbnClient }, use) => {
|
||||
const esArchiverInstance = createEsArchiver(esClient, kbnClient, log);
|
||||
// to speedup test execution we only allow to ingest the data indexes and only if index doesn't exist
|
||||
const loadIfNeeded = async (name: string, performance?: LoadActionPerfOptions | undefined) =>
|
||||
esArchiverInstance!.loadIfNeeded(name, performance);
|
||||
|
||||
use({ loadIfNeeded });
|
||||
},
|
||||
{ scope: 'worker' },
|
||||
],
|
||||
|
||||
samlAuth: [
|
||||
({ log, config }, use) => {
|
||||
use(createSamlSessionManager(config, log));
|
||||
},
|
||||
{ scope: 'worker' },
|
||||
],
|
||||
});
|
22
packages/kbn-scout/src/playwright/index.ts
Normal file
22
packages/kbn-scout/src/playwright/index.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { mergeTests } from 'playwright/test';
|
||||
import { scoutCoreFixtures } from './fixtures';
|
||||
|
||||
// Scout core fixtures: worker & test scope
|
||||
export const test = mergeTests(scoutCoreFixtures);
|
||||
|
||||
export { createPlaywrightConfig } from './config';
|
||||
export { createLazyPageObject } from './page_objects/utils';
|
||||
export { expect } from './expect';
|
||||
|
||||
export type { ScoutPlaywrightOptions, ScoutTestOptions } from './types';
|
||||
export type { PageObjects } from './page_objects';
|
||||
export type { ScoutTestFixtures, ScoutWorkerFixtures, ScoutPage } from './fixtures';
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { ScoutPage } from '../fixtures/types';
|
||||
import { expect } from '..';
|
||||
|
||||
export class DatePicker {
|
||||
constructor(private readonly page: ScoutPage) {}
|
||||
|
||||
async setAbsoluteRange({ from, to }: { from: string; to: string }) {
|
||||
await this.page.testSubj.click('superDatePickerShowDatesButton');
|
||||
// we start with end date
|
||||
await this.page.testSubj.click('superDatePickerendDatePopoverButton');
|
||||
await this.page.testSubj.click('superDatePickerAbsoluteTab');
|
||||
const inputFrom = this.page.testSubj.locator('superDatePickerAbsoluteDateInput');
|
||||
await inputFrom.clear();
|
||||
await inputFrom.fill(to);
|
||||
await this.page.testSubj.click('parseAbsoluteDateFormat');
|
||||
await this.page.testSubj.click('superDatePickerendDatePopoverButton');
|
||||
// and later change start date
|
||||
await this.page.testSubj.click('superDatePickerstartDatePopoverButton');
|
||||
await this.page.testSubj.click('superDatePickerAbsoluteTab');
|
||||
const inputTo = this.page.testSubj.locator('superDatePickerAbsoluteDateInput');
|
||||
await inputTo.clear();
|
||||
await inputTo.fill(from);
|
||||
await this.page.testSubj.click('parseAbsoluteDateFormat');
|
||||
await this.page.keyboard.press('Escape');
|
||||
|
||||
await expect(this.page.testSubj.locator('superDatePickerstartDatePopoverButton')).toHaveText(
|
||||
from
|
||||
);
|
||||
await expect(this.page.testSubj.locator('superDatePickerendDatePopoverButton')).toHaveText(to);
|
||||
await this.page.testSubj.click('querySubmitButton');
|
||||
}
|
||||
}
|
|
@ -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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { ScoutPage } from '../fixtures/types';
|
||||
|
||||
export class DiscoverApp {
|
||||
constructor(private readonly page: ScoutPage) {}
|
||||
|
||||
async goto() {
|
||||
await this.page.gotoApp('discover');
|
||||
}
|
||||
}
|
32
packages/kbn-scout/src/playwright/page_objects/index.ts
Normal file
32
packages/kbn-scout/src/playwright/page_objects/index.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { ScoutPage } from '../fixtures/types';
|
||||
import { DatePicker } from './date_picker';
|
||||
import { DiscoverApp } from './discover_app';
|
||||
import { createLazyPageObject } from './utils';
|
||||
|
||||
export interface PageObjects {
|
||||
datePicker: DatePicker;
|
||||
discover: DiscoverApp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a set of core page objects, each lazily instantiated on first access.
|
||||
*
|
||||
* @param page - `ScoutPage` instance used for initializing page objects.
|
||||
* @returns An object containing lazy-loaded core page objects.
|
||||
*/
|
||||
export function createCorePageObjects(page: ScoutPage): PageObjects {
|
||||
return {
|
||||
datePicker: createLazyPageObject(DatePicker, page),
|
||||
discover: createLazyPageObject(DiscoverApp, page),
|
||||
// Add new page objects here
|
||||
};
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { ScoutPage } from '../../fixtures/types';
|
||||
|
||||
/**
|
||||
* Creates a lazily instantiated proxy for a Page Object class, deferring the creation of the instance until
|
||||
* a property or method is accessed. It helps avoiding instantiation of page objects that may not be used
|
||||
* in certain test scenarios.
|
||||
*
|
||||
* @param PageObjectClass - The page object class to be instantiated lazily.
|
||||
* @param scoutPage - ScoutPage instance, that extendes the Playwright `page` fixture and passed to the page object class constructor.
|
||||
* @param constructorArgs - Additional arguments to be passed to the page object class constructor.
|
||||
* @returns A proxy object that behaves like an instance of the page object class, instantiating it on demand.
|
||||
*/
|
||||
export function createLazyPageObject<T extends object>(
|
||||
PageObjectClass: new (page: ScoutPage, ...args: any[]) => T,
|
||||
scoutPage: ScoutPage,
|
||||
...constructorArgs: any[]
|
||||
): T {
|
||||
let instance: T | null = null;
|
||||
return new Proxy({} as T, {
|
||||
get(_, prop: string | symbol) {
|
||||
if (!instance) {
|
||||
instance = new PageObjectClass(scoutPage, ...constructorArgs);
|
||||
}
|
||||
if (typeof prop === 'symbol' || !(prop in instance)) {
|
||||
return undefined;
|
||||
}
|
||||
return instance[prop as keyof T];
|
||||
},
|
||||
});
|
||||
}
|
46
packages/kbn-scout/src/playwright/runner/config_validator.ts
Normal file
46
packages/kbn-scout/src/playwright/runner/config_validator.ts
Normal file
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import * as Fs from 'fs';
|
||||
import { REPO_ROOT } from '@kbn/repo-info';
|
||||
import { PlaywrightTestConfig } from 'playwright/test';
|
||||
import path from 'path';
|
||||
import { createFlagError } from '@kbn/dev-cli-errors';
|
||||
import { ScoutTestOptions, VALID_CONFIG_MARKER } from '../types';
|
||||
|
||||
export async function validatePlaywrightConfig(configPath: string) {
|
||||
const fullPath = path.resolve(REPO_ROOT, configPath);
|
||||
|
||||
// Check if the path exists and has a .ts extension
|
||||
if (!configPath || !Fs.existsSync(fullPath) || !configPath.endsWith('.ts')) {
|
||||
throw createFlagError(
|
||||
`Path to a valid TypeScript config file is required: --config <relative path to .ts file>`
|
||||
);
|
||||
}
|
||||
|
||||
// Dynamically import the file to check for a default export
|
||||
const configModule = await import(fullPath);
|
||||
const config = configModule.default as PlaywrightTestConfig<ScoutTestOptions>;
|
||||
|
||||
// Check if the config's 'use' property has the valid marker
|
||||
if (!config?.use?.[VALID_CONFIG_MARKER]) {
|
||||
throw createFlagError(
|
||||
`The config file at "${configPath}" must be created with "createPlaywrightConfig" from '@kbn/scout' package:\n
|
||||
export default createPlaywrightConfig({
|
||||
testDir: './tests',
|
||||
});`
|
||||
);
|
||||
}
|
||||
|
||||
if (!config.testDir) {
|
||||
throw createFlagError(
|
||||
`The config file at "${configPath}" must export a valid Playwright configuration with "testDir" property.`
|
||||
);
|
||||
}
|
||||
}
|
52
packages/kbn-scout/src/playwright/runner/flags.ts
Normal file
52
packages/kbn-scout/src/playwright/runner/flags.ts
Normal file
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { FlagOptions, FlagsReader } from '@kbn/dev-cli-runner';
|
||||
import { createFlagError } from '@kbn/dev-cli-errors';
|
||||
import { SERVER_FLAG_OPTIONS, parseServerFlags } from '../../servers';
|
||||
import { CliSupportedServerModes } from '../../types';
|
||||
import { validatePlaywrightConfig } from './config_validator';
|
||||
|
||||
export interface RunTestsOptions {
|
||||
configPath: string;
|
||||
headed: boolean;
|
||||
mode: CliSupportedServerModes;
|
||||
esFrom: 'serverless' | 'source' | 'snapshot' | undefined;
|
||||
installDir: string | undefined;
|
||||
logsDir: string | undefined;
|
||||
}
|
||||
|
||||
export const TEST_FLAG_OPTIONS: FlagOptions = {
|
||||
...SERVER_FLAG_OPTIONS,
|
||||
boolean: [...(SERVER_FLAG_OPTIONS.boolean || []), 'headed'],
|
||||
string: [...(SERVER_FLAG_OPTIONS.string || []), 'config'],
|
||||
default: { headed: false },
|
||||
help: `${SERVER_FLAG_OPTIONS.help}
|
||||
--config Playwright config file path
|
||||
--headed Run Playwright with browser head
|
||||
`,
|
||||
};
|
||||
|
||||
export async function parseTestFlags(flags: FlagsReader) {
|
||||
const options = parseServerFlags(flags);
|
||||
const configPath = flags.string('config');
|
||||
const headed = flags.boolean('headed');
|
||||
|
||||
if (!configPath) {
|
||||
throw createFlagError(`Path to playwright config is required: --config <file path>`);
|
||||
}
|
||||
|
||||
await validatePlaywrightConfig(configPath);
|
||||
|
||||
return {
|
||||
...options,
|
||||
configPath,
|
||||
headed,
|
||||
};
|
||||
}
|
12
packages/kbn-scout/src/playwright/runner/index.ts
Normal file
12
packages/kbn-scout/src/playwright/runner/index.ts
Normal 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
export { runTests } from './run_tests';
|
||||
export { parseTestFlags, TEST_FLAG_OPTIONS } from './flags';
|
||||
export type { RunTestsOptions } from './flags';
|
84
packages/kbn-scout/src/playwright/runner/run_tests.ts
Normal file
84
packages/kbn-scout/src/playwright/runner/run_tests.ts
Normal file
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { resolve } from 'path';
|
||||
|
||||
import { ToolingLog } from '@kbn/tooling-log';
|
||||
import { withProcRunner } from '@kbn/dev-proc-runner';
|
||||
import { getTimeReporter } from '@kbn/ci-stats-reporter';
|
||||
import { REPO_ROOT } from '@kbn/repo-info';
|
||||
import { runElasticsearch, runKibanaServer } from '../../servers';
|
||||
import { loadServersConfig } from '../../config';
|
||||
import { silence } from '../../common';
|
||||
import { RunTestsOptions } from './flags';
|
||||
import { getExtraKbnOpts } from '../../servers/run_kibana_server';
|
||||
|
||||
export async function runTests(log: ToolingLog, options: RunTestsOptions) {
|
||||
const runStartTime = Date.now();
|
||||
const reportTime = getTimeReporter(log, 'scripts/scout_test');
|
||||
|
||||
const config = await loadServersConfig(options.mode, log);
|
||||
const playwrightConfigPath = options.configPath;
|
||||
|
||||
await withProcRunner(log, async (procs) => {
|
||||
const abortCtrl = new AbortController();
|
||||
|
||||
const onEarlyExit = (msg: string) => {
|
||||
log.error(msg);
|
||||
abortCtrl.abort();
|
||||
};
|
||||
|
||||
let shutdownEs;
|
||||
|
||||
try {
|
||||
shutdownEs = await runElasticsearch({
|
||||
onEarlyExit,
|
||||
config,
|
||||
log,
|
||||
esFrom: options.esFrom,
|
||||
logsDir: options.logsDir,
|
||||
});
|
||||
|
||||
await runKibanaServer({
|
||||
procs,
|
||||
onEarlyExit,
|
||||
config,
|
||||
installDir: options.installDir,
|
||||
extraKbnOpts: getExtraKbnOpts(options.installDir, config.get('serverless')),
|
||||
});
|
||||
|
||||
// wait for 5 seconds
|
||||
await silence(log, 5000);
|
||||
|
||||
// Running 'npx playwright test --config=${playwrightConfigPath}'
|
||||
await procs.run(`playwright`, {
|
||||
cmd: resolve(REPO_ROOT, './node_modules/.bin/playwright'),
|
||||
args: ['test', `--config=${playwrightConfigPath}`, ...(options.headed ? ['--headed'] : [])],
|
||||
cwd: resolve(REPO_ROOT),
|
||||
env: {
|
||||
...process.env,
|
||||
},
|
||||
wait: true,
|
||||
});
|
||||
} finally {
|
||||
try {
|
||||
await procs.stop('kibana');
|
||||
} finally {
|
||||
if (shutdownEs) {
|
||||
await shutdownEs();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reportTime(runStartTime, 'ready', {
|
||||
success: true,
|
||||
...options,
|
||||
});
|
||||
});
|
||||
}
|
24
packages/kbn-scout/src/playwright/types/index.ts
Normal file
24
packages/kbn-scout/src/playwright/types/index.ts
Normal file
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { PlaywrightTestConfig, PlaywrightTestOptions } from 'playwright/test';
|
||||
|
||||
export type Protocol = 'http' | 'https';
|
||||
|
||||
export const VALID_CONFIG_MARKER = Symbol('validConfig');
|
||||
|
||||
export interface ScoutTestOptions extends PlaywrightTestOptions {
|
||||
serversConfigDir: string;
|
||||
[VALID_CONFIG_MARKER]: boolean;
|
||||
}
|
||||
|
||||
export interface ScoutPlaywrightOptions extends Pick<PlaywrightTestConfig, 'testDir' | 'workers'> {
|
||||
testDir: string;
|
||||
workers?: 1 | 2;
|
||||
}
|
10
packages/kbn-scout/src/playwright/utils/index.ts
Normal file
10
packages/kbn-scout/src/playwright/utils/index.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
export const serviceLoadedMsg = (name: string) => `scout service loaded: ${name}`;
|
55
packages/kbn-scout/src/servers/flags.ts
Normal file
55
packages/kbn-scout/src/servers/flags.ts
Normal file
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { v4 as uuidV4 } from 'uuid';
|
||||
import { resolve } from 'path';
|
||||
import { FlagsReader, FlagOptions } from '@kbn/dev-cli-runner';
|
||||
import { createFlagError } from '@kbn/dev-cli-errors';
|
||||
import { REPO_ROOT } from '@kbn/repo-info';
|
||||
import { CliSupportedServerModes } from '../types';
|
||||
|
||||
export type StartServerOptions = ReturnType<typeof parseServerFlags>;
|
||||
|
||||
export const SERVER_FLAG_OPTIONS: FlagOptions = {
|
||||
string: ['serverless', 'esFrom', 'kibana-install-dir'],
|
||||
boolean: ['stateful', 'logToFile'],
|
||||
help: `
|
||||
--stateful Start Elasticsearch and Kibana with default ESS configuration
|
||||
--serverless Start Elasticsearch and Kibana with serverless project configuration: es | oblt | security
|
||||
--esFrom Build Elasticsearch from source or run snapshot or serverless. Default: $TEST_ES_FROM or "snapshot"
|
||||
--kibana-install-dir Run Kibana from existing install directory instead of from source
|
||||
--logToFile Write the log output from Kibana/ES to files instead of to stdout
|
||||
`,
|
||||
};
|
||||
|
||||
export function parseServerFlags(flags: FlagsReader) {
|
||||
const serverlessType = flags.enum('serverless', ['es', 'oblt', 'security']);
|
||||
const isStateful = flags.boolean('stateful');
|
||||
|
||||
if (!(serverlessType || isStateful) || (serverlessType && isStateful)) {
|
||||
throw createFlagError(`Expected exactly one of --serverless=<type> or --stateful flag`);
|
||||
}
|
||||
|
||||
const mode: CliSupportedServerModes = serverlessType
|
||||
? `serverless=${serverlessType}`
|
||||
: 'stateful';
|
||||
|
||||
const esFrom = flags.enum('esFrom', ['source', 'snapshot', 'serverless']);
|
||||
const installDir = flags.string('kibana-install-dir');
|
||||
const logsDir = flags.boolean('logToFile')
|
||||
? resolve(REPO_ROOT, 'data/ftr_servers_logs', uuidV4())
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
mode,
|
||||
esFrom,
|
||||
installDir,
|
||||
logsDir,
|
||||
};
|
||||
}
|
15
packages/kbn-scout/src/servers/index.ts
Normal file
15
packages/kbn-scout/src/servers/index.ts
Normal 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
export { parseServerFlags, SERVER_FLAG_OPTIONS } from './flags';
|
||||
export { startServers } from './start_servers';
|
||||
export { runKibanaServer } from './run_kibana_server';
|
||||
export { runElasticsearch } from './run_elasticsearch';
|
||||
|
||||
export type { StartServerOptions } from './flags';
|
194
packages/kbn-scout/src/servers/run_elasticsearch.ts
Normal file
194
packages/kbn-scout/src/servers/run_elasticsearch.ts
Normal file
|
@ -0,0 +1,194 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import Url from 'url';
|
||||
import { resolve } from 'path';
|
||||
import type { ToolingLog } from '@kbn/tooling-log';
|
||||
import { REPO_ROOT } from '@kbn/repo-info';
|
||||
import type { ArtifactLicense, ServerlessProjectType } from '@kbn/es';
|
||||
import { isServerlessProjectType, extractAndArchiveLogs } from '@kbn/es/src/utils';
|
||||
import { createTestEsCluster, esTestConfig } from '@kbn/test';
|
||||
import { Config } from '../config';
|
||||
|
||||
interface RunElasticsearchOptions {
|
||||
log: ToolingLog;
|
||||
esFrom?: string;
|
||||
esServerlessImage?: string;
|
||||
config: Config;
|
||||
onEarlyExit?: (msg: string) => void;
|
||||
logsDir?: string;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
type EsConfig = ReturnType<typeof getEsConfig>;
|
||||
|
||||
function getEsConfig({
|
||||
config,
|
||||
esFrom = config.get('esTestCluster.from'),
|
||||
esServerlessImage,
|
||||
}: RunElasticsearchOptions) {
|
||||
const ssl = !!config.get('esTestCluster.ssl');
|
||||
const license: ArtifactLicense = config.get('esTestCluster.license');
|
||||
const esArgs: string[] = config.get('esTestCluster.serverArgs');
|
||||
const esJavaOpts: string | undefined = config.get('esTestCluster.esJavaOpts');
|
||||
const isSecurityEnabled = esArgs.includes('xpack.security.enabled=true');
|
||||
|
||||
const port: number | undefined = config.get('servers.elasticsearch.port');
|
||||
|
||||
const password: string | undefined = isSecurityEnabled
|
||||
? 'changeme'
|
||||
: config.get('servers.elasticsearch.password');
|
||||
|
||||
const dataArchive: string | undefined = config.get('esTestCluster.dataArchive');
|
||||
const serverless: boolean = config.get('serverless');
|
||||
const files: string[] | undefined = config.get('esTestCluster.files');
|
||||
|
||||
const esServerlessOptions = serverless
|
||||
? getESServerlessOptions(esServerlessImage, config)
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
ssl,
|
||||
license,
|
||||
esArgs,
|
||||
esJavaOpts,
|
||||
isSecurityEnabled,
|
||||
esFrom,
|
||||
esServerlessOptions,
|
||||
port,
|
||||
password,
|
||||
dataArchive,
|
||||
serverless,
|
||||
files,
|
||||
};
|
||||
}
|
||||
|
||||
export async function runElasticsearch(
|
||||
options: RunElasticsearchOptions
|
||||
): Promise<() => Promise<void>> {
|
||||
const { log, logsDir, name } = options;
|
||||
const config = getEsConfig(options);
|
||||
|
||||
const node = await startEsNode({
|
||||
log,
|
||||
name: name ?? 'scout',
|
||||
logsDir,
|
||||
config,
|
||||
});
|
||||
return async () => {
|
||||
await node.cleanup();
|
||||
await extractAndArchiveLogs({ outputFolder: logsDir, log });
|
||||
};
|
||||
}
|
||||
|
||||
async function startEsNode({
|
||||
log,
|
||||
name,
|
||||
config,
|
||||
onEarlyExit,
|
||||
logsDir,
|
||||
}: {
|
||||
log: ToolingLog;
|
||||
name: string;
|
||||
config: EsConfig & { transportPort?: number };
|
||||
onEarlyExit?: (msg: string) => void;
|
||||
logsDir?: string;
|
||||
}) {
|
||||
const cluster = createTestEsCluster({
|
||||
clusterName: `cluster-${name}`,
|
||||
esArgs: config.esArgs,
|
||||
esFrom: config.esFrom,
|
||||
esServerlessOptions: config.esServerlessOptions,
|
||||
esJavaOpts: config.esJavaOpts,
|
||||
license: config.license,
|
||||
password: config.password,
|
||||
port: config.port,
|
||||
ssl: config.ssl,
|
||||
log,
|
||||
writeLogsToPath: logsDir ? resolve(logsDir, `es-cluster-${name}.log`) : undefined,
|
||||
basePath: resolve(REPO_ROOT, '.es'),
|
||||
nodes: [
|
||||
{
|
||||
name,
|
||||
dataArchive: config.dataArchive,
|
||||
},
|
||||
],
|
||||
transportPort: config.transportPort,
|
||||
onEarlyExit,
|
||||
serverless: config.serverless,
|
||||
files: config.files,
|
||||
});
|
||||
|
||||
await cluster.start();
|
||||
|
||||
return cluster;
|
||||
}
|
||||
|
||||
interface EsServerlessOptions {
|
||||
projectType: ServerlessProjectType;
|
||||
host?: string;
|
||||
resources: string[];
|
||||
kibanaUrl: string;
|
||||
tag?: string;
|
||||
image?: string;
|
||||
}
|
||||
|
||||
function getESServerlessOptions(
|
||||
esServerlessImageFromArg: string | undefined,
|
||||
config: Config
|
||||
): EsServerlessOptions {
|
||||
const esServerlessImageUrlOrTag =
|
||||
esServerlessImageFromArg ||
|
||||
esTestConfig.getESServerlessImage() ||
|
||||
(config.has('esTestCluster.esServerlessImage') &&
|
||||
config.get('esTestCluster.esServerlessImage'));
|
||||
const serverlessResources: string[] =
|
||||
(config.has('esServerlessOptions.resources') && config.get('esServerlessOptions.resources')) ||
|
||||
[];
|
||||
const serverlessHost: string | undefined =
|
||||
config.has('esServerlessOptions.host') && config.get('esServerlessOptions.host');
|
||||
|
||||
const kbnServerArgs =
|
||||
(config.has('kbnTestServer.serverArgs') &&
|
||||
(config.get('kbnTestServer.serverArgs') as string[])) ||
|
||||
[];
|
||||
|
||||
const projectType = kbnServerArgs
|
||||
.filter((arg) => arg.startsWith('--serverless'))
|
||||
.reduce((acc, arg) => {
|
||||
const match = arg.match(/--serverless[=\s](\w+)/);
|
||||
return acc + (match ? match[1] : '');
|
||||
}, '') as ServerlessProjectType;
|
||||
|
||||
if (!isServerlessProjectType(projectType)) {
|
||||
throw new Error(`Unsupported serverless projectType: ${projectType}`);
|
||||
}
|
||||
|
||||
const commonOptions = {
|
||||
projectType,
|
||||
host: serverlessHost,
|
||||
resources: serverlessResources,
|
||||
kibanaUrl: Url.format({
|
||||
protocol: config.get('servers.kibana.protocol'),
|
||||
hostname: config.get('servers.kibana.hostname'),
|
||||
port: config.get('servers.kibana.port'),
|
||||
}),
|
||||
};
|
||||
|
||||
if (esServerlessImageUrlOrTag) {
|
||||
return {
|
||||
...commonOptions,
|
||||
...(esServerlessImageUrlOrTag.includes(':')
|
||||
? { image: esServerlessImageUrlOrTag }
|
||||
: { tag: esServerlessImageUrlOrTag }),
|
||||
};
|
||||
}
|
||||
|
||||
return commonOptions;
|
||||
}
|
135
packages/kbn-scout/src/servers/run_kibana_server.ts
Normal file
135
packages/kbn-scout/src/servers/run_kibana_server.ts
Normal file
|
@ -0,0 +1,135 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import Path from 'path';
|
||||
import Os from 'os';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import type { ProcRunner } from '@kbn/dev-proc-runner';
|
||||
import { REPO_ROOT } from '@kbn/repo-info';
|
||||
import { parseRawFlags, getArgValue, remapPluginPaths, DedicatedTaskRunner } from '@kbn/test';
|
||||
import { Config } from '../config';
|
||||
|
||||
export async function runKibanaServer(options: {
|
||||
procs: ProcRunner;
|
||||
config: Config;
|
||||
installDir?: string;
|
||||
extraKbnOpts?: string[];
|
||||
logsDir?: string;
|
||||
onEarlyExit?: (msg: string) => void;
|
||||
}) {
|
||||
const { config, procs } = options;
|
||||
const runOptions = options.config.get('kbnTestServer.runOptions');
|
||||
const installDir = runOptions.alwaysUseSource ? undefined : options.installDir;
|
||||
const devMode = !installDir;
|
||||
const useTaskRunner = options.config.get('kbnTestServer.useDedicatedTaskRunner');
|
||||
|
||||
const procRunnerOpts = {
|
||||
cwd: installDir || REPO_ROOT,
|
||||
cmd: installDir
|
||||
? process.platform.startsWith('win')
|
||||
? Path.resolve(installDir, 'bin/kibana.bat')
|
||||
: Path.resolve(installDir, 'bin/kibana')
|
||||
: process.execPath,
|
||||
env: {
|
||||
FORCE_COLOR: 1,
|
||||
...process.env,
|
||||
...options.config.get('kbnTestServer.env'),
|
||||
},
|
||||
wait: runOptions.wait,
|
||||
onEarlyExit: options.onEarlyExit,
|
||||
};
|
||||
|
||||
const prefixArgs = devMode
|
||||
? [Path.relative(procRunnerOpts.cwd, Path.resolve(REPO_ROOT, 'scripts/kibana'))]
|
||||
: [];
|
||||
|
||||
const buildArgs: string[] = config.get('kbnTestServer.buildArgs') || [];
|
||||
const sourceArgs: string[] = config.get('kbnTestServer.sourceArgs') || [];
|
||||
const serverArgs: string[] = config.get('kbnTestServer.serverArgs') || [];
|
||||
|
||||
let kbnFlags = parseRawFlags([
|
||||
// When installDir is passed, we run from a built version of Kibana which uses different command line
|
||||
// arguments. If installDir is not passed, we run from source code.
|
||||
...(installDir ? [...buildArgs, ...serverArgs] : [...sourceArgs, ...serverArgs]),
|
||||
|
||||
// We also allow passing in extra Kibana server options, tack those on here so they always take precedence
|
||||
...(options.extraKbnOpts ?? []),
|
||||
]);
|
||||
|
||||
if (installDir) {
|
||||
kbnFlags = remapPluginPaths(kbnFlags, installDir);
|
||||
}
|
||||
|
||||
const mainName = useTaskRunner ? 'kbn-ui' : 'kibana';
|
||||
const promises = [
|
||||
// main process
|
||||
procs.run(mainName, {
|
||||
...procRunnerOpts,
|
||||
writeLogsToPath: options.logsDir
|
||||
? Path.resolve(options.logsDir, `${mainName}.log`)
|
||||
: undefined,
|
||||
args: [
|
||||
...prefixArgs,
|
||||
...parseRawFlags([
|
||||
...kbnFlags,
|
||||
...(!useTaskRunner
|
||||
? []
|
||||
: [
|
||||
'--node.roles=["ui"]',
|
||||
`--path.data=${Path.resolve(Os.tmpdir(), `scout-ui-${uuidv4()}`)}`,
|
||||
]),
|
||||
]),
|
||||
],
|
||||
}),
|
||||
];
|
||||
|
||||
if (useTaskRunner) {
|
||||
const mainUuid = getArgValue(kbnFlags, 'server.uuid');
|
||||
|
||||
// dedicated task runner
|
||||
promises.push(
|
||||
procs.run('kbn-tasks', {
|
||||
...procRunnerOpts,
|
||||
writeLogsToPath: options.logsDir
|
||||
? Path.resolve(options.logsDir, 'kbn-tasks.log')
|
||||
: undefined,
|
||||
args: [
|
||||
...prefixArgs,
|
||||
...parseRawFlags([
|
||||
...kbnFlags,
|
||||
`--server.port=${DedicatedTaskRunner.getPort(config.get('servers.kibana.port'))}`,
|
||||
'--node.roles=["background_tasks"]',
|
||||
`--path.data=${Path.resolve(Os.tmpdir(), `ftr-task-runner-${uuidv4()}`)}`,
|
||||
...(typeof mainUuid === 'string' && mainUuid
|
||||
? [`--server.uuid=${DedicatedTaskRunner.getUuid(mainUuid)}`]
|
||||
: []),
|
||||
...(devMode ? ['--no-optimizer'] : []),
|
||||
]),
|
||||
],
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
export function getExtraKbnOpts(installDir: string | undefined, isServerless: boolean) {
|
||||
if (installDir) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
'--dev',
|
||||
'--no-dev-config',
|
||||
'--no-dev-credentials',
|
||||
isServerless
|
||||
? '--server.versioned.versionResolution=newest'
|
||||
: '--server.versioned.versionResolution=oldest',
|
||||
];
|
||||
}
|
63
packages/kbn-scout/src/servers/start_servers.ts
Normal file
63
packages/kbn-scout/src/servers/start_servers.ts
Normal file
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import dedent from 'dedent';
|
||||
|
||||
import { ToolingLog } from '@kbn/tooling-log';
|
||||
import { withProcRunner } from '@kbn/dev-proc-runner';
|
||||
import { getTimeReporter } from '@kbn/ci-stats-reporter';
|
||||
import { runElasticsearch } from './run_elasticsearch';
|
||||
import { getExtraKbnOpts, runKibanaServer } from './run_kibana_server';
|
||||
import { StartServerOptions } from './flags';
|
||||
import { loadServersConfig } from '../config';
|
||||
import { silence } from '../common';
|
||||
|
||||
export async function startServers(log: ToolingLog, options: StartServerOptions) {
|
||||
const runStartTime = Date.now();
|
||||
const reportTime = getTimeReporter(log, 'scripts/scout_start_servers');
|
||||
|
||||
await withProcRunner(log, async (procs) => {
|
||||
const config = await loadServersConfig(options.mode, log);
|
||||
|
||||
const shutdownEs = await runElasticsearch({
|
||||
config,
|
||||
log,
|
||||
esFrom: options.esFrom,
|
||||
logsDir: options.logsDir,
|
||||
});
|
||||
|
||||
await runKibanaServer({
|
||||
procs,
|
||||
config,
|
||||
installDir: options.installDir,
|
||||
extraKbnOpts: getExtraKbnOpts(options.installDir, config.get('serverless')),
|
||||
});
|
||||
|
||||
reportTime(runStartTime, 'ready', {
|
||||
success: true,
|
||||
...options,
|
||||
});
|
||||
|
||||
// wait for 5 seconds of silence before logging the
|
||||
// success message so that it doesn't get buried
|
||||
await silence(log, 5000);
|
||||
|
||||
log.success(
|
||||
'\n\n' +
|
||||
dedent`
|
||||
Elasticsearch and Kibana are ready for functional testing.
|
||||
Use 'npx playwright test --config <path_to_Playwright.config.ts>' to run tests'
|
||||
` +
|
||||
'\n\n'
|
||||
);
|
||||
|
||||
await procs.waitForAllToStop();
|
||||
await shutdownEs();
|
||||
});
|
||||
}
|
14
packages/kbn-scout/src/types/cli.d.ts
vendored
Normal file
14
packages/kbn-scout/src/types/cli.d.ts
vendored
Normal 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
export type CliSupportedServerModes =
|
||||
| 'stateful'
|
||||
| 'serverless=es'
|
||||
| 'serverless=oblt'
|
||||
| 'serverless=security';
|
34
packages/kbn-scout/src/types/config.d.ts
vendored
Normal file
34
packages/kbn-scout/src/types/config.d.ts
vendored
Normal file
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { UrlParts } from '@kbn/test';
|
||||
|
||||
export interface ScoutLoaderConfig {
|
||||
serverless?: boolean;
|
||||
servers: {
|
||||
kibana: UrlParts;
|
||||
elasticsearch: UrlParts;
|
||||
fleet?: UrlParts;
|
||||
};
|
||||
dockerServers: any;
|
||||
esTestCluster: {
|
||||
from: string;
|
||||
license?: string;
|
||||
files: string[];
|
||||
serverArgs: string[];
|
||||
ssl: boolean;
|
||||
};
|
||||
kbnTestServer: {
|
||||
env?: any;
|
||||
buildArgs?: string[];
|
||||
sourceArgs?: string[];
|
||||
serverArgs: string[];
|
||||
useDedicatedTastRunner?: boolean;
|
||||
};
|
||||
}
|
12
packages/kbn-scout/src/types/index.ts
Normal file
12
packages/kbn-scout/src/types/index.ts
Normal 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
export * from './config';
|
||||
export * from './cli';
|
||||
export * from './servers';
|
26
packages/kbn-scout/src/types/servers.d.ts
vendored
Normal file
26
packages/kbn-scout/src/types/servers.d.ts
vendored
Normal 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { ServerlessProjectType } from '@kbn/es';
|
||||
|
||||
export interface ScoutServerConfig {
|
||||
serverless: boolean;
|
||||
projectType?: ServerlessProjectType;
|
||||
isCloud: boolean;
|
||||
cloudUsersFilePath: string;
|
||||
hosts: {
|
||||
kibana: string;
|
||||
elasticsearch: string;
|
||||
};
|
||||
auth: {
|
||||
username: string;
|
||||
password: string;
|
||||
};
|
||||
metadata?: any;
|
||||
}
|
31
packages/kbn-scout/tsconfig.json
Normal file
31
packages/kbn-scout/tsconfig.json
Normal file
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "target/types",
|
||||
"types": [
|
||||
"jest",
|
||||
"node"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*"
|
||||
],
|
||||
"kbn_references": [
|
||||
"@kbn/tooling-log",
|
||||
"@kbn/dev-cli-runner",
|
||||
"@kbn/dev-cli-errors",
|
||||
"@kbn/ci-stats-reporter",
|
||||
"@kbn/repo-info",
|
||||
"@kbn/es",
|
||||
"@kbn/dev-proc-runner",
|
||||
"@kbn/test",
|
||||
"@kbn/es-archiver",
|
||||
"@kbn/dev-utils",
|
||||
"@kbn/mock-idp-utils",
|
||||
"@kbn/test-suites-xpack",
|
||||
"@kbn/test-subj-selector",
|
||||
]
|
||||
}
|
|
@ -14,14 +14,23 @@ export { startServersCli, startServers } from './src/functional_tests/start_serv
|
|||
|
||||
// @internal
|
||||
export { runTestsCli, runTests } from './src/functional_tests/run_tests';
|
||||
export {
|
||||
runElasticsearch,
|
||||
runKibanaServer,
|
||||
parseRawFlags,
|
||||
getArgValue,
|
||||
remapPluginPaths,
|
||||
getKibanaCliArg,
|
||||
getKibanaCliLoggers,
|
||||
} from './src/functional_tests/lib';
|
||||
|
||||
export { initLogsDir } from './src/functional_tests/lib';
|
||||
export {
|
||||
SamlSessionManager,
|
||||
type SamlSessionManagerOptions,
|
||||
type HostOptions,
|
||||
type GetCookieOptions,
|
||||
} from './src/auth';
|
||||
export { runElasticsearch, runKibanaServer } from './src/functional_tests/lib';
|
||||
export { getKibanaCliArg, getKibanaCliLoggers } from './src/functional_tests/lib/kibana_cli_args';
|
||||
|
||||
export type {
|
||||
CreateTestEsClusterOptions,
|
||||
|
@ -38,6 +47,7 @@ export {
|
|||
} from './src/es';
|
||||
|
||||
export { kbnTestConfig } from './kbn_test_config';
|
||||
export type { UrlParts } from './kbn_test_config';
|
||||
|
||||
export {
|
||||
kibanaServerTestUser,
|
||||
|
|
|
@ -16,6 +16,7 @@ export {
|
|||
Lifecycle,
|
||||
LifecyclePhase,
|
||||
runCheckFtrConfigsCli,
|
||||
DedicatedTaskRunner,
|
||||
} from './lib';
|
||||
export { runFtrCli } from './cli';
|
||||
export * from './lib/docker_servers';
|
||||
|
|
|
@ -50,6 +50,11 @@ export async function runCheckFtrConfigsCli() {
|
|||
return false;
|
||||
}
|
||||
|
||||
// playwright config files
|
||||
if (file.match(/\/ui_tests\/*playwright*.config.ts$/)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!file.match(/(test|e2e).*config[^\/]*\.(t|j)s$/)) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -10,3 +10,11 @@
|
|||
export { runKibanaServer } from './run_kibana_server';
|
||||
export { runElasticsearch } from './run_elasticsearch';
|
||||
export * from './run_ftr';
|
||||
export {
|
||||
parseRawFlags,
|
||||
getArgValue,
|
||||
remapPluginPaths,
|
||||
getKibanaCliArg,
|
||||
getKibanaCliLoggers,
|
||||
} from './kibana_cli_args';
|
||||
export { initLogsDir } from './logs_dir';
|
||||
|
|
11
scripts/scout_start_servers.js
Normal file
11
scripts/scout_start_servers.js
Normal 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
require('../src/setup_node_env');
|
||||
require('@kbn/scout').startServersCli();
|
11
scripts/scout_test.js
Normal file
11
scripts/scout_test.js
Normal 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
require('../src/setup_node_env');
|
||||
require('@kbn/scout').runTestsCli();
|
|
@ -1534,6 +1534,8 @@
|
|||
"@kbn/saved-objects-tagging-plugin/*": ["x-pack/plugins/saved_objects_tagging/*"],
|
||||
"@kbn/saved-search-plugin": ["src/plugins/saved_search"],
|
||||
"@kbn/saved-search-plugin/*": ["src/plugins/saved_search/*"],
|
||||
"@kbn/scout": ["packages/kbn-scout"],
|
||||
"@kbn/scout/*": ["packages/kbn-scout/*"],
|
||||
"@kbn/screenshot-mode-example-plugin": ["examples/screenshot_mode_example"],
|
||||
"@kbn/screenshot-mode-example-plugin/*": ["examples/screenshot_mode_example/*"],
|
||||
"@kbn/screenshot-mode-plugin": ["src/plugins/screenshot_mode"],
|
||||
|
|
1
x-pack/.gitignore
vendored
1
x-pack/.gitignore
vendored
|
@ -12,3 +12,4 @@
|
|||
/.env
|
||||
/.kibana-plugin-helpers.dev.*
|
||||
.cache
|
||||
**/ui_tests/output
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"compilerOptions": {
|
||||
"outDir": "target/types",
|
||||
},
|
||||
"include": ["*.ts", "common/**/*", "public/**/*", "server/**/*"],
|
||||
"include": ["*.ts", "common/**/*", "public/**/*", "server/**/*", "ui_tests/**/*"],
|
||||
"kbn_references": [
|
||||
"@kbn/core",
|
||||
"@kbn/data-plugin",
|
||||
|
@ -21,6 +21,7 @@
|
|||
"@kbn/presentation-publishing",
|
||||
"@kbn/data-views-plugin",
|
||||
"@kbn/unified-search-plugin",
|
||||
"@kbn/scout",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
17
x-pack/plugins/discover_enhanced/ui_tests/README.md
Normal file
17
x-pack/plugins/discover_enhanced/ui_tests/README.md
Normal file
|
@ -0,0 +1,17 @@
|
|||
## How to run tests
|
||||
First start the servers with
|
||||
|
||||
```bash
|
||||
// ESS
|
||||
node scripts/scout_start_servers.js --stateful
|
||||
// Serverless
|
||||
node scripts/scout_start_servers.js --serverless=es
|
||||
```
|
||||
|
||||
Then you can run the tests multiple times in another terminal with:
|
||||
|
||||
```bash
|
||||
npx playwright test --config x-pack/plugins/discover_enhanced/ui_tests/playwright.config.ts
|
||||
```
|
||||
|
||||
Test results are available in `x-pack/plugins/discover_enhanced/ui_tests/output`
|
32
x-pack/plugins/discover_enhanced/ui_tests/fixtures/index.ts
Normal file
32
x-pack/plugins/discover_enhanced/ui_tests/fixtures/index.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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 {
|
||||
test as base,
|
||||
PageObjects,
|
||||
createLazyPageObject,
|
||||
ScoutTestFixtures,
|
||||
ScoutWorkerFixtures,
|
||||
} from '@kbn/scout';
|
||||
import { DemoPage } from './page_objects';
|
||||
|
||||
interface ExtendedScoutTestFixtures extends ScoutTestFixtures {
|
||||
pageObjects: PageObjects & {
|
||||
demo: DemoPage;
|
||||
};
|
||||
}
|
||||
|
||||
export const test = base.extend<ExtendedScoutTestFixtures, ScoutWorkerFixtures>({
|
||||
pageObjects: async ({ pageObjects, page }, use) => {
|
||||
const extendedPageObjects = {
|
||||
...pageObjects,
|
||||
demo: createLazyPageObject(DemoPage, page),
|
||||
};
|
||||
|
||||
await use(extendedPageObjects);
|
||||
},
|
||||
});
|
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* 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 { ScoutPage } from '@kbn/scout';
|
||||
|
||||
export class DemoPage {
|
||||
constructor(private readonly page: ScoutPage) {}
|
||||
|
||||
async goto() {
|
||||
this.page.gotoApp('not_implemented');
|
||||
}
|
||||
}
|
|
@ -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 { DemoPage } from './demo';
|
|
@ -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 { createPlaywrightConfig } from '@kbn/scout';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default createPlaywrightConfig({
|
||||
testDir: './tests',
|
||||
});
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* 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/scout';
|
||||
import { test } from '../fixtures';
|
||||
|
||||
test.describe('Discover app - value suggestions', () => {
|
||||
test.beforeAll(async ({ esArchiver, kbnClient }) => {
|
||||
await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/logstash_functional');
|
||||
await kbnClient.importExport.load(
|
||||
'x-pack/test/functional/fixtures/kbn_archiver/dashboard_drilldowns/drilldowns'
|
||||
);
|
||||
await kbnClient.uiSettings.update({
|
||||
defaultIndex: 'logstash-*', // TODO: investigate why it is required for `node scripts/playwright_test.js` run
|
||||
'doc_table:legacy': false,
|
||||
});
|
||||
});
|
||||
|
||||
test.afterAll(async ({ kbnClient }) => {
|
||||
await kbnClient.uiSettings.unset('doc_table:legacy');
|
||||
await kbnClient.uiSettings.unset('defaultIndex');
|
||||
await kbnClient.savedObjects.cleanStandardList();
|
||||
});
|
||||
|
||||
test.beforeEach(async ({ browserAuth, pageObjects }) => {
|
||||
await browserAuth.loginAsPrivilegedUser();
|
||||
await pageObjects.discover.goto();
|
||||
});
|
||||
|
||||
test('dont show up if outside of range', async ({ page, pageObjects }) => {
|
||||
await pageObjects.datePicker.setAbsoluteRange({
|
||||
from: 'Mar 1, 2020 @ 00:00:00.000',
|
||||
to: 'Nov 1, 2020 @ 00:00:00.000',
|
||||
});
|
||||
|
||||
await page.testSubj.fill('queryInput', 'extension.raw : ');
|
||||
await expect(page.testSubj.locator('autoCompleteSuggestionText')).toHaveCount(0);
|
||||
});
|
||||
|
||||
test('show up if in range', async ({ page, pageObjects }) => {
|
||||
await pageObjects.datePicker.setAbsoluteRange({
|
||||
from: 'Sep 19, 2015 @ 06:31:44.000',
|
||||
to: 'Sep 23, 2015 @ 18:31:44.000',
|
||||
});
|
||||
await page.testSubj.fill('queryInput', 'extension.raw : ');
|
||||
await expect(page.testSubj.locator('autoCompleteSuggestionText')).toHaveCount(5);
|
||||
const actualSuggestions = await page.testSubj
|
||||
.locator('autoCompleteSuggestionText')
|
||||
.allTextContents();
|
||||
expect(actualSuggestions.join(',')).toContain('jpg');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* 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/scout';
|
||||
import { test } from '../fixtures';
|
||||
|
||||
test.describe('Discover app - value suggestions non-time based', () => {
|
||||
test.beforeAll(async ({ esArchiver, kbnClient }) => {
|
||||
await esArchiver.loadIfNeeded(
|
||||
'test/functional/fixtures/es_archiver/index_pattern_without_timefield'
|
||||
);
|
||||
await kbnClient.importExport.load(
|
||||
'test/functional/fixtures/kbn_archiver/index_pattern_without_timefield'
|
||||
);
|
||||
await kbnClient.uiSettings.update({
|
||||
defaultIndex: 'without-timefield',
|
||||
'doc_table:legacy': false,
|
||||
});
|
||||
});
|
||||
|
||||
test.afterAll(async ({ kbnClient }) => {
|
||||
await kbnClient.uiSettings.unset('doc_table:legacy');
|
||||
await kbnClient.uiSettings.unset('defaultIndex');
|
||||
await kbnClient.savedObjects.cleanStandardList();
|
||||
});
|
||||
|
||||
test.beforeEach(async ({ browserAuth, pageObjects }) => {
|
||||
await browserAuth.loginAsPrivilegedUser();
|
||||
await pageObjects.discover.goto();
|
||||
});
|
||||
|
||||
test('shows all auto-suggest options for a filter in discover context app', async ({ page }) => {
|
||||
await page.testSubj.fill('queryInput', 'type.keyword : ');
|
||||
await expect(page.testSubj.locator('autoCompleteSuggestionText')).toHaveCount(1);
|
||||
const actualSuggestions = await page.testSubj
|
||||
.locator('autoCompleteSuggestionText')
|
||||
.allTextContents();
|
||||
expect(actualSuggestions.join(',')).toContain('"apache"');
|
||||
});
|
||||
});
|
|
@ -6876,6 +6876,10 @@
|
|||
version "0.0.0"
|
||||
uid ""
|
||||
|
||||
"@kbn/scout@link:packages/kbn-scout":
|
||||
version "0.0.0"
|
||||
uid ""
|
||||
|
||||
"@kbn/screenshot-mode-example-plugin@link:examples/screenshot_mode_example":
|
||||
version "0.0.0"
|
||||
uid ""
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue