From d3d88edaa091e85182055369e4391a3694ccb2ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Arrufat?= Date: Thu, 8 Oct 2026 10:01:58 +0200 Subject: [PATCH] Rename BiDiServer to WebDriverServer and drop the aliases The server speaks classic WebDriver as well as BiDi, so BiDiServer named only half of it, and #16 added WebDriverServer / AsyncWebDriverServer as aliases. Two public names for one class is one too many: make WebDriverServer and AsyncWebDriverServer the classes and remove BiDiServer and AsyncBiDiServer outright. The module moves from lightpanda.bidi to lightpanda.webdriver to match. This breaks imports of BiDiServer, which shipped in 1.0.0. --- README.md | 5 ++- lightpanda/__init__.py | 10 +++--- lightpanda/_serve.py | 2 +- lightpanda/{bidi.py => webdriver.py} | 33 +++++++------------ tests/conftest.py | 6 ++-- tests/test_serve.py | 6 ++-- tests/{test_bidi.py => test_webdriver.py} | 28 ++++++++-------- ...selenium.py => test_webdriver_selenium.py} | 18 ++++------ 8 files changed, 45 insertions(+), 63 deletions(-) rename lightpanda/{bidi.py => webdriver.py} (72%) rename tests/{test_bidi.py => test_webdriver.py} (60%) rename tests/{test_bidi_selenium.py => test_webdriver_selenium.py} (76%) diff --git a/README.md b/README.md index 4eeba8f..ba47360 100644 --- a/README.md +++ b/README.md @@ -115,8 +115,7 @@ own free port. Set `options.web_socket_url = True` to also get a WebDriver BiDi session and use `driver.browsing_context` / `driver.script`. `server.bidi_endpoint` (`ws://127.0.0.1:/session`) is the raw BiDi WebSocket for clients that -speak the protocol directly. `WebDriverServer` is an alias of `BiDiServer`, -`AsyncWebDriverServer` (alias of `AsyncBiDiServer`) is the asyncio twin, and +speak the protocol directly. `AsyncWebDriverServer` is the asyncio twin, and `args=["--protocol", "cdp"]` serves CDP on the same port as well. ## Respecting robots.txt @@ -127,7 +126,7 @@ through `args=`: ```python Browser(args=["--obey-robots"]) # also AsyncBrowser -CDPServer(args=["--obey-robots"]) # also BiDiServer, and the async twins +CDPServer(args=["--obey-robots"]) # also WebDriverServer, and the async twins run_script("saved.js", args=["--obey-robots"]) ``` diff --git a/lightpanda/__init__.py b/lightpanda/__init__.py index 984efc9..f3c4b7e 100644 --- a/lightpanda/__init__.py +++ b/lightpanda/__init__.py @@ -22,9 +22,9 @@ For Playwright or Puppeteer code, ``CDPServer`` runs the browser's own Chrome DevTools Protocol server and hands you the endpoint to connect to -(see its docs for an example). For Selenium, ``BiDiServer`` (alias -``WebDriverServer``) serves WebDriver, classic and BiDi, the same way and -hands you the ``command_executor`` URL. +(see its docs for an example). For Selenium, ``WebDriverServer`` serves +WebDriver, classic and BiDi, the same way and hands you the +``command_executor`` URL. The browser can enforce ``robots.txt``, which is off by default. Pass ``args=["--obey-robots"]`` to any of these and a disallowed request fails @@ -37,7 +37,7 @@ __docformat__ = "google" from .async_browser import AsyncBrowser, AsyncSession, run_script_async -from .bidi import AsyncBiDiServer, AsyncWebDriverServer, BiDiServer, WebDriverServer +from .webdriver import AsyncWebDriverServer, WebDriverServer from .browser import Browser, PageResult, Session, run_script from .cdp import AsyncCDPServer, CDPServer from .errors import LightpandaError, ProcessError, ProtocolError, ScriptError, ToolError @@ -52,8 +52,6 @@ "run_script_async", "CDPServer", "AsyncCDPServer", - "BiDiServer", - "AsyncBiDiServer", "WebDriverServer", "AsyncWebDriverServer", "LightpandaError", diff --git a/lightpanda/_serve.py b/lightpanda/_serve.py index 6c9f72d..3d47a87 100644 --- a/lightpanda/_serve.py +++ b/lightpanda/_serve.py @@ -1,4 +1,4 @@ -"""Shared shape of the ``lightpanda serve`` wrappers (``CDPServer``, ``BiDiServer``). +"""Shared shape of the ``lightpanda serve`` wrappers (``CDPServer``, ``WebDriverServer``). A serve wrapper owns one ``lightpanda serve`` process on a localhost port and hands out endpoints for third-party clients; the subclasses differ only diff --git a/lightpanda/bidi.py b/lightpanda/webdriver.py similarity index 72% rename from lightpanda/bidi.py rename to lightpanda/webdriver.py index bdf0f32..c5f876f 100644 --- a/lightpanda/bidi.py +++ b/lightpanda/webdriver.py @@ -1,9 +1,8 @@ """WebDriver: run ``lightpanda serve --protocol webdriver`` for Selenium and co. -:class:`BiDiServer` spawns the browser's WebDriver server (classic and BiDi) -on a free localhost port and owns the process; :class:`AsyncBiDiServer` is the -asyncio twin. ``WebDriverServer`` / ``AsyncWebDriverServer`` are aliases. See -:class:`BiDiServer` for what the browser serves. +:class:`WebDriverServer` spawns the browser's WebDriver server (classic and BiDi) +on a free localhost port and owns the process; :class:`AsyncWebDriverServer` is the +asyncio twin. See :class:`WebDriverServer` for what the browser serves. """ from __future__ import annotations @@ -15,17 +14,15 @@ @_documented -class BiDiServer(_ServeProcess): +class WebDriverServer(_ServeProcess): """A lightpanda process serving WebDriver, classic and BiDi, on 127.0.0.1. - Also importable as ``WebDriverServer``. - ```python - from lightpanda import BiDiServer + from lightpanda import WebDriverServer from selenium import webdriver from selenium.webdriver.common.by import By - with BiDiServer() as server: + with WebDriverServer() as server: # Selenium requires an options object; Lightpanda accepts any browser's. driver = webdriver.Remote(command_executor=server.http_endpoint, options=webdriver.ChromeOptions()) driver.get("https://example.com") @@ -66,26 +63,20 @@ def status(self) -> dict: @_documented -class AsyncBiDiServer(_AsyncServeProcess[BiDiServer]): - """:class:`BiDiServer` for asyncio: the process is spawned by +class AsyncWebDriverServer(_AsyncServeProcess[WebDriverServer]): + """:class:`WebDriverServer` for asyncio: the process is spawned by :meth:`start`, called automatically on ``async with`` entry.""" - _sync_cls = BiDiServer + _sync_cls = WebDriverServer @property def bidi_endpoint(self) -> str: - """See :attr:`BiDiServer.bidi_endpoint`.""" + """See :attr:`WebDriverServer.bidi_endpoint`.""" return self._started().bidi_endpoint async def status(self) -> dict: - """See :meth:`BiDiServer.status`.""" + """See :meth:`WebDriverServer.status`.""" return await asyncio.to_thread(self._started().status) -WebDriverServer = BiDiServer -"""Alias of :class:`BiDiServer`: the same server speaks classic WebDriver too.""" - -AsyncWebDriverServer = AsyncBiDiServer -"""Alias of :class:`AsyncBiDiServer`.""" - -__all__ = ["BiDiServer", "AsyncBiDiServer", "WebDriverServer", "AsyncWebDriverServer"] +__all__ = ["WebDriverServer", "AsyncWebDriverServer"] diff --git a/tests/conftest.py b/tests/conftest.py index 0ea1509..4a76d62 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -6,7 +6,7 @@ import pytest -from lightpanda import AsyncBrowser, BiDiServer, Browser, CDPServer +from lightpanda import AsyncBrowser, Browser, CDPServer, WebDriverServer BROWSER_CHECKOUT = Path(__file__).parent.parent.parent / "browser" FIXTURES = Path(__file__).parent / "fixtures" @@ -62,6 +62,6 @@ def cdp_server(binary): @pytest.fixture(scope="session") -def bidi_server(binary): - with BiDiServer(binary=binary) as server: +def webdriver_server(binary): + with WebDriverServer(binary=binary) as server: yield server diff --git a/tests/test_serve.py b/tests/test_serve.py index 9624ff6..f219a8c 100644 --- a/tests/test_serve.py +++ b/tests/test_serve.py @@ -1,14 +1,14 @@ """Lifecycle shared by the `lightpanda serve` wrappers, tested once per -class pair. Protocol-specific behaviour lives in test_cdp.py / test_bidi.py.""" +class pair. Protocol-specific behaviour lives in test_cdp.py / test_webdriver.py.""" import pytest from conftest import alive -from lightpanda import AsyncBiDiServer, AsyncCDPServer, BiDiServer, CDPServer, LightpandaError +from lightpanda import AsyncCDPServer, AsyncWebDriverServer, CDPServer, LightpandaError, WebDriverServer PAIRS = [ pytest.param(CDPServer, AsyncCDPServer, "version", "ws_endpoint", id="cdp"), - pytest.param(BiDiServer, AsyncBiDiServer, "status", "bidi_endpoint", id="bidi"), + pytest.param(WebDriverServer, AsyncWebDriverServer, "status", "bidi_endpoint", id="bidi"), ] diff --git a/tests/test_bidi.py b/tests/test_webdriver.py similarity index 60% rename from tests/test_bidi.py rename to tests/test_webdriver.py index dc537c0..fd81320 100644 --- a/tests/test_bidi.py +++ b/tests/test_webdriver.py @@ -1,12 +1,12 @@ -"""BiDiServer's endpoints and the WebDriver session bootstrap they promise, +"""WebDriverServer's endpoints and the WebDriver session bootstrap they promise, with the standard library only. A real BiDi client is exercised in -test_bidi_selenium.py; the shared process lifecycle in test_serve.py.""" +test_webdriver_selenium.py; the shared process lifecycle in test_serve.py.""" import json import urllib.error import urllib.request -from lightpanda import BiDiServer +from lightpanda import WebDriverServer BIDI_CAPS = {"capabilities": {"alwaysMatch": {"webSocketUrl": True}}} @@ -29,26 +29,26 @@ def _with_session(server): return value, lambda: _request("DELETE", f"{server.http_endpoint}/session/{value['sessionId']}") -def test_endpoints_and_status(bidi_server): - assert bidi_server.port > 0 - assert bidi_server.bidi_endpoint == f"ws://127.0.0.1:{bidi_server.port}/session" - assert bidi_server.http_endpoint == f"http://127.0.0.1:{bidi_server.port}" - assert bidi_server.status() == {"ready": True, "message": ""} +def test_endpoints_and_status(webdriver_server): + assert webdriver_server.port > 0 + assert webdriver_server.bidi_endpoint == f"ws://127.0.0.1:{webdriver_server.port}/session" + assert webdriver_server.http_endpoint == f"http://127.0.0.1:{webdriver_server.port}" + assert webdriver_server.status() == {"ready": True, "message": ""} -def test_session_bootstrap_advertises_bidi_endpoint(bidi_server): - value, done = _with_session(bidi_server) +def test_session_bootstrap_advertises_bidi_endpoint(webdriver_server): + value, done = _with_session(webdriver_server) try: assert value["capabilities"]["browserName"] == "Lightpanda" - assert value["capabilities"]["webSocketUrl"] == f"{bidi_server.bidi_endpoint}/{value['sessionId']}" + assert value["capabilities"]["webSocketUrl"] == f"{webdriver_server.bidi_endpoint}/{value['sessionId']}" finally: assert done() == (200, {"value": None}) -def test_extra_args_passthrough(bidi_server, binary): - assert _request("GET", f"{bidi_server.http_endpoint}/json/version")[0] == 404 # CDP off by default +def test_extra_args_passthrough(webdriver_server, binary): + assert _request("GET", f"{webdriver_server.http_endpoint}/json/version")[0] == 404 # CDP off by default args = ["--protocol", "cdp", "--advertise-host", "localhost"] - with BiDiServer(binary=binary, args=args) as server: + with WebDriverServer(binary=binary, args=args) as server: status, version = _request("GET", f"{server.http_endpoint}/json/version") # --protocol is additive assert status == 200 and version["Browser"].startswith("Lightpanda") value, done = _with_session(server) diff --git a/tests/test_bidi_selenium.py b/tests/test_webdriver_selenium.py similarity index 76% rename from tests/test_bidi_selenium.py rename to tests/test_webdriver_selenium.py index 8736bd8..c6eff33 100644 --- a/tests/test_bidi_selenium.py +++ b/tests/test_webdriver_selenium.py @@ -1,4 +1,4 @@ -"""Drive a BiDiServer with Selenium, over classic WebDriver and over BiDi. +"""Drive a WebDriverServer with Selenium, over classic WebDriver and over BiDi. Selenium is a dev-only dependency: `webdriver.Remote` needs its pip package, not a driver or browser download, and these tests skip when it is not installed.""" @@ -9,15 +9,9 @@ from selenium.webdriver.common.by import By # noqa: E402 from selenium.webdriver.common.options import ArgOptions # noqa: E402 -from lightpanda import BiDiServer, WebDriverServer # noqa: E402 - -def test_webdriver_server_alias(): - assert WebDriverServer is BiDiServer - - -def test_selenium_classic_session(bidi_server, fixture_url): - driver = webdriver.Remote(command_executor=bidi_server.http_endpoint, options=webdriver.ChromeOptions()) +def test_selenium_classic_session(webdriver_server, fixture_url): + driver = webdriver.Remote(command_executor=webdriver_server.http_endpoint, options=webdriver.ChromeOptions()) try: driver.get(f"{fixture_url}/index.html") assert driver.title == "Fixture Home" @@ -36,13 +30,13 @@ def test_selenium_classic_session(bidi_server, fixture_url): driver.quit() # DELETE /session/ -def test_selenium_bidi_session(bidi_server, fixture_url): +def test_selenium_bidi_session(webdriver_server, fixture_url): options = ArgOptions() options.web_socket_url = True - driver = webdriver.Remote(command_executor=bidi_server.http_endpoint, options=options) + driver = webdriver.Remote(command_executor=webdriver_server.http_endpoint, options=options) try: assert driver.caps["browserName"] == "Lightpanda" - assert driver.caps["webSocketUrl"] == f"{bidi_server.bidi_endpoint}/{driver.session_id}" + assert driver.caps["webSocketUrl"] == f"{webdriver_server.bidi_endpoint}/{driver.session_id}" assert driver.browsing_context.get_tree() == [] # first BiDi access opens the websocket context = driver.browsing_context.create(type="tab")