Selenium 4.10.0 异常抛出机制分析
1. 异常体系结构
Selenium的异常体系以 WebDriverException
为基类,位于 selenium4.10.0/common/exceptions.py
:
class WebDriverException(Exception):
"""Base webdriver exception."""
def __init__(
self, msg: Optional[str] = None, screen: Optional[str] = None, stacktrace: Optional[Sequence[str]] = None
) -> None:
super().__init__()
self.msg = msg
self.screen = screen
self.stacktrace = stacktrace
def __str__(self) -> str:
exception_msg = f"Message: {self.msg}\n"
if self.screen:
exception_msg += "Screenshot: available via screen\n"
if self.stacktrace:
stacktrace = "\n".join(self.stacktrace)
exception_msg += f"Stacktrace:\n{stacktrace}"
return exception_msg
2. 核心异常处理流程
2.1 命令执行流程
当执行WebDriver命令时,异常抛出的主要流程如下:
- 命令发送:
WebDriver.execute()
方法发送命令 - 响应接收:
RemoteConnection.execute()
接收服务器响应 - 异常检查:
ErrorHandler.check_response()
检查响应中的错误 - 异常抛出:根据错误代码抛出相应的异常
2.2 错误处理器核心逻辑
ErrorHandler.check_response()
方法是异常处理的核心:
def check_response(self, response: Dict[str, Any]) -> None:
"""Checks that a JSON response from the WebDriver does not have an
error.
:Args:
- response - The JSON response from the WebDriver server as a dictionary
object.
:Raises: If the response contains an error message.
"""
status = response.get("status", None)
if not status or status == ErrorCode.SUCCESS:
return
value = None
message = response.get("message", "")
screen: str = response.get("screen", "")
stacktrace = None
if isinstance(status, int):
value_json = response.get("value", None)
if value_json and isinstance(value_json, str):
import json
try:
value = json.loads(value_json)
if len(value) == 1:
value = value["value"]
status = value.get("error", None)
if not status:
status = value.get("status", ErrorCode.UNKNOWN_ERROR)
message = value.get("value") or value.get("message")
if not isinstance(message, str):
value = message
message = message.get("message")
else:
message = value.get("message", None)
except ValueError:
pass
exception_class: Type[WebDriverException]
if status in ErrorCode.NO_SUCH_ELEMENT:
exception_class = NoSuchElementException
elif status in ErrorCode.NO_SUCH_FRAME:
exception_class = NoSuchFrameException
elif status in ErrorCode.NO_SUCH_SHADOW_ROOT:
exception_class = NoSuchShadowRootException
elif status in ErrorCode.NO_SUCH_WINDOW:
exception_class = NoSuchWindowException
elif status in ErrorCode.STALE_ELEMENT_REFERENCE:
exception_class = StaleElementReferenceException
elif status in ErrorCode.ELEMENT_NOT_VISIBLE:
exception_class = ElementNotVisibleException
elif status in ErrorCode.INVALID_ELEMENT_STATE:
exception_class = InvalidElementStateException
elif (
status in ErrorCode.INVALID_SELECTOR
or status in ErrorCode.INVALID_XPATH_SELECTOR
or status in ErrorCode.INVALID_XPATH_SELECTOR_RETURN_TYPER
):
exception_class = InvalidSelectorException
elif status in ErrorCode.ELEMENT_IS_NOT_SELECTABLE:
exception_class = ElementNotSelectableException
elif status in ErrorCode.ELEMENT_NOT_INTERACTABLE:
exception_class = ElementNotInteractableException
elif status in ErrorCode.INVALID_COOKIE_DOMAIN:
exception_class = InvalidCookieDomainException
elif status in ErrorCode.UNABLE_TO_SET_COOKIE:
exception_class = UnableToSetCookieException
elif status in ErrorCode.TIMEOUT:
exception_class = TimeoutException
elif status in ErrorCode.SCRIPT_TIMEOUT:
exception_class = TimeoutException
elif status in ErrorCode.UNKNOWN_ERROR:
exception_class = WebDriverException
elif status in ErrorCode.UNEXPECTED_ALERT_OPEN:
exception_class = UnexpectedAlertPresentException
elif status in ErrorCode.NO_ALERT_OPEN:
exception_class = NoAlertPresentException
elif status in ErrorCode.IME_NOT_AVAILABLE:
exception_class = ImeNotAvailableException
elif status in ErrorCode.IME_ENGINE_ACTIVATION_FAILED:
exception_class = ImeActivationFailedException
elif status in ErrorCode.MOVE_TARGET_OUT_OF_BOUNDS:
exception_class = MoveTargetOutOfBoundsException
elif status in ErrorCode.JAVASCRIPT_ERROR:
exception_class = JavascriptException
elif status in ErrorCode.SESSION_NOT_CREATED:
exception_class = SessionNotCreatedException
elif status in ErrorCode.INVALID_ARGUMENT:
exception_class = InvalidArgumentException
elif status in ErrorCode.NO_SUCH_COOKIE:
exception_class = NoSuchCookieException
elif status in ErrorCode.UNABLE_TO_CAPTURE_SCREEN:
exception_class = ScreenshotException
elif status in ErrorCode.ELEMENT_CLICK_INTERCEPTED:
exception_class = ElementClickInterceptedException
elif status in ErrorCode.INSECURE_CERTIFICATE:
exception_class = InsecureCertificateException
elif status in ErrorCode.INVALID_COORDINATES:
exception_class = InvalidCoordinatesException
elif status in ErrorCode.INVALID_SESSION_ID:
exception_class = InvalidSessionIdException
elif status in ErrorCode.UNKNOWN_METHOD:
exception_class = UnknownMethodException
else:
exception_class = WebDriverException
if not value:
value = response["value"]
if isinstance(value, str):
raise exception_class(value)
if message == "" and "message" in value:
message = value["message"]
screen = None # type: ignore[assignment]
if "screen" in value:
screen = value["screen"]
stacktrace = None
st_value = value.get("stackTrace") or value.get("stacktrace")
if st_value:
if isinstance(st_value, str):
stacktrace = st_value.split("\n")
else:
stacktrace = []
try:
for frame in st_value:
line = frame.get("lineNumber", "")
file = frame.get("fileName", "<anonymous>")
if line:
file = f"{file}:{line}"
meth = frame.get("methodName", "<anonymous>")
if "className" in frame:
meth = f"{frame['className']}.{meth}"
msg = " at %s (%s)"
msg = msg % (meth, file)
stacktrace.append(msg)
except TypeError:
pass
if exception_class == UnexpectedAlertPresentException:
alert_text = None
if "data" in value:
alert_text = value["data"].get("text")
elif "alert" in value:
alert_text = value["alert"].get("text")
raise exception_class(message, screen, stacktrace, alert_text) # type: ignore[call-arg] # mypy is not smart enough here
raise exception_class(message, screen, stacktrace)
3. 异常抛出的具体方法
3.1 错误代码映射
ErrorCode
类定义了各种错误代码与异常类型的映射关系:
54:95:selenium4.10.0/webdriver/remote/errorhandler.py
class ErrorCode:
"""Error codes defined in the WebDriver wire protocol."""
# Keep in sync with org.openqa.selenium.remote.ErrorCodes and errorcodes.h
SUCCESS = 0
NO_SUCH_ELEMENT = [7, "no such element"]
NO_SUCH_FRAME = [8, "no such frame"]
NO_SUCH_SHADOW_ROOT = ["no such shadow root"]
UNKNOWN_COMMAND = [9, "unknown command"]
STALE_ELEMENT_REFERENCE = [10, "stale element reference"]
ELEMENT_NOT_VISIBLE = [11, "element not visible"]
INVALID_ELEMENT_STATE = [12, "invalid element state"]
UNKNOWN_ERROR = [13, "unknown error"]
ELEMENT_IS_NOT_SELECTABLE = [15, "element not selectable"]
JAVASCRIPT_ERROR = [17, "javascript error"]
XPATH_LOOKUP_ERROR = [19, "invalid selector"]
TIMEOUT = [21, "timeout"]
NO_SUCH_WINDOW = [23, "no such window"]
INVALID_COOKIE_DOMAIN = [24, "invalid cookie domain"]
UNABLE_TO_SET_COOKIE = [25, "unable to set cookie"]
UNEXPECTED_ALERT_OPEN = [26, "unexpected alert open"]
NO_ALERT_OPEN = [27, "no such alert"]
SCRIPT_TIMEOUT = [28, "script timeout"]
INVALID_ELEMENT_COORDINATES = [29, "invalid element coordinates"]
IME_NOT_AVAILABLE = [30, "ime not available"]
IME_ENGINE_ACTIVATION_FAILED = [31, "ime engine activation failed"]
INVALID_SELECTOR = [32, "invalid selector"]
SESSION_NOT_CREATED = [33, "session not created"]
MOVE_TARGET_OUT_OF_BOUNDS = [34, "move target out of bounds"]
INVALID_XPATH_SELECTOR = [51, "invalid selector"]
INVALID_XPATH_SELECTOR_RETURN_TYPER = [52, "invalid selector"]
ELEMENT_NOT_INTERACTABLE = [60, "element not interactable"]
INSECURE_CERTIFICATE = ["insecure certificate"]
INVALID_ARGUMENT = [61, "invalid argument"]
INVALID_COORDINATES = ["invalid coordinates"]
INVALID_SESSION_ID = ["invalid session id"]
NO_SUCH_COOKIE = [62, "no such cookie"]
UNABLE_TO_CAPTURE_SCREEN = [63, "unable to capture screen"]
ELEMENT_CLICK_INTERCEPTED = [64, "element click intercepted"]
UNKNOWN_METHOD = ["unknown method exception"]
METHOD_NOT_ALLOWED = [405, "unsupported operation"]
3.2 命令执行中的异常检查
在 WebDriver.execute()
方法中,每次命令执行后都会检查响应:
325:350:selenium4.10.0/webdriver/remote/webdriver.py
def execute(self, driver_command: str, params: dict = None) -> dict:
"""Sends a command to be executed by a command.CommandExecutor.
:Args:
- driver_command: The name of the command to execute as a string.
- params: A dictionary of named parameters to send with the command.
:Returns:
The command's JSON response loaded into a dictionary object.
"""
# 序列化操作
params = self._wrap_value(params)
if self.session_id:
if not params:
params = {"sessionId": self.session_id}
elif "sessionId" not in params:
params["sessionId"] = self.session_id
response = self.command_executor.execute(driver_command, params)
if response:
self.error_handler.check_response(response) # 这里检查异常
#反序列化操作
response["value"] = self._unwrap_value(response.get("value", None))
return response
# If the server doesn't send a response, assume the command was
# a success
return {"success": 0, "value": None, "sessionId": self.session_id}
4. 主要异常类型
Selenium定义了多种具体的异常类型,包括:
- 元素相关异常:
NoSuchElementException
、StaleElementReferenceException
、ElementNotVisibleException
- 交互相关异常:
ElementNotInteractableException
、ElementClickInterceptedException
- 选择器异常:
InvalidSelectorException
- 超时异常:
TimeoutException
- 会话异常:
SessionNotCreatedException
、InvalidSessionIdException
- JavaScript异常:
JavascriptException
5. 异常信息增强
某些异常类会增强错误信息,例如 NoSuchElementException
:
65:83:selenium4.10.0/common/exceptions.py
class NoSuchElementException(WebDriverException):
"""Thrown when element could not be found.
If you encounter this exception, you may want to check the following:
* Check your selector used in your find_by...
* Element may not yet be on the screen at the time of the find operation,
(webpage is still loading) see selenium.webdriver.support.wait.WebDriverWait()
for how to write a wait wrapper to wait for an element to appear.
"""
def __init__(
self, msg: Optional[str] = None, screen: Optional[str] = None, stacktrace: Optional[Sequence[str]] = None
) -> None:
with_support = f"{msg}; {SUPPORT_MSG} {ERROR_URL}#no-such-element-exception"
super().__init__(with_support, screen, stacktrace)
总结
Selenium的异常抛出机制主要通过以下步骤实现:
- 命令执行 → 服务器响应 → 错误检查 → 异常映射 → 异常抛出
- 使用
ErrorHandler.check_response()
作为核心异常检查方法 - 通过
ErrorCode
类映射错误代码到具体异常类型 - 异常包含详细的信息(消息、截图、堆栈跟踪)
- 某些异常会增强错误信息,提供解决建议