diff --git a/package.json b/package.json index 47c34dde928b..e2274db033c1 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/packages/kbn-config-schema/tsconfig.json b/packages/kbn-config-schema/tsconfig.json index 3b1982520da0..f6c61268da17 100644 --- a/packages/kbn-config-schema/tsconfig.json +++ b/packages/kbn-config-schema/tsconfig.json @@ -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" + ] +} diff --git a/packages/kbn-test/tsconfig.json b/packages/kbn-test/tsconfig.json new file mode 100644 index 000000000000..8d4281850228 --- /dev/null +++ b/packages/kbn-test/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../tsconfig.json", + "include": [ + "types/**/*" + ] +} diff --git a/packages/kbn-test/types/README.md b/packages/kbn-test/types/README.md new file mode 100644 index 000000000000..5e4b2c6da614 --- /dev/null +++ b/packages/kbn-test/types/README.md @@ -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. \ No newline at end of file diff --git a/packages/kbn-test/types/expect.js.d.ts b/packages/kbn-test/types/expect.js.d.ts new file mode 100644 index 000000000000..fd3cbd852f96 --- /dev/null +++ b/packages/kbn-test/types/expect.js.d.ts @@ -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 +// 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; +} diff --git a/packages/kbn-test/types/ftr.d.ts b/packages/kbn-test/types/ftr.d.ts new file mode 100644 index 000000000000..8cc425312063 --- /dev/null +++ b/packages/kbn-test/types/ftr.d.ts @@ -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 { + /** + * 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; +} + +/** + * 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 extends Promise ? AsyncInstance & 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 = { + [K in keyof T]: T[K] extends (...args: any[]) => any + ? MaybeAsyncInstance> + : never +}; + +export interface GenericFtrProviderContext< + ServiceProviders extends object, + PageObjectProviders extends object, + ServiceMap = ProvidedTypeMap, + PageObjectMap = ProvidedTypeMap +> { + /** + * Determine if a service is avaliable + * @param serviceName + */ + hasService(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(serviceName: T): ServiceMap[T]; + + /** + * Get a map of PageObjects + * @param pageObjects + */ + getPageObjects(pageObjects: K[]): Pick; + + /** + * 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; +} diff --git a/src/dev/typescript/projects.ts b/src/dev/typescript/projects.ts index f7d111158fa3..540441ab6b8d 100644 --- a/src/dev/typescript/projects.ts +++ b/src/dev/typescript/projects.ts @@ -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 diff --git a/src/es_archiver/es_archiver.d.ts b/src/es_archiver/es_archiver.d.ts new file mode 100644 index 000000000000..c50ae19d99cb --- /dev/null +++ b/src/es_archiver/es_archiver.d.ts @@ -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['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; + public load(name: string, options?: { skipExisting?: boolean }): Promise; + public unload(name: string): Promise; + public rebuildAll(): Promise; + public edit(prefix: string, handler: () => Promise): Promise; + public loadIfNeeded(name: string): Promise; + public emptyKibanaIndex(): Promise; +} diff --git a/test/typings/wrapper.ts b/src/es_archiver/index.d.ts similarity index 86% rename from test/typings/wrapper.ts rename to src/es_archiver/index.d.ts index 69a6f7547028..f7a579a98a42 100644 --- a/test/typings/wrapper.ts +++ b/src/es_archiver/index.d.ts @@ -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'; diff --git a/src/es_archiver/lib/stats.js b/src/es_archiver/lib/stats.js deleted file mode 100644 index 1101c8e06563..000000000000 --- a/src/es_archiver/lib/stats.js +++ /dev/null @@ -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(); -} diff --git a/src/es_archiver/lib/stats.ts b/src/es_archiver/lib/stats.ts new file mode 100644 index 000000000000..965981af3dc8 --- /dev/null +++ b/src/es_archiver/lib/stats.ts @@ -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 = {}; + 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 = {}) { + 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 = {}) { + 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]); + }); + } + }(); +} diff --git a/src/functional_test_runner/cli.js b/src/functional_test_runner/cli.js index 94892451155f..9414dcfe1cfd 100644 --- a/src/functional_test_runner/cli.js +++ b/src/functional_test_runner/cli.js @@ -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, diff --git a/src/functional_test_runner/lib/config/config.js b/src/functional_test_runner/lib/config/config.ts similarity index 54% rename from src/functional_test_runner/lib/config/config.js rename to src/functional_test_runner/lib/config/config.ts index 302619561e87..9819f23a656b 100644 --- a/src/functional_test_runner/lib/config/config.js +++ b/src/functional_test_runner/lib/config/config.ts @@ -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; + primary?: boolean; + path: string; +} + export class Config { - constructor(options = {}) { - const { - settings = {}, - primary = false, - path, - } = options; + private [$values]: Record; + + 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, + 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; } diff --git a/src/functional_test_runner/lib/config/schema.js b/src/functional_test_runner/lib/config/schema.js deleted file mode 100644 index 89cf0318fd83..000000000000 --- a/src/functional_test_runner/lib/config/schema.js +++ /dev/null @@ -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 = `/${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(); diff --git a/src/functional_test_runner/lib/config/schema.ts b/src/functional_test_runner/lib/config/schema.ts new file mode 100644 index 000000000000..66c4e0d693fb --- /dev/null +++ b/src/functional_test_runner/lib/config/schema.ts @@ -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 = `/${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(); diff --git a/test/common/services/index.js b/src/functional_test_runner/lib/index.d.ts similarity index 81% rename from test/common/services/index.js rename to src/functional_test_runner/lib/index.d.ts index c9a5813e1e85..6c71239d17bc 100644 --- a/test/common/services/index.js +++ b/src/functional_test_runner/lib/index.d.ts @@ -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'; diff --git a/src/functional_test_runner/lib/lifecycle.js b/src/functional_test_runner/lib/lifecycle.ts similarity index 66% rename from src/functional_test_runner/lib/lifecycle.js rename to src/functional_test_runner/lib/lifecycle.ts index 0f05eb45871d..97b7a5538fe4 100644 --- a/src/functional_test_runner/lib/lifecycle.js +++ b/src/functional_test_runner/lib/lifecycle.ts @@ -17,31 +17,34 @@ * under the License. */ +type Listener = (...args: any[]) => Promise | void; +export type Lifecycle = ReturnType; + 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(); + }, + }; } diff --git a/src/functional_test_runner/types.ts b/src/functional_test_runner/types.ts new file mode 100644 index 000000000000..0d53838b1e16 --- /dev/null +++ b/src/functional_test_runner/types.ts @@ -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; +} diff --git a/src/legacy/ui/public/utils/__tests__/cidr_mask.ts b/src/legacy/ui/public/utils/__tests__/cidr_mask.ts index 5375552d0ad0..8624bb1b6e0b 100644 --- a/src/legacy/ui/public/utils/__tests__/cidr_mask.ts +++ b/src/legacy/ui/public/utils/__tests__/cidr_mask.ts @@ -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'; diff --git a/test/common/config.js b/test/common/config.js index 9cfd2ee56bce..d8e5521c934e 100644 --- a/test/common/config.js +++ b/test/common/config.js @@ -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 }; } diff --git a/test/common/ftr_provider_context.d.ts b/test/common/ftr_provider_context.d.ts new file mode 100644 index 000000000000..60f4914a1d27 --- /dev/null +++ b/test/common/ftr_provider_context.d.ts @@ -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; diff --git a/test/common/services/es.js b/test/common/services/es.ts similarity index 89% rename from test/common/services/es.js rename to test/common/services/es.ts index 3445fcaf0e19..9a19c533651d 100644 --- a/test/common/services/es.js +++ b/test/common/services/es.ts @@ -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({ diff --git a/test/common/services/es_archiver.js b/test/common/services/es_archiver.ts similarity index 86% rename from test/common/services/es_archiver.js rename to test/common/services/es_archiver.ts index 2c8a2335d069..cf8474662306 100644 --- a/test/common/services/es_archiver.js +++ b/test/common/services/es_archiver.ts @@ -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')) { diff --git a/test/common/services/index.ts b/test/common/services/index.ts new file mode 100644 index 000000000000..60b6a5141b91 --- /dev/null +++ b/test/common/services/index.ts @@ -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, +}; diff --git a/test/common/services/retry/index.js b/test/common/services/retry/index.ts similarity index 100% rename from test/common/services/retry/index.js rename to test/common/services/retry/index.ts diff --git a/test/common/services/retry/retry.js b/test/common/services/retry/retry.ts similarity index 74% rename from test/common/services/retry/retry.js rename to test/common/services/retry/retry.ts index 897bfb453544..70c77f20848f 100644 --- a/test/common/services/retry/retry.js +++ b/test/common/services/retry/retry.ts @@ -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(timeout: number, block: () => Promise) { return await retryForSuccess(log, { timeout, methodName: 'retry.tryForTime', - block + block, }); } - async try(block) { + public async try(block: () => Promise) { return await retryForSuccess(log, { timeout: config.get('timeouts.try'), methodName: 'retry.try', - block - }); - } - - async tryMethod(object, method, ...args) { - return await retryForSuccess(log, { - timeout: config.get('timeouts.try'), - methodName: 'retry.tryMethod', - block: async () => ( - await object[method](...args) - ) + block, }); } - async waitForWithTimeout(description, timeout, block) { + public async waitForWithTimeout( + description: string, + timeout: number, + block: () => Promise + ) { await retryForTruthy(log, { timeout, methodName: 'retry.waitForWithTimeout', description, - block + block, }); } - async waitFor(description, block) { + public async waitFor(description: string, block: () => Promise) { await retryForTruthy(log, { timeout: config.get('timeouts.waitFor'), methodName: 'retry.waitFor', description, - block + block, }); } - }; + }(); } diff --git a/test/common/services/retry/retry_for_success.js b/test/common/services/retry/retry_for_success.ts similarity index 57% rename from test/common/services/retry/retry_for_success.js rename to test/common/services/retry/retry_for_success.ts index 2571650a9f6e..82c6c242861b 100644 --- a/test/common/services/retry/retry_for_success.js +++ b/test/common/services/retry/retry_for_success.ts @@ -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(block: () => Promise): 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 { + timeout: number; + methodName: string; + block: () => Promise; + onFailure?: ReturnType; + accept?: (v: T) => boolean; +} + +export async function retryForSuccess(log: ToolingLog, options: Options) { + 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); diff --git a/test/common/services/retry/retry_for_truthy.js b/test/common/services/retry/retry_for_truthy.ts similarity index 64% rename from test/common/services/retry/retry_for_truthy.js rename to test/common/services/retry/retry_for_truthy.ts index 65ebef1e3042..0e74370f669e 100644 --- a/test/common/services/retry/retry_for_truthy.js +++ b/test/common/services/retry/retry_for_truthy.ts @@ -17,33 +17,36 @@ * under the License. */ -import { retryForSuccess } from './retry_for_success'; - -export async function retryForTruthy(log, { - timeout, - methodName, - description, - block -}) { - log.debug(`Waiting up to ${timeout}ms for ${description}...`); +import { ToolingLog } from '@kbn/dev-utils'; - const accept = result => Boolean(result); - - const onFailure = lastError => { - let msg = `timed out waiting for ${description}`; +import { retryForSuccess } from './retry_for_success'; - if (lastError) { - msg = `${msg} -- last error: ${lastError.stack || lastError.message}`; - } +interface Options { + timeout: number; + methodName: string; + description: string; + block: () => Promise; +} - throw new Error(msg); - }; +export async function retryForTruthy( + log: ToolingLog, + { timeout, methodName, description, block }: Options +) { + log.debug(`Waiting up to ${timeout}ms for ${description}...`); 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), }); } diff --git a/test/functional/apps/console/_console.js b/test/functional/apps/console/_console.ts similarity index 84% rename from test/functional/apps/console/_console.js rename to test/functional/apps/console/_console.ts index 8ed8edede034..d4937962a224 100644 --- a/test/functional/apps/console/_console.js +++ b/test/functional/apps/console/_console.ts @@ -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 diff --git a/test/functional/apps/visualize/_tsvb_chart.ts b/test/functional/apps/visualize/_tsvb_chart.ts index 4fdff88d82b9..e51bff3a9348 100644 --- a/test/functional/apps/visualize/_tsvb_chart.ts +++ b/test/functional/apps/visualize/_tsvb_chart.ts @@ -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'); diff --git a/test/functional/config.js b/test/functional/config.js index c33682a51aa8..4b41ebe71c2c 100644 --- a/test/functional/config.js +++ b/test/functional/config.js @@ -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'), diff --git a/test/functional/ftr_provider_context.d.ts b/test/functional/ftr_provider_context.d.ts new file mode 100644 index 000000000000..2a3cad1dbb98 --- /dev/null +++ b/test/functional/ftr_provider_context.d.ts @@ -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; diff --git a/test/functional/page_objects/index.js b/test/functional/page_objects/index.js deleted file mode 100644 index 5d561a42e2fa..000000000000 --- a/test/functional/page_objects/index.js +++ /dev/null @@ -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'; diff --git a/test/functional/page_objects/index.ts b/test/functional/page_objects/index.ts new file mode 100644 index 000000000000..afc6830100ec --- /dev/null +++ b/test/functional/page_objects/index.ts @@ -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, +}; diff --git a/test/functional/services/apps_menu.js b/test/functional/services/apps_menu.js deleted file mode 100644 index d57ad68f9297..000000000000 --- a/test/functional/services/apps_menu.js +++ /dev/null @@ -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') - )); - } - }; -} diff --git a/test/functional/services/apps_menu.ts b/test/functional/services/apps_menu.ts new file mode 100644 index 000000000000..3926bca01681 --- /dev/null +++ b/test/functional/services/apps_menu.ts @@ -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') + ); + } + } + }(); +} diff --git a/test/functional/services/index.js b/test/functional/services/index.js deleted file mode 100644 index 1ae452363874..000000000000 --- a/test/functional/services/index.js +++ /dev/null @@ -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'; diff --git a/test/functional/services/index.ts b/test/functional/services/index.ts new file mode 100644 index 000000000000..941a46e3fb47 --- /dev/null +++ b/test/functional/services/index.ts @@ -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, +}; diff --git a/test/tsconfig.json b/test/tsconfig.json index c06a3cc4df48..f72d03a94f26 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -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": [ diff --git a/test/typings/index.ts b/test/types/index.ts similarity index 95% rename from test/typings/index.ts rename to test/types/index.ts index 73506fce4ae3..edfaeb574352 100644 --- a/test/typings/index.ts +++ b/test/types/index.ts @@ -17,4 +17,4 @@ * under the License. */ -export * from './wrapper'; +export * from './mocha_decorations'; diff --git a/test/types/mocha_decorations.d.ts b/test/types/mocha_decorations.d.ts new file mode 100644 index 000000000000..3bf009003408 --- /dev/null +++ b/test/types/mocha_decorations.d.ts @@ -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; + } +} diff --git a/tsconfig.json b/tsconfig.json index 5c1204a99208..46569f474db7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -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 diff --git a/tsconfig.types.json b/tsconfig.types.json index c50629931ddc..91029bdae57a 100644 --- a/tsconfig.types.json +++ b/tsconfig.types.json @@ -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" + ] +} diff --git a/x-pack/package.json b/x-pack/package.json index 18104bb4394d..d8fd87295fc7 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -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", diff --git a/x-pack/test/tsconfig.json b/x-pack/test/tsconfig.json index 87e34dc754cd..2e9fc43a6aa6 100644 --- a/x-pack/test/tsconfig.json +++ b/x-pack/test/tsconfig.json @@ -2,13 +2,13 @@ "extends": "../tsconfig.json", "compilerOptions": { "types": [ - "expect.js", + "@kbn/test/types/expect.js", "mocha", "node" ] }, "include": [ - "**/*", + "**/*" ], "exclude": [], } diff --git a/x-pack/test/types/providers.ts b/x-pack/test/types/providers.ts index 6865fece8605..9699775428a5 100644 --- a/x-pack/test/types/providers.ts +++ b/x-pack/test/types/providers.ts @@ -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; - unload(archiveName: string): Promise; -} +import { EsArchiver } from '../../../src/es_archiver'; export interface KibanaFunctionalTestDefaultProviders { getService(serviceName: 'esArchiver'): EsArchiver; diff --git a/x-pack/tsconfig.json b/x-pack/tsconfig.json index 31ec11765e13..febe406dc5d6 100644 --- a/x-pack/tsconfig.json +++ b/x-pack/tsconfig.json @@ -30,7 +30,8 @@ }, "types": [ "node", - "jest" + "jest", + "@kbn/test/types/expect.js" ] } } diff --git a/yarn.lock b/yarn.lock index bf64ce8ca1be..dcecd13b4d43 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"