如果你已經在使用 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,更是更現代化、更可靠的測試方法論。開始遷移吧,你會發現自己不僅獲得了更好的工具,也成為了更好的自動化測試工程師。 最好的遷移時間是一年前,其次是現在。