mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
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:
parent
687b8ee87a
commit
609297bac4
6 changed files with 177 additions and 66 deletions
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
23
test/functional/services/remote/browsers.ts
Normal file
23
test/functional/services/remote/browsers.ts
Normal 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',
|
||||
}
|
|
@ -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 };
|
||||
}
|
||||
|
|
|
@ -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 }) => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue