去年這個時候,我們還在被 Selenium 的“玄學失敗”折磨得睡不好覺。

每週一晨會,大家聊得最多的不是業務需求,而是:“昨天那幾個腳本又隨機掛了,誰幫忙看看?”

直到一個週五深夜,我第 N 次調試那個經典的 “Element not interactable” 錯誤——明明元素就在頁面上,就是點不了。那一刻我意識到:不是我們寫得不夠好,而是工具已經跟不上時代了。

三個月後,我們完成了核心迴歸套件從 Selenium 到 Playwright 的遷移。結果遠超預期:

  • 迴歸時間縮短 60%
  • 腳本維護成本下降 70%
  • 最重要的是——測試同學終於能在週五準點下班了。 今天想和大家聊聊這段遷移經歷,不吹不黑,只講實話。

一、為什麼非換不可?Selenium 的“中年危機”

Selenium 曾是 Web 自動化的代名詞,這點沒人否認。但今天的前端早已不是十年前的樣子:SPA、動態加載、微前端、Shadow DOM……而 Selenium 的底層架構,還停留在“請求-響應”的靜態時代。

我們最頭疼的三個問題:

1. 隨機失敗像“薛定諤的貓” 同一個腳本,今天跑通,明天就掛。排查半天,發現只是網絡慢了 200ms。

我們至少 30% 的時間花在“救火”上,而不是設計更有效的測試。

2. API 層級太深,代碼臃腫 想等一個元素出現?得這樣寫:

WebDriverWait(driver, 10).until(

 EC.presence_of_element_located((By.ID, "submit-btn")) )

還沒開始測邏輯,光等待就寫了三四行。更別説多層嵌套後的可讀性災難。

3. 執行速度慢,資源消耗大 我們的核心迴歸套件(約 300 個用例)在 Selenium 上要跑 2.5 小時。每次上線前,必須提前半天啓動測試,嚴重影響交付節奏。

二、Playwright 快在哪?關鍵在架構

很多人問:“都是控制瀏覽器,差別真有那麼大?”

答案藏在通信機制裏。

Selenium:三層轉發,層層損耗

你的代碼 → WebDriver 協議 → 瀏覽器驅動 → 瀏覽器

每一步都有序列化、反序列化、網絡延遲。就像寄快遞,中間經手人越多,越容易丟件、延誤。

Playwright:直連 DevTools Protocol

你的代碼 → 瀏覽器(通過 CDP)

沒有中間代理,直接與瀏覽器內核對話。這不僅是“快一點”,而是質的飛躍。

我們實測了一個典型場景:打開電商首頁 → 搜索“手機” → 加入購物車,重複 100 次: image.png

三、遷移實戰:如何平穩過渡?

我們沒搞“一刀切”,而是分階段推進。

階段一:雙軌並行,驗證價值(第1個月)新需求:全部用 Playwright 編寫

舊腳本:優先遷移高頻失敗的核心模塊(如登錄、支付) 對比驗證:同一功能兩套實現,看穩定性、速度、維護成本 以“用户登錄”為例:

Selenium 版本(45 行):

def login(username, password):
     driver.get(LOGIN_URL)
     WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.ID, "username"))) 
    driver.find_element(By.ID, "username").clear() 
    driver.find_element(By.ID, "username").send_keys(username)     # ... 類似重複操作 ...
     WebDriverWait(driver, 10).until(EC.url_contains("/dashboard"))

Playwright 版本(15 行):

async def login(page, username, password):
     await page.goto(LOGIN_URL)
     await page.fill("#username", username)
     await page.fill("#password", password)
     await page.click("#login-btn")
     await page.wait_for_url("**/dashboard")

代碼量減少 67%,且從未再出現“元素不可交互”類問題。

階段二:釋放 Playwright 的“超能力”(第2–3個月)遷移完成後,我們開始做以前“想做但做不到”的事:

✅ 智能等待,告別 time.sleep()

# 等待元素可見
 await page.locator(".product-card").first.wait_for(state="visible")
# 等待特定接口返回
 async with page.expect_response("**/api/products") as response_info:
   await page.click("#load-more")

✅ 多瀏覽器 + 移動端一鍵切換

# 同一測試,輕鬆跑在 Chromium / Firefox / WebKit 
# 甚至模擬 iPhone 14 Pro context = await browser.new_context(device="iPhone 14 Pro")

✅ 內置網絡攔截,Mock 不再依賴第三方

# 支付流程無需真實調用
await page.route("**/api/payment/**", lambda route: route.fulfill(     status=200,
     body='{"status":"success"}' ))

這些能力,在 Selenium 裏要麼做不到,要麼需要額外集成大量工具鏈。

四、踩過的坑:避雷指南

坑1:選擇器太脆弱

初期我們用錄製工具生成的選擇器:

# ❌ 容易因 DOM 結構變動而失效 "div.main > div:nth-child(3) > button"

後來我們和前端約定:關鍵交互元素加 data-testid 屬性。

# ✅ 穩定、語義清晰 "[data-testid='checkout-button']"

坑2:並行執行爆內存

第一次嘗試併發 50 個測試,機器直接 OOM。

解決方案:用 asyncio.Semaphore 控制併發數。

semaphore = asyncio.Semaphore(10)  # 最多10個併發

坑3:動態內容加載判斷不準

對無限滾動頁面,我們改用“元素數量不再增長”或“監聽特定 API 響應”來判斷加載完成,而非硬編碼等待。

五、真實數據對比

我們對商品搜索流程做了完整壓測(搜索 → 翻頁5次 → 點擊加載更多): image.png

更驚喜的是 Trace Viewer 功能——測試失敗後,可以直接回放整個操作過程,像看視頻一樣定位問題。很多偶現 Bug,靠它一錘定音。

六、如果你也想遷移:我的建議路線圖

  • 第1周:裝 Playwright,用 playwright codegen 錄一個簡單腳本,感受差異
  • 第1月:搭建基礎框架,遷移最不穩定的 10% 用例
  • 第2–3月:新需求全用 Playwright,逐步替換舊腳本
  • 長期:結合 Git 變更智能調度、視覺迴歸、性能基線等深度優化

七、寫在最後:工具升級,更是思維升級

遷移完成後,團隊裏一位幹了8年的老測試説:

“以前我是 Selenium 的‘消防員’,天天救火;現在我是 Playwright 的‘建築師’,能真正設計質量體系。”

這句話讓我感觸很深。

從 Selenium 到 Playwright,表面是換了個工具,實質是:

從 被動等待 到 智能同步 從 單點執行 到 並行協作 從 功能覆蓋 到 體驗保障 從 腳本維護者 到 質量賦能者 當然,Playwright 也不是萬能藥。如果你還在維護 IE 項目,或者重度依賴 Selenium Grid 的分佈式架構,可能還需謹慎評估。

但對於絕大多數現代 Web 應用——尤其是 React/Vue/Angular 架構——Playwright 帶來的效率提升,是革命性的。

最近面試時,我會問候選人:“如果讓你重新設計自動化框架,你會怎麼做?”

那些能清晰説出 Playwright 優勢,並展示實際落地思考的人,總能讓我眼前一亮。

工具不會決定你的上限,但選對工具,能讓你的價值被看見。

而這,或許才是技術人最該追求的事。