cabbie-sync
Version:
A synchronous webdriver client
432 lines (383 loc) • 13.9 kB
Flow
/**
* @flow
* This file is generated automatically, run npm run build to re-generate.
**/
import type { ApplicationCacheStatus } from './enums/application-cache-statuses';
import type { BrowserOrientation } from './enums/browser-orientations';
import type { HttpMethod } from './flow-types/http-method';
import type { Options } from './flow-types/options';
import type { Session } from './flow-types/session-data';
import type { LogEntry } from './log-entry';
import { parse as parseURL } from 'url';
import { readFileSync } from 'fs';
import { parse as parseEnv } from 'dotenv';
import depd from 'depd';
import getBrowser from 'available-browsers';
import spawn from 'cross-spawn';
import addDebugging from './add-debugging';
import autoRequest from 'sync-request';
import { fromBody } from './utils/errors';
import Connection from './connection';
import Browser from './browser';
import Debug from './debug';
import TimeOut from './time-out';
import Status from './status';
import parseResponse from './utils/parse-response';
import ActiveWindow from './active-window';
import IME from './ime';
import CookieStorage from './cookie-storage';
import LocalStorage from './local-storage';
import SessionStorage from './session-storage';
import WindowHandle from './window-handle';
const deprecate = depd('cabbie');
/*
* Create a new Driver session, remember to call `.dispose()`
* at the end to terminate the session.
*/
class Driver {
/*
* The current session. You can pass this session to createCabbieDriver to convert
* between sync and async drivers. e.g.
*
* ```js
* const driver = createCabbieDriver(oldDriver.remote, {...oldDriver.options, session: oldDriver.session});
* ```
*/
session: Session;
/*
* @private
*/
debug: Debug;
/*
* @private
*/
_connection: Connection;
/*
* The location of the selenium server
*/
remote: string;
/*
* The options that were originally passed in to createCabbieDriver
*/
options: Options;
/*
* The browser object.
*
* @private
*/
browser: Browser;
/*
* Timeout configuration
*/
timeOut: TimeOut;
/*
* The currently active window. This has most of the methods to interact with
* the the current page.
*/
activeWindow: ActiveWindow;
/*
* Get the IME object.
*/
ime: IME;
/*
* Get the Cookie-Storage object.
*/
cookieStorage: CookieStorage;
/*
* Get the Local-Storage object.
*/
localStorage: LocalStorage;
/*
* Get the Session-Storage object.
*/
sessionStorage: SessionStorage;
_closeTaxiRank: any;
constructor(remote: string, options: Options = {}) {
options = addEnvironment(options);
this.remote = remote;
this.options = options;
this.debug = new Debug(options);
let remoteURI = remote;
const capabilities = {
...(options.browser && (remote === 'saucelabs' || remote === 'browserstack' || remote === 'testingbot') ? getBrowser(remote, options.browser.name, options.browser.version, options.browser.platform) : {}),
...(options.capabilities || {})
};
const remoteAliases = {
'chrome-driver': 'chromedriver',
'taxi-rank': 'taxirank',
'sauce-labs': 'saucelabs',
'browser-stack': 'browserstack',
'testing-bot': 'testingbot'
};
if (remote in remoteAliases) {
remote = remoteAliases[remote];
}
switch (remote) {
case 'chromedriver':
remoteURI = 'http://localhost:9515/';
break;
case 'taxirank':
remoteURI = 'http://localhost:9516';
break;
case 'saucelabs':
const { sauceUsername, sauceAccessKey } = options;
if (!sauceUsername || !sauceAccessKey) {
throw new Error('To use sauce labs, you must specify SAUCE_USERNAME and SAUCE_ACCESS_KEY in enviornment variables or ' + 'provide sauceUsername and sauceAccessKey as options.');
}
remoteURI = `http://${sauceUsername}:${sauceAccessKey}@ondemand.saucelabs.com/wd/hub`;
break;
case 'browserstack':
const { browserStackUsername, browserStackAccessKey } = options;
if (!browserStackUsername || !browserStackAccessKey) {
throw new Error('To use browserstack, you must specify BROWSER_STACK_USERNAME and BROWSER_STACK_ACCESS_KEY in ' + 'enviornment variables or provide browserStackUsername and browserStackAccessKey as options.');
}
remoteURI = 'http://hub-cloud.browserstack.com/wd/hub';
capabilities['browserstack.user'] = browserStackUsername;
capabilities['browserstack.key'] = browserStackAccessKey;
break;
case 'testingbot':
const { testingBotKey, testingBotSecret } = options;
if (!testingBotKey || !testingBotSecret) {
throw new Error('To use testingbot, you must specify TESTING_BOT_KEY and TESTING_BOT_SECRET in enviornment ' + 'variables or provide testingBotKey and testingBotSecret as options.');
}
remoteURI = `http://${testingBotKey}:${testingBotSecret}@hub.testingbot.com/wd/hub`;
break;
}
this._connection = new Connection(remote, remoteURI, this.debug);
this.session = this._createSession(remote, this._connection, { ...options, capabilities });
this.browser = new Browser(this, options);
this.timeOut = new TimeOut(this);
this.activeWindow = new ActiveWindow(this, options);
this.ime = new IME(this);
this.cookieStorage = new CookieStorage(this);
this.localStorage = new LocalStorage(this);
this.sessionStorage = new SessionStorage(this);
deprecate.property(this, 'browser', 'All properties of browser are now directly available on the Driver object');
}
_createSession(remote: string, connection: Connection, options: Options): Session {
if (remote === 'taxirank') {
this._closeTaxiRank = startTaxiRank(connection);
}
return createSession(remote, this._connection, options);
}
/*
* Performs a context dependent JSON request for the current session.
* The result is parsed for errors.
* @private
*/
requestJSON(method: HttpMethod, path: string, body?: Object): any {
const session = this.session;
return this._connection.requestWithSession(session, method, path, { json: body });
}
/*
* Get an array of windows for all available windows
*/
getWindows(): Array<WindowHandle> {
const windowHandles = this.requestJSON('GET', '/window_handles');
return windowHandles.map(windowHandle => {
return new WindowHandle(this, windowHandle);
});
}
/*
* Get the current browser orientation
*/
getOrientation(): BrowserOrientation {
return this.requestJSON('GET', '/orientation');
}
/*
* Get the current browser orientation
*/
setOrientation(orientation: BrowserOrientation): void {
this.requestJSON('POST', '/orientation', { orientation });
}
/*
* Get the current geo location
*/
getGeoLocation(): { latitude: number; longitude: number; altitude: number; } {
return this.requestJSON('GET', '/location');
}
/*
* Set the current geo location
*/
setGeoLocation(loc: { latitude: number; longitude: number; altitude: number; }): void {
this.requestJSON('POST', '/location', loc);
}
/*
* Get the status of the html5 application cache
*/
getApplicationCacheStatus(): ApplicationCacheStatus {
return this.requestJSON('GET', '/application_cache/status');
}
/*
* Get the log for a given log type. Log buffer is reset after each request.
*/
getLogs(logType: string): Array<LogEntry> {
return this.requestJSON('POST', '/log', { type: logType });
}
/*
* Get available log types
*/
getLogTypes(): Array<string> {
return this.requestJSON('GET', '/log/types');
}
/*
* End this Driver session
*/
dispose(status?: Object): void {
if (status) {
this.sauceJobUpdate(status);
}
this.requestJSON('DELETE', '');
if (this._closeTaxiRank) {
this._closeTaxiRank();
}
}
/*
* Sauce Labs Methods
*/
sauceJobUpdate(body: Object): boolean {
const remote: string = this._connection.remote;
const session = this.session;
if (remote.indexOf('saucelabs') === -1) {
return false;
}
if (body === undefined) {
return true;
}
const auth = parseURL(remote).auth;
if (typeof auth !== 'string') {
throw new Error('Could not find sauce labs authentication in remote');
}
autoRequest('PUT', 'http://' + auth + '@saucelabs.com/rest/v1/' + auth.split(':')[0] + '/jobs/' + session.sessionID, { json: body });
return true;
}
// TODO: provide instructions for converting async driver to sync driver and visa versersa
}
/*
* End this Driver session
*
* Alias for `dispose`
* @method quit
*/
(Driver.prototype: any).quit = Driver.prototype.dispose;
function startTaxiRank(connection: Connection): () => void {
let started = false;
try {
const res = connection.request('GET', '/version', { noRetries: true });
if (res.statusCode === 200) {
const version = JSON.parse(res.body.toString('utf8'));
const expectedVersion = require('taxi-rank/package.json').version;
started = version === expectedVersion;
}
} catch (ex) {}
if (process.env.CI) {
if (started) return () => {};
const taxiRank = spawn(process.execPath, [require.resolve('./taxi-rank-ci.js')], {
stdio: ['inherit', 'inherit', 'inherit']
});
return () => taxiRank.kill();
}
if (!started) {
console.log('starting taxi rank');
spawn(process.execPath, [require.resolve('./taxi-rank.js')], {
detached: true,
stdio: ['ignore', 'ignore', 'ignore']
}).unref();
const startTime = Date.now();
try {
connection.request('GET', '/version');
} catch (ex) {
const endTime = Date.now();
console.log('Failed to start a detached server after ' + Math.round((endTime - startTime) / 1000) + ' seconds.');
console.log('Starting slower, attached server.');
spawn(process.execPath, [require.resolve('./taxi-rank.js')], {
stdio: ['inherit', 'inherit', 'inherit']
});
}
}
// this could be done in process for async mode, but must be a separate process for sync mode
const logger = spawn(process.execPath, [require.resolve('./taxi-rank-log.js')], {
stdio: ['inherit', 'inherit', 'inherit']
});
return () => {
logger.kill();
};
}
function createSession(remote: string, connection: Connection, options: Options): Session {
if (options.session !== undefined) {
return options.session;
}
const capabilities: Object = {};
capabilities.desiredCapabilities = options.capabilities || {};
if (options.requiredCapabilities) {
capabilities.requiredCapabilities = options.requiredCapabilities;
}
const res = connection.request('POST', '/session', { json: capabilities });
if (res.statusCode !== 200) {
switch (remote) {
case 'saucelabs':
if (res.statusCode === 401) {
const err = new Error('Sauce Labs Authentication Failed.\r\nCheck the value of the SAUCE_USERNAME and SAUCE_ACCESS_KEY ' + 'environment variables exactly match your credentials, you passed in: ' + JSON.stringify(options.sauceUsername) + ' and ' + JSON.stringify(options.sauceAccessKey) + '.');
throw err;
}
break;
case 'browserstack':
if (res.statusCode === 401) {
const err = new Error('Browser Stack Authentication Failed.\r\nCheck the value of the BROWSER_STACK_USERNAME and BROWSER_STACK_ACCESS_KEY ' + 'environment variables exactly match your credentials, you passed in: ' + JSON.stringify(options.browserStackUsername) + ' and ' + JSON.stringify(options.browserStackAccessKey) + '.');
throw err;
}
break;
case 'testingbot':
if (res.statusCode === 401) {
const err = new Error('Testing Bot Authentication Failed.\r\nCheck the value of the TESTING_BOT_KEY and TESTING_BOT_SECRET ' + 'environment variables exactly match your credentials, you passed in: ' + JSON.stringify(options.testingBotKey) + ' and ' + JSON.stringify(options.testingBotSecret) + '.');
throw err;
}
break;
}
throw new Error('Failed to start a Selenium session. ' + remote + ' responded with status code ' + res.statusCode + ':\n' + res.body.toString('utf8'));
}
const body = JSON.parse(res.body.toString('utf8'));
if (body.status === 0) {
return { sessionID: body.sessionId, capabilities: body.value };
} else {
throw fromBody(body);
}
}
const environmentAliases = {
SAUCE_USERNAME: 'sauceUsername',
SAUCE_ACCESS_KEY: 'sauceAccessKey',
BROWSER_STACK_USERNAME: 'browserStackUsername',
BROWSER_STACK_ACCESS_KEY: 'browserStackAccessKey',
TESTING_BOT_KEY: 'testingBotkey',
TESTING_BOT_SECRET: 'testingBotSecret',
// deprecated aliases
TESTINGBOT_KEY: 'testingBotkey',
TESTINGBOT_SECRET: 'testingBotSecret'
};
function addEnvironment(options: Options): Options {
const result = { ...options };
Object.keys(environmentAliases).forEach(key => {
if (result[environmentAliases[key]] === undefined && process.env[key]) {
result[environmentAliases[key]] = process.env[key];
}
});
try {
const parsedObj = parseEnv(readFileSync('.env.local', 'utf8'));
Object.keys(environmentAliases).forEach(key => {
if (result[environmentAliases[key]] === undefined && parsedObj[key]) {
result[environmentAliases[key]] = parsedObj[key];
}
});
} catch (e) {}
try {
const parsedObj = parseEnv(readFileSync('.env', 'utf8'));
Object.keys(environmentAliases).forEach(key => {
if (result[environmentAliases[key]] === undefined && parsedObj[key]) {
result[environmentAliases[key]] = parsedObj[key];
}
});
} catch (e) {}
return result;
}
addDebugging(Driver);
export default Driver;