加快您的 Selenium 脚本运行速度:Lightpanda 现已支持 WebDriver


Lightpanda 1.0 现已支持 WebDriver,包括经典版和 BiDi 版本,成为唯一能原生运行所有 Web 自动化协议的浏览器:CDP、WebDriver 和 MCP。只需将 Selenium 脚本指向 lightpanda serve --protocol webdriver 而非 chromedriver,或者使用 lightpanda Python 包自动启动它,脚本即可无需任何修改直接运行。在目前 Lightpanda 支持的 Selenium 官方文档测试中,它比无头版 Chrome 快最高 4.4 倍,内存占用少 7 倍。WebDriver 支持仍在完善中,大家的测试用例将决定我们下一步的开发方向。
WebDriver、CDP 和 BiDi:三种协议,多种客户端
如果你现在运行 Selenium 脚本,那么你已经在用 WebDriver 了。Selenium 是一个客户端库,而WebDriver 是它所使用的通信协议,源自 Selenium 项目,已发展为 W3C 标准。目前控制浏览器主要靠三种协议:
| 协议 | 通信方式 | 制定者 | 主要客户端 |
|---|---|---|---|
| WebDriver 经典版 | HTTP。浏览器只回复请求,因此客户端需轮询以获取变更 | W3C | Selenium、WebdriverIO、Nightwatch、Appium |
| WebDriver BiDi | WebSocket,双向开放。浏览器主动推送事件 | W3C,仍在草案阶段 | Selenium 4 BiDi API、WebdriverIO、Puppeteer |
| CDP | WebSocket,双向开放。浏览器主动推送事件 | Google,专用于 Chromium 系浏览器 | Puppeteer、Playwright、chromedp、Rod |

选择哪种协议,决定了你的脚本和浏览器之间隔着什么。
WebDriver 出现得最早。它于 2011 年并入 Selenium,2018 年成为 W3C 标准。测试脚本通过 HTTP 向 driver 发送命令,而每家浏览器厂商都有自己的实现:Chrome 用 chromedriver,Firefox 用 geckodriver,Safari 用 safaridriver。这个 driver 是额外多出来的一个进程,每次运行都得和浏览器一起安装、启动。
与此同时,Chrome 自研了一套协议。从 2012 年起,DevTools 协议(CDP)就能让检查器面板通过 WebSocket 控制页面,浏览器可以在事件发生时实时推送。它基于 Chrome 内部机制设计,覆盖的功能远不止自动化。2017 年,Chrome 团队在它之上构建了 Puppeteer,随后 Playwright 也加入进来,CDP 成了事实上的自动化标准。想深入了解它的工作原理,可以看看 CDP under the hood 这篇文章。
WebDriver BiDi 目前还是 W3C 草案,试图兼得两者之长:用 WebSocket 实现双向通信,同时是一个任何浏览器都能实现开放标准。不过 driver 依然存在:在 Chrome 上,chromedriver 仍负责开启会话,并把 BiDi 翻译成 CDP。
我们在此前文章中更详细地介绍过 web automation stack 及其整体架构。
Lightpanda 原生支持所有 Web 自动化协议
启动 Lightpanda 项目时,我们思考了一个问题:如果浏览器是为机器而生,它应该是什么样子?我们的答案是:不做图形渲染,让代码直接控制浏览器,中间不需要任何 driver。
最初,Lightpanda 支持 CDP,因此 Puppeteer 和 Playwright 脚本从一开始就能在其上运行。1.0 版本新增了对 WebDriver(经典版和 BiDi)的支持,让你的 Selenium 测试套件也能在此运行。现在,Lightpanda 是唯一原生支持所有主流 Web 自动化协议的浏览器:CDP、WebDriver 和 MCP。
在 Lightpanda 上运行 Selenium 测试套件
你需要:
curl -fsSL https://pkg.lightpanda.io/install.sh | bash -s "1.0.0"
示例针对 quotes.toscrape.com(一个公开的练习网站)运行两个测试。test_login 提交登录表单并等待出现 Logout 链接。test_js_rendered_quotes 检查在页面加载后由 JavaScript 注入的引言。
conftest.py 包含为每个测试打开新 WebDriver 会话的 pytest fixture。这是唯一需要针对 Lightpanda 特殊处理的代码:webdriver.Remote 让 Selenium 连接到正在运行的 Lightpanda 服务器,而不是启动 chromedriver。
# conftest.py
import pytest
from selenium import webdriver
@pytest.fixture
def driver():
# `lightpanda serve --protocol webdriver` 默认监听 9222 端口。
d = webdriver.Remote(
command_executor="http://127.0.0.1:9222",
# Selenium 需要一个 options 对象;Lightpanda 接受任意浏览器的 options。
options=webdriver.ChromeOptions(),
)
yield d
d.quit()
test_quotes.py 包含测试用例。它们是标准的 Selenium 代码,无需修改即可在 Chrome 上运行。
# test_quotes.py
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
BASE = "https://quotes.toscrape.com"
def test_login(driver):
driver.get(f"{BASE}/login")
driver.find_element(By.ID, "username").send_keys("panda")
driver.find_element(By.ID, "password").send_keys("secret")
driver.find_element(By.CSS_SELECTOR, "input[type=submit]").click()
logout = WebDriverWait(driver, 5).until(
EC.presence_of_element_located((By.LINK_TEXT, "Logout"))
)
assert logout.text == "Logout"
def test_js_rendered_quotes(driver):
# /js/ 页面上的引语由 JavaScript 注入,HTML 源码中不包含这些内容。
driver.get(f"{BASE}/js/")
quotes = WebDriverWait(driver, 5).until(
EC.presence_of_all_elements_located((By.CSS_SELECTOR, ".quote"))
)
assert len(quotes) == 10
assert driver.find_element(By.CSS_SELECTOR, ".quote .author").text == "Albert Einstein"
在第二个终端中启动 Lightpanda 的 WebDriver 模式,然后运行测试套件:
lightpanda serve --protocol webdriver
pytest -v
collected 2 items
test_quotes.py::test_login PASSED [ 50%]
test_quotes.py::test_js_rendered_quotes PASSED [100%]
============================== 2 passed in 1.20s ===============================
使用 Lightpanda Python 包
如果不想手动安装浏览器,可以使用 lightpanda 包。该包内置了 Lightpanda 并会自动为你启动服务器。保留原有的 test_quotes.py,并替换 conftest.py 为以下内容:
pip install lightpanda selenium pytest
# conftest.py
import pytest
from lightpanda import BiDiServer
from selenium import webdriver
@pytest.fixture(scope="session")
def server():
# 启动包内附带的 Lightpanda 可执行文件
with BiDiServer() as server:
yield server
@pytest.fixture
def driver(server):
d = webdriver.Remote(server.http_endpoint, options=webdriver.ChromeOptions())
yield d
d.quit()
然后运行 pytest -v。
基准测试:Selenium 官方测试在 Chrome 与 Lightpanda 上的表现
```我们跑了 Selenium 文档背后的 Python 示例,目标是其中 66 个使用标准 WebDriver 命令的测试,且只改了 conftest.py 里的 driver 配置。测试页面在本地提供服务,因此网络耗时不会影响数据。
Lightpanda 通过了 66 个测试中的 43 个(Chrome 为 65 个)。失败的测试用到了我们仍在开发的功能,详见下文Lightpanda 1.0 目前支持的功能。我们对双方都能通过的 43 个测试计时,每个重复 3 次,分别测了单个串行运行和 8 个并行运行:
| Lightpanda 1.0.0 | Chrome 154 + chromedriver | |
|---|---|---|
| 单个串行 | 40.8 秒,74 MB | 105.2 秒,552 MB |
| 8 个并行 | 12.1 秒,97 MB | 53.8 秒,759 MB |
串行运行时,Lightpanda 快 2.6 倍,内存少 7.5 倍;8 个并行时,快 4.4 倍,内存少 7.8 倍。原因是每个 Chrome 会话都要启动独立的进程树,而 Lightpanda 的会话只是单个运行进程中的一个上下文。
以上结果是 5 次运行的中位数,环境为 GitHub Actions Linux runner(2 核 CPU、8 GB 内存),对标 headless Chrome 154。脚本和具体版本见这个 Gist。
在 CI 中运行的技巧
在测试开始前先在后台启动服务,它几乎瞬间就能就绪:
lightpanda serve --protocol webdriver &
pytest
如果你的测试需要并行执行,记得调高会话上限。pytest-xdist(pytest -n)或 Selenium Grid 这类工具会为每个 worker 开一个会话,而 Lightpanda 默认最多同时接受 16 个。可以把 --cdp-max-connections(同样适用于 WebDriver 会话)设为 worker 数量的 1.5 倍左右,因为已关闭的会话需要时间才能释放槽位。以 32 个 worker 为例:
lightpanda serve --protocol webdriver --cdp-max-connections 48
在每个测试结束时都要调用 driver.quit()。用 Chrome 时,未关闭的会话会随 pytest 进程一起终止;但在使用共享的 Lightpanda 服务器时,该会话槽位会一直保留,直到触发 60 秒的空闲超时机制。如果堆积了太多这样的残留会话,下次运行就会因 too many sessions(会话过多)而失败。
Lightpanda 1.0 目前支持的功能
下表对比了 Lightpanda 在不同协议下的支持情况。
| 能力 | Classic 协议 | BiDi 协议 |
|---|---|---|
| 会话(每个测试一个) | ✅ | ✅ |
| 导航 | ✅ get, back, forward, refresh | ⚠️ 仅支持 navigate |
| 查找元素 | ✅ CSS, ID, XPath, 链接文本 | ✅ l |