org.openqa.selenium.remote.ErrorHandler Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of selenium-remote-driver Show documentation
Show all versions of selenium-remote-driver Show documentation
Selenium automates browsers. That's it! What you do with that power is entirely up to you.
// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The SFC licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
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. */
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;
}
@SuppressWarnings("unchecked")
public Response throwIfResponseFailed(Response response, long duration) throws RuntimeException {
if ("success".equals(response.getState())) {
return response;
}
if (response.getValue() instanceof Throwable) {
Throwable throwable = (Throwable) response.getValue();
if (throwable instanceof Error) {
throw (Error) throwable;
}
if (throwable instanceof RuntimeException) {
throw (RuntimeException) throwable;
}
throw new RuntimeException(throwable);
}
Class extends WebDriverException> 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 extends Throwable> throwableType = (Class extends Throwable>) 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