Skip to content

Commit

Permalink
Handle session errors (webdriverio#4690)
Browse files Browse the repository at this point in the history
* webdriver: handle session errors

* add unit tests

* update selenium server message

* webdriver: add unit test for utils

* fix strings, add illegal w3c case

* handle Response has empty body
  • Loading branch information
mgrybyk authored and christian-bromann committed Oct 31, 2019
1 parent 24d3622 commit 2cc10d6
Show file tree
Hide file tree
Showing 4 changed files with 162 additions and 4 deletions.
4 changes: 4 additions & 0 deletions packages/wdio-cli/src/interface.js
Expand Up @@ -169,6 +169,10 @@ export default class WDIOCLInterface extends EventEmitter {
return log.warn(`Can't identify message from worker: ${JSON.stringify(event)}, ignoring!`)
}

if (event.origin === 'worker' && event.name === 'error') {
return this.log(`[${event.cid}]`, 'Error:', event.content.message || event.content.stack || event.content)
}

if (event.origin !== 'reporter') {
return this.log(event.cid, event.origin, event.name, event.content)
}
Expand Down
31 changes: 29 additions & 2 deletions packages/wdio-cli/tests/interface.test.js
Expand Up @@ -106,15 +106,42 @@ describe('cli interface', () => {
wdioClInterface.printReporters = jest.fn()

expect(wdioClInterface.onMessage({
cid: '0-0',
origin: 'worker',
name: 'error',
name: 'barfoo',
content: 'foobar'
})).toEqual([undefined, 'worker', 'error', 'foobar'])
})).toEqual(['0-0', 'worker', 'barfoo', 'foobar'])

expect(wdioClInterface.messages).toEqual({ reporter: {} })
expect(wdioClInterface.printReporters).not.toBeCalled()
})

it('should print error message on worker error', () => {
const err = { message: 'foo', stack: 'bar' }
wdioClInterface.onMessage({
cid: '0-0',
origin: 'worker',
name: 'error',
content: err
})

expect(wdioClInterface.log).toBeCalledTimes(1)
expect(wdioClInterface.log).toBeCalledWith('[0-0]', 'Error:', 'foo')
})

it('should print message on worker error', () => {
const err = 'bar'
wdioClInterface.onMessage({
cid: '0-0',
origin: 'worker',
name: 'error',
content: err
})

expect(wdioClInterface.log).toBeCalledTimes(1)
expect(wdioClInterface.log).toBeCalledWith('[0-0]', 'Error:', 'bar')
})

it('should ignore messages that do not contain a proper origin', () => {
wdioClInterface.onMessage({ foo: 'bar' })
expect(wdioClInterface.messages).toEqual({ reporter: {} })
Expand Down
62 changes: 61 additions & 1 deletion packages/webdriver/src/utils.js
Expand Up @@ -16,6 +16,13 @@ const MOBILE_CAPABILITIES = [
'device-orientation', 'deviceOrientation', 'deviceName'
]

const BROWSER_DRIVER_ERRORS = [
'unknown command: wd/hub/session', // chromedriver
'HTTP method not allowed', // geckodriver
"'POST /wd/hub/session' was not found.", // safaridriver
'Command not found' // iedriver
]

/**
* start browser session with WebDriver protocol
*/
Expand Down Expand Up @@ -45,7 +52,14 @@ export async function startWebDriverSession (params) {
}
)

const response = await sessionRequest.makeRequest(params)
let response
try {
response = await sessionRequest.makeRequest(params)
} catch (err) {
log.error(err)
const message = getSessionError(err)
throw new Error('Failed to create session.\n' + message)
}
const sessionId = response.value.sessionId || response.sessionId

/**
Expand Down Expand Up @@ -382,3 +396,49 @@ export function setupDirectConnect(params) {
params.path = directConnectPath
}
}

/**
* get human readable message from response error
* @param {Error} err response error
*/
export const getSessionError = (err) => {
// browser driver / service is not started
if (err.code === 'ECONNREFUSED') {
return `Unable to connect to "${err.address}:${err.port}", make sure browser driver is running on that address.`
}

if (!err.message) {
return 'See logs for more information.'
}

// wrong path: selenium-standalone
if (err.message.includes('Whoops! The URL specified routes to this help page.')) {
return "It seems you are running a Selenium Standalone server and point to a wrong path. Please set `path: '/wd/hub'` in your wdio.conf.js!"
}

// wrong path: chromedriver, geckodriver, etc
if (BROWSER_DRIVER_ERRORS.some(m => err.message.includes(m))) {
return "Make sure to set `path: '/'` in your wdio.conf.js!"
}

// edge driver on localhost
if (err.message.includes('Bad Request - Invalid Hostname') && err.message.includes('HTTP Error 400')) {
return "Run edge driver on 127.0.0.1 instead of localhost, ex: --host=127.0.0.1, or set `hostname: 'localhost'` in your wdio.conf.js"
}

const w3cCapMessage = '\nMake sure to add vendor prefix like "goog:", "appium:", "moz:", etc to non W3C capabilities.' +
'\nSee more https://www.w3.org/TR/webdriver/#capabilities'

// Illegal w3c capability passed to selenium standalone
if (err.message.includes('Illegal key values seen in w3c capabilities')) {
return err.message + w3cCapMessage
}

// wrong host/port, port in use, illegal w3c capability passed to selenium grid
if (err.message === 'Response has empty body') {
return 'Make sure to connect to valid hostname:port or the port is not in use.' +
'\nIf you use a grid server ' + w3cCapMessage
}

return err.message
}
69 changes: 68 additions & 1 deletion packages/webdriver/tests/utils.test.js
@@ -1,6 +1,7 @@
import {
isSuccessfulResponse, getPrototype, environmentDetector, setupDirectConnect,
getErrorFromResponseBody, isW3C, CustomRequestError
getErrorFromResponseBody, isW3C, CustomRequestError, getSessionError,
startWebDriverSession
} from '../src/utils'

import appiumResponse from './__fixtures__/appium.response.json'
Expand Down Expand Up @@ -311,4 +312,70 @@ describe('utils', () => {
expect(params.path).toBe('')
})
})

describe('getSessionError', () => {
it('should return unchanged message', () => {
expect(getSessionError({ message: 'foobar' })).toEqual('foobar')
})

it('should return "more info" if no message', () => {
expect(getSessionError({})).toEqual('See logs for more information.')
})

it('ECONNREFUSED', () => {
expect(getSessionError({
code: 'ECONNREFUSED',
address: '127.0.0.1',
port: 4444,
message: 'ECONNREFUSED 127.0.0.1:4444'
})).toContain('Unable to connect to "127.0.0.1:4444"')
})

it('path: selenium-standalone path', () => {
expect(getSessionError({
message: 'Whoops! The URL specified routes to this help page.'
})).toContain("set `path: '/wd/hub'` in")
})

it('path: chromedriver, geckodriver, etc', () => {
expect(getSessionError({
message: 'HTTP method not allowed'
})).toContain("set `path: '/'` in")
})

it('edge driver localhost issue', () => {
expect(getSessionError({
message: 'Bad Request - Invalid Hostname 400 <br> HTTP Error 400'
})).toContain('127.0.0.1 instead of localhost')
})

it('illegal w3c cap passed to selenium standalone', () => {
const message = getSessionError({
message: 'Illegal key values seen in w3c capabilities: [chromeOptions]'
})
expect(message).toContain('[chromeOptions]')
expect(message).toContain('add vendor prefix')
})

it('wrong host port, port in use, illegal w3c cap passed to grid', () => {
const message = getSessionError({
message: 'Response has empty body'
})
expect(message).toContain('valid hostname:port or the port is not in use')
expect(message).toContain('add vendor prefix')
})
})

describe('startWebDriverSession', () => {
it('should handle sessionRequest error', async () => {
let error
try {
await startWebDriverSession({})
} catch (err) {
error = err
}

expect(error.message).toContain('Failed to create session')
})
})
})

0 comments on commit 2cc10d6

Please sign in to comment.