[ftr] convert remaining JS to TS (#35110) (#35319)

This commit is contained in:
Spencer 2019-04-18 15:21:34 -07:00 committed by GitHub
parent ed98a87115
commit 239f604f94
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 489 additions and 441 deletions

View file

@ -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<LogMessage>;

View file

@ -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,
});
}

View file

@ -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>', '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();

View file

@ -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> 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
`,
},
}
);

View file

@ -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;
}

View file

@ -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();
}

View file

@ -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<T = any>(
handler: (config: Config, coreProvider: ReturnType<typeof readProviderSpec>) => Promise<T>
): Promise<T> {
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');
}
}

View file

@ -17,5 +17,5 @@
* under the License.
*/
export { createFunctionalTestRunner } from './functional_test_runner';
export { FunctionalTestRunner } from './functional_test_runner';
export { readConfigFile } from './lib';

View file

@ -17,4 +17,5 @@
* under the License.
*/
export { Config } from './config';
export { readConfigFile } from './read_config_file';

View file

@ -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,

View file

@ -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'),
]);

View file

@ -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';

View file

@ -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';

View file

@ -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> | 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();

View file

@ -17,5 +17,6 @@
* under the License.
*/
// @ts-ignore will be replaced shortly
export { setupMocha } from './setup_mocha';
export { runTests } from './run_tests';

View file

@ -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<Number>} 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

View file

@ -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<T> = {
init: () => Promise<T>;
} & T;
export const createAsyncInstance = (type, name, promiseForValue) => {
let instance = INITIALIZING;
export const isAsyncInstance = <T = unknown>(val: any): val is AsyncInstance<T> =>
val && asyncInitFns.has(val.init);
const initPromise = promiseForValue.then(v => instance = v);
export const createAsyncInstance = <T>(
type: string,
name: string,
promiseForValue: Promise<T>
): AsyncInstance<T> => {
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<T>;
};

View file

@ -18,4 +18,4 @@
*/
export { ProviderCollection } from './provider_collection';
export { readProviderSpec } from './read_provider_spec';
export { Provider, readProviderSpec } from './read_provider_spec';

View file

@ -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<string, any> = {};
names.forEach(name => (pageObjects[name] = this.getPageObject(name)));
return pageObjects;
};
public loadExternalService(name: string, provider: (...args: any) => any) {
return this.getInstance('Service', name, provider);
}
loadExternalService(name, provider) {
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
);

View file

@ -17,7 +17,10 @@
* under the License.
*/
export function readProviderSpec(type, providers) {
export type Providers = ReturnType<typeof readProviderSpec>;
export type Provider = Providers extends Array<infer X> ? X : unknown;
export function readProviderSpec(type: string, providers: Record<string, (...args: any[]) => any>) {
return Object.keys(providers).map(name => {
return {
type,

View file

@ -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);
}
return Object.prototype.toString.call(arg);
}).join(', ');
if (Array.isArray(arg)) {
return `[${printArgs(arg)}]`;
}
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,
};
}

View file

@ -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'),
},

View file

@ -1,5 +1,5 @@
{
"extends": "../../../tsconfig.json",
"extends": "../../../../tsconfig.json",
"compilerOptions": {
"outDir": "./target",
"skipLibCheck": true

View file

@ -1,5 +1,5 @@
{
"extends": "../../../tsconfig.json",
"extends": "../../../../tsconfig.json",
"compilerOptions": {
"outDir": "./target",
"skipLibCheck": true

View file

@ -1,5 +1,5 @@
{
"extends": "../../../tsconfig.json",
"extends": "../../../../tsconfig.json",
"compilerOptions": {
"outDir": "./target",
"skipLibCheck": true
@ -11,4 +11,4 @@
"../../../../typings/**/*",
],
"exclude": []
}
}

View file

@ -4,6 +4,9 @@
"types": [
"node",
"mocha"
],
"lib": [
"esnext"
]
},
"include": [