import {errors, errorFromMJSONWPStatusCode, errorFromW3CJsonCode, isErrorType} from '../../../lib'; import {BadParametersError, getResponseForW3CError} from '../../../lib/protocol/errors'; import _ from 'lodash'; import {StatusCodes as HTTPStatusCodes} from 'http-status-codes'; import path from 'node:path'; const basename = path.basename(__filename); // Error codes and messages have been added according to JsonWireProtocol see // https://code.google.com/p/selenium/wiki/JsonWireProtocol#Response_Status_Codes let errorsList = [ { errorName: 'NoSuchDriverError', errorMsg: 'A session is either terminated or not started', error: 'invalid session id', errorCode: 6, }, { errorName: 'ElementClickInterceptedError', errorMsg: 'The Element Click command could not be completed because the element receiving the events is obscuring the element that was requested clicked', error: 'element click intercepted', }, { errorName: 'ElementNotInteractableError', errorMsg: 'A command could not be completed because the element is not pointer- or keyboard interactable', error: 'element not interactable', }, { errorName: 'InsecureCertificateError', errorMsg: 'Navigation caused the user agent to hit a certificate warning, which is usually the result of an expired or invalid TLS certificate', error: 'insecure certificate', }, { errorName: 'InvalidArgumentError', errorMsg: 'The arguments passed to the command are either invalid or malformed', error: 'invalid argument', }, { errorName: 'NoSuchElementError', errorMsg: 'An element could not be located on the page using the ' + 'given search parameters.', error: 'no such element', errorCode: 7, }, { errorName: 'NoSuchFrameError', errorMsg: 'A request to switch to a frame could not be satisfied ' + 'because the frame could not be found.', error: 'no such frame', errorCode: 8, }, { errorName: 'UnknownCommandError', errorMsg: 'The requested resource could not be found, or a request ' + 'was received using an HTTP method that is not supported by ' + 'the mapped resource.', error: 'unknown command', errorCode: 9, }, { errorName: 'StaleElementReferenceError', errorMsg: 'An element command failed because the referenced element is ' + 'no longer attached to the DOM.', error: 'stale element reference', errorCode: 10, }, { errorName: 'ElementNotVisibleError', errorMsg: 'An element command could not be completed because the ' + 'element is not visible on the page.', errorCode: 11, }, { errorName: 'InvalidElementStateError', errorMsg: 'An element command could not be completed because the element ' + 'is in an invalid state (e.g. attempting to click a disabled ' + 'element).', error: 'invalid element state', errorCode: 12, }, { errorName: 'UnknownError', errorMsg: 'An unknown server-side error occurred while processing the ' + 'command.', error: 'unknown error', errorCode: 13, }, { errorName: 'ElementIsNotSelectableError', errorMsg: 'An attempt was made to select an element that cannot ' + 'be selected.', error: 'element not selectable', errorCode: 15, }, { errorName: 'JavaScriptError', errorMsg: 'An error occurred while executing user supplied JavaScript.', error: 'javascript error', errorCode: 17, }, { errorName: 'XPathLookupError', errorMsg: 'An error occurred while searching for an element by XPath.', errorCode: 19, }, { errorName: 'TimeoutError', errorMsg: 'An operation did not complete before its timeout expired.', error: 'timeout', errorCode: 21, }, { errorName: 'NoSuchWindowError', errorMsg: 'A request to switch to a different window could not be ' + 'satisfied because the window could not be found.', error: 'no such window', errorCode: 23, }, { errorName: 'InvalidCookieDomainError', errorMsg: 'An illegal attempt was made to set a cookie under a different ' + 'domain than the current page.', error: 'invalid cookie domain', errorCode: 24, }, { errorName: 'InvalidCoordinatesError', errorMsg: 'The coordinates provided to an interactions operation are invalid.', error: 'invalid coordinates', }, { errorName: 'UnableToSetCookieError', errorMsg: `A request to set a cookie's value could not be satisfied.`, error: 'unable to set cookie', errorCode: 25, }, { errorName: 'UnexpectedAlertOpenError', errorMsg: 'A modal dialog was open, blocking this operation', error: 'unexpected alert open', errorCode: 26, }, { errorName: 'NoAlertOpenError', errorMsg: 'An attempt was made to operate on a modal dialog when one was ' + 'not open.', errorCode: 27, }, { errorName: 'ScriptTimeoutError', errorMsg: 'A script did not complete before its timeout expired.', error: 'script timeout', errorCode: 28, }, { errorName: 'InvalidElementCoordinatesError', errorMsg: 'The coordinates provided to an interactions operation are ' + 'invalid.', errorCode: 29, }, { errorName: 'IMENotAvailableError', errorMsg: 'IME was not available.', errorCode: 30, }, { errorName: 'IMEEngineActivationFailedError', errorMsg: 'An IME engine could not be started.', errorCode: 31, }, { errorName: 'InvalidSelectorError', errorMsg: 'Argument was an invalid selector (e.g. XPath/CSS).', error: 'invalid selector', errorCode: 32, }, { errorName: 'SessionNotCreatedError', errorMsg: 'A new session could not be created.', error: 'session not created', errorCode: 33, }, { errorName: 'MoveTargetOutOfBoundsError', errorMsg: 'Target provided for a move action is out of bounds.', error: 'move target out of bounds', errorCode: 34, }, { errorName: 'NoSuchAlertError', errorMsg: 'An attempt was made to operate on a modal dialog when one was not open.', error: 'no such alert', }, { errorName: 'NoSuchCookieError', errorMsg: 'No cookie matching the given path name was found amongst the associated cookies of the current browsing context’s active document', error: 'no such cookie', }, { errorName: 'NotYetImplementedError', errorMsg: 'Method has not yet been implemented', error: 'unknown method', errorCode: 405, }, { errorName: 'UnknownCommandError', errorMsg: 'The requested resource could not be found, or a request was received using an HTTP method that is not supported by the mapped resource.', error: 'unknown command', }, { errorName: 'UnknownMethodError', errorMsg: 'The requested command matched a known URL but did not match an method for that URL', error: 'unknown method', }, { errorName: 'UnsupportedOperationError', errorMsg: 'A server-side error occurred. Command cannot be supported.', error: 'unsupported operation', }, ]; describe('errors', function () { before(async function () { const chai = await import('chai'); chai.should(); }); for (let error of errorsList) { it(error.errorName + ' should have a JSONWP code or W3C code and message', function () { if (error.errorCode) { new errors[error.errorName]().should.have.property('jsonwpCode', error.errorCode); } else { new errors[error.errorName]().should.have.property('error', error.error); } new errors[error.errorName]().should.have.property('message', error.errorMsg); }); } }); describe('errorFromMJSONWPStatusCode', function () { before(async function () { const chai = await import('chai'); chai.should(); }); for (let error of errorsList) { if (error.errorName !== 'NotYetImplementedError') { it(error.errorCode + ' should return correct error', function () { if (error.errorCode) { errorFromMJSONWPStatusCode(error.errorCode).should.have.property( 'jsonwpCode', error.errorCode ); errorFromMJSONWPStatusCode(error.errorCode).should.have.property( 'message', error.errorMsg ); if (!_.includes([13, 33], error.errorCode)) { errorFromMJSONWPStatusCode(error.errorCode, 'abcd').should.have.property( 'jsonwpCode', error.errorCode ); errorFromMJSONWPStatusCode(error.errorCode, 'abcd').should.have.property( 'message', 'abcd' ); } } else { isErrorType(errorFromMJSONWPStatusCode(error.errorCode), errors.UnknownError).should.be .true; } }); } } it('should throw unknown error for unknown code', function () { errorFromMJSONWPStatusCode(99).should.have.property('jsonwpCode', 13); errorFromMJSONWPStatusCode(99).should.have.property( 'message', 'An unknown server-side error occurred ' + 'while processing the command.' ); }); }); describe('errorFromW3CJsonCode', function () { before(async function () { const chai = await import('chai'); chai.should(); }); for (let error of errorsList) { if (error.errorName !== 'NotYetImplementedError') { it(error.errorName + ' should return correct error', function () { const {error: w3cError} = error; if (w3cError) { errorFromW3CJsonCode(w3cError).error.should.equal(error.error); errorFromW3CJsonCode(w3cError).should.have.property('message', error.errorMsg); } else { isErrorType(errorFromW3CJsonCode(w3cError), errors.UnknownError).should.be.true; } }); } } it('should parse unknown errors', function () { isErrorType(errorFromW3CJsonCode('not a real error code'), errors.UnknownError).should.be.true; errorFromW3CJsonCode('not a real error code').message.should.match( /An unknown server-side error occurred/ ); errorFromW3CJsonCode('not a real error code').error.should.equal('unknown error'); }); }); describe('w3c Status Codes', function () { before(async function () { const chai = await import('chai'); chai.should(); }); it('should match the correct error codes', function () { let non400Errors = [ ['NoSuchDriverError', 404], ['NoSuchFrameError', 404], ['NoAlertOpenError', 404], ['NoSuchWindowError', 404], ['StaleElementReferenceError', 404], ['JavaScriptError', 500], ['MoveTargetOutOfBoundsError', 500], ['NoSuchCookieError', 404], ['NoSuchElementError', 404], ['ScriptTimeoutError', 408], ['SessionNotCreatedError', 500], ['TimeoutError', 408], ['UnableToSetCookieError', 500], ['UnableToCaptureScreen', 500], ['UnexpectedAlertOpenError', 500], ['UnknownCommandError', 404], ['UnknownError', 500], ['UnknownMethodError', 405], ['UnsupportedOperationError', 500], ]; // Test the errors that we don't expect to return 400 code for (let [errorName, expectedErrorCode] of non400Errors) { errors[errorName].should.exist; new errors[errorName]().should.have.property('w3cStatus', expectedErrorCode); } // Test an error that we expect to return 400 code new errors.ElementClickInterceptedError().should.have.property('w3cStatus', 400); }); }); describe('.getResponseForW3CError', function () { before(async function () { const chai = await import('chai'); chai.should(); }); 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('unknown error'); stacktrace.should.match(/caused by/); stacktrace.should.match(/Some random error/); stacktrace.should.contain(basename); } }); 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('no such element'); message.should.match(/specific error message/); stacktrace.should.contain(basename); }); it('should handle BadParametersError', function () { const badParamsError = new BadParametersError({ required: ['foo'], }, ['bar']); const [httpStatus, httpResponseBody] = getResponseForW3CError(badParamsError); httpStatus.should.equal(400); const {error, message, stacktrace} = httpResponseBody.value; error.should.equal('invalid argument'); message.should.match(/foo/); message.should.match(/bar/); stacktrace.should.contain(basename); }); it('should translate JSONWP errors', function () { const [httpStatus, httpResponseBody] = getResponseForW3CError( new errors.NoSuchElementError('My custom message') ); httpStatus.should.equal(404); const {error, message, stacktrace} = httpResponseBody.value; message.should.equal('My custom message'); error.should.equal('no such element'); stacktrace.should.exist; }); }); describe('.getActualError', function () { before(async function () { const chai = await import('chai'); chai.should(); }); describe('MJSONWP', function () { it('should map a status code 7 no such element error as a NoSuchElementError', function () { const actualError = new errors.ProxyRequestError('Error message does not matter', { value: 'does not matter', status: 7, }).getActualError(); isErrorType(actualError, errors.NoSuchElementError).should.be.true; }); it('should map a status code 10, StaleElementReferenceError', function () { const actualError = new errors.ProxyRequestError('Error message does not matter', { value: 'Does not matter', status: 10, }).getActualError(); isErrorType(actualError, errors.StaleElementReferenceError).should.be.true; }); it('should map an unknown error to UnknownError', function () { const actualError = new errors.ProxyRequestError('Error message does not matter', { value: 'Does not matter', status: -100, }).getActualError(); isErrorType(actualError, errors.UnknownError).should.be.true; }); it('should parse a JSON string', function () { const actualError = new errors.ProxyRequestError( 'Error message does not matter', JSON.stringify({ value: 'Does not matter', status: -100, }) ).getActualError(); isErrorType(actualError, errors.UnknownError).should.be.true; }); }); describe('W3C', function () { it('should map a 404 no such element error as a NoSuchElementError', function () { const actualError = new errors.ProxyRequestError( 'Error message does not matter', { value: { error: errors.NoSuchElementError.error(), }, }, HTTPStatusCodes.NOT_FOUND ).getActualError(); isErrorType(actualError, errors.NoSuchElementError).should.be.true; }); it('should map a 400 StaleElementReferenceError', function () { const actualError = new errors.ProxyRequestError( 'Error message does not matter', { value: { error: errors.StaleElementReferenceError.error(), }, }, HTTPStatusCodes.BAD_REQUEST ).getActualError(); isErrorType(actualError, errors.StaleElementReferenceError).should.be.true; }); it('should map an unknown error to UnknownError', function () { const actualError = new errors.ProxyRequestError( 'Error message does not matter', null, { value: { error: 'Not a valid w3c JSON code', }, }, 456 ).getActualError(); isErrorType(actualError, errors.UnknownError).should.be.true; }); it('should parse a JSON string', function () { const actualError = new errors.ProxyRequestError( 'Error message does not matter', JSON.stringify({ value: { error: errors.StaleElementReferenceError.error(), }, }), HTTPStatusCodes.BAD_REQUEST ).getActualError(); isErrorType(actualError, errors.StaleElementReferenceError).should.be.true; }); }); });