Update services to support functional tests in Firefox (#35648)

* add Browsers enum & changes to support FF

* revert moveTo signature to Promise<void>
This commit is contained in:
Dmitry Lemeshko 2019-05-02 15:02:59 +02:00 committed by GitHub
parent 687b8ee87a
commit 609297bac4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 177 additions and 66 deletions

View file

@ -25,8 +25,12 @@ import { WebElementWrapper } from './lib/web_element_wrapper';
import { FtrProviderContext } from '../ftr_provider_context';
import { Browsers } from './remote/browsers';
export async function BrowserProvider({ getService }: FtrProviderContext) {
const { driver, Key, LegacyActionSequence } = await getService('__webdriver__').init();
const { driver, Key, LegacyActionSequence, browserType } = await getService(
'__webdriver__'
).init();
class BrowserService {
/**
@ -34,11 +38,26 @@ export async function BrowserProvider({ getService }: FtrProviderContext) {
*/
public readonly keys: IKey = Key;
/**
* Browser name
*/
public readonly browserType: string = browserType;
/**
* Is WebDriver instance W3C compatible
*/
isW3CEnabled = (driver as any).executor_.w3c === true;
/**
* Returns instance of Actions API based on driver w3c flag
* https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/webdriver_exports_WebDriver.html#actions
*/
public getActions(): any {
return this.isW3CEnabled
? (driver as any).actions()
: (driver as any).actions({ bridge: true });
}
/**
* Retrieves the a rect describing the current top-level window's size and position.
* https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/webdriver_exports_Window.html
@ -116,20 +135,40 @@ export async function BrowserProvider({ getService }: FtrProviderContext) {
xOffset?: number,
yOffset?: number
): Promise<void> {
const mouse = (driver.actions() as any).mouse();
const actions = (driver as any).actions({ bridge: true });
if (element instanceof WebElementWrapper) {
await actions
.pause(mouse)
.move({ origin: element._webElement })
.perform();
} else if (isNaN(xOffset!) || isNaN(yOffset!) === false) {
await actions
.pause(mouse)
.move({ origin: { x: xOffset, y: yOffset } })
.perform();
} else {
throw new Error('Element or coordinates should be provided');
switch (this.browserType) {
case Browsers.Firefox: {
// Workaround for scrolling bug in Firefox
// https://github.com/mozilla/geckodriver/issues/776
await this.getActions()
.move({ x: 0, y: 0 })
.perform();
if (element instanceof WebElementWrapper) {
await this.getActions()
.move({ x: xOffset || 10, y: yOffset || 10, origin: element._webElement })
.perform();
} else {
await this.getActions()
.move({ origin: { x: xOffset, y: yOffset } })
.perform();
}
break;
}
case Browsers.Chrome: {
if (element instanceof WebElementWrapper) {
await this.getActions()
.pause(this.getActions().mouse)
.move({ origin: element._webElement })
.perform();
} else {
await this.getActions()
.pause(this.getActions().mouse)
.move({ origin: { x: xOffset, y: yOffset } })
.perform();
}
break;
}
default:
throw new Error(`unsupported browser: ${this.browserType}`);
}
}
@ -155,34 +194,63 @@ export async function BrowserProvider({ getService }: FtrProviderContext) {
: { x: 0, y: 0 };
// tslint:disable-next-line:variable-name
const _toOffset = to.offset ? { x: to.offset.x || 0, y: to.offset.y || 0 } : { x: 0, y: 0 };
// tslint:disable-next-line:variable-name
const _convertPointW3C = async (point: any, offset: { x: any; y: any }) => {
if (point.location instanceof WebElementWrapper) {
const position = await point.location.getPosition();
return {
x: Math.round(position.x + offset.x),
y: Math.round(position.y + offset.y),
};
} else {
return {
x: Math.round(point.location.x + offset.x),
y: Math.round(point.location.y + offset.y),
};
}
};
// tslint:disable-next-line:variable-name
const _convertPoint = (point: any) => {
return point.location instanceof WebElementWrapper
? point.location._webElement
: point.location;
};
if (from.location instanceof WebElementWrapper) {
_from = from.location._webElement;
} else {
_from = from.location;
}
if (this.isW3CEnabled) {
// tslint:disable-next-line:variable-name
_from = await _convertPointW3C(from, _fromOffset);
// tslint:disable-next-line:variable-name
_to = await _convertPointW3C(to, _toOffset);
// tslint:disable-next-line:variable-name
const _offset = { x: _to.x - _from.x, y: _to.y - _from.y };
if (to.location instanceof WebElementWrapper) {
_to = to.location._webElement;
} else {
_to = to.location;
}
if (from.location instanceof WebElementWrapper && typeof to.location.x === 'number') {
const actions = (driver as any).actions({ bridge: true });
return await actions
.move({ origin: _from })
return await this.getActions()
.move({ x: _from.x, y: _from.y, origin: 'pointer' })
.press()
.move({ x: _to.x, y: _to.y, origin: 'pointer' })
.move({ x: _offset.x, y: _offset.y, origin: 'pointer' })
.release()
.perform();
} else {
return await new LegacyActionSequence(driver)
.mouseMove(_from, _fromOffset)
.mouseDown()
.mouseMove(_to, _toOffset)
.mouseUp()
.perform();
// until Chromedriver is not supporting W3C Webdriver Actions API
// tslint:disable-next-line:variable-name
_from = _convertPoint(from);
// tslint:disable-next-line:variable-name
_to = _convertPoint(to);
if (from.location instanceof WebElementWrapper && typeof to.location.x === 'number') {
return await this.getActions()
.move({ origin: _from })
.press()
.move({ x: _to.x, y: _to.y, origin: 'pointer' })
.release()
.perform();
} else {
return await new LegacyActionSequence(driver)
.mouseMove(_from, _fromOffset)
.mouseDown()
.mouseMove(_to, _toOffset)
.mouseUp()
.perform();
}
}
}
@ -216,11 +284,10 @@ export async function BrowserProvider({ getService }: FtrProviderContext) {
public async pressKeys(keys: string | string[]): Promise<void>;
public async pressKeys(...args: string[]): Promise<void>;
public async pressKeys(...args: string[]): Promise<void> {
const actions = this.isW3CEnabled
? driver.actions()
: (driver as any).actions({ bridge: true });
const chord = this.keys.chord(...args);
await actions.sendKeys(chord).perform();
await this.getActions()
.sendKeys(chord)
.perform();
}
/**
@ -237,18 +304,16 @@ export async function BrowserProvider({ getService }: FtrProviderContext) {
public async clickMouseButton(element: any, xOffset: number, yOffset: number): Promise<void>;
public async clickMouseButton(element: WebElementWrapper): Promise<void>;
public async clickMouseButton(...args: unknown[]): Promise<void> {
const mouse = (driver.actions() as any).mouse();
const actions = (driver as any).actions({ bridge: true });
const arg0 = args[0];
if (arg0 instanceof WebElementWrapper) {
await actions
.pause(mouse)
await this.getActions()
.pause(this.getActions().mouse)
.move({ origin: arg0._webElement })
.click()
.perform();
} else if (isNaN(args[1] as number) || isNaN(args[2] as number) === false) {
await actions
.pause(mouse)
await this.getActions()
.pause(this.getActions().mouse)
.move({ origin: { x: args[1], y: args[2] } })
.click()
.perform();
@ -308,11 +373,14 @@ export async function BrowserProvider({ getService }: FtrProviderContext) {
* @return {Promise<void>}
*/
public async doubleClick(element?: WebElementWrapper): Promise<void> {
const actions = (driver as any).actions({ bridge: true });
if (element instanceof WebElementWrapper) {
await actions.doubleClick(element._webElement).perform();
await this.getActions()
.doubleClick(element._webElement)
.perform();
} else {
await actions.doubleClick().perform();
await this.getActions()
.doubleClick()
.perform();
}
}

View file

@ -30,13 +30,21 @@ export async function FindProvider({ getService }: FtrProviderContext) {
const driver = webdriver.driver;
const By = webdriver.By;
const until = webdriver.until;
const browserType = webdriver.browserType;
const WAIT_FOR_EXISTS_TIME = config.get('timeouts.waitForExists');
const defaultFindTimeout = config.get('timeouts.find');
const fixedHeaderHeight = config.get('layout.fixedHeaderHeight');
const wrap = (webElement: WebElement | WebElementWrapper) =>
new WebElementWrapper(webElement, webdriver, defaultFindTimeout, fixedHeaderHeight, log);
new WebElementWrapper(
webElement,
webdriver,
defaultFindTimeout,
fixedHeaderHeight,
log,
browserType
);
const wrapAll = (webElements: Array<WebElement | WebElementWrapper>) => webElements.map(wrap);

View file

@ -27,6 +27,7 @@ import testSubjSelector from '@kbn/test-subj-selector';
import { ToolingLog } from '@kbn/dev-utils';
// @ts-ignore not supported yet
import { scrollIntoViewIfNecessary } from './scroll_into_view_if_necessary';
import { Browsers } from '../../remote/browsers';
interface Driver {
driver: WebDriver;
@ -52,7 +53,8 @@ export class WebElementWrapper {
private webDriver: Driver,
private timeout: number,
private fixedHeaderHeight: number,
private logger: ToolingLog
private logger: ToolingLog,
private browserType: string
) {
if (webElement instanceof WebElementWrapper) {
return webElement;
@ -79,7 +81,8 @@ export class WebElementWrapper {
this.webDriver,
this.timeout,
this.fixedHeaderHeight,
this.logger
this.logger,
this.browserType
);
}
@ -324,12 +327,18 @@ export class WebElementWrapper {
*/
public async moveMouseTo(): Promise<void> {
await this.scrollIntoViewIfNecessary();
const mouse = (this.driver.actions() as any).mouse();
const actions = (this.driver as any).actions({ bridge: true });
await actions
.pause(mouse)
.move({ origin: this._webElement })
.perform();
if (this.browserType === Browsers.Firefox) {
const actions = (this.driver as any).actions();
await actions.move({ x: 0, y: 0 }).perform();
await actions.move({ x: 10, y: 10, origin: this._webElement }).perform();
} else {
const mouse = (this.driver.actions() as any).mouse();
const actions = (this.driver as any).actions({ bridge: true });
await actions
.pause(mouse)
.move({ origin: this._webElement })
.perform();
}
}
/**

View file

@ -0,0 +1,23 @@
/*
* 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 enum Browsers {
Chrome = 'chrome',
Firefox = 'firefox',
}

View file

@ -19,14 +19,15 @@
import { FtrProviderContext } from '../../ftr_provider_context';
import { initWebDriver } from './webdriver';
import { Browsers } from './browsers';
export async function RemoteProvider({ getService }: FtrProviderContext) {
const lifecycle = getService('lifecycle');
const log = getService('log');
const config = getService('config');
const browserType = process.env.TEST_BROWSER_TYPE || 'chrome';
const browserType: Browsers = (process.env.TEST_BROWSER_TYPE as Browsers) || Browsers.Chrome;
if (browserType !== 'chrome' && browserType !== 'firefox') {
if (browserType !== Browsers.Chrome && browserType !== Browsers.Firefox) {
throw new Error(
`Unexpected TEST_BROWSER_TYPE "${browserType}", only "chrome" and "firefox" are supported`
);
@ -34,11 +35,11 @@ export async function RemoteProvider({ getService }: FtrProviderContext) {
const { driver, By, Key, until, LegacyActionSequence } = await initWebDriver(log, browserType);
const caps = await driver.getCapabilities();
const browserVersion = caps.get(browserType === 'chrome' ? 'version' : 'browserVersion');
const browserVersion = caps.get(browserType === Browsers.Chrome ? 'version' : 'browserVersion');
log.info(`Remote initialized: ${caps.get('browserName')} ${browserVersion}`);
if (browserType === 'chrome') {
if (browserType === Browsers.Chrome) {
log.info(`Chromedriver version: ${caps.get('chrome').chromedriverVersion}`);
}
@ -64,5 +65,5 @@ export async function RemoteProvider({ getService }: FtrProviderContext) {
lifecycle.on('cleanup', async () => await driver.quit());
return { driver, By, Key, until, LegacyActionSequence };
return { driver, By, Key, until, LegacyActionSequence, browserType };
}

View file

@ -37,6 +37,8 @@ import { getLogger } from 'selenium-webdriver/lib/logging';
import { preventParallelCalls } from './prevent_parallel_calls';
import { Browsers } from './browsers';
const throttleOption = process.env.TEST_THROTTLE_NETWORK;
const SECOND = 1000;
const MINUTE = 60 * SECOND;
@ -56,7 +58,7 @@ Executor.prototype.execute = preventParallelCalls(
);
let attemptCounter = 0;
async function attemptToCreateCommand(log: ToolingLog, browserType: 'chrome' | 'firefox') {
async function attemptToCreateCommand(log: ToolingLog, browserType: Browsers) {
const attemptId = ++attemptCounter;
log.debug('[webdriver] Creating session');
@ -114,7 +116,7 @@ async function attemptToCreateCommand(log: ToolingLog, browserType: 'chrome' | '
return { driver: session, By, Key, until, LegacyActionSequence };
}
export async function initWebDriver(log: ToolingLog, browserType: 'chrome' | 'firefox') {
export async function initWebDriver(log: ToolingLog, browserType: Browsers) {
const logger = getLogger('webdriver.http.Executor');
logger.setLevel(logging.Level.FINEST);
logger.addHandler((entry: { message: string }) => {