Handle comments
diff --git a/AGENTS.md b/AGENTS.md index 0fa51a9..0f70128 100644 --- a/AGENTS.md +++ b/AGENTS.md
@@ -3,6 +3,7 @@ Language-specific details live in respective subdirectories. --> See @.local/AGENTS.md for additional guidance +See [skills.md](skills.md) for a consolidated cross-language quickstart and skill matrix. Selenium is a Bazel-built monorepo implementing the W3C WebDriver (and related) protocols, shipping multiple language bindings plus Grid and Selenium Manager.
diff --git a/py/selenium/webdriver/common/bidi/permissions.py b/py/selenium/webdriver/common/bidi/permissions.py index 17faa1f..6dd138d 100644 --- a/py/selenium/webdriver/common/bidi/permissions.py +++ b/py/selenium/webdriver/common/bidi/permissions.py
@@ -15,12 +15,20 @@ # specific language governing permissions and limitations # under the License. +"""WebDriver BiDi Permissions module.""" -from selenium.webdriver.common.bidi.common import command_builder +from __future__ import annotations + +from enum import Enum +from typing import Any + +from .common import command_builder + +_VALID_PERMISSION_STATES = {"granted", "denied", "prompt"} -class PermissionState: - """Represents the possible permission states.""" +class PermissionState(str, Enum): + """Permission state enumeration.""" GRANTED = "granted" DENIED = "denied" @@ -28,56 +36,69 @@ class PermissionDescriptor: - """Represents a permission descriptor.""" + """Descriptor for a permission.""" - def __init__(self, name: str): + def __init__(self, name: str) -> None: + """Initialize a PermissionDescriptor. + + Args: + name: The name of the permission (e.g., 'geolocation', 'microphone', 'camera') + """ self.name = name - def to_dict(self) -> dict: - return {"name": self.name} + def __repr__(self) -> str: + return f"PermissionDescriptor('{self.name}')" class Permissions: - """BiDi implementation of the permissions module.""" + """WebDriver BiDi Permissions module.""" - def __init__(self, conn): - self.conn = conn + def __init__(self, websocket_connection: Any) -> None: + """Initialize the Permissions module. + + Args: + websocket_connection: The WebSocket connection for sending BiDi commands + """ + self._conn = websocket_connection def set_permission( self, - descriptor: str | PermissionDescriptor, - state: str, - origin: str, + descriptor: PermissionDescriptor | str, + state: PermissionState | str, + origin: str | None = None, user_context: str | None = None, ) -> None: - """Sets a permission state for a given permission descriptor. + """Set a permission for a given origin. Args: - descriptor: The permission name (str) or PermissionDescriptor object. - Examples: "geolocation", "camera", "microphone". - state: The permission state (granted, denied, prompt). - origin: The origin for which the permission is set. - user_context: The user context id (optional). + descriptor: The permission descriptor or permission name as a string + state: The desired permission state + origin: The origin for which to set the permission + user_context: Optional user context ID to scope the permission Raises: - ValueError: If the permission state is invalid. + ValueError: If the state is not a valid permission state """ - if state not in [PermissionState.GRANTED, PermissionState.DENIED, PermissionState.PROMPT]: - valid_states = f"{PermissionState.GRANTED}, {PermissionState.DENIED}, {PermissionState.PROMPT}" - raise ValueError(f"Invalid permission state. Must be one of: {valid_states}") + state_value = state.value if isinstance(state, PermissionState) else state + if state_value not in _VALID_PERMISSION_STATES: + raise ValueError( + f"Invalid permission state: {state_value!r}. " + f"Must be one of {sorted(_VALID_PERMISSION_STATES)}" + ) if isinstance(descriptor, str): - permission_descriptor = PermissionDescriptor(descriptor) + descriptor_dict = {"name": descriptor} else: - permission_descriptor = descriptor + descriptor_dict = {"name": descriptor.name} - params = { - "descriptor": permission_descriptor.to_dict(), - "state": state, - "origin": origin, + params: dict[str, Any] = { + "descriptor": descriptor_dict, + "state": state_value, } - + if origin is not None: + params["origin"] = origin if user_context is not None: params["userContext"] = user_context - self.conn.execute(command_builder("permissions.setPermission", params)) + cmd = command_builder("permissions.setPermission", params) + self._conn.execute(cmd)
diff --git a/py/selenium/webdriver/common/bidi/session.py b/py/selenium/webdriver/common/bidi/session.py index 3481c2d..fcb42a4 100644 --- a/py/selenium/webdriver/common/bidi/session.py +++ b/py/selenium/webdriver/common/bidi/session.py
@@ -1,134 +1,246 @@ -# 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 +# DO NOT EDIT THIS FILE! # -# http://www.apache.org/licenses/LICENSE-2.0 +# This file is generated from the WebDriver BiDi specification. If you need to make +# changes, edit the generator and regenerate all of the modules. # -# 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. +# WebDriver BiDi module: session +from __future__ import annotations +from dataclasses import dataclass, field +from typing import Any -from selenium.webdriver.common.bidi.common import command_builder +from .common import command_builder class UserPromptHandlerType: - """Represents the behavior of the user prompt handler.""" + """UserPromptHandlerType.""" ACCEPT = "accept" DISMISS = "dismiss" IGNORE = "ignore" - VALID_TYPES = {ACCEPT, DISMISS, IGNORE} + +@dataclass +class CapabilitiesRequest: + """CapabilitiesRequest.""" + + always_match: Any | None = None + first_match: list[Any] = field(default_factory=list) +@dataclass +class CapabilityRequest: + """CapabilityRequest.""" + + accept_insecure_certs: bool | None = None + browser_name: str | None = None + browser_version: str | None = None + platform_name: str | None = None + proxy: Any | None = None + unhandled_prompt_behavior: Any | None = None + + +@dataclass +class AutodetectProxyConfiguration: + """AutodetectProxyConfiguration.""" + + proxy_type: str = field(default="autodetect", init=False) + + +@dataclass +class DirectProxyConfiguration: + """DirectProxyConfiguration.""" + + proxy_type: str = field(default="direct", init=False) + + +@dataclass +class ManualProxyConfiguration: + """ManualProxyConfiguration.""" + + proxy_type: str = field(default="manual", init=False) + http_proxy: str | None = None + ssl_proxy: str | None = None + no_proxy: list[Any] = field(default_factory=list) + + +@dataclass +class SocksProxyConfiguration: + """SocksProxyConfiguration.""" + + socks_proxy: str | None = None + socks_version: Any | None = None + + +@dataclass +class PacProxyConfiguration: + """PacProxyConfiguration.""" + + proxy_type: str = field(default="pac", init=False) + proxy_autoconfig_url: str | None = None + + +@dataclass +class SystemProxyConfiguration: + """SystemProxyConfiguration.""" + + proxy_type: str = field(default="system", init=False) + + +@dataclass +class SubscribeParameters: + """SubscribeParameters.""" + + events: list[str] = field(default_factory=list) + contexts: list[Any] = field(default_factory=list) + user_contexts: list[Any] = field(default_factory=list) + + +@dataclass +class UnsubscribeByIDRequest: + """UnsubscribeByIDRequest.""" + + subscriptions: list[Any] = field(default_factory=list) + + +@dataclass +class UnsubscribeByAttributesRequest: + """UnsubscribeByAttributesRequest.""" + + events: list[str] = field(default_factory=list) + + +@dataclass +class StatusResult: + """StatusResult.""" + + ready: bool | None = None + message: str | None = None + + +@dataclass +class NewParameters: + """NewParameters.""" + + capabilities: Any | None = None + + +@dataclass +class NewResult: + """NewResult.""" + + session_id: str | None = None + accept_insecure_certs: bool | None = None + browser_name: str | None = None + browser_version: str | None = None + platform_name: str | None = None + set_window_rect: bool | None = None + user_agent: str | None = None + proxy: Any | None = None + unhandled_prompt_behavior: Any | None = None + web_socket_url: str | None = None + + +@dataclass +class SubscribeResult: + """SubscribeResult.""" + + subscription: Any | None = None + + +@dataclass class UserPromptHandler: - """Represents the configuration of the user prompt handler.""" + """UserPromptHandler.""" - def __init__( - self, - alert: str | None = None, - before_unload: str | None = None, - confirm: str | None = None, - default: str | None = None, - file: str | None = None, - prompt: str | None = None, - ): - """Initialize UserPromptHandler. + alert: Any | None = None + before_unload: Any | None = None + confirm: Any | None = None + default: Any | None = None + file: Any | None = None + prompt: Any | None = None - Args: - alert: Handler type for alert prompts. - before_unload: Handler type for beforeUnload prompts. - confirm: Handler type for confirm prompts. - default: Default handler type for all prompts. - file: Handler type for file picker prompts. - prompt: Handler type for prompt dialogs. - - Raises: - ValueError: If any handler type is not valid. - """ - for field_name, value in [ - ("alert", alert), - ("before_unload", before_unload), - ("confirm", confirm), - ("default", default), - ("file", file), - ("prompt", prompt), - ]: - if value is not None and value not in UserPromptHandlerType.VALID_TYPES: - raise ValueError( - f"Invalid {field_name} handler type: {value}. Must be one of {UserPromptHandlerType.VALID_TYPES}" - ) - - self.alert = alert - self.before_unload = before_unload - self.confirm = confirm - self.default = default - self.file = file - self.prompt = prompt - - def to_dict(self) -> dict[str, str]: - """Convert the UserPromptHandler to a dictionary for BiDi protocol. - - Returns: - Dictionary representation suitable for BiDi protocol. - """ - field_mapping = { - "alert": "alert", - "before_unload": "beforeUnload", - "confirm": "confirm", - "default": "default", - "file": "file", - "prompt": "prompt", - } - + def to_bidi_dict(self) -> dict: + """Convert to BiDi protocol dict with camelCase keys.""" result = {} - for attr_name, dict_key in field_mapping.items(): - value = getattr(self, attr_name) - if value is not None: - result[dict_key] = value + if self.alert is not None: + result["alert"] = self.alert + if self.before_unload is not None: + result["beforeUnload"] = self.before_unload + if self.confirm is not None: + result["confirm"] = self.confirm + if self.default is not None: + result["default"] = self.default + if self.file is not None: + result["file"] = self.file + if self.prompt is not None: + result["prompt"] = self.prompt return result - class Session: - def __init__(self, conn): - self.conn = conn + """WebDriver BiDi session module.""" - def subscribe(self, *events, browsing_contexts=None): - params = { - "events": events, - } - if browsing_contexts is None: - browsing_contexts = [] - if browsing_contexts: - params["browsingContexts"] = browsing_contexts - return command_builder("session.subscribe", params) - - def unsubscribe(self, *events, browsing_contexts=None): - params = { - "events": events, - } - if browsing_contexts is None: - browsing_contexts = [] - if browsing_contexts: - params["browsingContexts"] = browsing_contexts - return command_builder("session.unsubscribe", params) + def __init__(self, conn) -> None: + self._conn = conn def status(self): - """The session.status command returns information about the remote end's readiness. + """Execute session.status.""" + params = { + } + params = {k: v for k, v in params.items() if v is not None} + cmd = command_builder("session.status", params) + result = self._conn.execute(cmd) + return result - Returns information about the remote end's readiness to create new sessions - and may include implementation-specific metadata. + def new(self, capabilities: Any | None = None): + """Execute session.new.""" + if capabilities is None: + raise TypeError("new() missing required argument: {{snake_param!r}}") - Returns: - Dictionary containing the ready state (bool), message (str) and metadata. - """ - cmd = command_builder("session.status", {}) - return self.conn.execute(cmd) + params = { + "capabilities": capabilities, + } + params = {k: v for k, v in params.items() if v is not None} + cmd = command_builder("session.new", params) + result = self._conn.execute(cmd) + return result + + def end(self): + """Execute session.end.""" + params = { + } + params = {k: v for k, v in params.items() if v is not None} + cmd = command_builder("session.end", params) + result = self._conn.execute(cmd) + return result + + def subscribe( + self, + events: list[Any] | None = None, + contexts: list[Any] | None = None, + user_contexts: list[Any] | None = None, + ): + """Execute session.subscribe.""" + if events is None: + raise TypeError("subscribe() missing required argument: {{snake_param!r}}") + + params = { + "events": events, + "contexts": contexts, + "userContexts": user_contexts, + } + params = {k: v for k, v in params.items() if v is not None} + cmd = command_builder("session.subscribe", params) + result = self._conn.execute(cmd) + return result + + def unsubscribe(self, events: list[Any] | None = None, subscriptions: list[Any] | None = None): + """Execute session.unsubscribe.""" + params = { + "events": events, + "subscriptions": subscriptions, + } + params = {k: v for k, v in params.items() if v is not None} + cmd = command_builder("session.unsubscribe", params) + result = self._conn.execute(cmd) + return result +
diff --git a/skills.md b/skills.md index 93ee0d2..9be642c 100644 --- a/skills.md +++ b/skills.md
@@ -2,6 +2,8 @@ This document captures best practices for contributors and coding agents working in the Selenium monorepo. +See also: [AGENTS.md](AGENTS.md) and language-specific guides such as `<language>/AGENTS.md` for per-language agent usage details. Those files are the authoritative source of truth; this guide is a consolidated companion. + ## Purpose Use this guide to make safe, focused changes across Selenium's multi-language bindings while staying aligned with Bazel-based workflows and project invariants. @@ -44,7 +46,7 @@ 3. Run the smallest meaningful tests first: ```bash -bazel test //path/to/area:target --test_output=all --cache_test_results=no +bazel test //path/to/area:target --test_output=all ``` 4. Expand test scope only when the focused tests pass. @@ -212,4 +214,4 @@ - Logging, deprecation, or public documentation conventions - High-risk area definitions -Treat the language-specific AGENTS files as source of truth and keep this file synchronized with them. +The language-specific AGENTS files and the root `AGENTS.md` are the authoritative source of truth. This guide is a consolidated summary and must not contradict them. When a language-specific AGENTS file changes, update the corresponding section here to stay consistent.