diff --git a/py/conftest.py b/py/conftest.py index e30bc8eadf31f..4cc981bcd2e0b 100644 --- a/py/conftest.py +++ b/py/conftest.py @@ -106,7 +106,15 @@ def pytest_generate_tests(metafunc): class ContainerProtocol: - def __contains__(self, name): + def __contains__(self, name) -> bool: + """Check if a name exists in the container (case-insensitive). + + Args: + name: The name to check for existence. + + Returns: + True if the name exists (case-insensitive), False otherwise. + """ if name.lower() in self.__dict__: return True return False diff --git a/py/pyproject.toml b/py/pyproject.toml index 97b689585eec4..be4b84b13509e 100644 --- a/py/pyproject.toml +++ b/py/pyproject.toml @@ -153,7 +153,6 @@ extend-ignore = [ "D102", # Missing docstring in public method "D103", # Missing docstring in public function "D104", # Missing docstring in public package - "D105", # Missing docstring in magic method "D107", # Missing docstring in `__init__` "D205", # 1 blank line required between summary line and description "D212", # Multi-line docstring summary should start at the first line diff --git a/py/selenium/common/exceptions.py b/py/selenium/common/exceptions.py index 9d895598ba430..2b9ce7c291905 100644 --- a/py/selenium/common/exceptions.py +++ b/py/selenium/common/exceptions.py @@ -35,6 +35,12 @@ def __init__( self.stacktrace = stacktrace def __str__(self) -> str: + """Return a formatted string representation of the exception. + + Returns: + A formatted string containing the error message, screenshot info, + and stacktrace if available. + """ exception_msg = f"Message: {self.msg}\n" if self.screen: exception_msg += "Screenshot: available via screen\n" @@ -147,6 +153,11 @@ def __init__( self.alert_text = alert_text def __str__(self) -> str: + """Return a formatted string representation of the alert exception. + + Returns: + A formatted string containing the alert text and parent exception info. + """ return f"Alert Text: {self.alert_text}\n{super().__str__()}" diff --git a/py/selenium/webdriver/common/action_chains.py b/py/selenium/webdriver/common/action_chains.py index 885546b84edb6..81014c7f02cdd 100644 --- a/py/selenium/webdriver/common/action_chains.py +++ b/py/selenium/webdriver/common/action_chains.py @@ -366,7 +366,19 @@ def scroll_from_origin(self, scroll_origin: ScrollOrigin, delta_x: int, delta_y: # Context manager so ActionChains can be used in a 'with .. as' statements. def __enter__(self) -> ActionChains: + """Enter the context manager. + + Returns: + The ActionChains instance for use in the with block. + """ return self # Return created instance of self. def __exit__(self, _type, _value, _traceback) -> None: + """Exit the context manager. + + Args: + _type: The exception type if an error occurred in the with block. + _value: The exception value if an error occurred in the with block. + _traceback: The traceback if an error occurred in the with block. + """ pass # Do nothing, does not require additional cleanup. diff --git a/py/selenium/webdriver/common/bidi/input.py b/py/selenium/webdriver/common/bidi/input.py index e3cf175015096..2348f1281e72d 100644 --- a/py/selenium/webdriver/common/bidi/input.py +++ b/py/selenium/webdriver/common/bidi/input.py @@ -63,6 +63,11 @@ class PointerParameters: pointer_type: str = PointerType.MOUSE def __post_init__(self): + """Validate the pointer type after dataclass initialization. + + Raises: + ValueError: If the pointer_type is not one of the valid types. + """ if self.pointer_type not in PointerType.VALID_TYPES: raise ValueError(f"Invalid pointer type: {self.pointer_type}. Must be one of {PointerType.VALID_TYPES}") @@ -84,6 +89,11 @@ class PointerCommonProperties: azimuth_angle: float = 0.0 def __post_init__(self): + """Validate pointer common properties after dataclass initialization. + + Raises: + ValueError: If any of the properties are outside their valid ranges. + """ if self.width < 1: raise ValueError("width must be at least 1") if self.height < 1: @@ -309,6 +319,10 @@ class PointerSourceActions: ) def __post_init__(self): + """Initialize default pointer parameters if not provided. + + Sets default PointerParameters if none are specified during initialization. + """ if self.parameters is None: self.parameters = PointerParameters() diff --git a/py/selenium/webdriver/common/service.py b/py/selenium/webdriver/common/service.py index e84a00a059637..b5dfa2edf1ee5 100644 --- a/py/selenium/webdriver/common/service.py +++ b/py/selenium/webdriver/common/service.py @@ -190,6 +190,13 @@ def _terminate_process(self) -> None: logger.error("Error terminating service process.", exc_info=True) def __del__(self) -> None: + """Cleanup and terminate the service process when the object is destroyed. + + Attempts to gracefully stop the service process when the instance is + garbage collected. `subprocess.Popen` doesn't send signals on `__del__`, + so we explicitly call stop() here. Note: globals are not referenced here + as they may be None during interpreter shutdown. + """ # `subprocess.Popen` doesn't send signal on `__del__`; # so we attempt to close the launched process when `__del__` # is triggered. diff --git a/py/selenium/webdriver/common/virtual_authenticator.py b/py/selenium/webdriver/common/virtual_authenticator.py index b3c951a98fdef..2d7f4190495b6 100644 --- a/py/selenium/webdriver/common/virtual_authenticator.py +++ b/py/selenium/webdriver/common/virtual_authenticator.py @@ -187,6 +187,11 @@ def from_dict(cls, data: dict[str, Any]) -> "Credential": return cls(_id, is_resident_credential, rp_id, user_handle, private_key, sign_count) def __str__(self) -> str: + """Return a string representation of the credential. + + Returns: + A formatted string containing all credential properties. + """ return f"Credential(id={self.id}, is_resident_credential={self.is_resident_credential}, rp_id={self.rp_id},\ user_handle={self.user_handle}, private_key={self.private_key}, sign_count={self.sign_count})" diff --git a/py/selenium/webdriver/remote/script_key.py b/py/selenium/webdriver/remote/script_key.py index 930b699c7d79b..77b6aeae58d8c 100644 --- a/py/selenium/webdriver/remote/script_key.py +++ b/py/selenium/webdriver/remote/script_key.py @@ -26,8 +26,21 @@ def __init__(self, id=None): def id(self): return self._id - def __eq__(self, other): + def __eq__(self, other) -> bool: + """Compare this ScriptKey with another object for equality. + + Args: + other: The object to compare with. + + Returns: + True if the script key ID equals the other object, False otherwise. + """ return self._id == other def __repr__(self) -> str: + """Return a string representation of the ScriptKey object. + + Returns: + A string representation showing the script key ID. + """ return f"ScriptKey(id={self.id})" diff --git a/py/selenium/webdriver/remote/shadowroot.py b/py/selenium/webdriver/remote/shadowroot.py index 16e33796a4e95..71b02b0b8393c 100644 --- a/py/selenium/webdriver/remote/shadowroot.py +++ b/py/selenium/webdriver/remote/shadowroot.py @@ -30,12 +30,31 @@ def __init__(self, session, id_) -> None: self._id = id_ def __eq__(self, other_shadowroot) -> bool: + """Compare this ShadowRoot with another ShadowRoot for equality. + + Args: + other_shadowroot: The other ShadowRoot object to compare with. + + Returns: + True if both ShadowRoot objects have the same ID, False otherwise. + """ return self._id == other_shadowroot._id def __hash__(self) -> int: + """Return the hash code for this ShadowRoot. + + Returns: + The integer hash of the ShadowRoot ID. + """ return int(md5_hash(self._id.encode("utf-8")).hexdigest(), 16) def __repr__(self) -> str: + """Return a string representation of the ShadowRoot object. + + Returns: + A string representation showing the module, class name, session ID, + and ShadowRoot element ID. + """ return '<{0.__module__}.{0.__name__} (session="{1}", element="{2}")>'.format( type(self), self.session.session_id, self._id ) diff --git a/py/selenium/webdriver/remote/webdriver.py b/py/selenium/webdriver/remote/webdriver.py index 17c7ac142f5fb..773f3d93c4ed6 100644 --- a/py/selenium/webdriver/remote/webdriver.py +++ b/py/selenium/webdriver/remote/webdriver.py @@ -271,9 +271,19 @@ def __init__( self._devtools = None def __repr__(self): + """Return a string representation of the WebDriver object. + + Returns: + A string representation showing the module, class name, and session ID. + """ return f'<{type(self).__module__}.{type(self).__name__} (session="{self.session_id}")>' def __enter__(self): + """Enter the context manager. + + Returns: + The WebDriver instance for use in the with block. + """ return self def __exit__( @@ -282,6 +292,15 @@ def __exit__( exc: Optional[BaseException], traceback: Optional[types.TracebackType], ): + """Exit the context manager. + + Quits the WebDriver session when exiting the with block. + + Args: + exc_type: The exception type if an error occurred in the with block. + exc: The exception value if an error occurred in the with block. + traceback: The traceback if an error occurred in the with block. + """ self.quit() @contextmanager diff --git a/py/selenium/webdriver/remote/webelement.py b/py/selenium/webdriver/remote/webelement.py index c90b48446660a..5185d4b77c5f4 100644 --- a/py/selenium/webdriver/remote/webelement.py +++ b/py/selenium/webdriver/remote/webelement.py @@ -73,6 +73,12 @@ def __init__(self, parent, id_) -> None: self._id = id_ def __repr__(self): + """Return a string representation of the WebElement object. + + Returns: + A string representation showing the module, class name, session ID, + and element ID. + """ return f'<{type(self).__module__}.{type(self).__name__} (session="{self.session_id}", element="{self._id}")>' @property @@ -488,10 +494,26 @@ def id(self) -> str: """ return self._id - def __eq__(self, element): + def __eq__(self, element) -> bool: + """Compare this WebElement with another element for equality. + + Args: + element: The element to compare with. + + Returns: + True if both elements have the same ID, False otherwise. + """ return hasattr(element, "id") and self._id == element.id - def __ne__(self, element): + def __ne__(self, element) -> bool: + """Compare this WebElement with another element for inequality. + + Args: + element: The element to compare with. + + Returns: + True if the elements are not equal, False otherwise. + """ return not self.__eq__(element) # Private Methods @@ -559,6 +581,11 @@ def find_elements(self, by=By.ID, value=None) -> list[WebElement]: return self._execute(Command.FIND_CHILD_ELEMENTS, {"using": by, "value": value})["value"] def __hash__(self) -> int: + """Return the hash code for this WebElement. + + Returns: + The integer hash of the WebElement ID. + """ return int(md5_hash(self._id.encode("utf-8")).hexdigest(), 16) def _upload(self, filename): diff --git a/py/selenium/webdriver/support/color.py b/py/selenium/webdriver/support/color.py index f263046b1b480..b24747da0aa42 100644 --- a/py/selenium/webdriver/support/color.py +++ b/py/selenium/webdriver/support/color.py @@ -152,23 +152,56 @@ def hex(self) -> str: return f"#{self.red:02x}{self.green:02x}{self.blue:02x}" def __eq__(self, other: object) -> bool: + """Compare this Color with another object for equality. + + Args: + other: The object to compare with. + + Returns: + True if both are Color objects with the same RGBA values, False + otherwise. Returns NotImplemented if other is not a Color. + """ if isinstance(other, Color): return self.rgba == other.rgba return NotImplemented def __ne__(self, other: Any) -> bool: + """Compare this Color with another object for inequality. + + Args: + other: The object to compare with. + + Returns: + True if the objects are not equal, False otherwise. Returns + NotImplemented if the comparison is not supported. + """ result = self.__eq__(other) if result is NotImplemented: return result return not result def __hash__(self) -> int: + """Return the hash code for this Color. + + Returns: + The integer hash of the Color's RGBA values. + """ return hash((self.red, self.green, self.blue, self.alpha)) def __repr__(self) -> str: + """Return a string representation of the Color object. + + Returns: + A string representation showing all RGBA components. + """ return f"Color(red={self.red}, green={self.green}, blue={self.blue}, alpha={self.alpha})" def __str__(self) -> str: + """Return a string representation of the Color. + + Returns: + A user-friendly string showing the RGBA value. + """ return f"Color: {self.rgba}" diff --git a/py/selenium/webdriver/support/event_firing_webdriver.py b/py/selenium/webdriver/support/event_firing_webdriver.py index f98ab2cec9b50..d7b411fc78fba 100644 --- a/py/selenium/webdriver/support/event_firing_webdriver.py +++ b/py/selenium/webdriver/support/event_firing_webdriver.py @@ -135,6 +135,20 @@ def _wrap_value(self, value): return WebDriver._wrap_value(self._driver, value) def __setattr__(self, item, value): + """Set an attribute on the EventFiringWebDriver or wrapped driver. + + Sets internal attributes (those starting with '_') on this instance. + Other attributes are delegated to the wrapped driver. Exceptions + are reported to the listener. + + Args: + item: The attribute name. + value: The attribute value. + + Raises: + Exception: Any exception from the wrapped driver is re-raised + after being reported to the listener. + """ if item.startswith("_") or not hasattr(self._driver, item): object.__setattr__(self, item, value) else: @@ -145,6 +159,23 @@ def __setattr__(self, item, value): raise def __getattr__(self, name): + """Get an attribute from the wrapped driver. + + Wraps returned WebElement objects and callable attributes. Exceptions + are reported to the listener. + + Args: + name: The attribute name. + + Returns: + The attribute value from the wrapped driver, with WebElement + objects wrapped and callables wrapped to report exceptions. + + Raises: + Exception: Any exception from the wrapped driver is re-raised + after being reported to the listener. + """ + def _wrap(*args, **kwargs): try: result = attrib(*args, **kwargs) @@ -205,6 +236,20 @@ def _dispatch(self, l_call, l_args, d_call, d_args): return _wrap_elements(result, self._ef_driver) def __setattr__(self, item, value): + """Set an attribute on the EventFiringWebElement or wrapped element. + + Sets internal attributes (those starting with '_') on this instance. + Other attributes are delegated to the wrapped element. Exceptions + are reported to the listener. + + Args: + item: The attribute name. + value: The attribute value. + + Raises: + Exception: Any exception from the wrapped element is re-raised + after being reported to the listener. + """ if item.startswith("_") or not hasattr(self._webelement, item): object.__setattr__(self, item, value) else: @@ -215,6 +260,23 @@ def __setattr__(self, item, value): raise def __getattr__(self, name): + """Get an attribute from the wrapped element. + + Wraps returned WebElement objects and callable attributes. Exceptions + are reported to the listener. + + Args: + name: The attribute name. + + Returns: + The attribute value from the wrapped element, with WebElement + objects wrapped and callables wrapped to report exceptions. + + Raises: + Exception: Any exception from the wrapped element is re-raised + after being reported to the listener. + """ + def _wrap(*args, **kwargs): try: result = attrib(*args, **kwargs) diff --git a/py/selenium/webdriver/support/wait.py b/py/selenium/webdriver/support/wait.py index 88c1a276ce1fa..f5271eda8021c 100644 --- a/py/selenium/webdriver/support/wait.py +++ b/py/selenium/webdriver/support/wait.py @@ -73,6 +73,11 @@ def __init__( self._ignored_exceptions = tuple(exceptions) def __repr__(self) -> str: + """Return a string representation of the WebDriverWait object. + + Returns: + A string representation showing the module, class name, and session ID. + """ return f'<{type(self).__module__}.{type(self).__name__} (session="{self._driver.session_id}")>' def until(self, method: Callable[[D], Union[Literal[False], T]], message: str = "") -> T: