Web 測試框架 Playwright
對於 web 自動化測試,一路走來我們用過許多方案,剛開始是用最多人知道的 Selenium,那是前端框架還不盛行的時代,也是要手動寫測試腳本的時代。
手動寫測試腳本這件事對工程師們來說,是繁重又缺乏創造力的工作,當時流行的 jQuery web 元件讓「抓 id / class」變成一件相當繁瑣的事,而最大的問題是人力的耗損,算式很簡單:會打碼 = 貴
,任何有成本概念的人都不會想把人力投放在測試上。(投放在測試上/投資在品質上,是兩個不同的概念,不要混為一談。)
第二階段,我們找到了 Sikuli,它是以比對螢幕圖像與位置為基礎的測試工具,也有寫與錄腳本的能力,操作也夠親切,只要有基礎的程式與邏輯概念的人都可以無痛寫(錄)出所有實境操作的劇本,但 Sikuli 的問題是只要換個解析度或換個作業系統,導致 web 元件位置變化或者 render 上的變化,那測試就過不了,只能重寫(錄)…,好在當時我們的 POS 的螢幕解析度都是固定的。
但是 POS 之外的產品專案,由於上面提到的問題,Sikuli 就不足以應付了,於是我們又看到了 Katalon。Katalon 是以 Selenium 為基礎的產品,它的錄製工具會自動幫我們抓網頁元素的 ID,省去了部份的抓 ID 工作,但對於複雜的元件(如 combo box、data grid)還是需要事後人工編修,但整體來說還是個可接受的解決方案。
而今天我們的新玩具叫做 Playwright,Playwright 是微軟開發的 web 自動化測試工具,它有幾項值得一提的特性:
- 跨平台,macOS、Linux、Windows 皆可用。
- 跨瀏覽器,可操控 WebKit、Firefox、Chromium 三大瀏覽器。
- 跨語言,Playwright 原本是以 Node.js 開發,後來微軟陸續移植到 Python、Java 和 .NET 上,雖然語法不同但有著相似的 API。
- 完整的工具鍊,Playwright 包括 Playwright 與 Playwright Test Runner 兩部份,想要拆開來用也可以。
- 年輕、開發活躍、有富爸爸支持,微軟自己也在用。
也有幾項採用前得注意的點:
- 不能操控手機,只能開啟瀏覽器的手機模擬模式,但我們都知道,真的和模擬的有部份的差異。
- 它的 WebKit 瀏覽器和 Safari 也不一樣,雖然 Safari 底層也是 WebKit,但一樣會有點差異。
碰 Playwright 前的前置作業
開一個空的 Node.js 專案,資料夾架構如下:
.├─node_modules ├─tests ├─package.json └─package-lock.json
所有的測試腳本都放在 tests/ 裡面,Playwright 會去跑 test/ 與旗下資料夾內所有 *.spec.js 與 *.spec.ts 的測試腳本。
安裝
Playwright 在 NPM 分為兩個主要套件:
playwright
:Playwright 套件@playwright/test
:Playwright Test Runner 套件
兩者的 API 用法略有差異,依 Playwright 自己的文件說,Playwright Test Runner 比較適合 end-to-end testing 的場景,這也是我們的應用場景,所以下文我們的 Playwright 都是指 Playwright Test Runner。
安裝 Playwright Test Runner:
npm install --save-dev @playwright/test
Playwright Test Runner 並不使用我們的瀏覽器,它有自帶瀏覽器,用這行指令把瀏覽器裝起來:
npx playwright install
一行搞定三大瀏覽器,因此我們也不需要配置任何的瀏覽器路徑等等。:)
基礎使用與配置
用錄的產生腳本
我們用錄的來上手:
npx playwright codegen wikipedia.org --output ./tests/wikipedia.spec.js
跳出瀏覽器和 Playwright Inspector,裡面有錄出來的腳本:
隨便點幾下之後,那個 wikipedia.spec.js 長這樣:
const { test, expect } = require('@playwright/test'); test('test', async ({ page }) => { // Go to https://www.wikipedia.org/ await page.goto('https://www.wikipedia.org/'); // Click text=中文 await page.click('text=中文'); expect(page.url()).toBe('https://zh.wikipedia.org/wiki/Wikipedia:首页'); // Check input[type="checkbox"] await page.check('input[type="checkbox"]'); // Click a:has-text("臺灣正體") await page.click('a:has-text("臺灣正體")'); expect(page.url()).toBe('https://zh.wikipedia.org/zh-tw/Wikipedia:首页'); // Click a:has-text("登入") await page.click('a:has-text("登入")'); // Click text=中文(繁體) await page.click('text=中文(繁體)'); // Click [placeholder="輸入您的使用者名稱"] await page.click('[placeholder="輸入您的使用者名稱"]'); // Fill [placeholder="輸入您的使用者名稱"] await page.fill('[placeholder="輸入您的使用者名稱"]', 'jimmy'); // Click [placeholder="輸入您的密碼"] await page.click('[placeholder="輸入您的密碼"]'); // Fill [placeholder="輸入您的密碼"] await page.fill('[placeholder="輸入您的密碼"]', 'walls'); // Click button:has-text("登入") await page.click('button:has-text("登入")'); // Go to https://zh.wikipedia.org/wiki/Wikipedia:首页 await page.goto('https://zh.wikipedia.org/wiki/Wikipedia:首页'); expect(page.url()).toBe('https://zh.wikipedia.org/wiki/Wikipedia:首页'); });
可以看到,錄出來的並非完美的,有好幾行多餘的敘述,而優點是非常簡單就能上手。
跑測試
要跑測試也很簡單:
npx playwright test --headed --browser=chromium
這行指令會跑所有在 test/ 與旗下資料夾內所有 *.spec.js 與 *.spec.ts 的測試腳本。
後面我們也用參數指定用 Chromium 瀏覽器的有頭模式跑測試。
如果要跑指定腳本,就指定一下:
npx playwright test tests/wikipedia.spec.js --headed --browser=chromium
用有頭模式跑測試時也可以叫出 Playwright Inspector:
# Linux/macOS PWDEBUG=1 npm run test # Windows with cmd.exe set PWDEBUG=1 npm run test # Windows with PowerShell $env:PWDEBUG=1 npm run test
Playwright Inspector 可以手動控制腳本的進度,方便我們對測試腳本 debug。(那我們需要測試腳本的測試腳本嗎?)
配置
配置檔放在專案根目錄下,檔名可以是 playwright.config.js 或 playwright.conifg.ts,目前我的配置很陽春如下:
const { devices } = require('@playwright/test') module.exports = { reporter: [ ['list'], ['json', { outputFile: 'test-results/results.json' }], ['junit', { outputFile: 'test-results/results.xml' }], ], use: { baseURL: 'http://localhost:63800', headless: false, slowMo: 10, screen: { width: 1100, height: 700 }, viewport: { width: 1100, height: 700 }, // Artifacts screenshot: 'on', trace: 'only-on-failure', video: 'on', }, };
其中 reporter
內設定了三組 reporter,不同的 reporter 代表不同的輸出格式,list report 讓我們方便在 console 看到測試結果的摘要,而另外兩個則分別把測試結果輸出成 JSON 和 JUnit 的結構化格式,方便整合至其它系統。
sloMo
則是讓測試的每個步驟停頓的時間,單位是 1/1000 秒,年紀大了要跑慢一點。
而 screenshot
、trace
、video
用於配置測試期間的抓圖、錄影和測試軌跡紀錄的行為,可以是「不論成功失敗都不存檔」、「不論成功失敗都存檔」、「只有失敗才存檔」。
Playwright 還有其它多如牛毛的配置參數,請參閱 Playwright 文件。
Trace
跑完測試的 trace 是完整的測試軌跡紀錄(也是最肥的),用 Playwright Trace Viewer 可以完整重現測試的所有軌跡:
npx playwright show-trace trace.zip
Expect API
Playwright 用 JEST 的 Expect 函式庫來驗證測試的條件,這部份也請參閱 Playwright 和 JEST 的文件。
使用技巧
這邊分享一些自己用到的或網路上挖到的小技巧。
等元素出現
前端框架已經是 web app 的主流方案,網頁元件大量使用 fetch,特別是資料型的表格,這種表格我們叫 data grid,Playwright 要操作 data grid 內的元素必須等待 fetch 的結果顯示出來,雖然 Playwright 有 auto-waiting 的機制,但也不是萬靈丹,對那些不是由用戶觸發的 event,auto-waiting 就不太靈光,還是要自己處理等待的機制。
Playwright 有提供好幾種 wait 函式,最原始的可以用 waitForTimeout()
,但粗暴地用秒數等待大法還是有可能遇到時間到了,fetch 還沒收到資料的問題,我們可以改用另一個 waitForFunction()
來自定一個等待函式,等到元素出現才往下走:
await page.waitForFunction(() => document.querySelectorAll('.data-grid row').length >= 1);
上面的匿名函式的部分我們定義了至少 data grid 內要有一列資料,滿足條件後,watiForFunction()
才結束等待。
使用心得
Playwright 有內建 auto-waiting 的機制,用於因應現代化的 SPA 頻繁存取後端 API 的特性,以往的 Selenium 都要在腳本手動寫下 wait 來確保前端收到回應後再跑下一步,而 Playwright 的 auto-waiting 的機制會自動偵測點擊之類會發送請求的事件,並自動等到有回應的時候再跑下一步…,但以上只是理想而已,實際操練一天下來,還是發現有需要手動定義 wait 的場景。
另外和 Selenium 或 Katalon 相似的是「抓 id / class」的負擔依舊存在,並沒有變輕鬆的感覺。
結語
本文僅粗淺的介紹了 Playwright Test Runner 最基本的用法,特別是 Playwright 自己的函式庫和 JEST Expect 函式庫更是偷懶的隻字未提,這部份的用法取決於各個專案自己的測試情境,只能請讀者大大自行參閱原始文件了。