mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
* [ts] add script to verify that all ts is in a project (#32727) Based on #32705 We currently have TypeScript code that was backported to 7.0, which was backported without issue because it falls outside of any TypeScript projects in 7.0. This means that the pre-commit hooks break on changes to these files, and that they are not getting type checked by the type_check script. To fix this we need to verify that every typescript file in the repository is covered by a tsconfig.json file as part of CI. * tests typescript migration (#31234) * add typescript support for functional tests * [ts][ftr] improve types for ftr and expect.js, cleanup changes to tsconfig files (#31948) 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. # Conflicts: # src/functional_test_runner/lib/config/schema.js # test/common/services/es.ts # test/functional/page_objects/index.ts # test/functional/services/apps_menu.js # yarn.lock
This commit is contained in:
parent
2c31657e78
commit
2c2637d4ee
54 changed files with 1529 additions and 682 deletions
|
@ -290,6 +290,7 @@
|
|||
"@types/listr": "^0.13.0",
|
||||
"@types/lodash": "^3.10.1",
|
||||
"@types/minimatch": "^2.0.29",
|
||||
"@types/mocha": "^5.2.6",
|
||||
"@types/moment-timezone": "^0.5.8",
|
||||
"@types/mustache": "^0.8.31",
|
||||
"@types/node": "^10.12.27",
|
||||
|
|
|
@ -5,7 +5,11 @@
|
|||
"declarationDir": "./target/types",
|
||||
"outDir": "./target/out",
|
||||
"stripInternal": true,
|
||||
"declarationMap": true
|
||||
"declarationMap": true,
|
||||
"types": [
|
||||
"jest",
|
||||
"node"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"./types/joi.d.ts",
|
||||
|
|
|
@ -1,16 +1,20 @@
|
|||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.tsx",
|
||||
"types/intl_format_cache.d.ts",
|
||||
"types/intl_relativeformat.d.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"target"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"declaration": true,
|
||||
"declarationDir": "./target/types",
|
||||
}
|
||||
}
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.tsx",
|
||||
"types/intl_format_cache.d.ts",
|
||||
"types/intl_relativeformat.d.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"target"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"declaration": true,
|
||||
"declarationDir": "./target/types",
|
||||
"types": [
|
||||
"jest",
|
||||
"node"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,16 @@
|
|||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"exclude": [
|
||||
"dist"
|
||||
],
|
||||
"include": [
|
||||
"./src/**/*.ts",
|
||||
"./types/index.d.ts"
|
||||
]
|
||||
}
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"exclude": [
|
||||
"dist"
|
||||
],
|
||||
"include": [
|
||||
"./src/**/*.ts",
|
||||
"./types/index.d.ts"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"types": [
|
||||
"jest",
|
||||
"node"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
6
packages/kbn-test/tsconfig.json
Normal file
6
packages/kbn-test/tsconfig.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"include": [
|
||||
"types/**/*"
|
||||
]
|
||||
}
|
6
packages/kbn-test/types/README.md
Normal file
6
packages/kbn-test/types/README.md
Normal 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
225
packages/kbn-test/types/expect.js.d.ts
vendored
Normal 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
80
packages/kbn-test/types/ftr.d.ts
vendored
Normal 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;
|
||||
}
|
|
@ -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';
|
||||
require('../src/setup_node_env');
|
||||
require('../src/dev/typescript/run_check_ts_projects_cli').runCheckTsProjectsCli();
|
|
@ -21,6 +21,8 @@ import { relative } from 'path';
|
|||
|
||||
import getopts from 'getopts';
|
||||
|
||||
import { Options } from './run';
|
||||
|
||||
export interface Flags {
|
||||
verbose: boolean;
|
||||
quiet: boolean;
|
||||
|
@ -57,11 +59,11 @@ export function getFlags(argv: string[]): Flags {
|
|||
};
|
||||
}
|
||||
|
||||
export function getHelp() {
|
||||
export function getHelp(options: Options) {
|
||||
return `
|
||||
node ${relative(process.cwd(), process.argv[1])}
|
||||
|
||||
Runs a dev task
|
||||
${options.helpDescription || 'Runs a dev task'}
|
||||
|
||||
Options:
|
||||
--verbose, -v Log verbosely
|
||||
|
|
|
@ -21,11 +21,17 @@ import { pickLevelFromFlags, ToolingLog } from '@kbn/dev-utils';
|
|||
import { isFailError } from './fail';
|
||||
import { Flags, getFlags, getHelp } from './flags';
|
||||
|
||||
export async function run(body: (args: { log: ToolingLog; flags: Flags }) => Promise<void> | void) {
|
||||
type RunFn = (args: { log: ToolingLog; flags: Flags }) => Promise<void> | void;
|
||||
|
||||
export interface Options {
|
||||
helpDescription?: string;
|
||||
}
|
||||
|
||||
export async function run(fn: RunFn, options: Options = {}) {
|
||||
const flags = getFlags(process.argv.slice(2));
|
||||
|
||||
if (flags.help) {
|
||||
process.stderr.write(getHelp());
|
||||
process.stderr.write(getHelp(options));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
|
@ -35,7 +41,7 @@ export async function run(body: (args: { log: ToolingLog; flags: Flags }) => Pro
|
|||
});
|
||||
|
||||
try {
|
||||
await body({ log, flags });
|
||||
await fn({ log, flags });
|
||||
} catch (error) {
|
||||
if (isFailError(error)) {
|
||||
log.error(error.message);
|
||||
|
|
|
@ -25,6 +25,7 @@ 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'),
|
||||
|
||||
|
|
91
src/dev/typescript/run_check_ts_projects_cli.ts
Normal file
91
src/dev/typescript/run_check_ts_projects_cli.ts
Normal file
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { resolve } from 'path';
|
||||
|
||||
import execa from 'execa';
|
||||
|
||||
import { run } from '../run';
|
||||
|
||||
const REPO_ROOT = resolve(__dirname, '../../../');
|
||||
import { File } from '../file';
|
||||
import { PROJECTS } from './projects';
|
||||
|
||||
export async function runCheckTsProjectsCli() {
|
||||
run(
|
||||
async ({ log }) => {
|
||||
const files = await execa.stdout('git', ['ls-tree', '--name-only', '-r', 'HEAD'], {
|
||||
cwd: REPO_ROOT,
|
||||
});
|
||||
|
||||
const isNotInTsProject: File[] = [];
|
||||
const isInMultipleTsProjects: File[] = [];
|
||||
|
||||
for (const lineRaw of files.split('\n')) {
|
||||
const line = lineRaw.trim();
|
||||
|
||||
if (!line) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const file = new File(resolve(REPO_ROOT, line));
|
||||
if (!file.isTypescript() || file.isFixture()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
log.verbose('Checking %s', file.getAbsolutePath());
|
||||
|
||||
const projects = PROJECTS.filter(p => p.isAbsolutePathSelected(file.getAbsolutePath()));
|
||||
if (projects.length === 0) {
|
||||
isNotInTsProject.push(file);
|
||||
}
|
||||
if (projects.length > 1) {
|
||||
isInMultipleTsProjects.push(file);
|
||||
}
|
||||
}
|
||||
|
||||
if (!isNotInTsProject.length && !isInMultipleTsProjects.length) {
|
||||
log.success('All ts files belong to a single ts project');
|
||||
return;
|
||||
}
|
||||
|
||||
if (isNotInTsProject.length) {
|
||||
log.error(
|
||||
`The following files do not belong to a tsconfig.json file, or that tsconfig.json file is not listed in src/dev/typescript/projects.ts\n${isNotInTsProject
|
||||
.map(file => ` - ${file.getRelativePath()}`)
|
||||
.join('\n')}`
|
||||
);
|
||||
}
|
||||
|
||||
if (isInMultipleTsProjects.length) {
|
||||
log.error(
|
||||
`The following files belong to multiple tsconfig.json files listed in src/dev/typescript/projects.ts\n${isInMultipleTsProjects
|
||||
.map(file => ` - ${file.getRelativePath()}`)
|
||||
.join('\n')}`
|
||||
);
|
||||
}
|
||||
|
||||
process.exit(1);
|
||||
},
|
||||
{
|
||||
helpDescription:
|
||||
'Check that all .ts and .tsx files in the repository are assigned to a tsconfig.json file',
|
||||
}
|
||||
);
|
||||
}
|
39
src/es_archiver/es_archiver.d.ts
vendored
Normal file
39
src/es_archiver/es_archiver.d.ts
vendored
Normal 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>;
|
||||
}
|
20
src/es_archiver/index.d.ts
vendored
Normal file
20
src/es_archiver/index.d.ts
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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 { EsArchiver } from './es_archiver';
|
|
@ -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();
|
||||
}
|
153
src/es_archiver/lib/stats.ts
Normal file
153
src/es_archiver/lib/stats.ts
Normal 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]);
|
||||
});
|
||||
}
|
||||
}();
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -1,189 +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'),
|
||||
}).default(),
|
||||
|
||||
updateBaselines: Joi.boolean().default(false),
|
||||
|
||||
junit: Joi.object().keys({
|
||||
enabled: Joi.boolean().default(!!process.env.CI),
|
||||
reportName: 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();
|
238
src/functional_test_runner/lib/config/schema.ts
Normal file
238
src/functional_test_runner/lib/config/schema.ts
Normal 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();
|
21
src/functional_test_runner/lib/index.d.ts
vendored
Normal file
21
src/functional_test_runner/lib/index.d.ts
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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 { Config } from './config/config';
|
||||
export { Lifecycle } from './lifecycle';
|
|
@ -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();
|
||||
},
|
||||
};
|
||||
}
|
27
src/functional_test_runner/types.ts
Normal file
27
src/functional_test_runner/types.ts
Normal 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;
|
||||
}
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -109,6 +109,15 @@ module.exports = function (grunt) {
|
|||
]
|
||||
},
|
||||
|
||||
// used by the test and jenkins:unit tasks
|
||||
// ensures that all typescript files belong to a typescript project
|
||||
checkTsProjects: {
|
||||
cmd: process.execPath,
|
||||
args: [
|
||||
require.resolve('../../scripts/check_ts_projects')
|
||||
]
|
||||
},
|
||||
|
||||
// used by the test and jenkins:unit tasks
|
||||
// runs the i18n_check script to check i18n engine usage
|
||||
i18nCheck: {
|
||||
|
|
|
@ -26,6 +26,7 @@ module.exports = function (grunt) {
|
|||
'run:eslint',
|
||||
'run:tslint',
|
||||
'run:sasslint',
|
||||
'run:checkTsProjects',
|
||||
'run:typeCheck',
|
||||
'run:i18nCheck',
|
||||
'run:checkFileCasing',
|
||||
|
|
|
@ -72,6 +72,7 @@ module.exports = function (grunt) {
|
|||
!grunt.option('quick') && 'run:eslint',
|
||||
!grunt.option('quick') && 'run:tslint',
|
||||
!grunt.option('quick') && 'run:sasslint',
|
||||
!grunt.option('quick') && 'run:checkTsProjects',
|
||||
!grunt.option('quick') && 'run:typeCheck',
|
||||
!grunt.option('quick') && 'run:i18nCheck',
|
||||
'run:checkFileCasing',
|
||||
|
|
|
@ -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
24
test/common/ftr_provider_context.d.ts
vendored
Normal 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, {}>;
|
|
@ -22,8 +22,9 @@ import { format as formatUrl } from 'url';
|
|||
import elasticsearch from 'elasticsearch';
|
||||
|
||||
import { DEFAULT_API_VERSION } from '../../../src/legacy/core_plugins/elasticsearch/lib/default_api_version';
|
||||
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({
|
|
@ -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')) {
|
31
test/common/services/index.ts
Normal file
31
test/common/services/index.ts
Normal 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,
|
||||
};
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
};
|
||||
}();
|
||||
}
|
|
@ -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);
|
|
@ -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),
|
||||
});
|
||||
}
|
|
@ -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
|
|
@ -18,24 +18,32 @@
|
|||
*/
|
||||
|
||||
import expect from 'expect.js';
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
|
||||
export default function ({ getService, getPageObjects }) {
|
||||
// tslint:disable-next-line:no-default-export
|
||||
export default function({ getService, getPageObjects }: FtrProviderContext) {
|
||||
const esArchiver = getService('esArchiver');
|
||||
const log = getService('log');
|
||||
const inspector = getService('inspector');
|
||||
const retry = getService('retry');
|
||||
const kibanaServer = getService('kibanaServer');
|
||||
const testSubjects = getService('testSubjects');
|
||||
const PageObjects = getPageObjects(['common', 'visualize', 'header', 'settings', 'visualBuilder', 'timePicker']);
|
||||
const PageObjects = getPageObjects([
|
||||
'common',
|
||||
'visualize',
|
||||
'header',
|
||||
'settings',
|
||||
'visualBuilder',
|
||||
'timePicker',
|
||||
]);
|
||||
|
||||
describe('visual builder', function describeIndexTests() {
|
||||
|
||||
describe('Time Series', function () {
|
||||
describe('Time Series', () => {
|
||||
before(async () => {
|
||||
await PageObjects.visualBuilder.resetPage();
|
||||
});
|
||||
|
||||
it('should show the correct count in the legend', async function () {
|
||||
it('should show the correct count in the legend', async () => {
|
||||
await retry.try(async () => {
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
const actualCount = await PageObjects.visualBuilder.getRhythmChartLegendValue();
|
||||
|
@ -43,7 +51,7 @@ export default function ({ getService, getPageObjects }) {
|
|||
});
|
||||
});
|
||||
|
||||
it('should show the correct count in the legend with 2h offset', async function () {
|
||||
it('should show the correct count in the legend with 2h offset', async () => {
|
||||
await PageObjects.visualBuilder.clickSeriesOption();
|
||||
await PageObjects.visualBuilder.enterOffsetSeries('2h');
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
|
@ -51,7 +59,7 @@ export default function ({ getService, getPageObjects }) {
|
|||
expect(actualCount).to.be('293');
|
||||
});
|
||||
|
||||
it('should show the correct count in the legend with -2h offset', async function () {
|
||||
it('should show the correct count in the legend with -2h offset', async () => {
|
||||
await PageObjects.visualBuilder.enterOffsetSeries('-2h');
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
const actualCount = await PageObjects.visualBuilder.getRhythmChartLegendValue();
|
||||
|
@ -62,7 +70,6 @@ export default function ({ getService, getPageObjects }) {
|
|||
// set back to no offset for the next test, an empty string didn't seem to work here
|
||||
await PageObjects.visualBuilder.enterOffsetSeries('0h');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('Math Aggregation', () => {
|
||||
|
@ -75,18 +82,17 @@ export default function ({ getService, getPageObjects }) {
|
|||
await PageObjects.visualBuilder.fillInExpression('params.test + 1');
|
||||
});
|
||||
|
||||
it('should not have inspector enabled', async function () {
|
||||
it('should not have inspector enabled', async () => {
|
||||
await inspector.expectIsNotEnabled();
|
||||
});
|
||||
|
||||
it('should show correct data', async function () {
|
||||
const expectedMetricValue = '157';
|
||||
it('should show correct data', async () => {
|
||||
const expectedMetricValue = '157';
|
||||
const value = await PageObjects.visualBuilder.getMetricValue();
|
||||
log.debug(`metric value: ${JSON.stringify(value)}`);
|
||||
log.debug(`metric value: ${value}`);
|
||||
expect(value).to.eql(expectedMetricValue);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('metric', () => {
|
||||
|
@ -95,18 +101,17 @@ export default function ({ getService, getPageObjects }) {
|
|||
await PageObjects.visualBuilder.clickMetric();
|
||||
});
|
||||
|
||||
it('should not have inspector enabled', async function () {
|
||||
it('should not have inspector enabled', async () => {
|
||||
await inspector.expectIsNotEnabled();
|
||||
});
|
||||
|
||||
it('should show correct data', async function () {
|
||||
const expectedMetricValue = '156';
|
||||
it('should show correct data', async () => {
|
||||
const expectedMetricValue = '156';
|
||||
await PageObjects.visualize.waitForVisualization();
|
||||
const value = await PageObjects.visualBuilder.getMetricValue();
|
||||
log.debug(`metric value: ${value}`);
|
||||
expect(value).to.eql(expectedMetricValue);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
// add a gauge test
|
||||
|
@ -117,7 +122,7 @@ export default function ({ getService, getPageObjects }) {
|
|||
log.debug('clicked on Gauge');
|
||||
});
|
||||
|
||||
it('should verify gauge label and count display', async function () {
|
||||
it('should verify gauge label and count display', async () => {
|
||||
await retry.try(async () => {
|
||||
await PageObjects.visualize.waitForVisualization();
|
||||
const labelString = await PageObjects.visualBuilder.getGaugeLabel();
|
||||
|
@ -136,7 +141,7 @@ export default function ({ getService, getPageObjects }) {
|
|||
log.debug('clicked on TopN');
|
||||
});
|
||||
|
||||
it('should verify topN label and count display', async function () {
|
||||
it('should verify topN label and count display', async () => {
|
||||
await retry.try(async () => {
|
||||
await PageObjects.visualize.waitForVisualization();
|
||||
const labelString = await PageObjects.visualBuilder.getTopNLabel();
|
||||
|
@ -152,7 +157,10 @@ export default function ({ getService, getPageObjects }) {
|
|||
before(async () => {
|
||||
await PageObjects.visualBuilder.resetPage();
|
||||
await PageObjects.visualBuilder.clickTable();
|
||||
await PageObjects.timePicker.setAbsoluteRange('2015-09-22 06:00:00.000', '2015-09-22 11:00:00.000');
|
||||
await PageObjects.timePicker.setAbsoluteRange(
|
||||
'2015-09-22 06:00:00.000',
|
||||
'2015-09-22 11:00:00.000'
|
||||
);
|
||||
log.debug('clicked on Table');
|
||||
});
|
||||
|
||||
|
@ -172,17 +180,20 @@ export default function ({ getService, getPageObjects }) {
|
|||
});
|
||||
|
||||
describe.skip('switch index patterns', () => {
|
||||
before(async function () {
|
||||
before(async () => {
|
||||
log.debug('Load kibana_sample_data_flights data');
|
||||
await esArchiver.loadIfNeeded('kibana_sample_data_flights');
|
||||
await PageObjects.visualBuilder.resetPage('2015-09-19 06:31:44.000', '2018-10-31 00:0:00.000');
|
||||
await PageObjects.visualBuilder.resetPage(
|
||||
'2015-09-19 06:31:44.000',
|
||||
'2018-10-31 00:0:00.000'
|
||||
);
|
||||
await PageObjects.visualBuilder.clickMetric();
|
||||
});
|
||||
after(async function () {
|
||||
after(async () => {
|
||||
await esArchiver.unload('kibana_sample_data_flights');
|
||||
});
|
||||
it('should be able to switch between index patterns', async () => {
|
||||
const expectedMetricValue = '156';
|
||||
const expectedMetricValue = '156';
|
||||
const value = await PageObjects.visualBuilder.getMetricValue();
|
||||
log.debug(`metric value: ${value}`);
|
||||
expect(value).to.eql(expectedMetricValue);
|
||||
|
@ -201,7 +212,7 @@ export default function ({ getService, getPageObjects }) {
|
|||
describe.skip('dark mode', () => {
|
||||
it('uses dark mode flag', async () => {
|
||||
await kibanaServer.uiSettings.update({
|
||||
'theme:darkMode': true
|
||||
'theme:darkMode': true,
|
||||
});
|
||||
|
||||
await PageObjects.visualBuilder.resetPage();
|
|
@ -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'),
|
||||
|
||||
|
|
29
test/functional/ftr_provider_context.d.ts
vendored
Normal file
29
test/functional/ftr_provider_context.d.ts
vendored
Normal 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>;
|
|
@ -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';
|
95
test/functional/services/apps_menu.ts
Normal file
95
test/functional/services/apps_menu.ts
Normal 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 buttons = await testSubjects.findAll('appsMenu appLink');
|
||||
try {
|
||||
return await Promise.all(
|
||||
(buttons as any[]).map(
|
||||
async element =>
|
||||
({
|
||||
text: await element.getVisibleText(),
|
||||
href: await element.getProperty('href'),
|
||||
} as {
|
||||
text: string;
|
||||
href: string;
|
||||
})
|
||||
)
|
||||
);
|
||||
} finally {
|
||||
await this.ensureMenuClosed();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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')
|
||||
);
|
||||
}
|
||||
}
|
||||
}();
|
||||
}
|
|
@ -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';
|
89
test/functional/services/index.ts
Normal file
89
test/functional/services/index.ts
Normal 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,
|
||||
};
|
13
test/tsconfig.json
Normal file
13
test/tsconfig.json
Normal file
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"types": [
|
||||
"node",
|
||||
"mocha",
|
||||
"@kbn/test/types/expect.js"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts"
|
||||
]
|
||||
}
|
20
test/types/index.ts
Normal file
20
test/types/index.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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 * from './mocha_decorations';
|
30
test/types/mocha_decorations.d.ts
vendored
Normal file
30
test/types/mocha_decorations.d.ts
vendored
Normal 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;
|
||||
}
|
||||
}
|
|
@ -46,7 +46,14 @@
|
|||
// Provide full support for iterables in for..of, spread and destructuring when targeting ES5 or ES3.
|
||||
"downlevelIteration": true,
|
||||
// import tslib helpers rather than inlining helpers for iteration or spreading, for instance
|
||||
"importHelpers": true
|
||||
"importHelpers": true,
|
||||
// adding global typings
|
||||
"types": [
|
||||
"node",
|
||||
"jest",
|
||||
"react",
|
||||
"@kbn/test/types/expect.js"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"kibana.d.ts",
|
||||
|
@ -55,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
|
||||
|
|
|
@ -31,13 +31,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",
|
||||
|
@ -45,7 +45,7 @@
|
|||
"@types/json-stable-stringify": "^1.0.32",
|
||||
"@types/jsonwebtoken": "^7.2.7",
|
||||
"@types/lodash": "^3.10.1",
|
||||
"@types/mocha": "^5.2.5",
|
||||
"@types/mocha": "^5.2.6",
|
||||
"@types/pngjs": "^3.3.1",
|
||||
"@types/prop-types": "^15.5.3",
|
||||
"@types/react": "16.3.14",
|
||||
|
|
|
@ -2,13 +2,13 @@
|
|||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"types": [
|
||||
"expect.js",
|
||||
"@kbn/test/types/expect.js",
|
||||
"mocha",
|
||||
"node"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"**/*",
|
||||
"**/*"
|
||||
],
|
||||
"exclude": [],
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -30,7 +30,8 @@
|
|||
},
|
||||
"types": [
|
||||
"node",
|
||||
"jest"
|
||||
"jest",
|
||||
"@kbn/test/types/expect.js"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
29
yarn.lock
29
yarn.lock
|
@ -1440,10 +1440,10 @@
|
|||
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@*":
|
||||
version "0.22.8"
|
||||
resolved "https://registry.yarnpkg.com/@types/cheerio/-/cheerio-0.22.8.tgz#5702f74f78b73e13f1eb1bd435c2c9de61a250d4"
|
||||
integrity sha512-LzF540VOFabhS2TR2yYFz2Mu/fTfkA+5AwYddtJbOJGwnYrr2e7fHadT7/Z3jNGJJdCRlO3ySxmW26NgRdwhNA==
|
||||
"@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==
|
||||
|
||||
"@types/classnames@^2.2.3":
|
||||
version "2.2.3"
|
||||
|
@ -1509,10 +1509,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" "*"
|
||||
|
||||
|
@ -1598,11 +1598,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"
|
||||
|
@ -1851,10 +1846,10 @@
|
|||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/mocha@^5.2.5":
|
||||
version "5.2.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-5.2.5.tgz#8a4accfc403c124a0bafe8a9fc61a05ec1032073"
|
||||
integrity sha512-lAVp+Kj54ui/vLUFxsJTMtWvZraZxum3w3Nwkble2dNuV5VnPA+Mi2oGX9XYJAaIvZi3tn3cbjS/qcJXRb6Bww==
|
||||
"@types/mocha@^5.2.6":
|
||||
version "5.2.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-5.2.6.tgz#b8622d50557dd155e9f2f634b7d68fd38de5e94b"
|
||||
integrity sha512-1axi39YdtBI7z957vdqXI4Ac25e7YihYQtJa+Clnxg1zTJEaIRbndt71O3sP4GAMgiAm0pY26/b9BrY4MR/PMw==
|
||||
|
||||
"@types/moment-timezone@^0.5.8":
|
||||
version "0.5.8"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue