org.openqa.selenium.remote.ErrorCodes.java Source code

Java tutorial

Introduction

Here is the source code for org.openqa.selenium.remote.ErrorCodes.java

Source

// 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 static java.net.HttpURLConnection.HTTP_INTERNAL_ERROR;

import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;

import org.openqa.selenium.Beta;
import org.openqa.selenium.ElementClickInterceptedException;
import org.openqa.selenium.ElementNotInteractableException;
import org.openqa.selenium.ElementNotSelectableException;
import org.openqa.selenium.ElementNotVisibleException;
import org.openqa.selenium.ImeActivationFailedException;
import org.openqa.selenium.ImeNotAvailableException;
import org.openqa.selenium.InvalidArgumentException;
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.NoSuchCookieException;
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.InvalidCoordinatesException;
import org.openqa.selenium.interactions.MoveTargetOutOfBoundsException;

import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.logging.Logger;
import java.util.stream.Collectors;

/**
 * Defines common error codes for the wire protocol.
 */
public class ErrorCodes {

    @Beta
    public static final String SUCCESS_STRING = "success";
    public static final int SUCCESS = 0;
    public static final int NO_SUCH_SESSION = 6;
    public static final int NO_SUCH_ELEMENT = 7;
    public static final int NO_SUCH_FRAME = 8;
    public static final int UNKNOWN_COMMAND = 9;
    public static final int STALE_ELEMENT_REFERENCE = 10;
    public static final int ELEMENT_NOT_VISIBLE = 11;
    public static final int INVALID_ELEMENT_STATE = 12;
    public static final int UNHANDLED_ERROR = 13;
    public static final int ELEMENT_NOT_SELECTABLE = 15;
    public static final int JAVASCRIPT_ERROR = 17;
    public static final int XPATH_LOOKUP_ERROR = 19;
    public static final int TIMEOUT = 21;
    public static final int NO_SUCH_WINDOW = 23;
    public static final int INVALID_COOKIE_DOMAIN = 24;
    public static final int UNABLE_TO_SET_COOKIE = 25;
    public static final int UNEXPECTED_ALERT_PRESENT = 26;
    public static final int NO_ALERT_PRESENT = 27;
    public static final int ASYNC_SCRIPT_TIMEOUT = 28;
    public static final int INVALID_ELEMENT_COORDINATES = 29;
    public static final int IME_NOT_AVAILABLE = 30;
    public static final int IME_ENGINE_ACTIVATION_FAILED = 31;
    public static final int INVALID_SELECTOR_ERROR = 32;
    public static final int SESSION_NOT_CREATED = 33;
    public static final int MOVE_TARGET_OUT_OF_BOUNDS = 34;
    public static final int INVALID_XPATH_SELECTOR = 51;
    public static final int INVALID_XPATH_SELECTOR_RETURN_TYPER = 52;

    // json wire protocol doesn't have analogous status codes for
    // these new W3C status response 'codes', so making some up!
    public static final int ELEMENT_NOT_INTERACTABLE = 60;
    public static final int INVALID_ARGUMENT = 61;
    public static final int NO_SUCH_COOKIE = 62;
    public static final int UNABLE_TO_CAPTURE_SCREEN = 63;
    public static final int ELEMENT_CLICK_INTERCEPTED = 64;

    // The following error codes are derived straight from HTTP return codes.
    public static final int METHOD_NOT_ALLOWED = 405;

    private static final Logger log = Logger.getLogger(ErrorCodes.class.getName());

    public String toState(Integer status) {
        if (status == null) {
            return toState(UNHANDLED_ERROR);
        }

        if (SUCCESS == status) {
            return SUCCESS_STRING;
        }

        Set<String> possibleMatches = KNOWN_ERRORS.stream().filter(knownError -> knownError.getJsonCode() == status)
                .filter(KnownError::isCanonicalForW3C).map(KnownError::getW3cCode).collect(Collectors.toSet());

        return Iterables.getOnlyElement(possibleMatches, "unhandled error");
    }

    public int toStatus(String webdriverState, Optional<Integer> httpStatus) {
        if (SUCCESS_STRING.equals(webdriverState)) {
            return 0;
        }

        List<KnownError> possibleMatches = KNOWN_ERRORS.stream()
                .filter(knownError -> knownError.getW3cCode().equals(webdriverState))
                .filter(KnownError::isCanonicalForW3C).sorted(Comparator.comparingInt(KnownError::getJsonCode))
                .collect(Collectors.toList());

        if (possibleMatches.isEmpty()) {
            return UNHANDLED_ERROR;
        }
        KnownError error = possibleMatches.get(0);
        if (httpStatus.isPresent() && httpStatus.get() != error.getW3cHttpStatus()) {
            log.info(String.format("HTTP Status: '%d' -> incorrect JSON status mapping for '%s' (%d expected)",
                    httpStatus.get(), webdriverState, error.getW3cHttpStatus()));
        }
        return error.getJsonCode();
    }

    public int getHttpStatusCode(Throwable throwable) {
        return KNOWN_ERRORS.stream().filter(error -> error.getException().isAssignableFrom(throwable.getClass()))
                .filter(KnownError::isCanonicalForW3C).map(KnownError::getW3cHttpStatus).findAny()
                .orElse(HTTP_INTERNAL_ERROR);
    }

    /**
     * Returns the exception type that corresponds to the given {@code statusCode}. All unrecognized
     * status codes will be mapped to {@link WebDriverException WebDriverException.class}.
     *
     * @param statusCode The status code to convert.
     * @return The exception type that corresponds to the provided status code or {@code null} if
     *         {@code statusCode == 0}.
     */
    public Class<? extends WebDriverException> getExceptionType(int statusCode) {
        if (SUCCESS == statusCode) {
            return null;
        }

        // We know that the tuple of (status code, exception) is distinct.
        Set<Class<? extends WebDriverException>> allPossibleExceptions = KNOWN_ERRORS.stream()
                .filter(knownError -> knownError.getJsonCode() == statusCode).map(KnownError::getException)
                .collect(Collectors.toSet());

        return Iterables.getOnlyElement(allPossibleExceptions, WebDriverException.class);
    }

    public Class<? extends WebDriverException> getExceptionType(String webdriverState) {
        Set<Class<? extends WebDriverException>> possibleMatches = KNOWN_ERRORS.stream()
                .filter(knownError -> knownError.getW3cCode().equals(webdriverState))
                .filter(KnownError::isCanonicalForW3C).map(KnownError::getException).collect(Collectors.toSet());

        return Iterables.getOnlyElement(possibleMatches, WebDriverException.class);
    }

    public int toStatusCode(Throwable e) {
        if (e == null) {
            return SUCCESS;
        }

        Set<Integer> possibleMatches = KNOWN_ERRORS.stream()
                .filter(knownError -> knownError.getException().equals(e.getClass()))
                .filter(knownError -> knownError.isCanonicalJsonCodeForException).map(KnownError::getJsonCode)
                .collect(Collectors.toSet());

        return Iterables.getOnlyElement(possibleMatches, UNHANDLED_ERROR);
    }

    public boolean isMappableError(Throwable rootCause) {
        if (rootCause == null) {
            return false;
        }
        Set<KnownError> possibleMatches = KNOWN_ERRORS.stream()
                .filter(knownError -> knownError.getException().equals(rootCause.getClass()))
                .collect(Collectors.toSet());

        return !possibleMatches.isEmpty();
    }

    // Every row on this table should be self-explanatory, except for the two booleans at the end.
    // The first of these is "isCanonicalJsonCodeForException". This means that when doing the mapping
    // for a JSON Wire Protocol status code, this KnownError provides the exception that should be
    // thrown. The second boolean is "isCanonicalForW3C". This means that when mapping a state or
    // exception to a W3C state, this KnownError provides the default exception and Json Wire Protocol
    // status to send.
    private static final ImmutableSet<KnownError> KNOWN_ERRORS = ImmutableSet.<KnownError>builder()
            .add(new KnownError(ASYNC_SCRIPT_TIMEOUT, "script timeout", 500, ScriptTimeoutException.class, true,
                    true))
            .add(new KnownError(ELEMENT_CLICK_INTERCEPTED, "element click intercepted", 400,
                    ElementClickInterceptedException.class, true, true))
            .add(new KnownError(ELEMENT_NOT_SELECTABLE, "element not selectable", 400,
                    ElementNotSelectableException.class, true, true))
            .add(new KnownError(ELEMENT_NOT_INTERACTABLE, "element not interactable", 400,
                    ElementNotInteractableException.class, true, true))
            .add(new KnownError(ELEMENT_NOT_VISIBLE, "element not visible", 400, ElementNotVisibleException.class,
                    true, true))
            .add(new KnownError(IME_ENGINE_ACTIVATION_FAILED, "unsupported operation", 500,
                    ImeActivationFailedException.class, true, false))
            .add(new KnownError(IME_NOT_AVAILABLE, "unsupported operation", 500, ImeNotAvailableException.class,
                    true, false))
            .add(new KnownError(INVALID_ARGUMENT, "invalid argument", 400, InvalidArgumentException.class, true,
                    true))
            .add(new KnownError(INVALID_COOKIE_DOMAIN, "invalid cookie domain", 400,
                    InvalidCookieDomainException.class, true, true))
            .add(new KnownError(INVALID_ELEMENT_COORDINATES, "invalid element coordinates", 400,
                    InvalidCoordinatesException.class, true, true))
            .add(new KnownError(INVALID_ELEMENT_STATE, "invalid element state", 400,
                    InvalidElementStateException.class, true, true))
            .add(new KnownError(INVALID_SELECTOR_ERROR, "invalid selector", 400, InvalidSelectorException.class,
                    true, true))
            .add(new KnownError(INVALID_XPATH_SELECTOR, "invalid selector", 400, InvalidSelectorException.class,
                    false, false))
            .add(new KnownError(INVALID_XPATH_SELECTOR_RETURN_TYPER, "invalid selector", 400,
                    InvalidSelectorException.class, false, true))
            .add(new KnownError(JAVASCRIPT_ERROR, "javascript error", 500, JavascriptException.class, true, true))
            .add(new KnownError(METHOD_NOT_ALLOWED, "unknown method", 405, UnsupportedCommandException.class, false,
                    true))
            .add(new KnownError(METHOD_NOT_ALLOWED, "unsupported operation", 500, UnsupportedCommandException.class,
                    false, true))
            .add(new KnownError(MOVE_TARGET_OUT_OF_BOUNDS, "move target out of bounds", 500,
                    MoveTargetOutOfBoundsException.class, true, true))
            .add(new KnownError(NO_ALERT_PRESENT, "no such alert", 404, NoAlertPresentException.class, true, true))
            .add(new KnownError(NO_SUCH_COOKIE, "no such cookie", 404, NoSuchCookieException.class, true, true))
            .add(new KnownError(NO_SUCH_ELEMENT, "no such element", 404, NoSuchElementException.class, true, true))
            .add(new KnownError(NO_SUCH_FRAME, "no such frame", 404, NoSuchFrameException.class, true, true))
            .add(new KnownError(NO_SUCH_SESSION, "invalid session id", 404, NoSuchSessionException.class, true,
                    true))
            .add(new KnownError(NO_SUCH_WINDOW, "no such window", 404, NoSuchWindowException.class, true, true))
            .add(new KnownError(SESSION_NOT_CREATED, "session not created", 500, SessionNotCreatedException.class,
                    true, true))
            .add(new KnownError(STALE_ELEMENT_REFERENCE, "stale element reference", 404,
                    StaleElementReferenceException.class, true, true))
            .add(new KnownError(TIMEOUT, "timeout", 500, TimeoutException.class, true, true))
            .add(new KnownError(XPATH_LOOKUP_ERROR, "invalid selector", 400, InvalidSelectorException.class, false,
                    false))
            .add(new KnownError(UNABLE_TO_CAPTURE_SCREEN, "unable to capture screen", 500,
                    ScreenshotException.class, true, true))
            .add(new KnownError(UNABLE_TO_SET_COOKIE, "unable to set cookie", 500, UnableToSetCookieException.class,
                    true, true))
            .add(new KnownError(UNEXPECTED_ALERT_PRESENT, "unexpected alert open", 500,
                    UnhandledAlertException.class, true, true))
            .add(new KnownError(UNHANDLED_ERROR, "unknown error", 500, WebDriverException.class, true, true))
            .add(new KnownError(UNKNOWN_COMMAND, "unknown command", 404, UnsupportedCommandException.class, true,
                    true))

            .build();

    static {
        {
            // Validate uniqueness constraints.
            //
            // There should be only one canonical JSON Wire protocol code per exception
            Map<Object, Set<KnownError>> matched = new HashMap<>();
            for (KnownError knownError : KNOWN_ERRORS) {
                matched.getOrDefault(knownError, new HashSet<>()).add(knownError);
            }
            for (Set<KnownError> errors : matched.values()) {
                if (errors.size() != 1) {
                    throw new RuntimeException("Duplicate canonical exceptions: " + errors);
                }
            }

            // There should only be one canonical W3C code to JSON Wire Protocol code
            matched = new HashMap<>();
            for (KnownError error : KNOWN_ERRORS) {
                matched.getOrDefault(error.getW3cCode(), new HashSet<>()).add(error);
            }
            for (Set<KnownError> errors : matched.values()) {
                if (errors.size() != 1) {
                    throw new RuntimeException("Duplicate canonical w3c state codes: " + errors);
                }
            }
        }
    }

    private static class KnownError {
        private final int jsonCode;
        private final String w3cCode;
        private final int w3cHttpStatus;
        private final Class<? extends WebDriverException> exception;
        private final boolean isCanonicalJsonCodeForException;
        private final boolean isCanonicalForW3C;

        public KnownError(int jsonCode, String w3cCode, int w3cHttpStatus,
                Class<? extends WebDriverException> exception, boolean isCanonicalJsonCodeForException,
                boolean isCanonicalForW3C) {
            this.jsonCode = jsonCode;
            this.w3cCode = w3cCode;
            this.w3cHttpStatus = w3cHttpStatus;
            this.exception = exception;
            this.isCanonicalJsonCodeForException = isCanonicalJsonCodeForException;
            this.isCanonicalForW3C = isCanonicalForW3C;
        }

        public int getJsonCode() {
            return jsonCode;
        }

        public String getW3cCode() {
            return w3cCode;
        }

        public int getW3cHttpStatus() {
            return w3cHttpStatus;
        }

        public Class<? extends WebDriverException> getException() {
            return exception;
        }

        public boolean isCanonicalForW3C() {
            return isCanonicalForW3C;
        }
    }
}