@christian-bromann/webdriverio
Version:
A nodejs bindings implementation for selenium 2.0/webdriver
244 lines (203 loc) • 8.32 kB
JavaScript
import url from 'url'
import request from 'request'
import merge from 'deepmerge'
import { ERROR_CODES } from '../helpers/constants'
import { RuntimeError } from './ErrorHandler'
import pkg from '../../package.json'
/**
* RequestHandler
*/
class RequestHandler {
constructor (options, eventHandler, logger) {
this.sessionID = null
this.startPath = options.path === '/' ? '' : options.path || '/wd/hub'
this.gridApiStartPath = '/grid/api'
this.eventHandler = eventHandler
this.logger = logger
this.defaultOptions = options
/**
* actually host is `hostname:port` but to keep config properties
* short we abuse host as hostname
*/
if (options.host !== undefined) {
options.hostname = options.host
delete options.host
}
/**
* set auth from user and password configs
*/
if (this.defaultOptions.user && this.defaultOptions.key) {
this.auth = {
user: this.defaultOptions.user,
pass: this.defaultOptions.key
}
delete this.defaultOptions.user
delete this.defaultOptions.key
}
}
/**
* merges default options with request options
*
* @param {Object} requestOptions request options
*/
createOptions (requestOptions, data) {
let newOptions = {}
/**
* if we don't have a session id we set it here, unless we call commands that don't require session ids, for
* example /sessions. The call to /sessions is not connected to a session itself and it therefore doesn't
* require it
*/
if (requestOptions.path.match(/:sessionId/) && !this.sessionID && requestOptions.requiresSession !== false) {
// throw session id error
throw new RuntimeError(101)
}
newOptions.uri = url.parse(
this.defaultOptions.protocol + '://' +
this.defaultOptions.hostname + ':' + this.defaultOptions.port +
(requestOptions.gridCommand ? this.gridApiStartPath : this.startPath) +
requestOptions.path.replace(':sessionId', this.sessionID || ''))
// send authentication credentials only when creating new session
if (requestOptions.path === '/session' && this.auth !== undefined) {
newOptions.auth = this.auth
}
if (requestOptions.method) {
newOptions.method = requestOptions.method
}
if (requestOptions.gridCommand) {
newOptions.gridCommand = requestOptions.gridCommand
}
newOptions.json = true
newOptions.followAllRedirects = true
newOptions.headers = {
'Connection': 'keep-alive',
'Accept': 'application/json',
'User-Agent': 'webdriverio/webdriverio/' + pkg.version
}
if (Object.keys(data).length > 0) {
newOptions.json = data
newOptions.method = 'POST'
newOptions.headers = merge(newOptions.headers, {
'Content-Type': 'application/json; charset=UTF-8',
'Content-Length': Buffer.byteLength(JSON.stringify(data), 'UTF-8')
})
}
newOptions.timeout = this.defaultOptions.connectionRetryTimeout
return newOptions
}
/**
* creates a http request with its given options and send the protocol
* command to the webdriver server
*
* @param {Object} requestOptions defines url, method and other request options
* @param {Object} data contains request data
*/
create (requestOptions, data) {
data = data || {}
/**
* allow to pass a string as shorthand argument
*/
if (typeof requestOptions === 'string') {
requestOptions = {
path: requestOptions
}
}
let fullRequestOptions = this.createOptions(requestOptions, data)
this.eventHandler.emit('command', {
method: fullRequestOptions.method || 'GET',
uri: fullRequestOptions.uri,
data: data
})
return this.request(fullRequestOptions, this.defaultOptions.connectionRetryCount).then(({body, response}) => {
/**
* if no session id was set before we've called the init command
*/
if (this.sessionID === null && requestOptions.requiresSession !== false) {
this.sessionID = body.sessionId
this.eventHandler.emit('init', {
sessionID: this.sessionID,
options: body.value,
desiredCapabilities: data.desiredCapabilities
})
this.eventHandler.emit('info', 'SET SESSION ID ' + this.sessionID)
}
if (body === undefined) {
body = {
status: 0,
orgStatusMessage: ERROR_CODES[0].message
}
}
this.eventHandler.emit('result', {
requestData: data,
requestOptions: fullRequestOptions,
response: response,
body: body
})
return body
})
}
request (fullRequestOptions, totalRetryCount, retryCount = 0) {
retryCount += 1
return new Promise((resolve, reject) => {
request(fullRequestOptions, (err, response, body) => {
/**
* Resolve with a healthy response
*/
if (!err && body && body.status === 0) {
return resolve({body, response})
}
if (fullRequestOptions.gridCommand) {
if (body.success) {
return resolve({body, response})
}
return reject(new RuntimeError({
status: 102,
type: ERROR_CODES[102].id,
message: ERROR_CODES[102].message,
orgStatusMessage: body.msg || 'unknown'
}))
}
/**
* in Appium you find sometimes more exact error messages in origValue
*/
if (body && body.value && typeof body.value.origValue === 'string' && typeof body.value.message === 'string') {
body.value.message += ' ' + body.value.origValue
}
if (body && typeof body === 'string') {
return reject(new RuntimeError(body))
}
if (body) {
let error = {
status: body.status,
type: ERROR_CODES[body.status] ? ERROR_CODES[body.status].id : 'unknown',
message: ERROR_CODES[body.status] ? ERROR_CODES[body.status].message : 'unknown',
orgStatusMessage: body.value ? body.value.message : ''
}
let screenshot = body.value && body.value.screen
if (screenshot) {
error.screenshot = screenshot
}
return reject(new RuntimeError(error))
}
if (retryCount === totalRetryCount) {
let error = null
if (err && err.message.indexOf('Nock') > -1) {
// for better unit test error output
error = err
} else {
error = new RuntimeError({
status: -1,
type: 'ECONNREFUSED',
message: 'Couldn\'t connect to selenium server',
orgStatusMessage: 'Couldn\'t connect to selenium server'
})
}
return reject(error)
}
this.request(fullRequestOptions, totalRetryCount, retryCount)
.then(resolve)
.catch(reject)
})
})
}
}
export default RequestHandler