diff --git a/java/src/org/openqa/selenium/remote/ErrorHandler.java b/java/src/org/openqa/selenium/remote/ErrorHandler.java index 59a52ddb389ca..86bc6ef79ef4f 100644 --- a/java/src/org/openqa/selenium/remote/ErrorHandler.java +++ b/java/src/org/openqa/selenium/remote/ErrorHandler.java @@ -17,66 +17,12 @@ package org.openqa.selenium.remote; -import java.lang.reflect.Constructor; -import java.math.BigDecimal; -import java.math.RoundingMode; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.function.Function; -import org.openqa.selenium.UnhandledAlertException; import org.openqa.selenium.WebDriverException; -/** Maps exceptions to status codes for sending over the wire. */ +/** A helper to throw decoded exceptions. */ public class ErrorHandler { - private static final String MESSAGE = "message"; - private static final String SCREEN_SHOT = "screen"; - private static final String CLASS = "class"; - private static final String STACK_TRACE = "stackTrace"; - private static final String LINE_NUMBER = "lineNumber"; - private static final String METHOD_NAME = "methodName"; - private static final String CLASS_NAME = "className"; - private static final String FILE_NAME = "fileName"; - private static final String UNKNOWN_CLASS = ""; - private static final String UNKNOWN_METHOD = ""; - private static final String UNKNOWN_FILE = null; - - private final ErrorCodes errorCodes; - - private boolean includeServerErrors; - - public ErrorHandler() { - this(true); - } - - /** - * @param includeServerErrors Whether to include server-side details in thrown exceptions if the - * information is available. - */ - public ErrorHandler(boolean includeServerErrors) { - this.includeServerErrors = includeServerErrors; - this.errorCodes = new ErrorCodes(); - } - - /** - * @param includeServerErrors Whether to include server-side details in thrown exceptions if the - * information is available. - * @param codes The ErrorCodes object to use for linking error codes to exceptions. - */ - public ErrorHandler(ErrorCodes codes, boolean includeServerErrors) { - this.includeServerErrors = includeServerErrors; - this.errorCodes = codes; - } - - public boolean isIncludeServerErrors() { - return includeServerErrors; - } - - public void setIncludeServerErrors(boolean includeServerErrors) { - this.includeServerErrors = includeServerErrors; - } + public ErrorHandler() {} @SuppressWarnings("unchecked") public Response throwIfResponseFailed(Response response, long duration) throws RuntimeException { @@ -92,254 +38,9 @@ public Response throwIfResponseFailed(Response response, long duration) throws R if (throwable instanceof RuntimeException) { throw (RuntimeException) throwable; } - throw new RuntimeException(throwable); + throw new WebDriverException(throwable); } - Class outerErrorType = - errorCodes.getExceptionType(response.getStatus()); - - Object value = response.getValue(); - String message = null; - Throwable cause = null; - - if (value instanceof Map) { - Map rawErrorData = (Map) value; - if (!rawErrorData.containsKey(MESSAGE) && rawErrorData.containsKey("value")) { - try { - rawErrorData = (Map) rawErrorData.get("value"); - } catch (ClassCastException cce) { - message = String.valueOf(cce); - } - } - try { - message = (String) rawErrorData.get(MESSAGE); - } catch (ClassCastException e) { - // Ok, try to recover gracefully. - message = String.valueOf(e); - } - - Throwable serverError = rebuildServerError(rawErrorData, response.getStatus()); - - // If serverError is null, then the server did not provide a className (only expected if - // the server is a Java process) or a stack trace. The lack of a className is OK, but - // not having a stacktrace really hurts our ability to debug problems. - if (serverError == null) { - if (includeServerErrors) { - // TODO: this should probably link to a wiki article with more info. - message += " (WARNING: The server did not provide any stacktrace information)"; - } - } else if (!includeServerErrors) { - // TODO: wiki article with more info. - message += " (WARNING: The client has suppressed server-side stacktraces)"; - } else { - cause = serverError; - if (cause.getStackTrace() == null || cause.getStackTrace().length == 0) { - message += " (WARNING: The server did not provide any stacktrace information)"; - } - } - - if (rawErrorData.get(SCREEN_SHOT) != null) { - cause = new ScreenshotException(String.valueOf(rawErrorData.get(SCREEN_SHOT)), cause); - } - } else if (value != null) { - message = String.valueOf(value); - } - - String duration1 = duration(duration); - - if (message != null && !message.contains(duration1)) { - message = message + duration1; - } - - WebDriverException toThrow = null; - - if (outerErrorType.equals(UnhandledAlertException.class) && value instanceof Map) { - toThrow = createUnhandledAlertException(value); - } - - if (toThrow == null) { - toThrow = - createThrowable( - outerErrorType, - new Class[] {String.class, Throwable.class, Integer.class}, - new Object[] {message, cause, response.getStatus()}); - } - - if (toThrow == null) { - toThrow = - createThrowable( - outerErrorType, - new Class[] {String.class, Throwable.class}, - new Object[] {message, cause}); - } - - if (toThrow == null) { - toThrow = - createThrowable(outerErrorType, new Class[] {String.class}, new Object[] {message}); - } - - if (toThrow == null) { - toThrow = new WebDriverException(message, cause); - } - - throw toThrow; - } - - @SuppressWarnings("unchecked") - private UnhandledAlertException createUnhandledAlertException(Object value) { - Map rawErrorData = (Map) value; - if (rawErrorData.containsKey("alert") || rawErrorData.containsKey("alertText")) { - Object alertText = rawErrorData.get("alertText"); - if (alertText == null) { - Map alert = (Map) rawErrorData.get("alert"); - if (alert != null) { - alertText = alert.get("text"); - } - } - return createThrowable( - UnhandledAlertException.class, - new Class[] {String.class, String.class}, - new Object[] {rawErrorData.get("message"), alertText}); - } - return null; - } - - private String duration(long duration) { - String prefix = "\nCommand duration or timeout: "; - if (duration < 1000) { - return prefix + duration + " milliseconds"; - } - return prefix - + (new BigDecimal(duration).divide(new BigDecimal(1000)).setScale(2, RoundingMode.HALF_UP)) - + " seconds"; - } - - private T createThrowable( - Class clazz, Class[] parameterTypes, Object[] parameters) { - try { - Constructor constructor = clazz.getConstructor(parameterTypes); - return constructor.newInstance(parameters); - } catch (OutOfMemoryError | ReflectiveOperationException e) { - // Do nothing - fall through. - } - return null; - } - - private Throwable rebuildServerError(Map rawErrorData, int responseStatus) { - - if (rawErrorData.get(CLASS) == null && rawErrorData.get(STACK_TRACE) == null) { - // Not enough information for us to try to rebuild an error. - return null; - } - - Throwable toReturn = null; - String message = (String) rawErrorData.get(MESSAGE); - Class clazz = null; - - // First: allow Remote Driver to specify the Selenium Server internal exception - if (rawErrorData.get(CLASS) != null) { - String className = (String) rawErrorData.get(CLASS); - try { - clazz = Class.forName(className); - } catch (ClassNotFoundException ignored) { - // Ok, fall-through - } - } - - // If the above fails, map Response Status to Exception class - if (null == clazz) { - clazz = errorCodes.getExceptionType(responseStatus); - } - - if (clazz.equals(UnhandledAlertException.class)) { - toReturn = createUnhandledAlertException(rawErrorData); - } else if (Throwable.class.isAssignableFrom(clazz)) { - @SuppressWarnings({"unchecked"}) - Class throwableType = (Class) clazz; - toReturn = - createThrowable(throwableType, new Class[] {String.class}, new Object[] {message}); - } - - if (toReturn == null) { - toReturn = new UnknownServerException(message); - } - - // Note: if we have a class name above, we should always have a stack trace. - // The inverse is not always true. - StackTraceElement[] stackTrace = new StackTraceElement[0]; - if (rawErrorData.get(STACK_TRACE) != null) { - @SuppressWarnings({"unchecked"}) - List> stackTraceInfo = - (List>) rawErrorData.get(STACK_TRACE); - - stackTrace = - stackTraceInfo.stream() - .map(entry -> new FrameInfoToStackFrame().apply(entry)) - .filter(Objects::nonNull) - .toArray(StackTraceElement[]::new); - } - - toReturn.setStackTrace(stackTrace); - return toReturn; - } - - /** Exception used as a place holder if the server returns an error without a stack trace. */ - public static class UnknownServerException extends WebDriverException { - private UnknownServerException(String s) { - super(s); - } - } - - /** - * Function that can rebuild a {@link StackTraceElement} from the frame info included with a - * WebDriver JSON response. - */ - private static class FrameInfoToStackFrame - implements Function, StackTraceElement> { - @Override - public StackTraceElement apply(Map frameInfo) { - if (frameInfo == null) { - return null; - } - - Optional maybeLineNumberInteger = Optional.empty(); - - final Object lineNumberObject = frameInfo.get(LINE_NUMBER); - if (lineNumberObject instanceof Number) { - maybeLineNumberInteger = Optional.of((Number) lineNumberObject); - } else if (lineNumberObject != null) { - // might be a Number as a String - try { - maybeLineNumberInteger = Optional.of(Integer.parseInt(lineNumberObject.toString())); - } catch (NumberFormatException e) { - maybeLineNumberInteger = Optional.empty(); - } - } - - // default -1 for unknown, see StackTraceElement constructor javadoc - final int lineNumber = maybeLineNumberInteger.orElse(-1).intValue(); - - // Gracefully handle remote servers that don't (or can't) send back - // complete stack trace info. At least some of this information should - // be included... - String className = - frameInfo.containsKey(CLASS_NAME) - ? toStringOrNull(frameInfo.get(CLASS_NAME)) - : UNKNOWN_CLASS; - String methodName = - frameInfo.containsKey(METHOD_NAME) - ? toStringOrNull(frameInfo.get(METHOD_NAME)) - : UNKNOWN_METHOD; - String fileName = - frameInfo.containsKey(FILE_NAME) - ? toStringOrNull(frameInfo.get(FILE_NAME)) - : UNKNOWN_FILE; - - return new StackTraceElement(className, methodName, fileName, lineNumber); - } - - private static String toStringOrNull(Object o) { - return o == null ? null : o.toString(); - } + throw new WebDriverException("response failed with unknown status: " + response.getState()); } } diff --git a/java/src/org/openqa/selenium/remote/codec/w3c/W3CHttpResponseCodec.java b/java/src/org/openqa/selenium/remote/codec/w3c/W3CHttpResponseCodec.java index 33cbf8a5f0e71..e97d685ccbd8b 100644 --- a/java/src/org/openqa/selenium/remote/codec/w3c/W3CHttpResponseCodec.java +++ b/java/src/org/openqa/selenium/remote/codec/w3c/W3CHttpResponseCodec.java @@ -18,7 +18,6 @@ package org.openqa.selenium.remote.codec.w3c; import static java.net.HttpURLConnection.HTTP_BAD_GATEWAY; -import static java.net.HttpURLConnection.HTTP_BAD_METHOD; import static java.net.HttpURLConnection.HTTP_GATEWAY_TIMEOUT; import static java.net.HttpURLConnection.HTTP_INTERNAL_ERROR; import static org.openqa.selenium.json.Json.MAP_TYPE; @@ -88,15 +87,12 @@ public Response decode(HttpResponse encodedResponse) { // text"} if (!encodedResponse.isSuccessful()) { LOG.fine("Processing an error"); - if (HTTP_BAD_METHOD == encodedResponse.getStatus()) { - response.setState("unknown command"); - response.setStatus(ErrorCodes.UNKNOWN_COMMAND); - response.setValue(content); - } else if (HTTP_GATEWAY_TIMEOUT == encodedResponse.getStatus() + if (HTTP_GATEWAY_TIMEOUT == encodedResponse.getStatus() || HTTP_BAD_GATEWAY == encodedResponse.getStatus()) { response.setState("unknown error"); response.setStatus(ErrorCodes.UNHANDLED_ERROR); - response.setValue(content); + response.setValue( + new WebDriverException("http gateway error: " + encodedResponse.getStatus())); } else { Map org = json.toType(content, MAP_TYPE); Map obj; diff --git a/java/test/org/openqa/selenium/remote/ErrorHandlerTest.java b/java/test/org/openqa/selenium/remote/ErrorHandlerTest.java index c4e9e18ba1db8..034aa3c243103 100644 --- a/java/test/org/openqa/selenium/remote/ErrorHandlerTest.java +++ b/java/test/org/openqa/selenium/remote/ErrorHandlerTest.java @@ -20,31 +20,11 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import com.google.common.collect.ImmutableMap; -import java.util.Collections; -import java.util.HashMap; import java.util.Map; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; -import org.openqa.selenium.InvalidCookieDomainException; -import org.openqa.selenium.InvalidElementStateException; -import org.openqa.selenium.InvalidSelectorException; -import org.openqa.selenium.JavascriptException; -import org.openqa.selenium.NoAlertPresentException; -import org.openqa.selenium.NoSuchElementException; -import org.openqa.selenium.NoSuchFrameException; -import org.openqa.selenium.NoSuchSessionException; -import org.openqa.selenium.NoSuchWindowException; -import org.openqa.selenium.ScriptTimeoutException; -import org.openqa.selenium.SessionNotCreatedException; -import org.openqa.selenium.StaleElementReferenceException; -import org.openqa.selenium.TimeoutException; -import org.openqa.selenium.UnableToSetCookieException; -import org.openqa.selenium.UnhandledAlertException; -import org.openqa.selenium.UnsupportedCommandException; import org.openqa.selenium.WebDriverException; -import org.openqa.selenium.interactions.MoveTargetOutOfBoundsException; import org.openqa.selenium.json.Json; @Tag("UnitTests") @@ -71,416 +51,38 @@ private static Map toMap(Object o) { @BeforeEach public void setUp() { handler = new ErrorHandler(); - handler.setIncludeServerErrors(true); } @Test void testShouldNotThrowIfResponseWasASuccess() { - handler.throwIfResponseFailed(createResponse(ErrorCodes.SUCCESS), 100); + handler.throwIfResponseFailed(createResponse("success"), 100); // All is well if this doesn't throw. } @Test - void testThrowsCorrectExceptionTypes() { - assertThrowsCorrectExceptionType(ErrorCodes.NO_SUCH_WINDOW, NoSuchWindowException.class); - assertThrowsCorrectExceptionType(ErrorCodes.NO_SUCH_FRAME, NoSuchFrameException.class); - assertThrowsCorrectExceptionType(ErrorCodes.NO_SUCH_ELEMENT, NoSuchElementException.class); - assertThrowsCorrectExceptionType(ErrorCodes.UNKNOWN_COMMAND, UnsupportedCommandException.class); - assertThrowsCorrectExceptionType( - ErrorCodes.METHOD_NOT_ALLOWED, UnsupportedCommandException.class); - assertThrowsCorrectExceptionType( - ErrorCodes.STALE_ELEMENT_REFERENCE, StaleElementReferenceException.class); - assertThrowsCorrectExceptionType( - ErrorCodes.INVALID_ELEMENT_STATE, InvalidElementStateException.class); - assertThrowsCorrectExceptionType(ErrorCodes.XPATH_LOOKUP_ERROR, InvalidSelectorException.class); - } - - private void assertThrowsCorrectExceptionType( - int status, Class type) { - assertThatExceptionOfType(RuntimeException.class) - .isThrownBy(() -> handler.throwIfResponseFailed(createResponse(status), 123)) - .satisfies(e -> assertThat(type.isAssignableFrom(e.getClass())).isTrue()); - } - - @Test - void testShouldThrowAVanillaWebDriverExceptionIfServerDoesNotProvideAValue() { - Response response = createResponse(ErrorCodes.UNHANDLED_ERROR); + void testShouldThrowIfResponseWasNotSuccess() { + Response response = createResponse("other"); assertThatExceptionOfType(WebDriverException.class) .isThrownBy(() -> handler.throwIfResponseFailed(response, 123)) - .withNoCause() - .withMessageContaining(new WebDriverException().getMessage()); - } - - @Test - void testShouldNotSetCauseIfResponseValueIsJustAString() { - assertThatExceptionOfType(WebDriverException.class) - .isThrownBy( - () -> - handler.throwIfResponseFailed( - createResponse(ErrorCodes.UNHANDLED_ERROR, "boom"), 123)) - .withNoCause() - .satisfies(expected -> assertThat(expected).isExactlyInstanceOf(WebDriverException.class)) - .withMessageContaining("boom") - .withMessageContaining(new WebDriverException().getMessage()); - } - - @Test - void testCauseShouldBeAnUnknownServerExceptionIfServerOnlyReturnsAMessage() { - assertThatExceptionOfType(WebDriverException.class) - .isThrownBy( - () -> - handler.throwIfResponseFailed( - createResponse(ErrorCodes.UNHANDLED_ERROR, ImmutableMap.of("message", "boom")), - 123)) - .withNoCause() - .withMessageContaining("boom") - .withMessageContaining(new WebDriverException().getMessage()); - } - - @Test - void testCauseShouldUseTheNamedClassIfAvailableOnTheClassPath() { - assertThatExceptionOfType(WebDriverException.class) - .isThrownBy( - () -> - handler.throwIfResponseFailed( - createResponse( - ErrorCodes.UNHANDLED_ERROR, - ImmutableMap.of( - "message", "boom", "class", NullPointerException.class.getName())), - 123)) - .withMessage( - new WebDriverException( - "boom (WARNING: The server did not provide any stacktrace information)\n" - + "Command duration or timeout: 123 milliseconds") - .getMessage()) - .withCauseInstanceOf(NullPointerException.class) - .satisfies(expected -> assertThat(expected.getCause()).hasMessage("boom")); - } - - @Test - void testCauseStackTraceShouldBeEmptyIfTheServerDidNotProvideThatInformation() { - assertThatExceptionOfType(WebDriverException.class) - .isThrownBy( - () -> - handler.throwIfResponseFailed( - createResponse( - ErrorCodes.UNHANDLED_ERROR, - ImmutableMap.of( - "message", "boom", "class", NullPointerException.class.getName())), - 1234)) - .withMessage( - new WebDriverException( - "boom (WARNING: The server did not provide any stacktrace information)\n" - + "Command duration or timeout: 1.23 seconds") - .getMessage()) - .withCauseInstanceOf(NullPointerException.class) - .satisfies( - expected -> { - assertThat(expected.getCause()).hasMessage("boom"); - assertThat(expected.getCause().getStackTrace()).isEmpty(); - }); - } - - @Test - void testShouldBeAbleToRebuildASerializedException() { - RuntimeException serverError = - new RuntimeException("foo bar baz!\nCommand duration or timeout: 123 milliseconds"); - - assertThatExceptionOfType(WebDriverException.class) - .isThrownBy( - () -> - handler.throwIfResponseFailed( - createResponse(ErrorCodes.UNHANDLED_ERROR, toMap(serverError)), 123)) - .withMessage(new WebDriverException(serverError.getMessage()).getMessage()) - .withCauseInstanceOf(serverError.getClass()) - .satisfies( - expected -> { - assertThat(expected.getCause().getMessage()).isEqualTo(serverError.getMessage()); - assertStackTracesEqual( - expected.getCause().getStackTrace(), serverError.getStackTrace()); - }); - } - - @Test - void testShouldDefaultToWebDriverExceptionIfClassIsNotSpecified() { - RuntimeException serverError = new RuntimeException("foo bar baz!"); - Map data = toMap(serverError); - data.remove("class"); - - assertThatExceptionOfType(WebDriverException.class) - .isThrownBy( - () -> - handler.throwIfResponseFailed( - createResponse(ErrorCodes.UNHANDLED_ERROR, data), 123)) - .withMessage( - new WebDriverException( - serverError.getMessage() + "\nCommand duration or timeout: 123 milliseconds", - new WebDriverException()) - .getMessage()) - .withCauseInstanceOf(WebDriverException.class) - .satisfies( - expected -> { - Throwable cause = expected.getCause(); - assertThat(cause.getMessage()) - .isEqualTo(new WebDriverException(serverError.getMessage()).getMessage()); - assertStackTracesEqual(serverError.getStackTrace(), cause.getStackTrace()); - }); - } - - @Test - void testShouldStillTryToBuildWebDriverExceptionIfClassIsNotProvidedAndStackTraceIsNotForJava() { - Map data = - ImmutableMap.of( - "message", - "some error message", - "stackTrace", - Collections.singletonList( - ImmutableMap.of( - "lineNumber", 1224, - "methodName", "someMethod", - "className", "MyClass", - "fileName", "Resource.m"))); - - assertThatExceptionOfType(WebDriverException.class) - .isThrownBy( - () -> - handler.throwIfResponseFailed( - createResponse(ErrorCodes.UNHANDLED_ERROR, data), 123)) - .withMessage( - new WebDriverException( - "some error message\nCommand duration or timeout: 123 milliseconds", - new WebDriverException()) - .getMessage()) - .withCauseInstanceOf(WebDriverException.class) - .satisfies( - expected -> { - StackTraceElement[] expectedTrace = { - new StackTraceElement("MyClass", "someMethod", "Resource.m", 1224) - }; - WebDriverException helper = new WebDriverException("some error message"); - helper.setStackTrace(expectedTrace); - - Throwable cause = expected.getCause(); - assertThat(cause.getMessage()).isEqualTo(helper.getMessage()); - assertStackTracesEqual(expectedTrace, cause.getStackTrace()); - }); + .withNoCause(); } @Test - void testShouldNotBuildWebDriverExceptionIfClassAndStackTraceIsNull() { - Map data = new HashMap<>(); - data.put("message", "some error message"); - data.put("class", null); - data.put("stackTrace", null); - - assertThatExceptionOfType(WebDriverException.class) - .isThrownBy( - () -> - handler.throwIfResponseFailed( - createResponse(ErrorCodes.UNHANDLED_ERROR, data), 123)) - .withMessageStartingWith( - new WebDriverException( - "some error message (WARNING: The server did not provide any stacktrace" - + " information)\n" - + "Command duration or timeout: 123 milliseconds", - new WebDriverException()) - .getMessage()); - } - - @Test - void testShouldNotBuildWebDriverExceptionIfClassNullAndStackTraceNotNull() { - Map data = new HashMap<>(); - data.put("message", "some error message"); - data.put("class", null); - data.put( - "stackTrace", - Collections.singletonList( - ImmutableMap.of( - "lineNumber", 1224, - "methodName", "someMethod", - "className", "MyClass", - "fileName", "Resource.m"))); - - assertThatExceptionOfType(WebDriverException.class) - .isThrownBy( - () -> - handler.throwIfResponseFailed( - createResponse(ErrorCodes.UNHANDLED_ERROR, data), 123)) - .withMessageStartingWith( - new WebDriverException( - "some error message\nCommand duration or timeout: 123 milliseconds", - new WebDriverException()) - .getMessage()); - } - - @Test - void testShouldNotBuildWebDriverExceptionIfClassNotNullAndStackTraceNull() { - Map data = new HashMap<>(); - data.put("message", "some error message"); - data.put("class", "a"); - data.put("stackTrace", null); - - assertThatExceptionOfType(WebDriverException.class) - .isThrownBy( - () -> - handler.throwIfResponseFailed( - createResponse(ErrorCodes.UNHANDLED_ERROR, data), 123)) - .withMessageStartingWith( - new WebDriverException( - "some error message (WARNING: The server did not provide any stacktrace" - + " information)\n" - + "Command duration or timeout: 123 milliseconds", - new WebDriverException()) - .getMessage()); - } - - @Test - void testToleratesNonNumericLineNumber() { - Map data = - ImmutableMap.of( - "message", - "some error message", - "stackTrace", - Collections.singletonList( - ImmutableMap.of( - "lineNumber", "some string, might be empty or 'Not available'", - "methodName", "someMethod", - "className", "MyClass", - "fileName", "Resource.m"))); - - assertThatExceptionOfType(WebDriverException.class) - .isThrownBy( - () -> - handler.throwIfResponseFailed( - createResponse(ErrorCodes.UNHANDLED_ERROR, data), 123)) - .withMessage( - new WebDriverException( - "some error message\nCommand duration or timeout: 123 milliseconds", - new WebDriverException()) - .getMessage()) - .withCauseInstanceOf(WebDriverException.class) - .satisfies( - expected -> { - StackTraceElement[] expectedTrace = { - new StackTraceElement("MyClass", "someMethod", "Resource.m", -1) - }; - WebDriverException helper = new WebDriverException("some error message"); - helper.setStackTrace(expectedTrace); - - Throwable cause = expected.getCause(); - assertThat(cause.getMessage()).isEqualTo(helper.getMessage()); - assertStackTracesEqual(expectedTrace, cause.getStackTrace()); - }); - } - - @Test - void testToleratesNumericLineNumberAsString() { - Map data = - ImmutableMap.of( - "message", - "some error message", - "stackTrace", - Collections.singletonList( - ImmutableMap.of( - "lineNumber", "1224", // number as a string - "methodName", "someMethod", - "className", "MyClass", - "fileName", "Resource.m"))); - - assertThatExceptionOfType(WebDriverException.class) - .isThrownBy( - () -> - handler.throwIfResponseFailed( - createResponse(ErrorCodes.UNHANDLED_ERROR, data), 123)) - .withMessage( - new WebDriverException( - "some error message\nCommand duration or timeout: 123 milliseconds", - new WebDriverException()) - .getMessage()) - .withCauseInstanceOf(WebDriverException.class) - .satisfies( - expected -> { - StackTraceElement[] expectedTrace = { - new StackTraceElement("MyClass", "someMethod", "Resource.m", 1224) - }; - WebDriverException helper = new WebDriverException("some error message"); - helper.setStackTrace(expectedTrace); - - Throwable cause = expected.getCause(); - assertThat(cause.getMessage()).isEqualTo(helper.getMessage()); - - assertStackTracesEqual(expectedTrace, cause.getStackTrace()); - }); - } - - @Test - void testShouldIndicateWhenTheServerReturnedAnExceptionThatWasSuppressed() { - RuntimeException serverError = new RuntimeException("foo bar baz!"); - - handler.setIncludeServerErrors(false); - + void testShouldThrowAVanillaWebDriverExceptionIfServerDoesNotProvideAValue() { + Response response = createResponse("unknown error"); assertThatExceptionOfType(WebDriverException.class) - .isThrownBy( - () -> - handler.throwIfResponseFailed( - createResponse(ErrorCodes.UNHANDLED_ERROR, toMap(serverError)), 123)) + .isThrownBy(() -> handler.throwIfResponseFailed(response, 123)) .withNoCause() - .withMessageContaining(serverError.getMessage()) .withMessageContaining(new WebDriverException().getMessage()); } - @Test - void testStatusCodesRaisedBackToStatusMatches() { - Map> exceptions = new HashMap<>(); - exceptions.put(ErrorCodes.NO_SUCH_SESSION, NoSuchSessionException.class); - exceptions.put(ErrorCodes.NO_SUCH_ELEMENT, NoSuchElementException.class); - exceptions.put(ErrorCodes.NO_SUCH_FRAME, NoSuchFrameException.class); - exceptions.put(ErrorCodes.UNKNOWN_COMMAND, UnsupportedCommandException.class); - exceptions.put(ErrorCodes.STALE_ELEMENT_REFERENCE, StaleElementReferenceException.class); - exceptions.put(ErrorCodes.INVALID_ELEMENT_STATE, InvalidElementStateException.class); - exceptions.put(ErrorCodes.UNHANDLED_ERROR, WebDriverException.class); - exceptions.put(ErrorCodes.JAVASCRIPT_ERROR, JavascriptException.class); - exceptions.put(ErrorCodes.XPATH_LOOKUP_ERROR, InvalidSelectorException.class); - exceptions.put(ErrorCodes.TIMEOUT, TimeoutException.class); - exceptions.put(ErrorCodes.NO_SUCH_WINDOW, NoSuchWindowException.class); - exceptions.put(ErrorCodes.INVALID_COOKIE_DOMAIN, InvalidCookieDomainException.class); - exceptions.put(ErrorCodes.UNABLE_TO_SET_COOKIE, UnableToSetCookieException.class); - exceptions.put(ErrorCodes.UNEXPECTED_ALERT_PRESENT, UnhandledAlertException.class); - exceptions.put(ErrorCodes.NO_ALERT_PRESENT, NoAlertPresentException.class); - exceptions.put(ErrorCodes.ASYNC_SCRIPT_TIMEOUT, ScriptTimeoutException.class); - exceptions.put(ErrorCodes.INVALID_SELECTOR_ERROR, InvalidSelectorException.class); - exceptions.put(ErrorCodes.SESSION_NOT_CREATED, SessionNotCreatedException.class); - exceptions.put(ErrorCodes.MOVE_TARGET_OUT_OF_BOUNDS, MoveTargetOutOfBoundsException.class); - exceptions.put(ErrorCodes.INVALID_XPATH_SELECTOR, InvalidSelectorException.class); - exceptions.put(ErrorCodes.INVALID_XPATH_SELECTOR_RETURN_TYPER, InvalidSelectorException.class); - - for (Map.Entry> exception : exceptions.entrySet()) { - assertThatExceptionOfType(WebDriverException.class) - .isThrownBy(() -> handler.throwIfResponseFailed(createResponse(exception.getKey()), 123)) - .satisfies( - e -> { - assertThat(e.getClass().getSimpleName()) - .isEqualTo(exception.getValue().getSimpleName()); - - // all of the special invalid selector exceptions are just mapped to the generic - // invalid selector - int expected = - e instanceof InvalidSelectorException - ? ErrorCodes.INVALID_SELECTOR_ERROR - : exception.getKey(); - assertThat(new ErrorCodes().toStatusCode(e)).isEqualTo(expected); - }); - } - } - - private Response createResponse(int status) { - return createResponse(status, null); + private Response createResponse(String state) { + return createResponse(state, null); } - private Response createResponse(int status, Object value) { + private Response createResponse(String state, Object value) { Response response = new Response(); - response.setStatus(status); - response.setState(new ErrorCodes().toState(status)); + response.setState(state); response.setValue(value); return response; } diff --git a/java/test/org/openqa/selenium/remote/RemotableByTest.java b/java/test/org/openqa/selenium/remote/RemotableByTest.java index df526e46efa0d..39b5e608c36b0 100644 --- a/java/test/org/openqa/selenium/remote/RemotableByTest.java +++ b/java/test/org/openqa/selenium/remote/RemotableByTest.java @@ -206,9 +206,11 @@ private Response createResponse(Object value) { private Response createError(Exception e) { Response res = new Response(); - res.setStatus(errorCodes.toStatusCode(e)); - res.setState(errorCodes.toState(res.getStatus())); - res.setValue(ErrorCodec.createDefault().encode(e)); + var encoded = ErrorCodec.createDefault().encode(e); + @SuppressWarnings("unchecked") + Map map = (Map) encoded.get("value"); + res.setState(map.get("error").toString()); + res.setValue(e); return res; } diff --git a/java/test/org/openqa/selenium/remote/RemoteWebDriverUnitTest.java b/java/test/org/openqa/selenium/remote/RemoteWebDriverUnitTest.java index 7d7302740307b..995c22428ea4b 100644 --- a/java/test/org/openqa/selenium/remote/RemoteWebDriverUnitTest.java +++ b/java/test/org/openqa/selenium/remote/RemoteWebDriverUnitTest.java @@ -791,7 +791,7 @@ void canHandleResponseWithErrorCodeButNoExceptionReturnedByCommandExecutor() { assertThatExceptionOfType(WebDriverException.class) .isThrownBy(fixture.driver::getCurrentUrl) - .withMessageStartingWith("BOOM!!!") + .withMessageStartingWith("response failed with unknown status: element click intercepted") .withMessageContaining("Build info: ") .withMessageContaining("Driver info: org.openqa.selenium.remote.RemoteWebDriver") .withMessageContaining(String.format("Session ID: %s", fixture.driver.getSessionId())) diff --git a/java/test/org/openqa/selenium/remote/RemoteWebElementTest.java b/java/test/org/openqa/selenium/remote/RemoteWebElementTest.java index d593c44293c4a..902ca85b17525 100644 --- a/java/test/org/openqa/selenium/remote/RemoteWebElementTest.java +++ b/java/test/org/openqa/selenium/remote/RemoteWebElementTest.java @@ -165,7 +165,7 @@ void canHandleResponseWithErrorCodeButNoExceptionReturnedByCommandExecutor() { assertThatExceptionOfType(WebDriverException.class) .isThrownBy(fixture.element::click) - .withMessageStartingWith("BOOM!!!") + .withMessageStartingWith("response failed with unknown status: element click intercepted") .withMessageContaining("Build info: ") .withMessageContaining("Driver info: org.openqa.selenium.remote.RemoteWebDriver") .withMessageContaining(String.format("Session ID: %s", fixture.driver.getSessionId())) diff --git a/java/test/org/openqa/selenium/remote/codec/w3c/W3CHttpResponseCodecTest.java b/java/test/org/openqa/selenium/remote/codec/w3c/W3CHttpResponseCodecTest.java index df480c356a99c..88c7e92ad83b1 100644 --- a/java/test/org/openqa/selenium/remote/codec/w3c/W3CHttpResponseCodecTest.java +++ b/java/test/org/openqa/selenium/remote/codec/w3c/W3CHttpResponseCodecTest.java @@ -79,7 +79,7 @@ void shouldBeAbleToHandleGatewayTimeoutError() { Response decoded = new W3CHttpResponseCodec().decode(response); assertThat(decoded.getStatus()).isEqualTo(ErrorCodes.UNHANDLED_ERROR); - assertThat(decoded.getValue()).isEqualTo(responseString); + assertThat(decoded.getValue()).isInstanceOf(WebDriverException.class); } @Test @@ -105,7 +105,7 @@ void shouldBeAbleToHandleBadGatewayError() { Response decoded = new W3CHttpResponseCodec().decode(response); assertThat(decoded.getStatus()).isEqualTo(ErrorCodes.UNHANDLED_ERROR); - assertThat(decoded.getValue()).isEqualTo(responseString); + assertThat(decoded.getValue()).isInstanceOf(WebDriverException.class); } @Test