Adding firefox to test suite (#17195) (#20282)

* Created general driver to accept an argument select the driver to run.

* Added environment variable for driver, changed package.json to use custim leadfoot, and made changes to browserdriver to use any driver.

* Made changes per PR.

* Fixed all nits.
This commit is contained in:
CJ Cenizal 2018-06-27 19:46:35 -07:00 committed by GitHub
parent 73718c54fd
commit 0da5bacd28
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 144 additions and 47 deletions

View file

@ -270,6 +270,7 @@
"event-stream": "3.3.2",
"expect.js": "0.3.1",
"faker": "1.1.0",
"geckodriver": "1.10.0",
"getopts": "2.0.0",
"globby": "^8.0.1",
"grunt": "1.0.1",
@ -296,7 +297,7 @@
"karma-junit-reporter": "1.2.0",
"karma-mocha": "1.3.0",
"karma-safari-launcher": "1.0.0",
"leadfoot": "1.7.1",
"leadfoot": "silne30/leadfoot#validation_for_response_in_ff58",
"license-checker": "^16.0.0",
"listr": "^0.14.1",
"load-grunt-config": "0.19.2",

View file

@ -127,6 +127,11 @@ export const schema = 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,

View file

@ -19,22 +19,22 @@
import { EventEmitter } from 'events';
import { createLocalChromedriverApi } from './chromedriver_local_api';
import { createRemoteChromedriverApi } from './chromedriver_remote_api';
import { createLocalBrowserDriverApi } from './browser_driver_local_api';
import { createRemoteBrowserDriverApi } from './browser_driver_remote_api';
import { ping } from './ping';
const noop = () => {};
/**
* Api for interacting with a local or remote instance of Chromedriver
* Api for interacting with a local or remote instance of a browser
*
* @type {Object}
*/
export class ChromedriverApi extends EventEmitter {
static async factory(log, url) {
export class BrowserDriverApi extends EventEmitter {
static async factory(log, url, browserType) {
return (await ping(url))
? createRemoteChromedriverApi(log, url)
: createLocalChromedriverApi(log, url);
? createRemoteBrowserDriverApi(log, url)
: createLocalBrowserDriverApi(log, url, browserType);
}
constructor(options = {}) {
@ -44,19 +44,22 @@ export class ChromedriverApi extends EventEmitter {
url,
start = noop,
stop = noop,
requiredCapabilities,
} = options;
if (!url) {
throw new TypeError('url is a required parameter');
}
this._requiredCapabilities = requiredCapabilities;
this._url = url;
this._state = undefined;
this._callCustomStart = () => start(this);
this._callCustomStop = () => stop(this);
this._beforeStopFns = [];
}
getRequiredCapabilities() {
return this._requiredCapabilities;
}
getUrl() {
return this._url;
}
@ -71,7 +74,7 @@ export class ChromedriverApi extends EventEmitter {
async start() {
if (this._state !== undefined) {
throw new Error('Chromedriver can only be started once');
throw new Error('Driver can only be started once');
}
this._state = 'started';
@ -80,7 +83,7 @@ export class ChromedriverApi extends EventEmitter {
async stop() {
if (this._state !== 'started') {
throw new Error('Chromedriver can only be stopped after being started');
throw new Error('Driver can only be stopped after being started');
}
this._state = 'stopped';

View file

@ -23,56 +23,67 @@ import { parse as parseUrl } from 'url';
import treeKill from 'tree-kill';
import { delay, fromNode as fcb } from 'bluebird';
import { path as CHROMEDRIVER_EXEC } from 'chromedriver';
import { path as FIREFOXDRIVER_EXEC } from 'geckodriver';
import { ping } from './ping';
import { ChromedriverApi } from './chromedriver_api';
import { BrowserDriverApi } from './browser_driver_api';
const START_TIMEOUT = 15000;
const PING_INTERVAL = 500;
export function createLocalChromedriverApi(log, url) {
export function createLocalBrowserDriverApi(log, url, browser) {
let runningDriver = null;
const driverName = browser + 'driver';
switch (browser) {
case 'firefox':
runningDriver = FIREFOXDRIVER_EXEC;
break;
default:
runningDriver = CHROMEDRIVER_EXEC;
}
let proc = null;
return new ChromedriverApi({
return new BrowserDriverApi({
url,
requiredCapabilities: Object.create({ browserType: browser }),
async start(api) {
const { port } = parseUrl(url);
log.info('Starting local chromedriver at port %d', port);
log.debug('Starting local ' + driverName + ' at port %d', port);
proc = spawn(CHROMEDRIVER_EXEC, [`--port=${port}`], {
proc = spawn(runningDriver, [`--port=${port}`], {
stdio: ['ignore', 'pipe', 'pipe'],
});
proc.stdout.on('data', chunk => {
log.debug('[chromedriver:stdout]', chunk.toString('utf8').trim());
log.debug('[' + driverName + ':stdout]', chunk.toString('utf8').trim());
});
proc.stderr.on('data', chunk => {
log.debug('[chromedriver:stderr]', chunk.toString('utf8').trim());
log.debug('[' + driverName + ':stderr]', chunk.toString('utf8').trim());
});
proc.on('exit', (code) => {
if (!api.isStopped() || code > 0) {
api.emit('error', new Error(`Chromedriver exited with code ${code}`));
api.emit('error', new Error(driverName + ` exited with code ${code}`));
}
});
const pingsStartedAt = Date.now();
while (true) {
log.debug('[chromedriver:ping] attempting to reach chromedriver at %j', url);
log.debug('[' + driverName + ':ping] attempting to reach at %j', url);
if (await ping(url)) {
log.debug('[chromedriver:ping] success');
log.debug('[' + driverName + ':ping] success');
break;
} else {
log.debug('[chromedriver:ping] failure');
log.debug('[' + driverName + ':ping] failure');
}
if ((Date.now() - pingsStartedAt) < START_TIMEOUT) {
log.debug('[chromedriver:ping] waiting for %d before next ping', PING_INTERVAL);
log.debug('[' + driverName + ':ping] waiting for %d before next ping', PING_INTERVAL);
await delay(PING_INTERVAL);
continue;
}
throw new Error(`Chromedriver did not start within the ${START_TIMEOUT}ms timeout`);
throw new Error(driverName + ` did not start within the ${START_TIMEOUT}ms timeout`);
}
},

View file

@ -17,10 +17,10 @@
* under the License.
*/
import { ChromedriverApi } from './chromedriver_api';
import { BrowserDriverApi } from './browser_driver_api';
export function createRemoteChromedriverApi(log, url) {
return new ChromedriverApi({
export function createRemoteBrowserDriverApi(log, url) {
return new BrowserDriverApi({
url,
start() {

View file

@ -17,4 +17,4 @@
* under the License.
*/
export { ChromedriverApi } from './chromedriver_api';
export { BrowserDriverApi } from './browser_driver_api';

View file

@ -27,15 +27,14 @@ const SECOND = 1000;
const MINUTE = 60 * SECOND;
let attemptCounter = 0;
async function attemptToCreateCommand(log, server, chromedriverApi) {
async function attemptToCreateCommand(log, server, driverApi) {
const attemptId = ++attemptCounter;
log.debug('[leadfoot:command] Creating session');
const session = await server.createSession({ browserName: 'chrome' });
const session = await server.createSession({}, driverApi.getRequiredCapabilities());
if (attemptId !== attemptCounter) return; // abort
log.debug('[leadfoot:command] Registerying session for teardown');
chromedriverApi.beforeStop(async () => session.quit());
driverApi.beforeStop(async () => session.quit());
if (attemptId !== attemptCounter) return; // abort
log.debug('[leadfoot:command] Completing session capabilities');
@ -48,7 +47,7 @@ async function attemptToCreateCommand(log, server, chromedriverApi) {
return { command: new Command(session) };
}
export async function initLeadfootCommand({ log, chromedriverApi }) {
export async function initLeadfootCommand({ log, browserDriverApi }) {
return await Promise.race([
(async () => {
await delay(2 * MINUTE);
@ -60,7 +59,7 @@ export async function initLeadfootCommand({ log, chromedriverApi }) {
// backend (chromedriver in this case). it helps with session management
// and all communication to the remote browser go through it, so we shim
// some of it's methods to enable very verbose logging.
const server = initVerboseRemoteLogging(log, new Server(chromedriverApi.getUrl()));
const server = initVerboseRemoteLogging(log, new Server(browserDriverApi.getUrl()));
// by default, calling server.createSession() automatically fixes the webdriver
// "capabilities" hash so that leadfoot knows the hoops it has to jump through
@ -78,7 +77,7 @@ export async function initLeadfootCommand({ log, chromedriverApi }) {
while (true) {
const command = await Promise.race([
delay(30 * SECOND),
attemptToCreateCommand(log, server, chromedriverApi)
attemptToCreateCommand(log, server, browserDriverApi)
]);
if (!command) {

View file

@ -19,19 +19,25 @@
import { initLeadfootCommand } from './leadfoot_command';
import { createRemoteInterceptors } from './interceptors';
import { ChromedriverApi } from './chromedriver_api';
import { BrowserDriverApi } from './browser_driver_api';
export async function RemoteProvider({ getService }) {
const lifecycle = getService('lifecycle');
const config = getService('config');
const log = getService('log');
const possibleBrowsers = ['chrome', 'firefox'];
const browserType = process.env.TEST_BROWSER_TYPE || 'chrome';
const chromedriverApi = await ChromedriverApi.factory(log, config.get('chromedriver.url'));
lifecycle.on('cleanup', async () => await chromedriverApi.stop());
if (!possibleBrowsers.includes(browserType)) {
throw new Error(`Unexpected TEST_BROWSER_TYPE "${browserType}". Valid options are ` + possibleBrowsers.join(','));
}
await chromedriverApi.start();
const browserDriverApi = await BrowserDriverApi.factory(log, config.get(browserType + 'driver.url'), browserType);
lifecycle.on('cleanup', async () => await browserDriverApi.stop());
const { command } = await initLeadfootCommand({ log, chromedriverApi });
await browserDriverApi.start();
const { command } = await initLeadfootCommand({ log, browserDriverApi: browserDriverApi });
const interceptors = createRemoteInterceptors(command);
log.info('Remote initialized');

View file

@ -507,6 +507,10 @@ add-event-listener@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/add-event-listener/-/add-event-listener-0.0.1.tgz#a76229ebc64c8aefae204a16273a2f255abea2d0"
adm-zip@0.4.7:
version "0.4.7"
resolved "https://registry.yarnpkg.com/adm-zip/-/adm-zip-0.4.7.tgz#8606c2cbf1c426ce8c8ec00174447fd49b6eafc1"
after@0.8.2:
version "0.8.2"
resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f"
@ -1851,6 +1855,10 @@ bluebird@3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.1.1.tgz#7e2e4318d62ae72a674f6aea6357bb4def1a6e41"
bluebird@3.4.6:
version "3.4.6"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.4.6.tgz#01da8d821d87813d158967e743d5fe6c62cf8c0f"
bluebird@^2.10.0:
version "2.11.0"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-2.11.0.tgz#534b9033c022c9579c56ba3b3e5a5caafbb650e1"
@ -3038,7 +3046,7 @@ create-ecdh@^4.0.0:
bn.js "^4.1.0"
elliptic "^6.0.0"
create-error-class@^3.0.0:
create-error-class@^3.0.0, create-error-class@^3.0.1:
version "3.0.2"
resolved "https://registry.yarnpkg.com/create-error-class/-/create-error-class-3.0.2.tgz#06be7abef947a3f14a30fd610671d401bca8b7b6"
dependencies:
@ -3970,7 +3978,7 @@ dtrace-provider@~0.6:
dependencies:
nan "^2.0.8"
duplexer2@~0.1.4:
duplexer2@^0.1.4, duplexer2@~0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1"
dependencies:
@ -5269,6 +5277,15 @@ gaze@^1.0.0:
dependencies:
globule "^1.0.0"
geckodriver@1.10.0:
version "1.10.0"
resolved "https://registry.yarnpkg.com/geckodriver/-/geckodriver-1.10.0.tgz#73e2f785666521d0d3a9ddc9fd5a0a5e3bf47845"
dependencies:
adm-zip "0.4.7"
bluebird "3.4.6"
got "5.6.0"
tar "4.0.2"
generate-function@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.0.0.tgz#6858fe7c0969b7d4e9093337647ac79f60dfbe74"
@ -5582,6 +5599,27 @@ good-squeeze@2.1.0:
hoek "2.x.x"
json-stringify-safe "5.0.x"
got@5.6.0:
version "5.6.0"
resolved "https://registry.yarnpkg.com/got/-/got-5.6.0.tgz#bb1d7ee163b78082bbc8eb836f3f395004ea6fbf"
dependencies:
create-error-class "^3.0.1"
duplexer2 "^0.1.4"
is-plain-obj "^1.0.0"
is-redirect "^1.0.0"
is-retry-allowed "^1.0.0"
is-stream "^1.0.0"
lowercase-keys "^1.0.0"
node-status-codes "^1.0.0"
object-assign "^4.0.1"
parse-json "^2.1.0"
pinkie-promise "^2.0.0"
read-all-stream "^3.0.0"
readable-stream "^2.0.5"
timed-out "^2.0.0"
unzip-response "^1.0.0"
url-parse-lax "^1.0.0"
got@^3.2.0:
version "3.3.1"
resolved "https://registry.yarnpkg.com/got/-/got-3.3.1.tgz#e5d0ed4af55fc3eef4d56007769d98192bcb2eca"
@ -7801,9 +7839,9 @@ lead@^1.0.0:
dependencies:
flush-write-stream "^1.0.2"
leadfoot@1.7.1:
version "1.7.1"
resolved "https://registry.yarnpkg.com/leadfoot/-/leadfoot-1.7.1.tgz#7dae9961f01c9bec862b621ccece0826c8c64599"
leadfoot@silne30/leadfoot#validation_for_response_in_ff58:
version "1.7.4"
resolved "https://codeload.github.com/silne30/leadfoot/tar.gz/460c8ed67c08177adc9c79243ff045880f4cad09"
dependencies:
dojo "2.0.0-alpha.7"
jszip "2.5.0"
@ -8715,6 +8753,18 @@ minimost@^1.0.0:
dependencies:
minimist "^1.2.0"
minipass@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.2.1.tgz#5ada97538b1027b4cf7213432428578cb564011f"
dependencies:
yallist "^3.0.0"
minizlib@^1.0.4:
version "1.1.0"
resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.1.0.tgz#11e13658ce46bc3a70a267aac58359d1e0c29ceb"
dependencies:
minipass "^2.2.1"
mixin-deep@^1.2.0:
version "1.3.1"
resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.1.tgz#a49e7268dce1a0d9698e45326c5626df3543d0fe"
@ -9039,6 +9089,10 @@ node-sass@^4.9.0:
stdout-stream "^1.4.0"
"true-case-path" "^1.0.2"
node-status-codes@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/node-status-codes/-/node-status-codes-1.0.0.tgz#5ae5541d024645d32a58fcddc9ceecea7ae3ac2f"
nodemailer@^4.6.4:
version "4.6.4"
resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-4.6.4.tgz#f0d72d0c6a6ec5f4369fa8f4bf5127a31baa2014"
@ -9527,7 +9581,7 @@ parse-headers@^2.0.0:
for-each "^0.3.2"
trim "0.0.1"
parse-json@^2.2.0:
parse-json@^2.1.0, parse-json@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9"
dependencies:
@ -12401,6 +12455,16 @@ tar@2.2.0:
fstream "^1.0.2"
inherits "2"
tar@4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/tar/-/tar-4.0.2.tgz#e8e22bf3eec330e5c616d415a698395e294e8fad"
dependencies:
chownr "^1.0.1"
minipass "^2.2.1"
minizlib "^1.0.4"
mkdirp "^0.5.0"
yallist "^3.0.2"
tar@^2.0.0, tar@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.1.tgz#8e4d2a256c0e2185c6b18ad694aec968b83cb1d1"
@ -13049,6 +13113,10 @@ unset-value@^1.0.0:
has-value "^0.3.1"
isobject "^3.0.0"
unzip-response@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-1.0.2.tgz#b984f0877fc0a89c2c773cc1ef7b5b232b5b06fe"
unzip-response@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-2.0.1.tgz#d2f0f737d16b0615e72a6935ed04214572d56f97"
@ -13966,6 +14034,10 @@ yallist@^2.0.0, yallist@^2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52"
yallist@^3.0.0, yallist@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.2.tgz#8452b4bb7e83c7c188d8041c1a837c773d6d8bb9"
yargs-parser@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-5.0.0.tgz#275ecf0d7ffe05c77e64e7c86e4cd94bf0e1228a"