UNPKG

cabbie-sync

Version:
432 lines (383 loc) 13.9 kB
/** * @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;