diff --git a/packages/kbn-dev-utils/src/tooling_log/tooling_log.d.ts b/packages/kbn-dev-utils/src/tooling_log/tooling_log.d.ts index 672de893236c68..2b4b86f3b1f76a 100644 --- a/packages/kbn-dev-utils/src/tooling_log/tooling_log.d.ts +++ b/packages/kbn-dev-utils/src/tooling_log/tooling_log.d.ts @@ -38,7 +38,7 @@ export class ToolingLog { public warning(...args: any[]): void; public error(errOrMsg: string | Error): void; public write(...args: any[]): void; - public indent(spaces: number): void; + public indent(spaces?: number): void; public getWriters(): ToolingLogWriter[]; public setWriters(reporters: ToolingLogWriter[]): void; public getWritten$(): Rx.Observable; diff --git a/packages/kbn-test/src/functional_tests/lib/run_ftr.js b/packages/kbn-test/src/functional_tests/lib/run_ftr.js index 05e5cbb3d4b55c..d81d2e195556ef 100644 --- a/packages/kbn-test/src/functional_tests/lib/run_ftr.js +++ b/packages/kbn-test/src/functional_tests/lib/run_ftr.js @@ -17,21 +17,17 @@ * under the License. */ -import * as FunctionalTestRunner from '../../../../../src/functional_test_runner'; +import { FunctionalTestRunner } from '../../../../../src/functional_test_runner'; import { CliError } from './run_cli'; function createFtr({ configPath, options: { log, bail, grep, updateBaselines, suiteTags } }) { - return FunctionalTestRunner.createFunctionalTestRunner({ - log, - configFile: configPath, - configOverrides: { - mochaOpts: { - bail: !!bail, - grep, - }, - updateBaselines, - suiteTags, + return new FunctionalTestRunner(log, configPath, { + mochaOpts: { + bail: !!bail, + grep, }, + updateBaselines, + suiteTags, }); } diff --git a/src/functional_test_runner/cli.js b/src/functional_test_runner/cli.js deleted file mode 100644 index 9414dcfe1cfd93..00000000000000 --- a/src/functional_test_runner/cli.js +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { resolve } from 'path'; - -import { Command } from 'commander'; - -import { ToolingLog } from '@kbn/dev-utils'; -import { createFunctionalTestRunner } from './functional_test_runner'; - -const cmd = new Command('node scripts/functional_test_runner'); -const resolveConfigPath = v => resolve(process.cwd(), v); -const defaultConfigPath = resolveConfigPath('test/functional/config.js'); - -const createMultiArgCollector = (map) => () => { - const paths = []; - return (arg) => { - paths.push(map ? map(arg) : arg); - return paths; - }; -}; - -const collectExcludePaths = createMultiArgCollector(a => resolve(a)); -const collectIncludeTags = createMultiArgCollector(); -const collectExcludeTags = createMultiArgCollector(); - -cmd - .option('--config [path]', 'Path to a config file', resolveConfigPath, defaultConfigPath) - .option('--bail', 'stop tests after the first failure', false) - .option('--grep ', 'pattern used to select which tests to run') - .option('--invert', 'invert grep to exclude tests', false) - .option('--exclude [file]', 'Path to a test file that should not be loaded', collectExcludePaths(), []) - .option('--include-tag [tag]', 'A tag to be included, pass multiple times for multiple tags', collectIncludeTags(), []) - .option('--exclude-tag [tag]', 'A tag to be excluded, pass multiple times for multiple tags', collectExcludeTags(), []) - .option('--test-stats', 'Print the number of tests (included and excluded) to STDERR', false) - .option('--verbose', 'Log everything', false) - .option('--quiet', 'Only log errors', false) - .option('--silent', 'Log nothing', false) - .option('--updateBaselines', 'Replace baseline screenshots with whatever is generated from the test', false) - .option('--debug', 'Run in debug mode', false) - .parse(process.argv); - -let logLevel = 'info'; -if (cmd.silent) logLevel = 'silent'; -if (cmd.quiet) logLevel = 'error'; -if (cmd.debug) logLevel = 'debug'; -if (cmd.verbose) logLevel = 'verbose'; - -const log = new ToolingLog({ - level: logLevel, - writeTo: process.stdout -}); - -const functionalTestRunner = createFunctionalTestRunner({ - log, - configFile: cmd.config, - configOverrides: { - mochaOpts: { - bail: cmd.bail, - grep: cmd.grep, - invert: cmd.invert, - }, - suiteTags: { - include: cmd.includeTag, - exclude: cmd.excludeTag, - }, - updateBaselines: cmd.updateBaselines, - excludeTestFiles: cmd.exclude - } -}); - -async function run() { - try { - if (cmd.testStats) { - process.stderr.write(JSON.stringify( - await functionalTestRunner.getTestStats(), - null, - 2 - ) + '\n'); - } else { - const failureCount = await functionalTestRunner.run(); - process.exitCode = failureCount ? 1 : 0; - } - } catch (err) { - await teardown(err); - } finally { - await teardown(); - } -} - -let teardownRun = false; -async function teardown(err) { - if (teardownRun) return; - - teardownRun = true; - if (err) { - log.indent(-log.indent()); - log.error(err); - process.exitCode = 1; - } - - try { - await functionalTestRunner.close(); - } finally { - process.exit(); - } -} - -process.on('unhandledRejection', err => teardown(err)); -process.on('SIGTERM', () => teardown()); -process.on('SIGINT', () => teardown()); -run(); diff --git a/src/functional_test_runner/cli.ts b/src/functional_test_runner/cli.ts new file mode 100644 index 00000000000000..17bde6a50cbf1b --- /dev/null +++ b/src/functional_test_runner/cli.ts @@ -0,0 +1,106 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { resolve } from 'path'; + +import { run } from '../dev/run'; +import { FunctionalTestRunner } from './functional_test_runner'; + +run( + async ({ flags, log }) => { + const resolveConfigPath = (v: string) => resolve(process.cwd(), v); + const toArray = (v: string | string[]) => ([] as string[]).concat(v || []); + + const functionalTestRunner = new FunctionalTestRunner( + log, + resolveConfigPath(flags.config as string), + { + mochaOpts: { + bail: flags.bail, + grep: flags.grep || undefined, + invert: flags.invert, + }, + suiteTags: { + include: toArray(flags['include-tag'] as string | string[]), + exclude: toArray(flags['exclude-tag'] as string | string[]), + }, + updateBaselines: flags.updateBaselines, + excludeTestFiles: flags.exclude || undefined, + } + ); + + let teardownRun = false; + const teardown = async (err?: Error) => { + if (teardownRun) return; + + teardownRun = true; + if (err) { + log.indent(-log.indent()); + log.error(err); + process.exitCode = 1; + } + + try { + await functionalTestRunner.close(); + } finally { + process.exit(); + } + }; + + process.on('unhandledRejection', err => teardown(err)); + process.on('SIGTERM', () => teardown()); + process.on('SIGINT', () => teardown()); + + try { + if (flags['test-stats']) { + process.stderr.write( + JSON.stringify(await functionalTestRunner.getTestStats(), null, 2) + '\n' + ); + } else { + const failureCount = await functionalTestRunner.run(); + process.exitCode = failureCount ? 1 : 0; + } + } catch (err) { + await teardown(err); + } finally { + await teardown(); + } + }, + { + flags: { + string: ['config', 'grep', 'exclude', 'include-tag', 'exclude-tag'], + boolean: ['bail', 'invert', 'test-stats', 'updateBaselines'], + default: { + config: 'test/functional/config.js', + debug: true, + }, + help: ` + --config=path path to a config file + --bail stop tests after the first failure + --grep pattern used to select which tests to run + --invert invert grep to exclude tests + --exclude=file path to a test file that should not be loaded + --include-tag=tag a tag to be included, pass multiple times for multiple tags + --exclude-tag=tag a tag to be excluded, pass multiple times for multiple tags + --test-stats print the number of tests (included and excluded) to STDERR + --updateBaselines replace baseline screenshots with whatever is generated from the test + `, + }, + } +); diff --git a/src/functional_test_runner/fake_mocha_types.d.ts b/src/functional_test_runner/fake_mocha_types.d.ts new file mode 100644 index 00000000000000..12390a95a4961a --- /dev/null +++ b/src/functional_test_runner/fake_mocha_types.d.ts @@ -0,0 +1,44 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * The real mocha types conflict with the global jest types, because + * globals are terrible. So instead of using any for everything this + * tries to mock out simple versions of the Mocha types + */ + +import EventEmitter from 'events'; + +export interface Suite { + suites: Suite[]; + tests: Test[]; +} + +export interface Test { + fullTitle(): string; +} + +export interface Runner extends EventEmitter { + abort(): void; + failures: any[]; +} + +export interface Mocha { + run(cb: () => void): Runner; +} diff --git a/src/functional_test_runner/functional_test_runner.js b/src/functional_test_runner/functional_test_runner.js deleted file mode 100644 index 9356412ba0738a..00000000000000 --- a/src/functional_test_runner/functional_test_runner.js +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { - createLifecycle, - readConfigFile, - ProviderCollection, - readProviderSpec, - setupMocha, - runTests, -} from './lib'; - -export function createFunctionalTestRunner({ log, configFile, configOverrides }) { - const lifecycle = createLifecycle(); - - lifecycle.on('phaseStart', name => { - log.verbose('starting %j lifecycle phase', name); - }); - - lifecycle.on('phaseEnd', name => { - log.verbose('ending %j lifecycle phase', name); - }); - - - class FunctionalTestRunner { - async run() { - return await this._run(async (config, coreProviders) => { - const providers = new ProviderCollection(log, [ - ...coreProviders, - ...readProviderSpec('Service', config.get('services')), - ...readProviderSpec('PageObject', config.get('pageObjects')) - ]); - - await providers.loadAll(); - - const mocha = await setupMocha(lifecycle, log, config, providers); - await lifecycle.trigger('beforeTests'); - log.info('Starting tests'); - - return await runTests(lifecycle, log, mocha); - }); - } - - async getTestStats() { - return await this._run(async (config, coreProviders) => { - // replace the function of custom service providers so that they return - // promise-like objects which never resolve, essentially disabling them - // allowing us to load the test files and populate the mocha suites - const stubProvider = provider => ( - coreProviders.includes(provider) - ? provider - : { - ...provider, - fn: () => ({ - then: () => {} - }) - } - ); - - const providers = new ProviderCollection(log, [ - ...coreProviders, - ...readProviderSpec('Service', config.get('services')), - ...readProviderSpec('PageObject', config.get('pageObjects')) - ].map(stubProvider)); - - const mocha = await setupMocha(lifecycle, log, config, providers); - - const countTests = suite => ( - suite.suites.reduce( - (sum, suite) => sum + countTests(suite), - suite.tests.length - ) - ); - - return { - testCount: countTests(mocha.suite), - excludedTests: mocha.excludedTests.map(t => t.fullTitle()) - }; - }); - } - - async _run(handler) { - let runErrorOccurred = false; - - try { - const config = await readConfigFile(log, configFile, configOverrides); - log.info('Config loaded'); - - if (config.get('testFiles').length === 0) { - log.warning('No test files defined.'); - return; - } - - // base level services that functional_test_runner exposes - const coreProviders = readProviderSpec('Service', { - lifecycle: () => lifecycle, - log: () => log, - config: () => config, - }); - - return await handler(config, coreProviders); - } catch (runError) { - runErrorOccurred = true; - throw runError; - - } finally { - try { - await this.close(); - - } catch (closeError) { - if (runErrorOccurred) { - log.error('failed to close functional_test_runner'); - log.error(closeError); - } else { - throw closeError; - } - } - } - } - - async close() { - if (this._closed) return; - - this._closed = true; - await lifecycle.trigger('cleanup'); - } - } - - return new FunctionalTestRunner(); -} diff --git a/src/functional_test_runner/functional_test_runner.ts b/src/functional_test_runner/functional_test_runner.ts new file mode 100644 index 00000000000000..459c52997e2297 --- /dev/null +++ b/src/functional_test_runner/functional_test_runner.ts @@ -0,0 +1,145 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { ToolingLog } from '@kbn/dev-utils'; +import { Suite, Test } from './fake_mocha_types'; + +import { + createLifecycle, + readConfigFile, + ProviderCollection, + readProviderSpec, + setupMocha, + runTests, + Config, +} from './lib'; + +export class FunctionalTestRunner { + public readonly lifecycle = createLifecycle(); + private closed = false; + + constructor( + private readonly log: ToolingLog, + private readonly configFile: string, + private readonly configOverrides: any + ) { + this.lifecycle.on('phaseStart', name => { + log.verbose('starting %j lifecycle phase', name); + }); + + this.lifecycle.on('phaseEnd', name => { + log.verbose('ending %j lifecycle phase', name); + }); + } + + async run() { + return await this._run(async (config, coreProviders) => { + const providers = new ProviderCollection(this.log, [ + ...coreProviders, + ...readProviderSpec('Service', config.get('services')), + ...readProviderSpec('PageObject', config.get('pageObjects')), + ]); + + await providers.loadAll(); + + const mocha = await setupMocha(this.lifecycle, this.log, config, providers); + await this.lifecycle.trigger('beforeTests'); + this.log.info('Starting tests'); + + return await runTests(this.lifecycle, mocha); + }); + } + + async getTestStats() { + return await this._run(async (config, coreProviders) => { + // replace the function of custom service providers so that they return + // promise-like objects which never resolve, essentially disabling them + // allowing us to load the test files and populate the mocha suites + const readStubbedProviderSpec = (type: string, providers: any) => + readProviderSpec(type, providers).map(p => ({ + ...p, + fn: () => ({ + then: () => {}, + }), + })); + + const providers = new ProviderCollection(this.log, [ + ...coreProviders, + ...readStubbedProviderSpec('Service', config.get('services')), + ...readStubbedProviderSpec('PageObject', config.get('pageObjects')), + ]); + + const mocha = await setupMocha(this.lifecycle, this.log, config, providers); + + const countTests = (suite: Suite): number => + suite.suites.reduce((sum, s) => sum + countTests(s), suite.tests.length); + + return { + testCount: countTests(mocha.suite), + excludedTests: mocha.excludedTests.map((t: Test) => t.fullTitle()), + }; + }); + } + + async _run( + handler: (config: Config, coreProvider: ReturnType) => Promise + ): Promise { + let runErrorOccurred = false; + + try { + const config = await readConfigFile(this.log, this.configFile, this.configOverrides); + this.log.info('Config loaded'); + + if (config.get('testFiles').length === 0) { + throw new Error('No test files defined.'); + } + + // base level services that functional_test_runner exposes + const coreProviders = readProviderSpec('Service', { + lifecycle: () => this.lifecycle, + log: () => this.log, + config: () => config, + }); + + return await handler(config, coreProviders); + } catch (runError) { + runErrorOccurred = true; + throw runError; + } finally { + try { + await this.close(); + } catch (closeError) { + if (runErrorOccurred) { + this.log.error('failed to close functional_test_runner'); + this.log.error(closeError); + } else { + // eslint-disable-next-line no-unsafe-finally + throw closeError; + } + } + } + } + + async close() { + if (this.closed) return; + + this.closed = true; + await this.lifecycle.trigger('cleanup'); + } +} diff --git a/src/functional_test_runner/index.js b/src/functional_test_runner/index.ts similarity index 92% rename from src/functional_test_runner/index.js rename to src/functional_test_runner/index.ts index 161e9bb89e7704..50b4c1c2a63f76 100644 --- a/src/functional_test_runner/index.js +++ b/src/functional_test_runner/index.ts @@ -17,5 +17,5 @@ * under the License. */ -export { createFunctionalTestRunner } from './functional_test_runner'; +export { FunctionalTestRunner } from './functional_test_runner'; export { readConfigFile } from './lib'; diff --git a/src/functional_test_runner/lib/config/index.js b/src/functional_test_runner/lib/config/index.ts similarity index 96% rename from src/functional_test_runner/lib/config/index.js rename to src/functional_test_runner/lib/config/index.ts index c9b8e993dc8a3d..3445480f4c673a 100644 --- a/src/functional_test_runner/lib/config/index.js +++ b/src/functional_test_runner/lib/config/index.ts @@ -17,4 +17,5 @@ * under the License. */ +export { Config } from './config'; export { readConfigFile } from './read_config_file'; diff --git a/src/functional_test_runner/lib/config/read_config_file.js b/src/functional_test_runner/lib/config/read_config_file.ts similarity index 58% rename from src/functional_test_runner/lib/config/read_config_file.js rename to src/functional_test_runner/lib/config/read_config_file.ts index ce118020ea2d08..4067219f2a8568 100644 --- a/src/functional_test_runner/lib/config/read_config_file.js +++ b/src/functional_test_runner/lib/config/read_config_file.ts @@ -17,6 +17,7 @@ * under the License. */ +import { ToolingLog } from '@kbn/dev-utils'; import { defaultsDeep } from 'lodash'; import { Config } from './config'; @@ -24,37 +25,34 @@ import { transformDeprecations } from './transform_deprecations'; const cache = new WeakMap(); -async function getSettingsFromFile(log, path, settingOverrides) { - const configModule = require(path); // eslint-disable-line import/no-dynamic-require - const configProvider = configModule.__esModule - ? configModule.default - : configModule; +async function getSettingsFromFile(log: ToolingLog, path: string, settingOverrides: any) { + const configModule = require(path); // eslint-disable-line @typescript-eslint/no-var-requires + const configProvider = configModule.__esModule ? configModule.default : configModule; if (!cache.has(configProvider)) { log.debug('Loading config file from %j', path); - cache.set(configProvider, configProvider({ - log, - async readConfigFile(...args) { - return new Config({ - settings: await getSettingsFromFile(log, ...args), - primary: false, - path, - }); - } - })); + cache.set( + configProvider, + configProvider({ + log, + async readConfigFile(p: string, o: any) { + return new Config({ + settings: await getSettingsFromFile(log, p, o), + primary: false, + path: p, + }); + }, + }) + ); } - const settingsWithDefaults = defaultsDeep( - {}, - settingOverrides, - await cache.get(configProvider) - ); + const settingsWithDefaults = defaultsDeep({}, settingOverrides, await cache.get(configProvider)!); - const logDeprecation = (...args) => log.error(...args); + const logDeprecation = (error: string | Error) => log.error(error); return transformDeprecations(settingsWithDefaults, logDeprecation); } -export async function readConfigFile(log, path, settingOverrides) { +export async function readConfigFile(log: ToolingLog, path: string, settingOverrides: any) { return new Config({ settings: await getSettingsFromFile(log, path, settingOverrides), primary: true, diff --git a/src/functional_test_runner/lib/config/transform_deprecations.js b/src/functional_test_runner/lib/config/transform_deprecations.ts similarity index 77% rename from src/functional_test_runner/lib/config/transform_deprecations.js rename to src/functional_test_runner/lib/config/transform_deprecations.ts index 6bb3cf35ebe301..1b8538529b26fc 100644 --- a/src/functional_test_runner/lib/config/transform_deprecations.js +++ b/src/functional_test_runner/lib/config/transform_deprecations.ts @@ -17,8 +17,16 @@ * under the License. */ +// @ts-ignore import { createTransform, Deprecations } from '../../../legacy/deprecation'; -export const transformDeprecations = createTransform([ - Deprecations.unused('servers.webdriver') +type DeprecationTransformer = ( + settings: object, + log: (msg: string) => void +) => { + [key: string]: any; +}; + +export const transformDeprecations: DeprecationTransformer = createTransform([ + Deprecations.unused('servers.webdriver'), ]); diff --git a/src/functional_test_runner/lib/index.js b/src/functional_test_runner/lib/index.js deleted file mode 100644 index e1f93d8ac627c4..00000000000000 --- a/src/functional_test_runner/lib/index.js +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export { createLifecycle } from './lifecycle'; -export { readConfigFile } from './config'; -export { setupMocha, runTests } from './mocha'; -export { readProviderSpec, ProviderCollection } from './providers'; diff --git a/src/functional_test_runner/lib/index.d.ts b/src/functional_test_runner/lib/index.ts similarity index 77% rename from src/functional_test_runner/lib/index.d.ts rename to src/functional_test_runner/lib/index.ts index 6c71239d17bc97..88995e9acc5fea 100644 --- a/src/functional_test_runner/lib/index.d.ts +++ b/src/functional_test_runner/lib/index.ts @@ -17,5 +17,7 @@ * under the License. */ -export { Config } from './config/config'; -export { Lifecycle } from './lifecycle'; +export { createLifecycle, Lifecycle } from './lifecycle'; +export { readConfigFile, Config } from './config'; +export { readProviderSpec, ProviderCollection, Provider } from './providers'; +export { runTests, setupMocha } from './mocha'; diff --git a/src/functional_test_runner/lib/load_tracer.js b/src/functional_test_runner/lib/load_tracer.ts similarity index 83% rename from src/functional_test_runner/lib/load_tracer.js rename to src/functional_test_runner/lib/load_tracer.ts index 53b80f54b9384a..26a0e9617a7c70 100644 --- a/src/functional_test_runner/lib/load_tracer.js +++ b/src/functional_test_runner/lib/load_tracer.ts @@ -17,7 +17,8 @@ * under the License. */ -const globalLoadPath = []; +const globalLoadPath: Array<{ ident: string; description: string }> = []; + function getPath(startAt = 0) { return globalLoadPath .slice(startAt) @@ -25,9 +26,15 @@ function getPath(startAt = 0) { .join(' -> '); } -function addPathToMessage(message, startAt) { +const errorsFromLoadTracer = new WeakSet(); + +function addPathToMessage(message: string, startAt?: number) { const path = getPath(startAt); - if (!path) return message; + + if (!path) { + return message; + } + return `${message} -- from ${path}`; } @@ -41,7 +48,7 @@ function addPathToMessage(message, startAt) { * @param {Function} load function that executes this step * @return {Any} the value produced by load() */ -export function loadTracer(ident, description, load) { +export function loadTracer(ident: any, description: string, load: () => Promise | void) { const isCircular = globalLoadPath.find(step => step.ident === ident); if (isCircular) { throw new Error(addPathToMessage(`Circular reference to "${description}"`)); @@ -51,13 +58,13 @@ export function loadTracer(ident, description, load) { globalLoadPath.unshift({ ident, description }); return load(); } catch (err) { - if (err.__fromLoadTracer) { + if (errorsFromLoadTracer.has(err)) { throw err; } const wrapped = new Error(addPathToMessage(`Failure to load ${description}`, 1)); wrapped.stack = `${wrapped.message}\n\n Original Error: ${err.stack}`; - wrapped.__fromLoadTracer = true; + errorsFromLoadTracer.add(wrapped); throw wrapped; } finally { globalLoadPath.shift(); diff --git a/src/functional_test_runner/lib/mocha/index.js b/src/functional_test_runner/lib/mocha/index.ts similarity index 95% rename from src/functional_test_runner/lib/mocha/index.js rename to src/functional_test_runner/lib/mocha/index.ts index f188c62d616c18..bbbda1697b2ce4 100644 --- a/src/functional_test_runner/lib/mocha/index.js +++ b/src/functional_test_runner/lib/mocha/index.ts @@ -17,5 +17,6 @@ * under the License. */ +// @ts-ignore will be replaced shortly export { setupMocha } from './setup_mocha'; export { runTests } from './run_tests'; diff --git a/src/functional_test_runner/lib/mocha/run_tests.js b/src/functional_test_runner/lib/mocha/run_tests.ts similarity index 88% rename from src/functional_test_runner/lib/mocha/run_tests.js rename to src/functional_test_runner/lib/mocha/run_tests.ts index e561b1f61c3495..2d98052b1062a4 100644 --- a/src/functional_test_runner/lib/mocha/run_tests.js +++ b/src/functional_test_runner/lib/mocha/run_tests.ts @@ -17,6 +17,9 @@ * under the License. */ +import { Lifecycle } from '../lifecycle'; +import { Mocha } from '../../fake_mocha_types'; + /** * Run the tests that have already been loaded into * mocha. aborts tests on 'cleanup' lifecycle runs @@ -26,7 +29,7 @@ * @param {Mocha} mocha * @return {Promise} resolves to the number of test failures */ -export async function runTests(lifecycle, log, mocha) { +export async function runTests(lifecycle: Lifecycle, mocha: Mocha) { let runComplete = false; const runner = mocha.run(() => { runComplete = true; @@ -36,7 +39,7 @@ export async function runTests(lifecycle, log, mocha) { if (!runComplete) runner.abort(); }); - return new Promise((resolve) => { + return new Promise(resolve => { const respond = () => resolve(runner.failures); // if there are no tests, mocha.run() is sync diff --git a/src/functional_test_runner/lib/providers/async_instance.js b/src/functional_test_runner/lib/providers/async_instance.ts similarity index 50% rename from src/functional_test_runner/lib/providers/async_instance.js rename to src/functional_test_runner/lib/providers/async_instance.ts index ab7d46f590710d..7bb1b2bc153c1b 100644 --- a/src/functional_test_runner/lib/providers/async_instance.js +++ b/src/functional_test_runner/lib/providers/async_instance.ts @@ -20,22 +20,29 @@ const INITIALIZING = Symbol('async instance initializing'); const asyncInitFns = new WeakSet(); -export const isAsyncInstance = val => ( - val && asyncInitFns.has(val.init) -); +type AsyncInstance = { + init: () => Promise; +} & T; -export const createAsyncInstance = (type, name, promiseForValue) => { - let instance = INITIALIZING; +export const isAsyncInstance = (val: any): val is AsyncInstance => + val && asyncInitFns.has(val.init); - const initPromise = promiseForValue.then(v => instance = v); +export const createAsyncInstance = ( + type: string, + name: string, + promiseForValue: Promise +): AsyncInstance => { + let instance: T | symbol = INITIALIZING; + + const initPromise = promiseForValue.then(v => (instance = v)); const loadingTarget = { init() { return initPromise; - } + }, }; asyncInitFns.add(loadingTarget.init); - const assertReady = desc => { + const assertReady = (desc: string) => { if (instance === INITIALIZING) { throw new Error(` ${type} \`${desc}\` is loaded asynchronously but isn't available yet. Either await the @@ -52,81 +59,93 @@ export const createAsyncInstance = (type, name, promiseForValue) => { }; return new Proxy(loadingTarget, { - apply(target, context, args) { + apply(_, context, args) { assertReady(`${name}()`); - return Reflect.apply(instance, context, args); + return Reflect.apply(instance as any, context, args); }, - construct(target, args, newTarget) { + construct(_, args, newTarget) { assertReady(`new ${name}()`); - return Reflect.construct(instance, args, newTarget); + return Reflect.construct(instance as any, args, newTarget); }, - defineProperty(target, prop, descriptor) { - assertReady(`${name}.${prop}`); - return Reflect.defineProperty(instance, prop, descriptor); + defineProperty(_, prop, descriptor) { + if (typeof prop !== 'symbol') { + assertReady(`${name}.${prop}`); + } + return Reflect.defineProperty(instance as any, prop, descriptor); }, - deleteProperty(target, prop) { - assertReady(`${name}.${prop}`); - return Reflect.deleteProperty(instance, prop); + deleteProperty(_, prop) { + if (typeof prop !== 'symbol') { + assertReady(`${name}.${prop}`); + } + return Reflect.deleteProperty(instance as any, prop); }, - get(target, prop, receiver) { + get(_, prop, receiver) { if (loadingTarget.hasOwnProperty(prop)) { - return Reflect.get(loadingTarget, prop, receiver); + return Reflect.get(loadingTarget as any, prop, receiver); } - assertReady(`${name}.${prop}`); - return Reflect.get(instance, prop, receiver); + if (typeof prop !== 'symbol') { + assertReady(`${name}.${prop}`); + } + return Reflect.get(instance as any, prop, receiver); }, - getOwnPropertyDescriptor(target, prop) { + getOwnPropertyDescriptor(_, prop) { if (loadingTarget.hasOwnProperty(prop)) { return Reflect.getOwnPropertyDescriptor(loadingTarget, prop); } - assertReady(`${name}.${prop}`); - return Reflect.getOwnPropertyDescriptor(instance, prop); + if (typeof prop !== 'symbol') { + assertReady(`${name}.${prop}`); + } + return Reflect.getOwnPropertyDescriptor(instance as any, prop); }, getPrototypeOf() { assertReady(`${name}`); - return Reflect.getPrototypeOf(instance); + return Reflect.getPrototypeOf(instance as any); }, - has(target, prop) { + has(_, prop) { if (!loadingTarget.hasOwnProperty(prop)) { return Reflect.has(loadingTarget, prop); } - assertReady(`${name}.${prop}`); - return Reflect.has(instance, prop); + if (typeof prop !== 'symbol') { + assertReady(`${name}.${prop}`); + } + return Reflect.has(instance as any, prop); }, isExtensible() { assertReady(`${name}`); - return Reflect.isExtensible(instance); + return Reflect.isExtensible(instance as any); }, ownKeys() { assertReady(`${name}`); - return Reflect.ownKeys(instance); + return Reflect.ownKeys(instance as any); }, preventExtensions() { assertReady(`${name}`); - return Reflect.preventExtensions(instance); + return Reflect.preventExtensions(instance as any); }, - set(target, prop, value, receiver) { - assertReady(`${name}.${prop}`); - return Reflect.set(instance, prop, value, receiver); + set(_, prop, value, receiver) { + if (typeof prop !== 'symbol') { + assertReady(`${name}.${prop}`); + } + return Reflect.set(instance as any, prop, value, receiver); }, - setPrototypeOf(target, prototype) { + setPrototypeOf(_, prototype) { assertReady(`${name}`); - return Reflect.setPrototypeOf(instance, prototype); - } - }); + return Reflect.setPrototypeOf(instance as any, prototype); + }, + }) as AsyncInstance; }; diff --git a/src/functional_test_runner/lib/providers/index.js b/src/functional_test_runner/lib/providers/index.ts similarity index 92% rename from src/functional_test_runner/lib/providers/index.js rename to src/functional_test_runner/lib/providers/index.ts index f0e9ead5977900..edcff851810457 100644 --- a/src/functional_test_runner/lib/providers/index.js +++ b/src/functional_test_runner/lib/providers/index.ts @@ -18,4 +18,4 @@ */ export { ProviderCollection } from './provider_collection'; -export { readProviderSpec } from './read_provider_spec'; +export { Provider, readProviderSpec } from './read_provider_spec'; diff --git a/src/functional_test_runner/lib/providers/provider_collection.js b/src/functional_test_runner/lib/providers/provider_collection.ts similarity index 59% rename from src/functional_test_runner/lib/providers/provider_collection.js rename to src/functional_test_runner/lib/providers/provider_collection.ts index 259b762c1f207d..a5d192a1f72053 100644 --- a/src/functional_test_runner/lib/providers/provider_collection.js +++ b/src/functional_test_runner/lib/providers/provider_collection.ts @@ -17,52 +17,47 @@ * under the License. */ +import { ToolingLog } from '@kbn/dev-utils'; + import { loadTracer } from '../load_tracer'; import { createAsyncInstance, isAsyncInstance } from './async_instance'; +import { Providers } from './read_provider_spec'; import { createVerboseInstance } from './verbose_instance'; export class ProviderCollection { - constructor(log, providers) { - this._log = log; - this._instances = new Map(); - this._providers = providers; - } + private readonly instances = new Map(); - getService = name => ( - this._getInstance('Service', name) - ) + constructor(private readonly log: ToolingLog, private readonly providers: Providers) {} - hasService = name => ( - Boolean(this._findProvider('Service', name)) - ) + public getService = (name: string) => this.getInstance('Service', name); - getPageObject = name => ( - this._getInstance('PageObject', name) - ) + public hasService = (name: string) => Boolean(this.findProvider('Service', name)); - getPageObjects = names => { - const pageObjects = {}; - names.forEach(name => pageObjects[name] = this.getPageObject(name)); + public getPageObject = (name: string) => this.getInstance('PageObject', name); + + public getPageObjects = (names: string[]) => { + const pageObjects: Record = {}; + names.forEach(name => (pageObjects[name] = this.getPageObject(name))); return pageObjects; - } + }; - loadExternalService(name, provider) { - return this._getInstance('Service', name, provider); + public loadExternalService(name: string, provider: (...args: any) => any) { + return this.getInstance('Service', name, provider); } - async loadAll() { + public async loadAll() { const asyncInitFailures = []; await Promise.all( - this._providers.map(async ({ type, name }) => { + this.providers.map(async ({ type, name }) => { try { - const instance = this._getInstance(type, name); + const instance = this.getInstance(type, name); if (isAsyncInstance(instance)) { await instance.init(); } } catch (err) { - this._log.warning('Failure loading service %j', name); - this._log.error(err); + this.log.warning('Failure loading service %j', name); + this.log.error(err); asyncInitFailures.push(name); } }) @@ -73,20 +68,20 @@ export class ProviderCollection { } } - _findProvider(type, name) { - return this._providers.find(p => p.type === type && p.name === name); + private findProvider(type: string, name: string) { + return this.providers.find(p => p.type === type && p.name === name); } - _getProvider(type, name) { - const providerDef = this._findProvider(type, name); + private getProvider(type: string, name: string) { + const providerDef = this.findProvider(type, name); if (!providerDef) { throw new Error(`Unknown ${type} "${name}"`); } return providerDef.fn; } - _getInstance(type, name, provider = this._getProvider(type, name)) { - const instances = this._instances; + private getInstance(type: string, name: string, provider = this.getProvider(type, name)) { + const instances = this.instances; return loadTracer(provider, `${type}(${name})`, () => { if (!provider) { @@ -105,9 +100,15 @@ export class ProviderCollection { instance = createAsyncInstance(type, name, instance); } - if (name !== '__webdriver__' && name !== 'log' && name !== 'config' && instance && typeof instance === 'object') { + if ( + name !== '__webdriver__' && + name !== 'log' && + name !== 'config' && + instance && + typeof instance === 'object' + ) { instance = createVerboseInstance( - this._log, + this.log, type === 'PageObject' ? `PageObjects.${name}` : name, instance ); diff --git a/src/functional_test_runner/lib/providers/read_provider_spec.js b/src/functional_test_runner/lib/providers/read_provider_spec.ts similarity index 79% rename from src/functional_test_runner/lib/providers/read_provider_spec.js rename to src/functional_test_runner/lib/providers/read_provider_spec.ts index 763c663f959ef1..be8e25f102b095 100644 --- a/src/functional_test_runner/lib/providers/read_provider_spec.js +++ b/src/functional_test_runner/lib/providers/read_provider_spec.ts @@ -17,7 +17,10 @@ * under the License. */ -export function readProviderSpec(type, providers) { +export type Providers = ReturnType; +export type Provider = Providers extends Array ? X : unknown; + +export function readProviderSpec(type: string, providers: Record any>) { return Object.keys(providers).map(name => { return { type, diff --git a/src/functional_test_runner/lib/providers/verbose_instance.js b/src/functional_test_runner/lib/providers/verbose_instance.ts similarity index 63% rename from src/functional_test_runner/lib/providers/verbose_instance.js rename to src/functional_test_runner/lib/providers/verbose_instance.ts index 60245126c7de6f..93a87f3496b54c 100644 --- a/src/functional_test_runner/lib/providers/verbose_instance.js +++ b/src/functional_test_runner/lib/providers/verbose_instance.ts @@ -19,45 +19,54 @@ import { inspect } from 'util'; -function printArgs(args) { - return args.map((arg) => { - if (typeof arg === 'string' || typeof arg === 'number' || arg instanceof Date) { - return inspect(arg); - } +import { ToolingLog } from '@kbn/dev-utils'; - if (Array.isArray(arg)) { - return `[${printArgs(arg)}]`; - } +function printArgs(args: any[]): string { + return args + .map(arg => { + if (typeof arg === 'string' || typeof arg === 'number' || arg instanceof Date) { + return inspect(arg); + } + + if (Array.isArray(arg)) { + return `[${printArgs(arg)}]`; + } - return Object.prototype.toString.call(arg); - }).join(', '); + return Object.prototype.toString.call(arg); + }) + .join(', '); } -export function createVerboseInstance(log, name, instance) { - if (!log.getWriters().some(l => l.level.flags.verbose)) { +export function createVerboseInstance( + log: ToolingLog, + name: string, + instance: { [k: string]: any; [i: number]: any } +) { + if (!log.getWriters().some(l => (l as any).level.flags.verbose)) { return instance; } return new Proxy(instance, { get(_, prop) { - const value = instance[prop]; + const value = (instance as any)[prop]; - if (typeof value !== 'function' || prop === 'init') { + if (typeof value !== 'function' || prop === 'init' || typeof prop === 'symbol') { return value; } - return function (...args) { + return function(this: any, ...args: any[]) { log.verbose(`${name}.${prop}(${printArgs(args)})`); log.indent(2); let result; try { result = { - returned: value.apply(this, args) + returned: value.apply(this, args), }; } catch (error) { result = { - thrown: error + returned: undefined, + thrown: error, }; } diff --git a/test/interpreter_functional/config.js b/test/interpreter_functional/config.js index 47731f8897f96f..e8700262e273a2 100644 --- a/test/interpreter_functional/config.js +++ b/test/interpreter_functional/config.js @@ -39,7 +39,6 @@ export default async function ({ readConfigFile }) { esArchiver: { directory: path.resolve(__dirname, '../es_archives') }, - screenshots: functionalConfig.get('screenshots'), snapshots: { directory: path.resolve(__dirname, 'snapshots'), }, diff --git a/test/plugin_functional/plugins/core_plugin_a/tsconfig.json b/test/plugin_functional/plugins/core_plugin_a/tsconfig.json index 170bd9d887cae5..5fcaeafbb0d852 100644 --- a/test/plugin_functional/plugins/core_plugin_a/tsconfig.json +++ b/test/plugin_functional/plugins/core_plugin_a/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../../tsconfig.json", + "extends": "../../../../tsconfig.json", "compilerOptions": { "outDir": "./target", "skipLibCheck": true diff --git a/test/plugin_functional/plugins/core_plugin_b/tsconfig.json b/test/plugin_functional/plugins/core_plugin_b/tsconfig.json index 170bd9d887cae5..5fcaeafbb0d852 100644 --- a/test/plugin_functional/plugins/core_plugin_b/tsconfig.json +++ b/test/plugin_functional/plugins/core_plugin_b/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../../tsconfig.json", + "extends": "../../../../tsconfig.json", "compilerOptions": { "outDir": "./target", "skipLibCheck": true diff --git a/test/plugin_functional/plugins/kbn_tp_sample_panel_action/tsconfig.json b/test/plugin_functional/plugins/kbn_tp_sample_panel_action/tsconfig.json index aba7203377affa..5fcaeafbb0d852 100644 --- a/test/plugin_functional/plugins/kbn_tp_sample_panel_action/tsconfig.json +++ b/test/plugin_functional/plugins/kbn_tp_sample_panel_action/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../../tsconfig.json", + "extends": "../../../../tsconfig.json", "compilerOptions": { "outDir": "./target", "skipLibCheck": true @@ -11,4 +11,4 @@ "../../../../typings/**/*", ], "exclude": [] -} \ No newline at end of file +} diff --git a/test/tsconfig.json b/test/tsconfig.json index d620d28f3d1de4..e64442de9c9ace 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -4,6 +4,9 @@ "types": [ "node", "mocha" + ], + "lib": [ + "esnext" ] }, "include": [