Add W3C Error Response (#176)

* Fixed HTTPResponseBody for W3C error
  * Refactored `getResponseForW3CError` into errors.js file
  * Changed method so that it returns `{value: {stack: '...', message: '...', error: '...'}}`
    * Message is the same as the JSONWP error message
    * Error is a static message indicating the type of error
    * 'stack' is the callstack provided in the error
  * Added W3C 'error' messages to all of the errors
* Make 'getResponseForW3CError' handle BadParametersError
* Refactor 'getResponseForJsonwpError' to errors.js
This commit is contained in:
Dan Graham
2018-01-25 10:50:50 -08:00
committed by GitHub
parent 129dd86976
commit b2a7c5e73f
4 changed files with 281 additions and 80 deletions
+213 -32
View File
@@ -1,17 +1,20 @@
import ES6Error from 'es6-error';
import _ from 'lodash';
import { util } from 'appium-support';
import { util, logger } from 'appium-support';
import HTTPStatusCodes from 'http-status-codes';
const log = logger.getLogger('MJSONWP');
// base error class for all of our errors
class MJSONWPError extends ES6Error {
constructor (msg, jsonwpCode, w3cStatus = 400) {
constructor (msg, jsonwpCode, w3cStatus, error) {
super(msg);
this.jsonwpCode = jsonwpCode;
this.error = error || 'An unknown error has occurred';
if (this.jsonwpCode === null) {
this.jsonwpCode = 13;
}
this.w3cStatus = w3cStatus;
this.w3cStatus = w3cStatus || HTTPStatusCodes.BAD_REQUEST;
}
}
@@ -23,11 +26,15 @@ class NoSuchDriverError extends MJSONWPError {
static w3cStatus () {
return HTTPStatusCodes.NOT_FOUND;
}
static error () {
return 'The given session id is not in the list of active sessions';
}
constructor (err) {
super(err || 'A session is either terminated or not started', NoSuchDriverError.code(), NoSuchDriverError.w3cStatus());
super(err || 'A session is either terminated or not started', NoSuchDriverError.code(), NoSuchDriverError.w3cStatus(), NoSuchDriverError.error());
}
}
class NoSuchElementError extends MJSONWPError {
static code () {
return 7;
@@ -35,9 +42,12 @@ class NoSuchElementError extends MJSONWPError {
static w3cStatus () {
return HTTPStatusCodes.NOT_FOUND;
}
static error () {
return 'An element could not be located on the page using the given search parameters';
}
constructor (err) {
super(err || 'An element could not be located on the page using the given ' +
'search parameters.', NoSuchElementError.code(), NoSuchElementError.w3cStatus());
'search parameters.', NoSuchElementError.code(), NoSuchElementError.w3cStatus(), NoSuchElementError.error());
}
}
@@ -45,9 +55,12 @@ class NoSuchFrameError extends MJSONWPError {
static code () {
return 8;
}
static error () {
return 'A command to switch to a frame could not be satisfied because the frame could not be found';
}
constructor (err) {
super(err || 'A request to switch to a frame could not be satisfied because ' +
'the frame could not be found.', NoSuchFrameError.code());
'the frame could not be found.', NoSuchFrameError.code(), null, NoSuchFrameError.error());
}
}
@@ -58,10 +71,13 @@ class UnknownCommandError extends MJSONWPError {
static w3cStatus () {
return HTTPStatusCodes.NOT_FOUND;
}
static error () {
return 'A command could not be executed because the remote end is not aware of it';
}
constructor (err) {
super(err || 'The requested resource could not be found, or a request was ' +
'received using an HTTP method that is not supported by the mapped ' +
'resource.', UnknownCommandError.code(), UnknownCommandError.w3cStatus());
'resource.', UnknownCommandError.code(), UnknownCommandError.w3cStatus(), UnknownCommandError.error());
}
}
@@ -69,9 +85,12 @@ class StaleElementReferenceError extends MJSONWPError {
static code () {
return 10;
}
static error () {
return 'A command failed because the referenced element is no longer attached to the DOM';
}
constructor (err) {
super(err || 'An element command failed because the referenced element is no ' +
'longer attached to the DOM.', StaleElementReferenceError.code());
'longer attached to the DOM.', StaleElementReferenceError.code(), null, StaleElementReferenceError.error());
}
}
@@ -89,10 +108,13 @@ class InvalidElementStateError extends MJSONWPError {
static code () {
return 12;
}
static error () {
return 'A command could not be completed because the element is in an invalid state';
}
constructor (err) {
super(err || 'An element command could not be completed because the element is ' +
'in an invalid state (e.g. attempting to click a disabled element).',
InvalidElementStateError.code());
InvalidElementStateError.code(), null, InvalidElementStateError.error());
}
}
@@ -103,6 +125,9 @@ class UnknownError extends MJSONWPError {
static w3cStatus () {
return HTTPStatusCodes.INTERNAL_SERVER_ERROR;
}
static error () {
return 'An unknown error occurred in the remote end while processing the command';
}
constructor (originalError) {
let origMessage = originalError;
if (originalError instanceof Error) {
@@ -114,7 +139,7 @@ class UnknownError extends MJSONWPError {
message = `${message} Original error: ${origMessage}`;
}
super(message, UnknownError.code(), UnknownError.w3cStatus());
super(message, UnknownError.code(), UnknownError.w3cStatus(), UnknownError.error());
}
}
@@ -122,8 +147,11 @@ class UnknownMethodError extends MJSONWPError {
static w3cStatus () {
return HTTPStatusCodes.METHOD_NOT_ALLOWED;
}
static error () {
return 'The requested command matched a known URL but did not match an method for that URL';
}
constructor (err) {
super(err || 'Unknown method.', null, UnknownMethodError.w3cStatus());
super(err || 'Unknown method.', null, UnknownMethodError.w3cStatus(), UnknownMethodError.error());
}
}
@@ -131,8 +159,12 @@ class UnsupportedOperationError extends MJSONWPError {
static w3cStatus () {
return HTTPStatusCodes.INTERNAL_SERVER_ERROR;
}
static error () {
return 'A command that should have executed properly cannot be supported for some reason';
}
constructor (err) {
super(err || 'A server-side error occurred. Command cannot be supported.', null, UnsupportedOperationError.w3cStatus());
super(err || 'A server-side error occurred. Command cannot be supported.',
null, UnsupportedOperationError.w3cStatus(), UnsupportedOperationError.error());
}
}
@@ -140,9 +172,39 @@ class ElementIsNotSelectableError extends MJSONWPError {
static code () {
return 15;
}
static error () {
return 'An attempt was made to select an element that cannot be selected';
}
constructor (err) {
super(err || 'An attempt was made to select an element that cannot be selected.',
ElementIsNotSelectableError.code());
ElementIsNotSelectableError.code(), null, ElementIsNotSelectableError.error());
}
}
class ElementClickInterceptedError extends MJSONWPError {
static error () {
return 'The Element Click command could not be completed because the element receiving the events is obscuring the element that was requested clicked';
}
constructor (err) {
super(err || ElementClickInterceptedError.error(), ElementIsNotSelectableError.code(), null, ElementClickInterceptedError.error());
}
}
class ElementNotInteractableError extends MJSONWPError {
static error () {
return 'A command could not be completed because the element is not pointer- or keyboard interactable';
}
constructor (err) {
super(err || ElementNotInteractableError.error(), ElementIsNotSelectableError.code(), null, ElementNotInteractableError.error());
}
}
class InsecureCertificateError extends MJSONWPError {
static error () {
return 'An action caused the user agent to hit a certificate warning, which is usually the result of an expired or invalid TLS certificate.';
}
constructor (err) {
super(err || InsecureCertificateError.error(), ElementIsNotSelectableError.code(), null, InsecureCertificateError.error());
}
}
@@ -151,11 +213,14 @@ class JavaScriptError extends MJSONWPError {
return 17;
}
static w3cStatus () {
return 500;
return HTTPStatusCodes.INTERNAL_SERVER_ERROR;
}
static error () {
return 'An error occurred while executing JavaScript supplied by the user';
}
constructor (err) {
super(err || 'An error occurred while executing user supplied JavaScript.',
JavaScriptError.code(), JavaScriptError.w3cStatus());
JavaScriptError.code(), JavaScriptError.w3cStatus(), JavaScriptError.error());
}
}
@@ -176,9 +241,12 @@ class TimeoutError extends MJSONWPError {
static w3cStatus () {
return HTTPStatusCodes.REQUEST_TIMEOUT;
}
static error () {
return 'An operation did not complete before its timeout expired';
}
constructor (err) {
super(err || 'An operation did not complete before its timeout expired.',
TimeoutError.code(), TimeoutError.w3cStatus());
TimeoutError.code(), TimeoutError.w3cStatus(), TimeoutError.error());
}
}
@@ -186,15 +254,18 @@ class NoSuchWindowError extends MJSONWPError {
static code () {
return 23;
}
static error () {
return 'A command to switch to a window could not be satisfied because the window could not be found';
}
constructor (err) {
super(err || 'A request to switch to a different window could not be satisfied ' +
'because the window could not be found.', NoSuchWindowError.code());
'because the window could not be found.', NoSuchWindowError.code(), null, NoSuchWindowError.error());
}
}
class InvalidArgumentError extends MJSONWPError {
constructor (err) {
super(err || 'The arguments passed to the command are either invalid or malformed');
super(err || 'The arguments passed to the command are either invalid or malformed', null, null, BadParametersError.error());
}
}
@@ -202,9 +273,21 @@ class InvalidCookieDomainError extends MJSONWPError {
static code () {
return 24;
}
static error () {
return 'An illegal attempt was made to set a cookie under a different domain than the current page';
}
constructor (err) {
super(err || 'An illegal attempt was made to set a cookie under a different ' +
'domain than the current page.', InvalidCookieDomainError.code());
'domain than the current page.', InvalidCookieDomainError.code(), null, InvalidCookieDomainError.error());
}
}
class InvalidCoordinatesError extends MJSONWPError {
static error () {
return 'The coordinates provided to an interactions operation are invalid';
}
constructor (err) {
super(err || InvalidCoordinatesError.error(), ElementIsNotSelectableError.code(), null, InvalidCoordinatesError.error());
}
}
@@ -212,8 +295,11 @@ class NoSuchCookieError extends MJSONWPError {
static w3cStatus () {
return HTTPStatusCodes.NOT_FOUND;
}
static error () {
return 'No cookie matching the given path name was found amongst the associated cookies of the current browsing context’s active document';
}
constructor (err) {
super(err || 'Could not find cookie', null, NoSuchCookieError.w3cStatus());
super(err || 'Could not find cookie', null, NoSuchCookieError.w3cStatus(), NoSuchCookieError.error());
}
}
@@ -224,9 +310,12 @@ class UnableToSetCookieError extends MJSONWPError {
static w3cStatus () {
return HTTPStatusCodes.INTERNAL_SERVER_ERROR;
}
static error () {
return 'A command to set a cookie’s value could not be satisfied';
}
constructor (err) {
super(err || 'A request to set a cookie\'s value could not be satisfied.',
UnableToSetCookieError.code(), UnableToSetCookieError.w3cStatus());
UnableToSetCookieError.code(), UnableToSetCookieError.w3cStatus(), UnableToSetCookieError.error());
}
}
@@ -237,9 +326,12 @@ class UnexpectedAlertOpenError extends MJSONWPError {
static w3cStatus () {
return HTTPStatusCodes.INTERNAL_SERVER_ERROR;
}
static error () {
return 'A modal dialog was open, blocking this operation';
}
constructor (err) {
super(err || 'A modal dialog was open, blocking this operation',
UnexpectedAlertOpenError.code(), UnexpectedAlertOpenError.w3cStatus());
UnexpectedAlertOpenError.code(), UnexpectedAlertOpenError.w3cStatus(), UnexpectedAlertOpenError.error());
}
}
@@ -260,9 +352,12 @@ class ScriptTimeoutError extends MJSONWPError {
static w3cStatus () {
return HTTPStatusCodes.REQUEST_TIMEOUT;
}
static error () {
return 'A script did not complete before its timeout expired';
}
constructor (err) {
super(err || 'A script did not complete before its timeout expired.',
ScriptTimeoutError.code(), ScriptTimeoutError.w3cStatus());
ScriptTimeoutError.code(), ScriptTimeoutError.w3cStatus(), ScriptTimeoutError.error());
}
}
@@ -299,9 +394,12 @@ class InvalidSelectorError extends MJSONWPError {
static code () {
return 32;
}
static error () {
return 'Argument was an invalid selector';
}
constructor (err) {
super(err || 'Argument was an invalid selector (e.g. XPath/CSS).',
InvalidSelectorError.code());
InvalidSelectorError.code(), null, InvalidSelectorError.error());
}
}
@@ -312,13 +410,16 @@ class SessionNotCreatedError extends MJSONWPError {
static w3cStatus () {
return HTTPStatusCodes.INTERNAL_SERVER_ERROR;
}
static error () {
return 'A new session could not be created';
}
constructor (details) {
let message = 'A new session could not be created.';
if (details) {
message += ` Details: ${details}`;
}
super(message, SessionNotCreatedError.code(), SessionNotCreatedError.w3cStatus());
super(message, SessionNotCreatedError.code(), SessionNotCreatedError.w3cStatus(), SessionNotCreatedError.error());
}
}
@@ -327,11 +428,23 @@ class MoveTargetOutOfBoundsError extends MJSONWPError {
return 34;
}
static w3cStatus () {
return 500;
return HTTPStatusCodes.INTERNAL_SERVER_ERROR;
}
static error () {
return 'The target for mouse interaction is not in the browser’s viewport and cannot be brought into that viewport';
}
constructor (err) {
super(err || 'Target provided for a move action is out of bounds.',
MoveTargetOutOfBoundsError.code(), MoveTargetOutOfBoundsError.w3cStatus());
MoveTargetOutOfBoundsError.code(), MoveTargetOutOfBoundsError.w3cStatus(), MoveTargetOutOfBoundsError.error());
}
}
class NoSuchAlertError extends MJSONWPError {
static error () {
return 'An attempt was made to operate on a modal dialog when one was not open';
}
constructor (err) {
super(err || NoSuchAlertError.error(), null, null, NoSuchAlertError.error());
}
}
@@ -354,6 +467,7 @@ class InvalidContextError extends MJSONWPError {
}
}
// Treat this as an alias for UnknownCommandError
class NotYetImplementedError extends MJSONWPError {
static code () {
return 13;
@@ -362,7 +476,8 @@ class NotYetImplementedError extends MJSONWPError {
return HTTPStatusCodes.NOT_FOUND; // W3C equivalent is called 'Unknown Command' (A command could not be executed because the remote end is not aware of it)
}
constructor (err) {
super(err || 'Method has not yet been implemented', NotYetImplementedError.code(), NotYetImplementedError.w3cStatus());
super(err || 'Method has not yet been implemented',
NotYetImplementedError.code(), NotYetImplementedError.w3cStatus(), UnknownCommandError.error());
}
}
@@ -382,14 +497,20 @@ class UnableToCaptureScreen extends MJSONWPError {
static w3cStatus () {
return HTTPStatusCodes.INTERNAL_SERVER_ERROR;
}
static error () {
return 'A screen capture was made impossible';
}
constructor (err) {
super(err || 'Unable to capture screen', null, UnableToCaptureScreen.w3cStatus());
super(err || 'Unable to capture screen', null, UnableToCaptureScreen.w3cStatus(), UnableToCaptureScreen.error());
}
}
// Equivalent to W3C InvalidArgumentError
class BadParametersError extends ES6Error {
static error () {
return 'The arguments passed to a command are either invalid or malformed';
}
constructor (requiredParams, actualParams, errMessage) {
let message;
if (!errMessage) {
@@ -427,6 +548,7 @@ class ProxyRequestError extends ES6Error {
//returns actual error cause for request failure based on `jsonwp.status`
return errorFromCode(this.jsonwp.status, this.jsonwp.value);
}
// TODO: Handle W3C proxying
return new UnknownError(this.message);
}
}
@@ -434,6 +556,7 @@ class ProxyRequestError extends ES6Error {
const errors = {NotYetImplementedError,
NotImplementedError,
BadParametersError,
InvalidArgumentError,
NoSuchDriverError,
NoSuchElementError,
UnknownCommandError,
@@ -441,14 +564,17 @@ const errors = {NotYetImplementedError,
ElementNotVisibleError,
InvalidElementStateError,
UnknownError,
InvalidArgumentError,
ElementIsNotSelectableError,
ElementClickInterceptedError,
ElementNotInteractableError,
InsecureCertificateError,
JavaScriptError,
XPathLookupError,
TimeoutError,
NoSuchWindowError,
NoSuchCookieError,
InvalidCookieDomainError,
InvalidCoordinatesError,
UnableToSetCookieError,
UnexpectedAlertOpenError,
NoAlertOpenError,
@@ -459,6 +585,7 @@ const errors = {NotYetImplementedError,
InvalidSelectorError,
SessionNotCreatedError,
MoveTargetOutOfBoundsError,
NoSuchAlertError,
NoSuchContextError,
InvalidContextError,
NoSuchFrameError,
@@ -500,4 +627,58 @@ function errorFromCode (code, message) {
return new UnknownError(message);
}
export { MJSONWPError, errors, isErrorType, errorFromCode };
function getResponseForW3CError (err) {
let httpStatus;
let error;
if (!err.w3cStatus) {
log.error(`Encountered internal error running command: ${err.stack}`);
err = new errors.UnknownError(err.message);
}
if (isErrorType(err, errors.BadParametersError)) {
// respond with a 400 if we have bad parameters
log.debug(`Bad parameters: ${err}`);
httpStatus = HTTPStatusCodes.BAD_REQUEST;
error = 'The arguments passed to a command are either invalid or malformed';
} else {
error = err.error;
}
httpStatus = err.w3cStatus;
let httpResBody = {
value: {
error,
message: err.message,
stacktrace: err.stack,
}
};
return [httpStatus, httpResBody];
}
function getResponseForJsonwpError (err) {
let httpStatus = HTTPStatusCodes.INTERNAL_SERVER_ERROR;
let httpResBody = {
status: err.jsonwpCode,
value: {
message: err.message
}
};
if (isErrorType(err, errors.BadParametersError)) {
// respond with a 400 if we have bad parameters
log.debug(`Bad parameters: ${err}`);
httpStatus = HTTPStatusCodes.BAD_REQUEST;
httpResBody = err.message;
} else if (isErrorType(err, errors.NotYetImplementedError) ||
isErrorType(err, errors.NotImplementedError)) {
// respond with a 501 if the method is not implemented
httpStatus = HTTPStatusCodes.NOT_IMPLEMENTED;
} else if (isErrorType(err, errors.NoSuchDriverError)) {
// respond with a 404 if there is no driver for the session
httpStatus = HTTPStatusCodes.NOT_FOUND;
}
return [httpStatus, httpResBody];
}
export { MJSONWPError, errors, isErrorType, errorFromCode, getResponseForW3CError, getResponseForJsonwpError };
+1 -41
View File
@@ -1,7 +1,7 @@
import _ from 'lodash';
import { logger, util } from 'appium-support';
import { validators } from './validators';
import { errors, isErrorType, MJSONWPError, errorFromCode } from './errors';
import { errors, isErrorType, MJSONWPError, errorFromCode, getResponseForW3CError, getResponseForJsonwpError } from './errors';
import { METHOD_MAP, NO_SESSION_ID_COMMANDS } from './routes';
import { renameKey } from '../basedriver/helpers';
import B from 'bluebird';
@@ -166,46 +166,6 @@ function makeArgs (requestParams, jsonObj, payloadParams, protocol) {
return args;
}
function getResponseForJsonwpError (err) {
let httpStatus = 500;
let httpResBody = {
status: err.jsonwpCode,
value: {
message: err.message
}
};
if (isErrorType(err, errors.BadParametersError)) {
// respond with a 400 if we have bad parameters
log.debug(`Bad parameters: ${err}`);
httpStatus = 400;
httpResBody = err.message;
} else if (isErrorType(err, errors.NotYetImplementedError) ||
isErrorType(err, errors.NotImplementedError)) {
// respond with a 501 if the method is not implemented
httpStatus = 501;
} else if (isErrorType(err, errors.NoSuchDriverError)) {
// respond with a 404 if there is no driver for the session
httpStatus = 404;
}
return [httpStatus, httpResBody];
}
function getResponseForW3CError (err) {
let httpStatus;
if (!err.w3cStatus) {
log.error(`Encountered internal error running command: ${err.stack}`);
err = new errors.UnknownError(err.message);
}
httpStatus = err.w3cStatus;
let httpResBody = err.message;
return [httpStatus, httpResBody];
}
function routeConfiguringFunction (driver) {
if (!driver.sessionExists) {
throw new Error('Drivers used with MJSONWP must implement `sessionExists`');
+36
View File
@@ -1,4 +1,5 @@
import { errors, errorFromCode } from '../..';
import { getResponseForW3CError } from '../../lib/mjsonwp/errors';
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
import _ from 'lodash';
@@ -177,3 +178,38 @@ describe('w3c Status Codes', function () {
(new errors.NoSuchFrameError()).should.have.property('w3cStatus', 400);
});
});
describe('.getResponseForW3CError', function () {
it('should return an error, message and stacktrace for just a generic exception', function () {
try {
throw new Error('Some random error');
} catch (e) {
const [httpStatus, httpResponseBody] = getResponseForW3CError(e);
httpStatus.should.equal(500);
const {error, message, stacktrace} = httpResponseBody.value;
message.should.match(/Some random error/);
error.should.equal('An unknown error occurred in the remote end while processing the command');
stacktrace.should.match(/at getResponseForW3CError/);
stacktrace.should.match(/Some random error/);
stacktrace.should.match(/errors-specs.js/);
}
});
it('should return an error, message and stacktrace for a NoSuchElementError', function () {
const noSuchElementError = new errors.NoSuchElementError('specific error message');
const [httpStatus, httpResponseBody] = getResponseForW3CError(noSuchElementError);
httpStatus.should.equal(404);
const {error, message, stacktrace} = httpResponseBody.value;
error.should.equal('An element could not be located on the page using the given search parameters');
message.should.match(/specific error message/);
stacktrace.should.match(/errors-specs.js/);
});
it('should handle BadParametersError', function () {
const badParamsError = new errors.BadParametersError('__FOO__', '__BAR__', '__HELLO_WORLD__');
const [httpStatus, httpResponseBody] = getResponseForW3CError(badParamsError);
httpStatus.should.equal(400);
const {error, message, stacktrace} = httpResponseBody.value;
error.should.equal('The arguments passed to a command are either invalid or malformed');
message.should.match(/__BAR__/);
message.should.match(/__HELLO_WORLD__/);
stacktrace.should.match(/errors-specs.js/);
});
});
+31 -7
View File
@@ -426,34 +426,51 @@ describe('MJSONWP', async function () {
});
it(`should throw 400 Bad Parameters exception if the parameters are bad`, async function () {
const {message, statusCode} = await request.post(`${sessionUrl}/actions`, {
const {statusCode, error} = await request.post(`${sessionUrl}/actions`, {
json: {
bad: 'params',
}
}).should.eventually.be.rejected;
message.should.match(/Parameters were incorrect/);
statusCode.should.equal(400);
const {error:w3cError, message, stacktrace} = error.value;
message.should.match(/Parameters were incorrect/);
stacktrace.should.match(/mjsonwp.js/);
w3cError.should.be.a.string;
w3cError.should.equal(errors.BadParametersError.error());
});
it(`should throw 404 Not Found exception if the command hasn't been implemented yet`, async function () {
const {message, statusCode} = await request.post(`${sessionUrl}/actions`, {
const {statusCode, error} = await request.post(`${sessionUrl}/actions`, {
json: {
actions: [],
}
}).should.eventually.be.rejected;
message.should.match(/Method has not yet been implemented/);
statusCode.should.equal(404);
const {error:w3cError, message, stacktrace} = error.value;
message.should.match(/Method has not yet been implemented/);
stacktrace.should.match(/mjsonwp.js/);
w3cError.should.be.a.string;
w3cError.should.equal(errors.UnknownCommandError.error());
message.should.match(/Method has not yet been implemented/);
});
it(`should throw 500 Unknown Error if the command throws an unexpected exception`, async function () {
driver.performActions = () => { throw new Error(`Didn't work`); };
const {message, statusCode} = await request.post(`${sessionUrl}/actions`, {
const {statusCode, error} = await request.post(`${sessionUrl}/actions`, {
json: {
actions: [],
}
}).should.eventually.be.rejected;
message.should.match(/500[\w\W]*Didn't work/);
statusCode.should.equal(500);
const {error:w3cError, message, stacktrace} = error.value;
stacktrace.should.match(/mjsonwp.js/);
w3cError.should.be.a.string;
w3cError.should.equal(errors.UnknownError.error());
message.should.match(/Didn't work/);
delete driver.performActions;
});
@@ -496,7 +513,7 @@ describe('MJSONWP', async function () {
it(`should fail with a 408 error if it throws a TimeoutError exception`, async () => {
sinon.stub(driver, 'setUrl', () => { throw new errors.TimeoutError; });
let {statusCode} = await request({
let {statusCode, error} = await request({
url: `${sessionUrl}/url`,
method: 'POST',
json: {
@@ -504,6 +521,13 @@ describe('MJSONWP', async function () {
}
}).should.eventually.be.rejected;
statusCode.should.equal(408);
const {error:w3cError, message, stacktrace} = error.value;
stacktrace.should.match(/mjsonwp.js/);
w3cError.should.be.a.string;
w3cError.should.equal(errors.TimeoutError.error());
message.should.match(/An operation did not complete before its timeout expired/);
sinon.restore(driver, 'setUrl');
});