如果你已經在使用 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
核心API遷移對照表
瀏覽器初始化
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")
高級特性遷移指南
1. 頁面切換管理
# 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")
2. 網絡請求攔截
Playwright 的網絡請求控制能力強大得多:
# 攔截特定請求
await page.route("**/api/*", lambda route: route.fulfill(
status=200,
content_type="application/json",
body='{"success": true}'
))
# 修改請求頭
await page.route("**/*", lambda route: route.continue_(headers={
**route.request.headers,
"X-Custom-Header": "value"
}))
3. 文件上傳下載
# 文件上傳(不再需要複雜的 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")
遷移常見問題與解決方案
問題1:自定義等待條件怎麼辦?
# 原來的 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
)
問題2:如何處理 Shadow DOM?
# 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")
### 問題3:並行測試怎麼處理?
# 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%
- 調試更方便:內置的追蹤和截圖功能強大 跨瀏覽器測試更可靠:真正的多瀏覽器支持
開始你的遷移之旅
遷移不是一夜之間完成的。建議採取漸進式遷移策略:先從簡單的測試用例開始,積累經驗後再處理複雜的測試。建立遷移的節奏——比如每週遷移10%的測試用例,同時保持原有 Selenium 測試的正常運行。 記住,遷移不僅僅是技術替換,更是測試理念的升級。Playwright 提供的不僅是新的 API,更是更現代化、更可靠的測試方法論。開始遷移吧,你會發現自己不僅獲得了更好的工具,也成為了更好的自動化測試工程師。 最好的遷移時間是一年前,其次是現在。