[7.x] Typescript-ify FTR Remote (#32447) (#32564)

Backports the following commits to 7.x:
 - Typescript-ify FTR Remote  (#32447)
This commit is contained in:
Spencer 2019-03-06 09:02:52 -08:00 committed by GitHub
parent 68df4292b5
commit 71b1b60f70
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 133 additions and 181 deletions

View file

@ -266,6 +266,7 @@
"@types/bluebird": "^3.1.1",
"@types/boom": "^7.2.0",
"@types/chance": "^1.0.0",
"@types/chromedriver": "^2.38.0",
"@types/classnames": "^2.2.3",
"@types/d3": "^3.5.41",
"@types/dedent": "^0.7.0",

View file

@ -17,7 +17,8 @@
* under the License.
*/
import { DefaultServiceProviders } from '../../../src/functional_test_runner/types';
import { ToolingLog } from '@kbn/dev-utils';
import { Config, Lifecycle } from '../../../src/functional_test_runner/lib';
interface AsyncInstance<T> {
/**
@ -39,22 +40,23 @@ type MaybeAsyncInstance<T> = T extends Promise<infer X> ? AsyncInstance<X> & X :
* 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> = {
type ProvidedTypeMap<T extends {}> = {
[K in keyof T]: T[K] extends (...args: any[]) => any
? MaybeAsyncInstance<ReturnType<T[K]>>
: never
: unknown
};
export interface GenericFtrProviderContext<
ServiceProviders extends object,
PageObjectProviders extends object,
ServiceMap = ProvidedTypeMap<ServiceProviders & DefaultServiceProviders>,
ServiceProviders extends {},
PageObjectProviders extends {},
ServiceMap = ProvidedTypeMap<ServiceProviders>,
PageObjectMap = ProvidedTypeMap<PageObjectProviders>
> {
/**
* Determine if a service is avaliable
* @param serviceName
*/
hasService(serviceName: 'config' | 'log' | 'lifecycle'): true;
hasService<K extends keyof ServiceMap>(serviceName: K): serviceName is K;
hasService(serviceName: string): serviceName is keyof ServiceMap;
@ -63,6 +65,9 @@ export interface GenericFtrProviderContext<
* outside of a test/hook, then make sure to call its `.init()` method and await it's promise.
* @param serviceName
*/
getService(serviceName: 'config'): Config;
getService(serviceName: 'log'): ToolingLog;
getService(serviceName: 'lifecycle'): Lifecycle;
getService<T extends keyof ServiceMap>(serviceName: T): ServiceMap[T];
/**

View file

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

View file

@ -17,34 +17,44 @@
* under the License.
*/
export function preventParallelCalls(fn, filter) {
const execQueue = [];
export function preventParallelCalls<C extends void, A, R>(
fn: (this: C, arg: A) => Promise<R>,
filter: (arg: A) => boolean
) {
const execQueue: Task[] = [];
return async function (arg) {
class Task {
public promise: Promise<R>;
private resolve!: (result: R) => void;
private reject!: (error: Error) => void;
constructor(private readonly context: C, private readonly arg: A) {
this.promise = new Promise((resolve, reject) => {
this.resolve = resolve;
this.reject = reject;
});
}
public async exec() {
try {
this.resolve(await fn.call(this.context, this.arg));
} catch (error) {
this.reject(error);
} finally {
execQueue.shift();
if (execQueue.length) {
execQueue[0].exec();
}
}
}
}
return async function(this: C, arg: A) {
if (filter(arg)) {
return await fn.call(this, arg);
}
const task = {
exec: async () => {
try {
task.resolve(await fn.call(this, arg));
} catch (error) {
task.reject(error);
} finally {
execQueue.shift();
if (execQueue.length) {
execQueue[0].exec();
}
}
}
};
task.promise = new Promise((resolve, reject) => {
task.resolve = resolve;
task.reject = reject;
});
const task = new Task(this, arg);
if (execQueue.push(task) === 1) {
// only item in the queue, kick it off
task.exec();

View file

@ -17,32 +17,42 @@
* under the License.
*/
import { FtrProviderContext } from '../../ftr_provider_context';
import { initWebDriver } from './webdriver';
export async function RemoteProvider({ getService }) {
export async function RemoteProvider({ getService }: FtrProviderContext) {
const lifecycle = getService('lifecycle');
const log = getService('log');
const config = getService('config');
const possibleBrowsers = ['chrome', 'firefox', 'ie'];
const browserType = process.env.TEST_BROWSER_TYPE || 'chrome';
if (!possibleBrowsers.includes(browserType)) {
throw new Error(`Unexpected TEST_BROWSER_TYPE "${browserType}". Valid options are ` + possibleBrowsers.join(','));
if (browserType !== 'chrome' && browserType !== 'firefox') {
throw new Error(
`Unexpected TEST_BROWSER_TYPE "${browserType}", only "chrome" and "firefox" are supported`
);
}
const { driver, By, Key, until, LegacyActionSequence } = await initWebDriver({ log, browserType });
const { driver, By, Key, until, LegacyActionSequence } = await initWebDriver(log, browserType);
log.info('Remote initialized');
lifecycle.on('beforeTests', async () => {
// hard coded default, can be overridden per suite using `browser.setWindowSize()`
// and will be automatically reverted after each suite
await driver.manage().window().setRect({ width: 1600, height: 1000 });
await driver
.manage()
.window()
.setRect({ width: 1600, height: 1000 });
});
const windowSizeStack = [];
const windowSizeStack: Array<{ width: number; height: number }> = [];
lifecycle.on('beforeTestSuite', async () => {
windowSizeStack.unshift(await driver.manage().window().getRect());
windowSizeStack.unshift(
await driver
.manage()
.window()
.getRect()
);
});
lifecycle.on('beforeEachTest', async () => {
@ -50,8 +60,11 @@ export async function RemoteProvider({ getService }) {
});
lifecycle.on('afterTestSuite', async () => {
const { width, height } = windowSizeStack.shift();
await driver.manage().window().setRect({ width: width, height: height });
const { width, height } = windowSizeStack.shift()!;
await driver
.manage()
.window()
.setRect({ width, height });
});
lifecycle.on('cleanup', async () => await driver.quit());

View file

@ -1,56 +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 { green, magentaBright } from 'chalk';
export function initVerboseRemoteLogging(log, server) {
const wrap = (original, httpMethod) => (path, requestData, pathParts) => {
const url = '/' + path.split('/').slice(2).join('/').replace(/\$(\d)/, function (_, index) {
return encodeURIComponent(pathParts[index]);
});
if (requestData == null) {
log.verbose('[remote] > %s %s', httpMethod, url);
} else {
log.verbose('[remote] > %s %s %j', httpMethod, url, requestData);
}
return original.call(server, path, requestData, pathParts)
.then(result => {
log.verbose(`[remote] < %s %s ${green('OK')}`, httpMethod, url);
return result;
})
.catch(error => {
let message;
try {
message = JSON.parse(error.response.data).value.message;
} catch (err) {
message = err.message;
}
log.verbose(`[remote] < %s %s ${magentaBright('ERR')} %j`, httpMethod, url, message.split(/\r?\n/)[0]);
throw error;
});
};
server._get = wrap(server._get, 'GET');
server._post = wrap(server._post, 'POST');
server._delete = wrap(server._delete, 'DELETE');
return server;
}

View file

@ -17,26 +17,31 @@
* under the License.
*/
import { ToolingLog } from '@kbn/dev-utils';
import { delay } from 'bluebird';
import { Builder, By, Key, until, logging } from 'selenium-webdriver';
const { LegacyActionSequence } = require('selenium-webdriver/lib/actions');
const { getLogger } = require('selenium-webdriver/lib/logging');
const { Executor } = require('selenium-webdriver/lib/http');
const chrome = require('selenium-webdriver/chrome');
const firefox = require('selenium-webdriver/firefox');
const geckoDriver = require('geckodriver');
const chromeDriver = require('chromedriver');
const throttleOption = process.env.TEST_THROTTLE_NETWORK;
import chromeDriver from 'chromedriver';
// @ts-ignore types not available
import geckoDriver from 'geckodriver';
// @ts-ignore types for 4.0 not available yet
import { Builder, By, Key, logging, until } from 'selenium-webdriver';
// @ts-ignore types not available
import chrome from 'selenium-webdriver/chrome';
// @ts-ignore types not available
import firefox from 'selenium-webdriver/firefox';
// @ts-ignore internal modules are not typed
import { LegacyActionSequence } from 'selenium-webdriver/lib/actions';
// @ts-ignore internal modules are not typed
import { Executor } from 'selenium-webdriver/lib/http';
// @ts-ignore internal modules are not typed
import { getLogger } from 'selenium-webdriver/lib/logging';
import { preventParallelCalls } from './prevent_parallel_calls';
const throttleOption = process.env.TEST_THROTTLE_NETWORK;
const SECOND = 1000;
const MINUTE = 60 * SECOND;
const NO_QUEUE_COMMANDS = [
'getStatus',
'newSession',
'quit'
];
const NO_QUEUE_COMMANDS = ['getStatus', 'newSession', 'quit'];
/**
* Best we can tell WebDriver locks up sometimes when we send too many
@ -46,24 +51,26 @@ const NO_QUEUE_COMMANDS = [
* queue all calls to Executor#send() if there is already a call in
* progress.
*/
Executor.prototype.execute = preventParallelCalls(Executor.prototype.execute, (command) => (
NO_QUEUE_COMMANDS.includes(command.getName())
));
Executor.prototype.execute = preventParallelCalls(
Executor.prototype.execute,
(command: { getName: () => string }) => NO_QUEUE_COMMANDS.includes(command.getName())
);
let attemptCounter = 0;
async function attemptToCreateCommand(log, browserType) {
async function attemptToCreateCommand(log: ToolingLog, browserType: 'chrome' | 'firefox') {
const attemptId = ++attemptCounter;
log.debug('[webdriver] Creating session');
const buildDriverInstance = async (browserType) => {
const buildDriverInstance = async () => {
switch (browserType) {
case 'chrome':
const chromeOptions = new chrome.Options();
const loggingPref = new logging.Preferences().setLevel(logging.Type.BROWSER, logging.Level.ALL);
const loggingPref = new logging.Preferences();
loggingPref.setLevel(logging.Type.BROWSER, logging.Level.ALL);
chromeOptions.setLoggingPrefs(loggingPref);
if (process.env.TEST_BROWSER_HEADLESS) {
//Use --disable-gpu to avoid an error from a missing Mesa library, as per
//See: https://chromium.googlesource.com/chromium/src/+/lkgr/headless/README.md
// Use --disable-gpu to avoid an error from a missing Mesa library, as per
// See: https://chromium.googlesource.com/chromium/src/+/lkgr/headless/README.md
chromeOptions.addArguments('headless', 'disable-gpu');
}
return new Builder()
@ -74,7 +81,7 @@ async function attemptToCreateCommand(log, browserType) {
case 'firefox':
const firefoxOptions = new firefox.Options();
if (process.env.TEST_BROWSER_HEADLESS) {
//See: https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Headless_mode
// See: https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Headless_mode
firefoxOptions.addArguments('-headless');
}
return new Builder()
@ -87,31 +94,34 @@ async function attemptToCreateCommand(log, browserType) {
}
};
const session = await buildDriverInstance(browserType);
const session = await buildDriverInstance();
if (throttleOption === 'true' && browserType === 'chrome') { //Only chrome supports this option.
if (throttleOption === 'true' && browserType === 'chrome') {
// Only chrome supports this option.
log.debug('NETWORK THROTTLED: 768k down, 256k up, 100ms latency.');
session.setNetworkConditions({
offline: false,
latency: 100, // Additional latency (ms).
download_throughput: 768 * 1024, // These speeds are in bites per second, not kilobytes.
upload_throughput: 256 * 1024
upload_throughput: 256 * 1024,
});
}
if (attemptId !== attemptCounter) return; // abort
if (attemptId !== attemptCounter) {
return;
} // abort
return { driver: session, By, Key, until, LegacyActionSequence };
}
export async function initWebDriver({ log, browserType }) {
export async function initWebDriver(log: ToolingLog, browserType: 'chrome' | 'firefox') {
const logger = getLogger('webdriver.http.Executor');
logger.setLevel(logging.Level.FINEST);
logger.addHandler((entry) => {
logger.addHandler((entry: { message: string }) => {
log.verbose(entry.message);
});
return await Promise.race([
(async () => {
await delay(2 * MINUTE);
@ -122,7 +132,7 @@ export async function initWebDriver({ log, browserType }) {
while (true) {
const command = await Promise.race([
delay(30 * SECOND),
attemptToCreateCommand(log, browserType)
attemptToCreateCommand(log, browserType),
]);
if (!command) {
@ -131,6 +141,6 @@ export async function initWebDriver({ log, browserType }) {
return command;
}
})()
})(),
]);
}

View file

@ -1151,6 +1151,13 @@
resolved "https://registry.yarnpkg.com/@types/cheerio/-/cheerio-0.22.10.tgz#780d552467824be4a241b29510a7873a7432c4a6"
integrity sha512-fOM/Jhv51iyugY7KOBZz2ThfT1gwvsGCfWxpLpZDgkGjpEO4Le9cld07OdskikLjDUQJ43dzDaVRSFwQlpdqVg==
"@types/chromedriver@^2.38.0":
version "2.38.0"
resolved "https://registry.yarnpkg.com/@types/chromedriver/-/chromedriver-2.38.0.tgz#971032b73eb7f44036f4f5bed59a7fd5b468014f"
integrity sha512-vcPGkZt1y2YVXKAY8SwCvU0u9mgw9+7tBV4HGb0YX/6bu1WXbb61bf8Y/N+xNCYwEj/Ug1UAMnhCcsSohXzRXw==
dependencies:
"@types/node" "*"
"@types/classnames@^2.2.3":
version "2.2.3"
resolved "https://registry.yarnpkg.com/@types/classnames/-/classnames-2.2.3.tgz#3f0ff6873da793870e20a260cada55982f38a9e5"
@ -6194,11 +6201,6 @@ core-js@^2.5.1, core-js@^2.5.3, core-js@^2.5.7:
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.7.tgz#f972608ff0cead68b841a16a932d0b183791814e"
integrity sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw==
core-js@~2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.3.0.tgz#fab83fbb0b2d8dc85fa636c4b9d34c75420c6d65"
integrity sha1-+rg/uwstjchfpjbEudNMdUIMbWU=
core-util-is@1.0.2, core-util-is@~1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
@ -7960,11 +7962,6 @@ es6-promise@^4.0.3, es6-promise@~4.2.4:
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.4.tgz#dc4221c2b16518760bd8c39a52d8f356fc00ed29"
integrity sha512-/NdNZVJg+uZgtm9eS3O6lrOLYmQag2DjdEXuPaHlZ6RuVqgqaVZfgYCepEIKsLqwdQArOPtC3XzRLqGGfT8KQQ==
es6-promise@~3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-3.0.2.tgz#010d5858423a5f118979665f46486a95c6ee2bb6"
integrity sha1-AQ1YWEI6XxGJeWZfRkhqlcbuK7Y=
es6-promisify@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203"
@ -13281,15 +13278,14 @@ jsx-ast-utils@^2.0.1:
array-includes "^3.0.3"
jszip@^3.1.3:
version "3.1.5"
resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.1.5.tgz#e3c2a6c6d706ac6e603314036d43cd40beefdf37"
integrity sha512-5W8NUaFRFRqTOL7ZDDrx5qWHJyBXy6velVudIzQUSoqAAYqzSh2Z7/m0Rf1QbmQJccegD0r+YZxBjzqoBiEeJQ==
version "3.2.0"
resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.2.0.tgz#1c179e8692777490ca4e9b8f3ced08f9b820da2c"
integrity sha512-4WjbsaEtBK/DHeDZOPiPw5nzSGLDEDDreFRDEgnoMwmknPjTqa+23XuYFk6NiGbeiAeZCctiQ/X/z0lQBmDVOQ==
dependencies:
core-js "~2.3.0"
es6-promise "~3.0.2"
lie "~3.1.0"
lie "~3.3.0"
pako "~1.0.2"
readable-stream "~2.0.6"
readable-stream "~2.3.6"
set-immediate-shim "~1.0.1"
just-extend@^1.1.27:
version "1.1.27"
@ -13627,10 +13623,10 @@ license-checker@^16.0.0:
spdx-satisfies "^0.1.3"
treeify "^1.0.1"
lie@~3.1.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/lie/-/lie-3.1.1.tgz#9a436b2cc7746ca59de7a41fa469b3efb76bd87e"
integrity sha1-mkNrLMd0bKWd56QfpGmz77dr2H4=
lie@~3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a"
integrity sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==
dependencies:
immediate "~3.0.5"
@ -16256,9 +16252,9 @@ pako@^0.2.5:
integrity sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU=
pako@~1.0.2:
version "1.0.8"
resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.8.tgz#6844890aab9c635af868ad5fecc62e8acbba3ea4"
integrity sha512-6i0HVbUfcKaTv+EG8ZTr75az7GFXcLYk9UyLEg7Notv/Ma+z/UG3TCoz6GiNeOrn1E/e63I0X/Hpw18jHOTUnA==
version "1.0.10"
resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.10.tgz#4328badb5086a426aa90f541977d4955da5c9732"
integrity sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw==
pako@~1.0.5:
version "1.0.6"
@ -18105,7 +18101,7 @@ read-pkg@^3.0.0:
normalize-package-data "^2.3.2"
path-type "^3.0.0"
"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.2.9, readable-stream@^2.3.0, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6:
"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.2.9, readable-stream@^2.3.0, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6:
version "2.3.6"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf"
integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==
@ -18147,7 +18143,7 @@ readable-stream@~1.1.0, readable-stream@~1.1.9:
isarray "0.0.1"
string_decoder "~0.10.x"
readable-stream@~2.0.0, readable-stream@~2.0.6:
readable-stream@~2.0.0:
version "2.0.6"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.0.6.tgz#8f90341e68a53ccc928788dacfcd11b36eb9b78e"
integrity sha1-j5A0HmilPMySh4jaz80Rs265t44=
@ -19427,7 +19423,7 @@ set-getter@^0.1.0:
dependencies:
to-object-path "^0.3.0"
set-immediate-shim@^1.0.0:
set-immediate-shim@^1.0.0, set-immediate-shim@~1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61"
integrity sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=