[ts][ftr] improve types for ftr and expect.js, cleanup changes to tsconfig files (#31948) (#32250)

In https://github.com/elastic/kibana/pull/31234 there were some extra changes that I've reverted, like use of the `tsconfig-paths` package to magically rewrite import statements to defy the standard node module resolution algorithm, the inclusion of several unnecessary options in the `test/tsconfig.json` file, and changes of the line-endings in the config files. This also brings a few enhancements from https://github.com/elastic/kibana/pull/30190 including a modularized version of the expect.js types, and options for explicit mappings for the PageObjects and services used in ftr tests.
This commit is contained in:
Spencer 2019-02-28 14:26:32 -08:00 committed by GitHub
parent a7c02d41a0
commit ba82d027cd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
48 changed files with 1365 additions and 768 deletions

View file

@ -401,7 +401,6 @@
"ts-jest": "^23.1.4",
"ts-loader": "^5.2.2",
"ts-node": "^7.0.1",
"tsconfig-paths": "^3.8.0",
"tslint": "^5.11.0",
"tslint-config-prettier": "^1.15.0",
"tslint-microsoft-contrib": "^6.0.0",

View file

@ -1,21 +1,21 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"declaration": true,
"declarationDir": "./target/types",
"outDir": "./target/out",
"stripInternal": true,
"declarationMap": true,
"types": [
"jest",
"node"
]
},
"include": [
"./types/joi.d.ts",
"./src/**/*.ts"
],
"exclude": [
"target"
]
}
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"declaration": true,
"declarationDir": "./target/types",
"outDir": "./target/out",
"stripInternal": true,
"declarationMap": true,
"types": [
"jest",
"node"
]
},
"include": [
"./types/joi.d.ts",
"./src/**/*.ts"
],
"exclude": [
"target"
]
}

View file

@ -0,0 +1,6 @@
{
"extends": "../../tsconfig.json",
"include": [
"types/**/*"
]
}

View file

@ -0,0 +1,6 @@
# @kbn/test/types
Shared types used by different parts of the tests
- **`expect.js.d.ts`**: This is a fork of the expect.js types that have been slightly modified to only expose a module type for `import expect from 'expect.js'` statements. The `@types/expect.js` includes types for the `expect` global, which is useful for some uses of the library but conflicts with the jest types we use. Making the type "module only" prevents them from conflicting.
- **`ftr.d.ts`**: These types are generic types for using the functional test runner. They are here because we plan to move the functional test runner into the `@kbn/test` package at some point and having them here makes them a lot easier to import from all over the place like we do.

225
packages/kbn-test/types/expect.js.d.ts vendored Normal file
View file

@ -0,0 +1,225 @@
// tslint:disable
// Type definitions for expect.js 0.3.1
// Project: https://github.com/Automattic/expect.js
// Definitions by: Teppei Sato <https://github.com/teppeis>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
// License: MIT
declare module 'expect.js' {
function expect(target?: any): Root;
interface Assertion {
/**
* Assert typeof / instanceof.
*/
an: An;
/**
* Check if the value is truthy
*/
ok(): void;
/**
* Creates an anonymous function which calls fn with arguments.
*/
withArgs(...args: any[]): Root;
/**
* Assert that the function throws.
*
* @param fn callback to match error string against
*/
throwError(fn?: (exception: any) => void): void;
/**
* Assert that the function throws.
*
* @param fn callback to match error string against
*/
throwException(fn?: (exception: any) => void): void;
/**
* Assert that the function throws.
*
* @param regexp regexp to match error string against
*/
throwError(regexp: RegExp): void;
/**
* Assert that the function throws.
*
* @param fn callback to match error string against
*/
throwException(regexp: RegExp): void;
/**
* Checks if the array is empty.
*/
empty(): Assertion;
/**
* Checks if the obj exactly equals another.
*/
equal(obj: any): Assertion;
/**
* Checks if the obj sortof equals another.
*/
eql(obj: any): Assertion;
/**
* Assert within start to finish (inclusive).
*
* @param start
* @param finish
*/
within(start: number, finish: number): Assertion;
/**
* Assert typeof.
*/
a(type: string): Assertion;
/**
* Assert instanceof.
*/
a(type: Function): Assertion;
/**
* Assert numeric value above n.
*/
greaterThan(n: number): Assertion;
/**
* Assert numeric value above n.
*/
above(n: number): Assertion;
/**
* Assert numeric value below n.
*/
lessThan(n: number): Assertion;
/**
* Assert numeric value below n.
*/
below(n: number): Assertion;
/**
* Assert string value matches regexp.
*
* @param regexp
*/
match(regexp: RegExp): Assertion;
/**
* Assert property "length" exists and has value of n.
*
* @param n
*/
length(n: number): Assertion;
/**
* Assert property name exists, with optional val.
*
* @param name
* @param val
*/
property(name: string, val?: any): Assertion;
/**
* Assert that string contains str.
*/
contain(str: string): Assertion;
string(str: string): Assertion;
/**
* Assert that the array contains obj.
*/
contain(obj: any): Assertion;
string(obj: any): Assertion;
/**
* Assert exact keys or inclusion of keys by using the `.own` modifier.
*/
key(keys: string[]): Assertion;
/**
* Assert exact keys or inclusion of keys by using the `.own` modifier.
*/
key(...keys: string[]): Assertion;
/**
* Assert exact keys or inclusion of keys by using the `.own` modifier.
*/
keys(keys: string[]): Assertion;
/**
* Assert exact keys or inclusion of keys by using the `.own` modifier.
*/
keys(...keys: string[]): Assertion;
/**
* Assert a failure.
*/
fail(message?: string): Assertion;
}
interface Root extends Assertion {
not: Not;
to: To;
only: Only;
have: Have;
be: Be;
}
interface Be extends Assertion {
/**
* Checks if the obj exactly equals another.
*/
(obj: any): Assertion;
an: An;
}
interface An extends Assertion {
/**
* Assert typeof.
*/
(type: string): Assertion;
/**
* Assert instanceof.
*/
(type: Function): Assertion;
}
interface Not extends NotBase {
to: ToBase;
}
interface NotBase extends Assertion {
be: Be;
have: Have;
include: Assertion;
only: Only;
}
interface To extends ToBase {
not: NotBase;
}
interface ToBase extends Assertion {
be: Be;
have: Have;
include: Assertion;
only: Only;
}
interface Only extends Assertion {
have: Have;
}
interface Have extends Assertion {
own: Assertion;
}
export default expect;
}

80
packages/kbn-test/types/ftr.d.ts vendored Normal file
View file

@ -0,0 +1,80 @@
/*
* 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 { DefaultServiceProviders } from '../../../src/functional_test_runner/types';
interface AsyncInstance<T> {
/**
* Services that are initialized async are not ready before the tests execute, so you might need
* to call `init()` and await the promise it returns before interacting with the service
*/
init(): Promise<T>;
}
/**
* When a provider returns a promise it is initialized as an AsyncInstance that is a
* proxy to the eventual result with an added init() method which returns the eventual
* result. Automatically unwrap these promises and convert them to AsyncInstances + Instance
* types.
*/
type MaybeAsyncInstance<T> = T extends Promise<infer X> ? AsyncInstance<X> & X : T;
/**
* Convert a map of providers to a map of the instance types they provide, also converting
* promise types into the async instances that other providers will receive.
*/
type ProvidedTypeMap<T extends object> = {
[K in keyof T]: T[K] extends (...args: any[]) => any
? MaybeAsyncInstance<ReturnType<T[K]>>
: never
};
export interface GenericFtrProviderContext<
ServiceProviders extends object,
PageObjectProviders extends object,
ServiceMap = ProvidedTypeMap<ServiceProviders & DefaultServiceProviders>,
PageObjectMap = ProvidedTypeMap<PageObjectProviders>
> {
/**
* Determine if a service is avaliable
* @param serviceName
*/
hasService<K extends keyof ServiceMap>(serviceName: K): serviceName is K;
hasService(serviceName: string): serviceName is keyof ServiceMap;
/**
* Get the instance of a service, if the service is loaded async and the service needs to be used
* outside of a test/hook, then make sure to call its `.init()` method and await it's promise.
* @param serviceName
*/
getService<T extends keyof ServiceMap>(serviceName: T): ServiceMap[T];
/**
* Get a map of PageObjects
* @param pageObjects
*/
getPageObjects<K extends keyof PageObjectMap>(pageObjects: K[]): Pick<PageObjectMap, K>;
/**
* Synchronously load a test file, can be called within a `describe()` block to add
* common setup/teardown steps to several suites
* @param path
*/
loadTestFile(path: string): void;
}

View file

@ -25,9 +25,9 @@ import { Project } from './project';
export const PROJECTS = [
new Project(resolve(REPO_ROOT, 'tsconfig.json')),
new Project(resolve(REPO_ROOT, 'test/tsconfig.json'), 'kibana/test'),
new Project(resolve(REPO_ROOT, 'x-pack/tsconfig.json')),
new Project(resolve(REPO_ROOT, 'x-pack/test/tsconfig.json'), 'x-pack/test'),
new Project(resolve(REPO_ROOT, 'test/tsconfig.json')),
// NOTE: using glob.sync rather than glob-all or globby
// because it takes less than 10 ms, while the other modules

39
src/es_archiver/es_archiver.d.ts vendored Normal file
View file

@ -0,0 +1,39 @@
/*
* 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 { Client } from 'elasticsearch';
import { createStats } from './lib/stats';
export type JsonStats = ReturnType<ReturnType<typeof createStats>['toJSON']>;
export class EsArchiver {
constructor(options: { client: Client; dataDir: string; log: ToolingLog; kibanaUrl: string });
public save(
name: string,
indices: string | string[],
options?: { raw?: boolean }
): Promise<JsonStats>;
public load(name: string, options?: { skipExisting?: boolean }): Promise<JsonStats>;
public unload(name: string): Promise<JsonStats>;
public rebuildAll(): Promise<void>;
public edit(prefix: string, handler: () => Promise<void>): Promise<void>;
public loadIfNeeded(name: string): Promise<JsonStats>;
public emptyKibanaIndex(): Promise<JsonStats>;
}

View file

@ -17,7 +17,4 @@
* under the License.
*/
export interface TestWrapper {
getService(service: string): any;
getPageObjects(pages: string[]): { [name: string]: any };
}
export { EsArchiver } from './es_archiver';

View file

@ -1,102 +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 { cloneDeep } from 'lodash';
export function createStats(name, log) {
const info = (msg, ...args) => log.info(`[${name}] ${msg}`, ...args);
const debug = (msg, ...args) => log.debug(`[${name}] ${msg}`, ...args);
const indices = {};
const getOrCreate = index => {
if (!indices[index]) {
indices[index] = {
skipped: false,
deleted: false,
created: false,
archived: false,
waitForSnapshot: 0,
configDocs: {
upgraded: 0,
tagged: 0,
upToDate: 0,
},
docs: {
indexed: 0,
archived: 0,
}
};
}
return indices[index];
};
class Stats {
skippedIndex(index) {
getOrCreate(index).skipped = true;
info('Skipped restore for existing index %j', index);
}
waitingForInProgressSnapshot(index) {
getOrCreate(index).waitForSnapshot += 1;
info('Waiting for snapshot of %j to complete', index);
}
deletedIndex(index) {
getOrCreate(index).deleted = true;
info('Deleted existing index %j', index);
}
createdIndex(index, metadata) {
getOrCreate(index).created = true;
info('Created index %j', index);
Object.keys(metadata || {}).forEach(name => {
debug('%j %s %j', index, name, metadata[name]);
});
}
archivedIndex(index, metadata) {
getOrCreate(index).archived = true;
info('Archived %j', index);
Object.keys(metadata || {}).forEach(name => {
debug('%j %s %j', index, name, metadata[name]);
});
}
indexedDoc(index) {
getOrCreate(index).docs.indexed += 1;
}
archivedDoc(index) {
getOrCreate(index).docs.archived += 1;
}
toJSON() {
return cloneDeep(indices);
}
forEachIndex(fn) {
const clone = this.toJSON();
Object.keys(clone).forEach(index => {
fn(index, clone[index]);
});
}
}
return new Stats();
}

View file

@ -0,0 +1,153 @@
/*
* 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 { cloneDeep } from 'lodash';
export interface IndexStats {
skipped: boolean;
deleted: boolean;
created: boolean;
archived: boolean;
waitForSnapshot: number;
configDocs: {
upgraded: number;
tagged: number;
upToDate: number;
};
docs: {
indexed: number;
archived: number;
};
}
export function createStats(name: string, log: ToolingLog) {
const info = (msg: string, ...args: any[]) => log.info(`[${name}] ${msg}`, ...args);
const debug = (msg: string, ...args: any[]) => log.debug(`[${name}] ${msg}`, ...args);
const indices: Record<string, IndexStats> = {};
const getOrCreate = (index: string) => {
if (!indices[index]) {
indices[index] = {
skipped: false,
deleted: false,
created: false,
archived: false,
waitForSnapshot: 0,
configDocs: {
upgraded: 0,
tagged: 0,
upToDate: 0,
},
docs: {
indexed: 0,
archived: 0,
},
};
}
return indices[index];
};
return new class Stats {
/**
* Record that an index was not restored because it already existed
* @param index
*/
public skippedIndex(index: string) {
getOrCreate(index).skipped = true;
info('Skipped restore for existing index %j', index);
}
/**
* Record that the esArchiver waited for an index that was in the middle of being snapshotted
* @param index
*/
public waitingForInProgressSnapshot(index: string) {
getOrCreate(index).waitForSnapshot += 1;
info('Waiting for snapshot of %j to complete', index);
}
/**
* Record that an index was deleted
* @param index
*/
public deletedIndex(index: string) {
getOrCreate(index).deleted = true;
info('Deleted existing index %j', index);
}
/**
* Record that an index was created
* @param index
*/
public createdIndex(index: string, metadata: Record<string, any> = {}) {
getOrCreate(index).created = true;
info('Created index %j', index);
Object.keys(metadata).forEach(key => {
debug('%j %s %j', index, key, metadata[key]);
});
}
/**
* Record that an index was written to the archives
* @param index
*/
public archivedIndex(index: string, metadata: Record<string, any> = {}) {
getOrCreate(index).archived = true;
info('Archived %j', index);
Object.keys(metadata).forEach(key => {
debug('%j %s %j', index, key, metadata[key]);
});
}
/**
* Record that a document was written to elasticsearch
* @param index
*/
public indexedDoc(index: string) {
getOrCreate(index).docs.indexed += 1;
}
/**
* Record that a document was added to the archives
* @param index
*/
public archivedDoc(index: string) {
getOrCreate(index).docs.archived += 1;
}
/**
* Get a plain object version of the stats by index
*/
public toJSON() {
return cloneDeep(indices);
}
/**
* Iterate the status for each index
* @param fn
*/
public forEachIndex(fn: (index: string, stats: IndexStats) => void) {
const clone = this.toJSON();
Object.keys(clone).forEach(index => {
fn(index, clone[index]);
});
}
}();
}

View file

@ -75,7 +75,6 @@ const functionalTestRunner = createFunctionalTestRunner({
bail: cmd.bail,
grep: cmd.grep,
invert: cmd.invert,
require: `ts-node/register --project tests/tsconfig.json -r tsconfig-paths/register -T "test/**/*.{ts,js}"`
},
suiteTags: {
include: cmd.includeTag,

View file

@ -17,20 +17,27 @@
* under the License.
*/
import { get, has, cloneDeep } from 'lodash';
import { Schema } from 'joi';
import { cloneDeep, get, has } from 'lodash';
// @ts-ignore internal lodash module is not typed
import toPath from 'lodash/internal/toPath';
import { schema } from './schema';
const $values = Symbol('values');
interface Options {
settings?: Record<string, any>;
primary?: boolean;
path: string;
}
export class Config {
constructor(options = {}) {
const {
settings = {},
primary = false,
path,
} = options;
private [$values]: Record<string, any>;
constructor(options: Options) {
const { settings = {}, primary = false, path = null } = options || {};
if (!path) {
throw new TypeError('path is a required option');
@ -41,38 +48,52 @@ export class Config {
context: {
primary: !!primary,
path,
}
},
});
if (error) throw error;
if (error) {
throw error;
}
this[$values] = value;
}
has(key) {
function recursiveHasCheck(path, values, schema) {
if (!schema._inner) return false;
public has(key: string) {
function recursiveHasCheck(
remainingPath: string[],
values: Record<string, any>,
childSchema: any
): boolean {
if (!childSchema._inner) {
return false;
}
// normalize child and pattern checks so we can iterate the checks in a single loop
const checks = [].concat(
const checks: Array<{ test: (k: string) => boolean; schema: Schema }> = [
// match children first, they have priority
(schema._inner.children || []).map(child => ({
test: key => child.key === key,
schema: child.schema
...(childSchema._inner.children || []).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
(schema._inner.patterns || []).map(pattern => ({
test: key => pattern.regex.test(key) && has(values, key),
schema: pattern.rule
}))
);
...(childSchema._inner.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(path[0])) {
if (!check.test(remainingPath[0])) {
continue;
}
if (path.length > 1) {
return recursiveHasCheck(path.slice(1), get(values, path[0]), check.schema);
if (remainingPath.length > 1) {
return recursiveHasCheck(
remainingPath.slice(1),
get(values, remainingPath[0]),
check.schema
);
}
return true;
@ -82,16 +103,18 @@ export class Config {
}
const path = toPath(key);
if (!path.length) return true;
if (!path.length) {
return true;
}
return recursiveHasCheck(path, this[$values], schema);
}
get(key, defaultValue) {
public get(key: string, defaultValue?: any) {
if (!this.has(key)) {
throw new Error(`Unknown config key "${key}"`);
}
return cloneDeep(get(this[$values], key, defaultValue), (v) => {
return cloneDeep(get(this[$values], key, defaultValue), v => {
if (typeof v === 'function') {
return v;
}

View file

@ -1,191 +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, dirname } from 'path';
import Joi from 'joi';
// valid pattern for ID
// enforced camel-case identifiers for consistency
const ID_PATTERN = /^[a-zA-Z0-9_]+$/;
const INSPECTING = (
process.execArgv.includes('--inspect') ||
process.execArgv.includes('--inspect-brk')
);
const urlPartsSchema = () => Joi.object().keys({
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 /')
}).default();
const appUrlPartsSchema = () => Joi.object().keys({
pathname: Joi.string().regex(/^\//, 'start with a /'),
hash: Joi.string().regex(/^\//, 'start with a /')
}).default();
const defaultRelativeToConfigPath = path => {
const makeDefault = (locals, options) => (
resolve(dirname(options.context.path), path)
);
makeDefault.description = `<config.js directory>/${path}`;
return makeDefault;
};
export const schema = Joi.object().keys({
testFiles: Joi.array().items(Joi.string()).when('$primary', {
is: true,
then: Joi.required(),
otherwise: Joi.default([]),
}),
excludeTestFiles: Joi.array().items(Joi.string()).default([]),
suiteTags: Joi.object().keys({
include: Joi.array().items(Joi.string()).default([]),
exclude: Joi.array().items(Joi.string()).default([]),
}).default(),
services: Joi.object().pattern(
ID_PATTERN,
Joi.func().required()
).default(),
pageObjects: Joi.object().pattern(
ID_PATTERN,
Joi.func().required()
).default(),
timeouts: Joi.object().keys({
find: Joi.number().default(10000),
try: Joi.number().default(120000),
waitFor: Joi.number().default(20000),
esRequestTimeout: Joi.number().default(30000),
kibanaStabilize: Joi.number().default(15000),
navigateStatusPageCheck: Joi.number().default(250),
// Many of our tests use the `exists` functions to determine where the user is. For
// example, you'll see a lot of code like:
// if (!testSubjects.exists('someElementOnPageA')) {
// navigateToPageA();
// }
// If the element doesn't exist, selenium would wait up to defaultFindTimeout for it to
// appear. Because there are many times when we expect it to not be there, we don't want
// to wait the full amount of time, or it would greatly slow our tests down. We used to have
// this value at 1 second, but this caused flakiness because sometimes the element was deemed missing
// only because the page hadn't finished loading.
// The best path forward it to prefer functions like `testSubjects.existOrFail` or
// `testSubjects.missingOrFail` instead of just the `exists` checks, and be deterministic about
// where your user is and what they should click next.
waitForExists: Joi.number().default(2500),
}).default(),
mochaOpts: Joi.object().keys({
bail: Joi.boolean().default(false),
grep: Joi.string(),
invert: Joi.boolean().default(false),
slow: Joi.number().default(30000),
timeout: Joi.number().default(INSPECTING ? Infinity : 360000),
ui: Joi.string().default('bdd'),
require: Joi.string().default('')
}).default(),
updateBaselines: Joi.boolean().default(false),
junit: Joi.object().keys({
enabled: Joi.boolean().default(!!process.env.CI),
reportName: Joi.string(),
rootDirectory: Joi.string(),
}).default(),
mochaReporter: Joi.object().keys({
captureLogOutput: Joi.boolean().default(!!process.env.CI),
}).default(),
users: Joi.object().pattern(
ID_PATTERN,
Joi.object().keys({
username: Joi.string().required(),
password: Joi.string().required(),
}).required()
),
servers: Joi.object().keys({
kibana: urlPartsSchema(),
elasticsearch: urlPartsSchema(),
}).default(),
esTestCluster: Joi.object().keys({
license: Joi.string().default('oss'),
from: Joi.string().default('snapshot'),
serverArgs: Joi.array(),
dataArchive: Joi.string(),
}).default(),
kbnTestServer: Joi.object().keys({
buildArgs: Joi.array(),
sourceArgs: Joi.array(),
serverArgs: Joi.array(),
}).default(),
chromedriver: Joi.object().keys({
url: Joi.string().uri({ scheme: /https?/ }).default('http://localhost:9515')
}).default(),
firefoxdriver: Joi.object().keys({
url: Joi.string().uri({ scheme: /https?/ }).default('http://localhost:2828')
}).default(),
// definition of apps that work with `common.navigateToApp()`
apps: Joi.object().pattern(
ID_PATTERN,
appUrlPartsSchema()
).default(),
// settings for the esArchiver module
esArchiver: Joi.object().keys({
directory: Joi.string().default(defaultRelativeToConfigPath('fixtures/es_archiver')),
}).default(),
// settings for the kibanaServer.uiSettings module
uiSettings: Joi.object().keys({
defaults: Joi.object().unknown(true)
}).default(),
// settings for the screenshots module
screenshots: Joi.object().keys({
directory: Joi.string().default(defaultRelativeToConfigPath('screenshots'))
}).default(),
// settings for the failureDebugging module
failureDebugging: Joi.object().keys({
htmlDirectory: Joi.string().default(defaultRelativeToConfigPath('failure_debug/html'))
}).default(),
// settings for the find service
layout: Joi.object().keys({
fixedHeaderHeight: Joi.number().default(50),
}).default(),
}).default();

View file

@ -0,0 +1,238 @@
/*
* 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 { dirname, resolve } from 'path';
import Joi from 'joi';
// valid pattern for ID
// enforced camel-case identifiers for consistency
const ID_PATTERN = /^[a-zA-Z0-9_]+$/;
const INSPECTING =
process.execArgv.includes('--inspect') || process.execArgv.includes('--inspect-brk');
const urlPartsSchema = () =>
Joi.object()
.keys({
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 /'),
})
.default();
const appUrlPartsSchema = () =>
Joi.object()
.keys({
pathname: Joi.string().regex(/^\//, 'start with a /'),
hash: Joi.string().regex(/^\//, 'start with a /'),
})
.default();
const defaultRelativeToConfigPath = (path: string) => {
const makeDefault: any = (_: any, options: any) => resolve(dirname(options.context.path), path);
makeDefault.description = `<config.js directory>/${path}`;
return makeDefault;
};
export const schema = Joi.object()
.keys({
testFiles: Joi.array()
.items(Joi.string())
.when('$primary', {
is: true,
then: Joi.required(),
otherwise: Joi.any().default([]),
}),
excludeTestFiles: Joi.array()
.items(Joi.string())
.default([]),
suiteTags: Joi.object()
.keys({
include: Joi.array()
.items(Joi.string())
.default([]),
exclude: Joi.array()
.items(Joi.string())
.default([]),
})
.default(),
services: Joi.object()
.pattern(ID_PATTERN, Joi.func().required())
.default(),
pageObjects: Joi.object()
.pattern(ID_PATTERN, Joi.func().required())
.default(),
timeouts: Joi.object()
.keys({
find: Joi.number().default(10000),
try: Joi.number().default(120000),
waitFor: Joi.number().default(20000),
esRequestTimeout: Joi.number().default(30000),
kibanaStabilize: Joi.number().default(15000),
navigateStatusPageCheck: Joi.number().default(250),
// Many of our tests use the `exists` functions to determine where the user is. For
// example, you'll see a lot of code like:
// if (!testSubjects.exists('someElementOnPageA')) {
// navigateToPageA();
// }
// If the element doesn't exist, selenium would wait up to defaultFindTimeout for it to
// appear. Because there are many times when we expect it to not be there, we don't want
// to wait the full amount of time, or it would greatly slow our tests down. We used to have
// this value at 1 second, but this caused flakiness because sometimes the element was deemed missing
// only because the page hadn't finished loading.
// The best path forward it to prefer functions like `testSubjects.existOrFail` or
// `testSubjects.missingOrFail` instead of just the `exists` checks, and be deterministic about
// where your user is and what they should click next.
waitForExists: Joi.number().default(2500),
})
.default(),
mochaOpts: Joi.object()
.keys({
bail: Joi.boolean().default(false),
grep: Joi.string(),
invert: Joi.boolean().default(false),
slow: Joi.number().default(30000),
timeout: Joi.number().default(INSPECTING ? Infinity : 360000),
ui: Joi.string().default('bdd'),
})
.default(),
updateBaselines: Joi.boolean().default(false),
junit: Joi.object()
.keys({
enabled: Joi.boolean().default(!!process.env.CI),
reportName: Joi.string(),
rootDirectory: Joi.string(),
})
.default(),
mochaReporter: Joi.object()
.keys({
captureLogOutput: Joi.boolean().default(!!process.env.CI),
})
.default(),
users: Joi.object().pattern(
ID_PATTERN,
Joi.object()
.keys({
username: Joi.string().required(),
password: Joi.string().required(),
})
.required()
),
servers: Joi.object()
.keys({
kibana: urlPartsSchema(),
elasticsearch: urlPartsSchema(),
})
.default(),
esTestCluster: Joi.object()
.keys({
license: Joi.string().default('oss'),
from: Joi.string().default('snapshot'),
serverArgs: Joi.array(),
dataArchive: Joi.string(),
})
.default(),
kbnTestServer: Joi.object()
.keys({
buildArgs: Joi.array(),
sourceArgs: Joi.array(),
serverArgs: Joi.array(),
})
.default(),
chromedriver: Joi.object()
.keys({
url: Joi.string()
.uri({ scheme: /https?/ })
.default('http://localhost:9515'),
})
.default(),
firefoxdriver: Joi.object()
.keys({
url: Joi.string()
.uri({ scheme: /https?/ })
.default('http://localhost:2828'),
})
.default(),
// definition of apps that work with `common.navigateToApp()`
apps: Joi.object()
.pattern(ID_PATTERN, appUrlPartsSchema())
.default(),
// settings for the esArchiver module
esArchiver: Joi.object()
.keys({
directory: Joi.string().default(defaultRelativeToConfigPath('fixtures/es_archiver')),
})
.default(),
// settings for the kibanaServer.uiSettings module
uiSettings: Joi.object()
.keys({
defaults: Joi.object().unknown(true),
})
.default(),
// settings for the screenshots module
screenshots: Joi.object()
.keys({
directory: Joi.string().default(defaultRelativeToConfigPath('screenshots')),
})
.default(),
// settings for the failureDebugging module
failureDebugging: Joi.object()
.keys({
htmlDirectory: Joi.string().default(defaultRelativeToConfigPath('failure_debug/html')),
})
.default(),
// settings for the find service
layout: Joi.object()
.keys({
fixedHeaderHeight: Joi.number().default(50),
})
.default(),
})
.default();

View file

@ -17,7 +17,5 @@
* under the License.
*/
export { KibanaServerProvider } from './kibana_server';
export { EsProvider } from './es';
export { EsArchiverProvider } from './es_archiver';
export { RetryProvider } from './retry';
export { Config } from './config/config';
export { Lifecycle } from './lifecycle';

View file

@ -17,31 +17,34 @@
* under the License.
*/
type Listener = (...args: any[]) => Promise<void> | void;
export type Lifecycle = ReturnType<typeof createLifecycle>;
export function createLifecycle() {
const listeners = {
beforeLoadTests: [],
beforeTests: [],
beforeTestSuite: [],
beforeEachTest: [],
afterTestSuite: [],
testFailure: [],
testHookFailure: [],
cleanup: [],
phaseStart: [],
phaseEnd: [],
beforeLoadTests: [] as Listener[],
beforeTests: [] as Listener[],
beforeTestSuite: [] as Listener[],
beforeEachTest: [] as Listener[],
afterTestSuite: [] as Listener[],
testFailure: [] as Listener[],
testHookFailure: [] as Listener[],
cleanup: [] as Listener[],
phaseStart: [] as Listener[],
phaseEnd: [] as Listener[],
};
class Lifecycle {
on(name, fn) {
return {
on(name: keyof typeof listeners, fn: Listener) {
if (!listeners[name]) {
throw new TypeError(`invalid lifecycle event "${name}"`);
}
listeners[name].push(fn);
return this;
}
},
async trigger(name, ...args) {
async trigger(name: keyof typeof listeners, ...args: any[]) {
if (!listeners[name]) {
throw new TypeError(`invalid lifecycle event "${name}"`);
}
@ -51,16 +54,12 @@ export function createLifecycle() {
await this.trigger('phaseStart', name);
}
await Promise.all(listeners[name].map(
async fn => await fn(...args)
));
await Promise.all(listeners[name].map(async fn => await fn(...args)));
} finally {
if (name !== 'phaseStart' && name !== 'phaseEnd') {
await this.trigger('phaseEnd', name);
}
}
}
}
return new Lifecycle();
},
};
}

View file

@ -0,0 +1,27 @@
/*
* 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 { Config, Lifecycle } from './lib';
export interface DefaultServiceProviders {
config(): Config;
log(): ToolingLog;
lifecycle(): Lifecycle;
}

View file

@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
// @ts-ignore
import expect from 'expect.js';
import { CidrMask } from '../cidr_mask';

View file

@ -19,12 +19,7 @@
import { format as formatUrl } from 'url';
import { OPTIMIZE_BUNDLE_DIR, esTestConfig, kbnTestConfig } from '@kbn/test';
import {
KibanaServerProvider,
EsProvider,
EsArchiverProvider,
RetryProvider,
} from './services';
import { services } from './services';
export default function () {
const servers = {
@ -64,11 +59,6 @@ export default function () {
],
},
services: {
kibanaServer: KibanaServerProvider,
retry: RetryProvider,
es: EsProvider,
esArchiver: EsArchiverProvider,
}
services
};
}

24
test/common/ftr_provider_context.d.ts vendored Normal file
View file

@ -0,0 +1,24 @@
/*
* 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 { GenericFtrProviderContext } from '@kbn/test/types/ftr';
import { services } from './services';
export type FtrProviderContext = GenericFtrProviderContext<typeof services, {}>;

View file

@ -22,8 +22,9 @@ import { format as formatUrl } from 'url';
import elasticsearch from 'elasticsearch';
import { DEFAULT_API_VERSION } from '../../../src/core/server/elasticsearch/elasticsearch_config';
import { FtrProviderContext } from '../ftr_provider_context';
export function EsProvider({ getService }) {
export function EsProvider({ getService }: FtrProviderContext): elasticsearch.Client {
const config = getService('config');
return new elasticsearch.Client({

View file

@ -18,11 +18,13 @@
*/
import { format as formatUrl } from 'url';
import { FtrProviderContext } from '../ftr_provider_context';
import { EsArchiver } from '../../../src/es_archiver';
// @ts-ignore not TS yet
import * as KibanaServer from './kibana_server';
export function EsArchiverProvider({ getService, hasService }) {
export function EsArchiverProvider({ getService, hasService }: FtrProviderContext): EsArchiver {
const config = getService('config');
const client = getService('es');
const log = getService('log');
@ -37,7 +39,7 @@ export function EsArchiverProvider({ getService, hasService }) {
client,
dataDir,
log,
kibanaUrl: formatUrl(config.get('servers.kibana'))
kibanaUrl: formatUrl(config.get('servers.kibana')),
});
if (hasService('kibanaServer')) {

View file

@ -0,0 +1,31 @@
/*
* 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 { EsProvider } from './es';
import { EsArchiverProvider } from './es_archiver';
// @ts-ignore not TS yet
import { KibanaServerProvider } from './kibana_server';
import { RetryProvider } from './retry';
export const services = {
es: EsProvider,
esArchiver: EsArchiverProvider,
kibanaServer: KibanaServerProvider,
retry: RetryProvider,
};

View file

@ -17,56 +17,51 @@
* under the License.
*/
import { retryForTruthy } from './retry_for_truthy';
import { FtrProviderContext } from '../../ftr_provider_context';
import { retryForSuccess } from './retry_for_success';
import { retryForTruthy } from './retry_for_truthy';
export function RetryProvider({ getService }) {
export function RetryProvider({ getService }: FtrProviderContext) {
const config = getService('config');
const log = getService('log');
return new class Retry {
async tryForTime(timeout, block) {
public async tryForTime<T>(timeout: number, block: () => Promise<T>) {
return await retryForSuccess(log, {
timeout,
methodName: 'retry.tryForTime',
block
block,
});
}
async try(block) {
public async try<T>(block: () => Promise<T>) {
return await retryForSuccess(log, {
timeout: config.get('timeouts.try'),
methodName: 'retry.try',
block
block,
});
}
async tryMethod(object, method, ...args) {
return await retryForSuccess(log, {
timeout: config.get('timeouts.try'),
methodName: 'retry.tryMethod',
block: async () => (
await object[method](...args)
)
});
}
async waitForWithTimeout(description, timeout, block) {
public async waitForWithTimeout(
description: string,
timeout: number,
block: () => Promise<boolean>
) {
await retryForTruthy(log, {
timeout,
methodName: 'retry.waitForWithTimeout',
description,
block
block,
});
}
async waitFor(description, block) {
public async waitFor(description: string, block: () => Promise<boolean>) {
await retryForTruthy(log, {
timeout: config.get('timeouts.waitFor'),
methodName: 'retry.waitFor',
description,
block
block,
});
}
};
}();
}

View file

@ -17,15 +17,14 @@
* under the License.
*/
import { ToolingLog } from '@kbn/dev-utils';
import { inspect } from 'util';
const delay = ms => new Promise(resolve => (
setTimeout(resolve, ms)
));
const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
const returnTrue = () => true;
const defaultOnFailure = (methodName) => (lastError) => {
const defaultOnFailure = (methodName: string) => (lastError: Error) => {
throw new Error(`${methodName} timeout: ${lastError.stack || lastError.message}`);
};
@ -33,51 +32,56 @@ const defaultOnFailure = (methodName) => (lastError) => {
* Run a function and return either an error or result
* @param {Function} block
*/
async function runAttempt(block) {
async function runAttempt<T>(block: () => Promise<T>): Promise<{ result: T } | { error: Error }> {
try {
return {
result: await block()
result: await block(),
};
} catch (error) {
return {
// we rely on error being truthy and throwing falsy values is *allowed*
// so we cast falsy values to errors
error: error || new Error(`${inspect(error)} thrown`),
error: error instanceof Error ? error : new Error(`${inspect(error)} thrown`),
};
}
}
export async function retryForSuccess(log, {
timeout,
methodName,
block,
onFailure = defaultOnFailure(methodName),
accept = returnTrue
}) {
interface Options<T> {
timeout: number;
methodName: string;
block: () => Promise<T>;
onFailure?: ReturnType<typeof defaultOnFailure>;
accept?: (v: T) => boolean;
}
export async function retryForSuccess<T>(log: ToolingLog, options: Options<T>) {
const { timeout, methodName, block, accept = returnTrue } = options;
const { onFailure = defaultOnFailure(methodName) } = options;
const start = Date.now();
const retryDelay = 502;
let lastError;
while (true) {
if (Date.now() - start > timeout) {
if (lastError && Date.now() - start > timeout) {
await onFailure(lastError);
throw new Error('expected onFailure() option to throw an error');
}
const { result, error } = await runAttempt(block);
const attempt = await runAttempt(block);
if (!error && accept(result)) {
return result;
if ('result' in attempt && accept(attempt.result)) {
return attempt.result;
}
if (error) {
if (lastError && lastError.message === error.message) {
if ('error' in attempt) {
if (lastError && lastError.message === attempt.error.message) {
log.debug(`--- ${methodName} failed again with the same message...`);
} else {
log.debug(`--- ${methodName} error: ${error.message}`);
log.debug(`--- ${methodName} error: ${attempt.error.message}`);
}
lastError = error;
lastError = attempt.error;
}
await delay(retryDelay);

View file

@ -17,33 +17,36 @@
* under the License.
*/
import { ToolingLog } from '@kbn/dev-utils';
import { retryForSuccess } from './retry_for_success';
export async function retryForTruthy(log, {
timeout,
methodName,
description,
block
}) {
interface Options {
timeout: number;
methodName: string;
description: string;
block: () => Promise<boolean>;
}
export async function retryForTruthy(
log: ToolingLog,
{ timeout, methodName, description, block }: Options
) {
log.debug(`Waiting up to ${timeout}ms for ${description}...`);
const accept = result => Boolean(result);
const onFailure = lastError => {
let msg = `timed out waiting for ${description}`;
if (lastError) {
msg = `${msg} -- last error: ${lastError.stack || lastError.message}`;
}
throw new Error(msg);
};
await retryForSuccess(log, {
timeout,
methodName,
block,
onFailure,
accept
onFailure: lastError => {
let msg = `timed out waiting for ${description}`;
if (lastError) {
msg = `${msg} -- last error: ${lastError.stack || lastError.message}`;
}
throw new Error(msg);
},
accept: result => Boolean(result),
});
}

View file

@ -18,6 +18,7 @@
*/
import expect from 'expect.js';
import { FtrProviderContext } from '../../ftr_provider_context';
const DEFAULT_REQUEST = `
@ -30,38 +31,39 @@ GET _search
`.trim();
export default function ({ getService, getPageObjects }) {
// tslint:disable-next-line no-default-export
export default function({ getService, getPageObjects }: FtrProviderContext) {
const retry = getService('retry');
const log = getService('log');
const PageObjects = getPageObjects(['common', 'console']);
describe('console app', function describeIndexTests() {
before(async function () {
before(async () => {
log.debug('navigateTo console');
await PageObjects.common.navigateToApp('console');
});
it('should show the default request', async function () {
it('should show the default request', async () => {
// collapse the help pane because we only get the VISIBLE TEXT, not the part that is scrolled
await PageObjects.console.collapseHelp();
await retry.try(async function () {
await retry.try(async () => {
const actualRequest = await PageObjects.console.getRequest();
log.debug(actualRequest);
expect(actualRequest.trim()).to.eql(DEFAULT_REQUEST);
});
});
it('default request response should include `"timed_out" : false`', async function () {
it('default request response should include `"timed_out" : false`', async () => {
const expectedResponseContains = '"timed_out" : false,';
await PageObjects.console.clickPlay();
await retry.try(async function () {
await retry.try(async () => {
const actualResponse = await PageObjects.console.getResponse();
log.debug(actualResponse);
expect(actualResponse).to.contain(expectedResponseContains);
});
});
it('settings should allow changing the text size', async function () {
it('settings should allow changing the text size', async () => {
await PageObjects.console.setFontSizeSetting(20);
await retry.try(async () => {
// the settings are not applied synchronously, so we retry for a time

View file

@ -17,12 +17,11 @@
* under the License.
*/
// @ts-ignore
import expect from 'expect.js';
import { TestWrapper } from 'typings';
import { FtrProviderContext } from '../../ftr_provider_context';
// tslint:disable-next-line:no-default-export
export default function({ getService, getPageObjects }: TestWrapper) {
export default function({ getService, getPageObjects }: FtrProviderContext) {
const esArchiver = getService('esArchiver');
const log = getService('log');
const inspector = getService('inspector');

View file

@ -17,50 +17,9 @@
* under the License.
*/
import {
CommonPageProvider,
ConsolePageProvider,
ShieldPageProvider,
ContextPageProvider,
DiscoverPageProvider,
HeaderPageProvider,
HomePageProvider,
DashboardPageProvider,
VisualizePageProvider,
SettingsPageProvider,
MonitoringPageProvider,
PointSeriesPageProvider,
VisualBuilderPageProvider,
TimelionPageProvider,
SharePageProvider,
TimePickerPageProvider,
} from './page_objects';
import {
RemoteProvider,
FilterBarProvider,
QueryBarProvider,
FindProvider,
TestSubjectsProvider,
DocTableProvider,
ScreenshotsProvider,
DashboardVisualizationProvider,
DashboardExpectProvider,
FailureDebuggingProvider,
VisualizeListingTableProvider,
DashboardAddPanelProvider,
DashboardPanelActionsProvider,
FlyoutProvider,
ComboBoxProvider,
EmbeddingProvider,
RenderableProvider,
TableProvider,
BrowserProvider,
InspectorProvider,
PieChartProvider,
AppsMenuProvider,
GlobalNavProvider,
} from './services';
import { pageObjects } from './page_objects';
import { services } from './services';
import { services as commonServiceProviders } from '../common/services';
export default async function ({ readConfigFile }) {
const commonConfig = await readConfigFile(require.resolve('../common/config'));
@ -79,52 +38,10 @@ export default async function ({ readConfigFile }) {
require.resolve('./apps/visualize'),
require.resolve('./apps/xpack'),
],
pageObjects: {
common: CommonPageProvider,
console: ConsolePageProvider,
shield: ShieldPageProvider,
context: ContextPageProvider,
discover: DiscoverPageProvider,
header: HeaderPageProvider,
home: HomePageProvider,
dashboard: DashboardPageProvider,
visualize: VisualizePageProvider,
settings: SettingsPageProvider,
monitoring: MonitoringPageProvider,
pointSeries: PointSeriesPageProvider,
visualBuilder: VisualBuilderPageProvider,
timelion: TimelionPageProvider,
share: SharePageProvider,
timePicker: TimePickerPageProvider,
},
pageObjects,
services: {
es: commonConfig.get('services.es'),
esArchiver: commonConfig.get('services.esArchiver'),
kibanaServer: commonConfig.get('services.kibanaServer'),
retry: commonConfig.get('services.retry'),
__leadfoot__: RemoteProvider,
filterBar: FilterBarProvider,
queryBar: QueryBarProvider,
find: FindProvider,
testSubjects: TestSubjectsProvider,
docTable: DocTableProvider,
screenshots: ScreenshotsProvider,
dashboardVisualizations: DashboardVisualizationProvider,
dashboardExpect: DashboardExpectProvider,
failureDebugging: FailureDebuggingProvider,
visualizeListingTable: VisualizeListingTableProvider,
dashboardAddPanel: DashboardAddPanelProvider,
dashboardPanelActions: DashboardPanelActionsProvider,
flyout: FlyoutProvider,
comboBox: ComboBoxProvider,
embedding: EmbeddingProvider,
renderable: RenderableProvider,
table: TableProvider,
browser: BrowserProvider,
pieChart: PieChartProvider,
inspector: InspectorProvider,
appsMenu: AppsMenuProvider,
globalNav: GlobalNavProvider,
...commonServiceProviders,
...services
},
servers: commonConfig.get('servers'),

View file

@ -0,0 +1,29 @@
/*
* 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 { GenericFtrProviderContext } from '@kbn/test/types/ftr';
import { services as CommonServiceProviders } from '../common/services';
import { pageObjects as FunctionalPageObjectProviders } from './page_objects';
import { services as FunctionalServiceProviders } from './services';
type ServiceProviders = typeof CommonServiceProviders & typeof FunctionalServiceProviders;
type PageObjectProviders = typeof FunctionalPageObjectProviders;
export type FtrProviderContext = GenericFtrProviderContext<ServiceProviders, PageObjectProviders>;

View file

@ -1,35 +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 { ConsolePageProvider } from './console_page';
export { CommonPageProvider } from './common_page';
export { ShieldPageProvider } from './shield_page';
export { ContextPageProvider } from './context_page';
export { DiscoverPageProvider } from './discover_page';
export { HeaderPageProvider } from './header_page';
export { HomePageProvider } from './home_page';
export { DashboardPageProvider } from './dashboard_page';
export { VisualizePageProvider } from './visualize_page';
export { SettingsPageProvider } from './settings_page';
export { MonitoringPageProvider } from './monitoring_page';
export { PointSeriesPageProvider } from './point_series_page';
export { VisualBuilderPageProvider } from './visual_builder_page';
export { TimelionPageProvider } from './timelion_page';
export { SharePageProvider } from './share_page';
export { TimePickerPageProvider } from './time_picker';

View file

@ -0,0 +1,70 @@
/*
* 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.
*/
// @ts-ignore not TS yet
import { CommonPageProvider } from './common_page';
// @ts-ignore not TS yet
import { ConsolePageProvider } from './console_page';
// @ts-ignore not TS yet
import { ContextPageProvider } from './context_page';
// @ts-ignore not TS yet
import { DashboardPageProvider } from './dashboard_page';
// @ts-ignore not TS yet
import { DiscoverPageProvider } from './discover_page';
// @ts-ignore not TS yet
import { HeaderPageProvider } from './header_page';
// @ts-ignore not TS yet
import { HomePageProvider } from './home_page';
// @ts-ignore not TS yet
import { MonitoringPageProvider } from './monitoring_page';
// @ts-ignore not TS yet
import { PointSeriesPageProvider } from './point_series_page';
// @ts-ignore not TS yet
import { SettingsPageProvider } from './settings_page';
// @ts-ignore not TS yet
import { SharePageProvider } from './share_page';
// @ts-ignore not TS yet
import { ShieldPageProvider } from './shield_page';
// @ts-ignore not TS yet
import { TimePickerPageProvider } from './time_picker';
// @ts-ignore not TS yet
import { TimelionPageProvider } from './timelion_page';
// @ts-ignore not TS yet
import { VisualBuilderPageProvider } from './visual_builder_page';
// @ts-ignore not TS yet
import { VisualizePageProvider } from './visualize_page';
export const pageObjects = {
common: CommonPageProvider,
console: ConsolePageProvider,
context: ContextPageProvider,
dashboard: DashboardPageProvider,
discover: DiscoverPageProvider,
header: HeaderPageProvider,
home: HomePageProvider,
monitoring: MonitoringPageProvider,
pointSeries: PointSeriesPageProvider,
settings: SettingsPageProvider,
share: SharePageProvider,
shield: ShieldPageProvider,
timelion: TimelionPageProvider,
timePicker: TimePickerPageProvider,
visualBuilder: VisualBuilderPageProvider,
visualize: VisualizePageProvider,
};

View file

@ -1,72 +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 function AppsMenuProvider({ getService }) {
const testSubjects = getService('testSubjects');
const log = getService('log');
const retry = getService('retry');
const globalNav = getService('globalNav');
return new class AppsMenu {
async readLinks() {
await this._ensureMenuOpen();
const buttons = await testSubjects.findAll('appsMenu appLink');
try {
return Promise.all(buttons.map(async (element) => ({
text: await element.getVisibleText(),
href: await element.getProperty('href'),
})));
} finally {
await this._ensureMenuClosed();
}
}
async linkExists(name) {
return (await this.readLinks()).some(nl => nl.text === name);
}
async clickLink(appTitle) {
try {
log.debug(`click "${appTitle}" tab`);
await this._ensureMenuOpen();
const container = await testSubjects.find('appsMenu');
const link = await container.findByPartialLinkText(appTitle);
await link.click();
} finally {
await this._ensureMenuClosed();
}
}
async _ensureMenuOpen() {
if (!await testSubjects.exists('navDrawer&expanded')) {
await testSubjects.moveMouseTo('navDrawer');
await retry.waitFor('apps drawer open', async () => (
await testSubjects.exists('navDrawer&expanded')
));
}
}
async _ensureMenuClosed() {
await globalNav.moveMouseToLogo();
await retry.waitFor('apps drawer closed', async () => (
await testSubjects.exists('navDrawer&collapsed')
));
}
};
}

View file

@ -0,0 +1,95 @@
/*
* 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 { FtrProviderContext } from '../ftr_provider_context';
export function AppsMenuProvider({ getService }: FtrProviderContext) {
const testSubjects = getService('testSubjects');
const log = getService('log');
const retry = getService('retry');
const globalNav = getService('globalNav');
return new class AppsMenu {
/**
* Get the text and href from each of the links in the apps menu
*/
public async readLinks() {
await this.ensureMenuOpen();
const appMenu = await testSubjects.find('navDrawer&expanded appsMenu');
const $ = await appMenu.parseDomContent();
const links: Array<{
text: string;
href: string;
}> = $.findTestSubjects('appLink')
.toArray()
.map((link: any) => {
return {
text: $(link).text(),
href: $(link).attr('href'),
};
});
await this.ensureMenuClosed();
return links;
}
/**
* Determine if an app link with the given name exists
* @param name
*/
public async linkExists(name: string) {
return (await this.readLinks()).some(nl => nl.text === name);
}
/**
* Click the app link within the app menu that has the given name
* @param name
*/
public async clickLink(name: string) {
try {
log.debug(`click "${name}" app link`);
await this.ensureMenuOpen();
const container = await testSubjects.find('navDrawer&expanded appsMenu');
const link = await container.findByPartialLinkText(name);
await link.click();
} finally {
await this.ensureMenuClosed();
}
}
private async ensureMenuClosed() {
await globalNav.moveMouseToLogo();
await retry.waitFor(
'apps drawer closed',
async () => await testSubjects.exists('navDrawer&collapsed')
);
}
private async ensureMenuOpen() {
if (!(await testSubjects.exists('navDrawer&expanded'))) {
await testSubjects.moveMouseTo('navDrawer');
await retry.waitFor(
'apps drawer open',
async () => await testSubjects.exists('navDrawer&expanded')
);
}
}
}();
}

View file

@ -1,40 +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 { QueryBarProvider } from './query_bar';
export { FilterBarProvider } from './filter_bar';
export { FindProvider } from './find';
export { TestSubjectsProvider } from './test_subjects';
export { RemoteProvider } from './remote';
export { DocTableProvider } from './doc_table';
export { ScreenshotsProvider } from './screenshots';
export { FailureDebuggingProvider } from './failure_debugging';
export { VisualizeListingTableProvider } from './visualize_listing_table';
export { FlyoutProvider } from './flyout';
export { EmbeddingProvider } from './embedding';
export { ComboBoxProvider } from './combo_box';
export { RenderableProvider } from './renderable';
export { TableProvider } from './table';
export { BrowserProvider } from './browser';
export { InspectorProvider } from './inspector';
export { AppsMenuProvider } from './apps_menu';
export { GlobalNavProvider } from './global_nav';
export * from './visualizations';
export * from './dashboard';

View file

@ -0,0 +1,89 @@
/*
* 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 { AppsMenuProvider } from './apps_menu';
// @ts-ignore not TS yet
import { BrowserProvider } from './browser';
// @ts-ignore not TS yet
import { ComboBoxProvider } from './combo_box';
import {
DashboardAddPanelProvider,
DashboardExpectProvider,
DashboardPanelActionsProvider,
DashboardVisualizationProvider,
// @ts-ignore not TS yet
} from './dashboard';
// @ts-ignore not TS yet
import { DocTableProvider } from './doc_table';
// @ts-ignore not TS yet
import { EmbeddingProvider } from './embedding';
// @ts-ignore not TS yet
import { FailureDebuggingProvider } from './failure_debugging';
// @ts-ignore not TS yet
import { FilterBarProvider } from './filter_bar';
// @ts-ignore not TS yet
import { FindProvider } from './find';
// @ts-ignore not TS yet
import { FlyoutProvider } from './flyout';
// @ts-ignore not TS yet
import { GlobalNavProvider } from './global_nav';
// @ts-ignore not TS yet
import { InspectorProvider } from './inspector';
// @ts-ignore not TS yet
import { QueryBarProvider } from './query_bar';
// @ts-ignore not TS yet
import { RemoteProvider } from './remote';
// @ts-ignore not TS yet
import { RenderableProvider } from './renderable';
// @ts-ignore not TS yet
import { ScreenshotsProvider } from './screenshots';
// @ts-ignore not TS yet
import { TableProvider } from './table';
// @ts-ignore not TS yet
import { TestSubjectsProvider } from './test_subjects';
// @ts-ignore not TS yet
import { PieChartProvider } from './visualizations';
// @ts-ignore not TS yet
import { VisualizeListingTableProvider } from './visualize_listing_table';
export const services = {
__leadfoot__: RemoteProvider,
filterBar: FilterBarProvider,
queryBar: QueryBarProvider,
find: FindProvider,
testSubjects: TestSubjectsProvider,
docTable: DocTableProvider,
screenshots: ScreenshotsProvider,
dashboardVisualizations: DashboardVisualizationProvider,
dashboardExpect: DashboardExpectProvider,
failureDebugging: FailureDebuggingProvider,
visualizeListingTable: VisualizeListingTableProvider,
dashboardAddPanel: DashboardAddPanelProvider,
dashboardPanelActions: DashboardPanelActionsProvider,
flyout: FlyoutProvider,
comboBox: ComboBoxProvider,
embedding: EmbeddingProvider,
renderable: RenderableProvider,
table: TableProvider,
browser: BrowserProvider,
pieChart: PieChartProvider,
inspector: InspectorProvider,
appsMenu: AppsMenuProvider,
globalNav: GlobalNavProvider,
};

View file

@ -1,18 +1,10 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"module": "commonjs",
"allowJs": true,
"outDir": "target",
"baseUrl": "./",
"paths": {
"*":[
"*"
]
},
"types": [
"node",
"mocha"
"mocha",
"@kbn/test/types/expect.js"
]
},
"include": [

View file

@ -17,4 +17,4 @@
* under the License.
*/
export * from './wrapper';
export * from './mocha_decorations';

30
test/types/mocha_decorations.d.ts vendored Normal file
View file

@ -0,0 +1,30 @@
/*
* 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 { Suite } from 'mocha';
// tslint:disable-next-line:no-namespace We need to use the namespace here to match the Mocha definition
declare module 'mocha' {
interface Suite {
/**
* Assign tags to the test suite to determine in which CI job it should be run.
*/
tags(tags: string[] | string): void;
}
}

View file

@ -47,7 +47,13 @@
"downlevelIteration": true,
// import tslib helpers rather than inlining helpers for iteration or spreading, for instance
"importHelpers": true,
"skipLibCheck": true
// adding global typings
"types": [
"node",
"jest",
"react",
"@kbn/test/types/expect.js"
]
},
"include": [
"kibana.d.ts",
@ -56,7 +62,7 @@
"test_utils/**/*"
],
"exclude": [
"src/**/__fixtures__/**/*",
"src/**/__fixtures__/**/*"
// In the build we actually exclude **/public/**/* from this config so that
// we can run the TSC on both this and the .browser version of this config
// file, but if we did it during development IDEs would not be able to find

View file

@ -1,17 +1,13 @@
{
"extends": "./tsconfig",
"compilerOptions": {
"declaration": true,
"declarationDir": "./target/types",
"stripInternal": true,
"emitDeclarationOnly": true,
"declarationMap": true,
"types": [
"node",
"jest"
]
},
"include": [
"./src/type_exports.ts"
]
}
{
"extends": "./tsconfig",
"compilerOptions": {
"declaration": true,
"declarationDir": "./target/types",
"stripInternal": true,
"emitDeclarationOnly": true,
"declarationMap": true
},
"include": [
"./src/type_exports.ts"
]
}

View file

@ -30,13 +30,13 @@
"@kbn/plugin-helpers": "9.0.2",
"@kbn/test": "1.0.0",
"@types/angular": "1.6.50",
"@types/cheerio": "^0.22.10",
"@types/d3-array": "^1.2.1",
"@types/d3-scale": "^2.0.0",
"@types/d3-shape": "^1.2.2",
"@types/d3-shape": "^1.3.1",
"@types/d3-time": "^1.0.7",
"@types/d3-time-format": "^2.1.0",
"@types/elasticsearch": "^5.0.30",
"@types/expect.js": "^0.3.29",
"@types/graphql": "^0.13.1",
"@types/history": "^4.6.2",
"@types/jest": "^24.0.9",

View file

@ -2,13 +2,13 @@
"extends": "../tsconfig.json",
"compilerOptions": {
"types": [
"expect.js",
"@kbn/test/types/expect.js",
"mocha",
"node"
]
},
"include": [
"**/*",
"**/*"
],
"exclude": [],
}

View file

@ -4,14 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
export interface EsArchiverOptions {
skipExisting?: boolean;
}
export interface EsArchiver {
load(archiveName: string, options?: EsArchiverOptions): Promise<void>;
unload(archiveName: string): Promise<void>;
}
import { EsArchiver } from '../../../src/es_archiver';
export interface KibanaFunctionalTestDefaultProviders {
getService(serviceName: 'esArchiver'): EsArchiver;

View file

@ -30,7 +30,8 @@
},
"types": [
"node",
"jest"
"jest",
"@kbn/test/types/expect.js"
]
}
}

View file

@ -1146,7 +1146,7 @@
resolved "https://registry.yarnpkg.com/@types/chance/-/chance-1.0.1.tgz#c10703020369602c40dd9428cc6e1437027116df"
integrity sha512-jtV6Bv/j+xk4gcXeLlESwNc/m/I/dIZA0xrt29g0uKcjyPob8iisj/5z0ARE+Ldfx4MxjNFNECG0z++J7zJgqg==
"@types/cheerio@*":
"@types/cheerio@*", "@types/cheerio@^0.22.10":
version "0.22.10"
resolved "https://registry.yarnpkg.com/@types/cheerio/-/cheerio-0.22.10.tgz#780d552467824be4a241b29510a7873a7432c4a6"
integrity sha512-fOM/Jhv51iyugY7KOBZz2ThfT1gwvsGCfWxpLpZDgkGjpEO4Le9cld07OdskikLjDUQJ43dzDaVRSFwQlpdqVg==
@ -1215,10 +1215,10 @@
dependencies:
"@types/d3-time" "*"
"@types/d3-shape@^1.2.2":
version "1.2.3"
resolved "https://registry.yarnpkg.com/@types/d3-shape/-/d3-shape-1.2.3.tgz#cadc9f93a626db9190f306048a650df4ffa4e500"
integrity sha512-iP9TcX0EVi+LlX+jK9ceS+yhEz5abTitF+JaO2ugpRE/J+bccaYLe/0/3LETMmdaEkYarIyboZW8OF67Mpnj1w==
"@types/d3-shape@^1.3.1":
version "1.3.1"
resolved "https://registry.yarnpkg.com/@types/d3-shape/-/d3-shape-1.3.1.tgz#1b4f92b7efd7306fe2474dc6ee94c0f0ed2e6ab6"
integrity sha512-usqdvUvPJ7AJNwpd2drOzRKs1ELie53p2m2GnPKr076/ADM579jVTJ5dPsoZ5E/CMNWk8lvPWYQSvilpp6jjwg==
dependencies:
"@types/d3-path" "*"
@ -1304,11 +1304,6 @@
dependencies:
"@types/node" "*"
"@types/expect.js@^0.3.29":
version "0.3.29"
resolved "https://registry.yarnpkg.com/@types/expect.js/-/expect.js-0.3.29.tgz#28dd359155b84b8ecb094afc3f4b74c3222dca3b"
integrity sha1-KN01kVW4S47LCUr8P0t0wyItyjs=
"@types/fetch-mock@7.2.1":
version "7.2.1"
resolved "https://registry.yarnpkg.com/@types/fetch-mock/-/fetch-mock-7.2.1.tgz#5630999aa75532e00af42a54cbe05e1651f4a080"
@ -1465,11 +1460,6 @@
resolved "https://registry.yarnpkg.com/@types/json-stable-stringify/-/json-stable-stringify-1.0.32.tgz#121f6917c4389db3923640b2e68de5fa64dda88e"
integrity sha512-q9Q6+eUEGwQkv4Sbst3J4PNgDOvpuVuKj79Hl/qnmBMEIPzB5QoFRUtjcgcg2xNUZyYUGXBk5wYIBKHt0A+Mxw==
"@types/json5@^0.0.29":
version "0.0.29"
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4=
"@types/json5@^0.0.30":
version "0.0.30"
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.30.tgz#44cb52f32a809734ca562e685c6473b5754a7818"
@ -7074,7 +7064,7 @@ deep-is@~0.1.3:
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34"
integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=
deepmerge@2.2.1, deepmerge@^2.0.1:
deepmerge@2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-2.2.1.tgz#5d3ff22a01c00f645405a2fbc17d0778a1801170"
integrity sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==
@ -21254,17 +21244,6 @@ ts-node@^7.0.1:
source-map-support "^0.5.6"
yn "^2.0.0"
tsconfig-paths@^3.8.0:
version "3.8.0"
resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.8.0.tgz#4e34202d5b41958f269cf56b01ed95b853d59f72"
integrity sha512-zZEYFo4sjORK8W58ENkRn9s+HmQFkkwydDG7My5s/fnfr2YYCaiyXe/HBUcIgU8epEKOXwiahOO+KZYjiXlWyQ==
dependencies:
"@types/json5" "^0.0.29"
deepmerge "^2.0.1"
json5 "^1.0.1"
minimist "^1.2.0"
strip-bom "^3.0.0"
tslib@1.9.3, tslib@^1.7.1, tslib@^1.8.0, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.2, tslib@^1.9.3:
version "1.9.3"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286"