Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci-python.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ jobs:
pip install tox
- name: Run type checking
run: |
tox -c py/tox.ini || true
tox -c py/tox.ini
env:
TOXENV: typecheck

Expand Down
7 changes: 5 additions & 2 deletions py/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -314,11 +314,14 @@ py_wheel(
python_requires = ">=3.10",
python_tag = "py3",
requires = [
"urllib3[socks]>=2.5.0,<3.0",
"certifi>=2025.10.5",
"trio>=0.31.0,<1.0",
"trio-websocket>=0.12.2,<1.0",
"certifi>=2025.10.5",
"trio-typing>=0.10.0",
"types-certifi>=2021.10.8.3",
"types-urllib3>=1.26.25.14",
"typing_extensions>=4.15.0,<5.0",
"urllib3[socks]>=2.6.0,<3.0",
"websocket-client>=1.8.0,<2.0",
],
strip_path_prefixes = [
Expand Down
20 changes: 8 additions & 12 deletions py/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,14 @@ classifiers = [
"Programming Language :: Python :: 3.14",
]
dependencies = [
"urllib3[socks]>=2.6.0,<3.0",
"certifi>=2025.10.5",
"trio>=0.31.0,<1.0",
"trio-websocket>=0.12.2,<1.0",
"certifi>=2025.10.5",
"trio-typing>=0.10.0",
"types-certifi>=2021.10.8.3",
"types-urllib3>=1.26.25.14",
"typing_extensions>=4.15.0,<5.0",
"urllib3[socks]>=2.6.0,<3.0",
"websocket-client>=1.8.0,<2.0",
]

Expand All @@ -48,10 +51,6 @@ lint = [
]
typecheck = [
"mypy==1.19.1",
"types-urllib3==1.26.25.14",
"types-certifi==2021.10.8.3",
"trio-typing==0.10.0",
"trio-websocket>=0.12.2,<1.0",
]
validate = [
"validate-pyproject==0.24.1",
Expand Down Expand Up @@ -111,14 +110,11 @@ testpaths = ["test"]
# mypy global options
[tool.mypy]
exclude = "selenium/webdriver/common/devtools"
# The aim in future here is we would be able to turn (most) of these flags on, however the typing technical
# debt is quite colossal right now. For now we should maybe get everything working with the config here
# then look at going after partially or completely untyped defs as a phase-2.
files = "selenium"
# The aim in future is we would be able to turn (most) of these flags on
# warn about per-module sections in the config file that do not match any files processed.
warn_unused_configs = true
# disallows subclassing of typing.Any.
disallow_subclassing_any = false
disallow_subclassing_any = true
# disallow usage of generic types that do not specify explicit type parameters.
disallow_any_generics = false
# disallow calling functions without type annotations from functions that have type annotations.
Expand All @@ -132,7 +128,7 @@ check_untyped_defs = false
# reports an error whenever a function with type annotations is decorated with a decorator without annotations.
disallow_untyped_decorators = false
# changes the treatment of arguments with a default value of None by not implicitly making their type `typing.Optional`.
no_implicit_optional = false
no_implicit_optional = true
# warns about casting an expression to it's inferred type.
warn_redundant_casts = true
# warns about unneeded `# type: ignore` comments.
Expand Down
3 changes: 3 additions & 0 deletions py/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,11 @@ sortedcontainers==2.4.0
tomli==2.3.0
tox==4.32.0
trio==0.32.0
trio-typing==0.10.0
trio-websocket==0.12.2
twine==6.2.0
types-certifi==2021.10.8.3
types-urllib3==1.26.25.14
typing_extensions==4.15.0
urllib3[socks]==2.6.2
virtualenv==20.35.4
Expand Down
24 changes: 24 additions & 0 deletions py/requirements_lock.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
#
# bazel run //py:requirements.update
#
async-generator==1.10 \
--hash=sha256:01c7bf666359b4967d2cda0000cc2e4af16a0ae098cbffcb8472fb9e8ad6585b \
--hash=sha256:6ebb3d106c12920aaae42ccb6f787ef5eefdcdd166ea3d628fa8476abe712144
# via trio-typing
attrs==25.4.0 \
--hash=sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11 \
--hash=sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373
Expand Down Expand Up @@ -360,6 +364,7 @@ importlib-metadata==8.7.1 \
# via
# -r py/requirements.txt
# keyring
# trio-typing
inflection==0.5.1 \
--hash=sha256:1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417 \
--hash=sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2
Expand Down Expand Up @@ -420,6 +425,10 @@ more-itertools==10.8.0 \
# -r py/requirements.txt
# jaraco-classes
# jaraco-functools
mypy-extensions==1.1.0 \
--hash=sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505 \
--hash=sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558
# via trio-typing
nh3==0.3.2 \
--hash=sha256:019ecbd007536b67fdf76fab411b648fb64e2257ca3262ec80c3425c24028c80 \
--hash=sha256:03d617e5c8aa7331bd2659c654e021caf9bba704b109e7b2b28b039a00949fe5 \
Expand Down Expand Up @@ -466,6 +475,7 @@ packaging==25.0 \
# pyproject-api
# pytest
# tox
# trio-typing
# twine
platformdirs==4.5.1 \
--hash=sha256:61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda \
Expand Down Expand Up @@ -640,7 +650,12 @@ trio==0.32.0 \
# via
# -r py/requirements.txt
# pytest-trio
# trio-typing
# trio-websocket
trio-typing==0.10.0 \
--hash=sha256:065ee684296d52a8ab0e2374666301aec36ee5747ac0e7a61f230250f8907ac3 \
--hash=sha256:6d0e7ec9d837a2fe03591031a172533fbf4a1a95baf369edebfc51d5a49f0264
# via -r py/requirements.txt
trio-websocket==0.12.2 \
--hash=sha256:22c72c436f3d1e264d0910a3951934798dcc5b00ae56fc4ee079d46c7cf20fae \
--hash=sha256:df605665f1db533f4a386c94525870851096a223adcb97f72a07e8b4beba45b6
Expand All @@ -649,6 +664,14 @@ twine==6.2.0 \
--hash=sha256:418ebf08ccda9a8caaebe414433b0ba5e25eb5e4a927667122fbe8f829f985d8 \
--hash=sha256:e5ed0d2fd70c9959770dce51c8f39c8945c574e18173a7b81802dab51b4b75cf
# via -r py/requirements.txt
types-certifi==2021.10.8.3 \
--hash=sha256:72cf7798d165bc0b76e1c10dd1ea3097c7063c42c21d664523b928e88b554a4f \
--hash=sha256:b2d1e325e69f71f7c78e5943d410e650b4707bb0ef32e4ddf3da37f54176e88a
# via -r py/requirements.txt
types-urllib3==1.26.25.14 \
--hash=sha256:229b7f577c951b8c1b92c1bc2b2fdb0b49847bd2af6d1cc2a2e3dd340f3bda8f \
--hash=sha256:9683bbb7fb72e32bfe9d2be6e04875fbe1b3eeec3cbb4ea231435aa7fd6b4f0e
# via -r py/requirements.txt
typing-extensions==4.15.0 \
--hash=sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466 \
--hash=sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548
Expand All @@ -657,6 +680,7 @@ typing-extensions==4.15.0 \
# cryptography
# exceptiongroup
# tox
# trio-typing
# virtualenv
urllib3[socks]==2.6.2 \
--hash=sha256:016f9c98bb7e98085cb2b4b17b87d2c702975664e4f060c6532e64d1c1a5e797 \
Expand Down
2 changes: 1 addition & 1 deletion py/selenium/webdriver/chrome/remote_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def __init__(
self,
remote_server_addr: str,
keep_alive: bool = True,
ignore_proxy: bool | None = False,
ignore_proxy: bool = False,
client_config: ClientConfig | None = None,
) -> None:
super().__init__(
Expand Down
3 changes: 1 addition & 2 deletions py/selenium/webdriver/chrome/webdriver.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
# specific language governing permissions and limitations
# under the License.


from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chromium.webdriver import ChromiumDriver
Expand All @@ -36,7 +35,7 @@ def __init__(
Starts the service and then creates new instance of chrome driver.

Args:
options: This takes an instance of ChromeOptions.
options: Instance of Options.
service: Service object for handling the browser driver if you need to pass extra details.
keep_alive: Whether to configure ChromeRemoteConnection to use HTTP keep-alive.
"""
Expand Down
2 changes: 1 addition & 1 deletion py/selenium/webdriver/chromium/remote_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def __init__(
vendor_prefix: str,
browser_name: str,
keep_alive: bool = True,
ignore_proxy: bool | None = False,
ignore_proxy: bool = False,
client_config: ClientConfig | None = None,
) -> None:
client_config = client_config or ClientConfig(
Expand Down
19 changes: 9 additions & 10 deletions py/selenium/webdriver/chromium/webdriver.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
# specific language governing permissions and limitations
# under the License.


from selenium.webdriver.chromium.options import ChromiumOptions
from selenium.webdriver.chromium.remote_connection import ChromiumRemoteConnection
from selenium.webdriver.chromium.service import ChromiumService
Expand All @@ -29,8 +28,8 @@ class ChromiumDriver(LocalWebDriver):

def __init__(
self,
browser_name: str | None = None,
vendor_prefix: str | None = None,
browser_name: str,
vendor_prefix: str,
options: ChromiumOptions | None = None,
service: ChromiumService | None = None,
keep_alive: bool = True,
Expand All @@ -40,17 +39,17 @@ def __init__(
Args:
browser_name: Browser name used when matching capabilities.
vendor_prefix: Company prefix to apply to vendor-specific WebDriver extension commands.
options: This takes an instance of ChromiumOptions.
options: Instance of ChromiumOptions.
service: Service object for handling the browser driver if you need to pass extra details.
keep_alive: Whether to configure ChromiumRemoteConnection to use HTTP keep-alive.
"""
self.service = service if service else ChromiumService()
options = options if options else ChromiumOptions()
self.options = options if options else ChromiumOptions()

finder = DriverFinder(self.service, options)
finder = DriverFinder(self.service, self.options)
if finder.get_browser_path():
options.binary_location = finder.get_browser_path()
options.browser_version = None
self.options.binary_location = finder.get_browser_path()
self.options.browser_version = None

self.service.path = self.service.env_path() or finder.get_driver_path()
self.service.start()
Expand All @@ -60,11 +59,11 @@ def __init__(
browser_name=browser_name,
vendor_prefix=vendor_prefix,
keep_alive=keep_alive,
ignore_proxy=options._ignore_local_proxy,
ignore_proxy=self.options._ignore_local_proxy,
)

try:
super().__init__(command_executor=executor, options=options)
super().__init__(command_executor=executor, options=self.options)
except Exception:
self.quit()
raise
Expand Down
6 changes: 6 additions & 0 deletions py/selenium/webdriver/common/bidi/cdp.py
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,8 @@ def _handle_event(self, data: dict):
data: event as a JSON dictionary
"""
global devtools
if devtools is None:
raise RuntimeError("CDP devtools module not loaded. Call import_devtools() first.")
event = devtools.util.parse_json_event(data)
logger.debug("Received event: %s", event)
to_remove = set()
Expand Down Expand Up @@ -424,6 +426,8 @@ async def open_session(self, target_id) -> AsyncIterator[CdpSession]:
async def connect_session(self, target_id) -> "CdpSession":
"""Returns a new :class:`CdpSession` connected to the specified target."""
global devtools
if devtools is None:
raise RuntimeError("CDP devtools module not loaded. Call import_devtools() first.")
session_id = await self.execute(devtools.target.attach_to_target(target_id, True))
session = CdpSession(self.ws, session_id, target_id)
self.sessions[session_id] = session
Expand All @@ -435,6 +439,8 @@ async def _reader_task(self):
Dispatches responses to commands and events to listeners.
"""
global devtools
if devtools is None:
raise RuntimeError("CDP devtools module not loaded. Call import_devtools() first.")
while True:
try:
message = await self.ws.get_message()
Expand Down
4 changes: 2 additions & 2 deletions py/selenium/webdriver/common/webdriver.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
# specific language governing permissions and limitations
# under the License.


from selenium.webdriver.remote.webdriver import WebDriver as RemoteWebDriver


Expand All @@ -39,7 +38,8 @@ def quit(self) -> None:
# We don't care about the message because something probably has gone wrong
pass
finally:
self.service.stop()
if hasattr(self, "service") and self.service is not None:
self.service.stop()

def download_file(self, *args, **kwargs):
"""Only implemented in RemoteWebDriver."""
Expand Down
2 changes: 1 addition & 1 deletion py/selenium/webdriver/edge/remote_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def __init__(
self,
remote_server_addr: str,
keep_alive: bool = True,
ignore_proxy: bool | None = False,
ignore_proxy: bool = False,
client_config: ClientConfig | None = None,
) -> None:
super().__init__(
Expand Down
17 changes: 7 additions & 10 deletions py/selenium/webdriver/edge/webdriver.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
# specific language governing permissions and limitations
# under the License.


from selenium.webdriver.chromium.webdriver import ChromiumDriver
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
from selenium.webdriver.edge.options import Options
Expand All @@ -36,19 +35,17 @@ def __init__(
Starts the service and then creates new instance of edge driver.

Args:
options: An instance of EdgeOptions.
service: Service object for handling the browser driver if you need
to pass extra details.
keep_alive: Whether to configure EdgeRemoteConnection to use HTTP
keep-alive.
options: Instance of Options.
service: Service object for handling the browser driver if you need to pass extra details.
keep_alive: Whether to configure EdgeRemoteConnection to use HTTP keep-alive.
"""
service = service if service else Service()
options = options if options else Options()
self.service = service if service else Service()
self.options = options if options else Options()

super().__init__(
browser_name=DesiredCapabilities.EDGE["browserName"],
vendor_prefix="ms",
options=options,
service=service,
options=self.options,
service=self.service,
keep_alive=keep_alive,
)
4 changes: 2 additions & 2 deletions py/selenium/webdriver/firefox/remote_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@


class FirefoxRemoteConnection(RemoteConnection):
browser_name = DesiredCapabilities.FIREFOX["browserName"]
browser_name = DesiredCapabilities.FIREFOX["browserName"] # type: ignore

def __init__(
self,
remote_server_addr: str,
keep_alive: bool = True,
ignore_proxy: bool | None = False,
ignore_proxy: bool = False,
client_config: ClientConfig | None = None,
) -> None:
client_config = client_config or ClientConfig(
Expand Down
Loading