如果你已经在使用 Selenium 进行 Web 自动化测试,可能会注意到近年来 Playwright 的崛起。这不是简单的替代关系,而是一次测试能力的全面升级。我在去年带领团队完成从 Selenium 到 Playwright 的迁移后,测试执行速度提升了 40%,代码维护成本降低了 30%。更重要的是,那些曾经令人头疼的等待问题、不稳定性问题,都得到了显著改善。
在开始迁移前,理解两个框架的本质差异至关重要:
Selenium 像是一个翻译官——它将你的指令翻译成不同浏览器的原生 API 调用,中间经过 WebDriver 协议。这个额外的抽象层虽然带来了广泛的浏览器支持,但也增加了复杂性和不稳定性。
Playwright 则像是直接与浏览器对话——它通过 DevTools 协议直接控制浏览器,支持 Chromium、Firefox 和 WebKit 三大引擎,提供了更一致、更可靠的执行环境。
在开始编码迁移之前,花时间评估当前测试状态:
统计现有测试用例数量和复杂度
识别重度依赖 Selenium 特性的部分
规划迁移顺序(建议从简单的测试开始)
# 卸载旧依赖
pip uninstall selenium
# 安装 Playwright
pip install playwright pytest-playwright
# 安装浏览器
playwright install chromium firefox webkit
将原来的 Selenium 配置转换为 Playwright 的配置文件:
# 原来的 Selenium 配置
# from selenium import webdriver
# options = webdriver.ChromeOptions()
# options.add_argument('--headless')
# driver = webdriver.Chrome(options=options)
# Playwright 配置
import asyncio
from playwright.async_api import async_playwright
asyncdef create_browser():
asyncwith async_playwright() as p:
browser = await p.chromium.launch(headless=False)
context = await browser.new_context()
page = await context.new_page()
return browser, page
Selenium 方式:
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
driver = webdriver.Chrome()
wait = WebDriverWait(driver, 10)
Playwright 方式:
from playwright.sync_api import Page, expect
# 自动等待内置于大多数操作中
page.wait_for_timeout(1000) # 尽量避免使用,利用自动等待
点击元素:
# Selenium
element = driver.find_element(By.ID, "submit-btn")
element.click()
# Playwright (更简洁)
page.click("#submit-btn")
# 或者使用更明确的选择器
page.get_by_role("button", name="提交").click()
输入文本:
# Selenium
search_box = driver.find_element(By.NAME, "q")
search_box.send_keys("Playwright 教程")
# Playwright
page.fill("input[name='q']", "Playwright 教程")
# 或者
page.get_by_placeholder("搜索...").fill("Playwright 教程")
这是迁移中最需要调整思维的地方:
# Selenium 显式等待
element = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.ID, "dynamic-element"))
)
# Playwright 自动等待(推荐)
page.wait_for_selector("#dynamic-element", state="visible")
# 或者更优雅的:
expect(page.locator("#dynamic-element")).to_be_visible()
# Selenium(需要配置期望)
alert = driver.switch_to.alert
alert.accept()
# Playwright(监听对话框)
page.on("dialog", lambda dialog: dialog.accept())
page.click("#trigger-alert")
# Playwright 的上下文管理更清晰
async with await browser.new_context() as context:
page = await context.new_page()
await page.goto("https://example.com")
# 打开新标签页
async with await context.new_page() as new_page:
await new_page.goto("https://another-site.com")
Playwright 的网络请求控制能力强大得多:
# 拦截特定请求
await page.route("**/api/*", lambda route: route.fulfill(
status=200,
content_type="application/json",
div='{"success": true}'
))
# 修改请求头
await page.route("**/*", lambda route: route.continue_(headers={
**route.request.headers,
"X-Custom-Header": "value"
}))
# 文件上传(不再需要复杂的 send_keys)
async with page.expect_file_chooser() as fc_info:
await page.click("#upload-button")
file_chooser = await fc_info.value
await file_chooser.set_files("myfile.pdf")
# 下载文件
async with page.expect_download() as download_info:
await page.click("#download-button")
download = await download_info.value
await download.save_as("downloaded_file.pdf")
# 原来的 Selenium 自定义等待
def element_has_class(locator, class_name):
def _predicate(driver):
element = driver.find_element(*locator)
return class_name in element.get_attribute("class")
return _predicate
# Playwright 解决方案
async def wait_for_class(page, selector, class_name, timeout=10000):
await page.wait_for_function(
"""([selector, className]) => {
const el = document.querySelector(selector);
return el && el.classList.contains(className);
}""",
[selector, class_name],
timeout=timeout
)
# Playwright 原生支持 Shadow DOM
shadow_host = page.locator("#shadow-host")
shadow_root = shadow_host.element_handle().evaluate_handle("el => el.shadowRoot")
shadow_element = shadow_root.query_selector(".inner-element")
# Playwright 的 BrowserContext 是天然隔离的
import pytest
@pytest.fixture(scope="function")
async def page(browser):
context = await browser.new_context()
page = await context.new_page()
yield page
await context.close()
# 可以在不同的上下文中并行执行测试
重用浏览器实例:创建多个上下文而不是多个浏览器
合理使用 headless 模式:CI/CD 环境中使用 headless
视频录制选择性开启:只在失败的测试中录制视频
优化截图策略:只在需要时截图,使用 full_page=True 参数控制
# 配置示例
browser = await chromium.launch(
headless=True,
args=['--disable-dev-shm-usage']
)
context = await browser.new_context(
viewport={'width': 1920, 'height': 1080},
record_video_dir='videos/' if config.record_video else None
)
更新依赖项和配置文件
重写浏览器初始化逻辑
更新元素定位器(优先使用角色和文本定位器)
替换显式等待为 Playwright 的自动等待模式
更新文件操作相关代码
重写弹窗和对话框处理逻辑
更新测试报告集成
配置 CI/CD 流水线支持
建立性能基准并对比
完成迁移后,你将会发现:
稳定性显著提升:减少了约 60%的 flaky tests
执行速度加快:平均测试执行时间减少 30-50%
代码更简洁:代码行数通常减少 40%
调试更方便:内置的追踪和截图功能强大
跨浏览器测试更可靠:真正的多浏览器支持
【霍格沃兹测试开发】7天软件测试快速入门 带你从零基础/转行/小白/就业/ 测试用例设计实战
【霍格沃兹测试开发】最新版!Web自动化测试从入门到精通/电子商务产品实战/Selenium(上集)
【霍格沃兹测试开发】最新版!Web自动化测试从入门到精通/电子商务产品实战/Selenium(下集)
【霍格沃兹测试开发】明星讲师精心打造最新Python教程软件测试开发从业者必学(上集)
【霍格沃兹测试开发】明星讲师精心打造最新Python教程软件测试开发从业者必学(下集)
【霍格沃兹测试开发】精品课合集/自动化测试/性能测试/精准测试/测试左移/测试右移/人工智能测试
【霍格沃兹测试开发】腾讯/百度/阿里/字节测试专家技术沙龙分享合集/精准化测试/流量回放/Diff
【霍格沃兹测试开发】Pytest用例结构/编写规范 /免费分享
【霍格沃兹测试开发】JMeter实时性能监控平台/数据分析展示系统Grafana/Docker安装
【霍格沃兹测试开发】接口自动化测试的场景有哪些?为什么要做接口自动化测试?如何一键生成测试报告?
【霍格沃兹测试开发】面试技巧指导/测试开发能力评级/1V1模拟面试实战/冲刺年薪百万!
【霍格沃兹测试开发】腾讯软件测试能力评级标准/要评级表格的联系我
【霍格沃兹测试开发】Pytest与Allure2一键生成测试报告/测试用例断言/数据驱动/参数化
【霍格沃兹测试开发】App功能测试实战快速入门/adb常用命令/adb压力测试
【霍格沃兹测试开发】阿里/百度/腾讯/滴滴/字节/一线大厂面试真题讲解,卷完拿高薪Offer!
【霍格沃兹测试开发】App自动化测试零基础快速入门/Appium/自动化用例录制/参数配置
【霍格沃兹测试开发】如何用Postman做接口测试,从入门到实战/接口抓包(最新最全教程)
【霍格沃兹测试开发】6小时轻松上手功能测试/软件测试工作流程/测试用例设计/Bug管理
【霍格沃兹测试开发】零基础小白如何使用Postman,从零到一做接口自动化测试/从零基础到进阶到实战
【霍格沃兹测试开发】建议收藏全国CCF测试开发大赛Python接口自动化测试赛前辅导 /项目实战