本文轉載自 Leon 的網誌
對於 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. 可以看到,錄出來的並非完美的,有好幾行多餘的敘述,而優點是非常簡單就能上手。 要跑測試也很簡單: 這行指令會跑所有在 test/ 與旗下資料夾內所有 *.spec.js 與 *.spec.ts 的測試腳本。 後面我們也用參數指定用 Chromium 瀏覽器的有頭模式跑測試。 如果要跑指定腳本,就指定一下: 用有頭模式跑測試時也可以叫出 Playwright Inspector: Playwright Inspector 可以手動控制腳本的進度,方便我們對測試腳本 debug。(那我們需要測試腳本的測試腳本嗎?) 配置檔放在專案根目錄下,檔名可以是 playwright. 其中 而 Playwright 還有其它多如牛毛的配置參數,請參閱 Playwright 文件。 跑完測試的 trace 是完整的測試軌跡紀錄(也是最肥的),用 Playwright Trace Viewer 可以完整重現測試的所有軌跡: Playwright 用 JEST 的 Expect 函式庫來驗證測試的條件,這部份也請參閱 Playwright 和 JEST 的文件。 這邊分享一些自己用到的或網路上挖到的小技巧。 前端框架已經是 web app 的主流方案,網頁元件大量使用 fetch,特別是資料型的表格,這種表格我們叫 data grid,Playwright 要操作 data grid 內的元素必須等待 fetch 的結果顯示出來,雖然 Playwright 有 auto-waiting 的機制,但也不是萬靈丹,對那些不是由用戶觸發的 event,auto-waiting 就不太靈光,還是要自己處理等待的機制。 Playwright 有提供好幾種 wait 函式,最原始的可以用 上面的匿名函式的部分我們定義了至少 data grid 內要有一列資料,滿足條件後, Playwright 有內建 auto-waiting 的機制,用於因應現代化的 SPA 頻繁存取後端 API 的特性,以往的 Selenium 都要在腳本手動寫下 wait 來確保前端收到回應後再跑下一步,而 Playwright 的 auto-waiting 的機制會自動偵測點擊之類會發送請求的事件,並自動等到有回應的時候再跑下一步…,但以上只是理想而已,實際操練一天下來,還是發現有需要手動定義 wait 的場景。 另外和 Selenium 或 Katalon 相似的是「抓 id / class」的負擔依舊存在,並沒有變輕鬆的感覺。 本文僅粗淺的介紹了 Playwright Test Runner 最基本的用法,特別是 Playwright 自己的函式庫和 JEST Expect 函式庫更是偷懶的隻字未提,這部份的用法取決於各個專案自己的測試情境,只能請讀者大大自行參閱原始文件了。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:%E9%A6%96%E9%A1%B5');
// 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:%E9%A6%96%E9%A1%B5');
// 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:%E9%A6%96%E9%A1%B5
await page.goto('https://zh.wikipedia.org/wiki/Wikipedia:%E9%A6%96%E9%A1%B5');
expect(page.url()).toBe('https://zh.wikipedia.org/wiki/Wikipedia:%E9%A6%96%E9%A1%B5');
});
跑測試
npx playwright test --headed --browser=chromium
npx playwright test tests/wikipedia.spec.js --headed --browser=chromium
# 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
配置
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
用於配置測試期間的抓圖、錄影和測試軌跡紀錄的行為,可以是「不論成功失敗都不存檔」、「不論成功失敗都存檔」、「只有失敗才存檔」。Trace
npx playwright show-trace trace.zip
Expect API
使用技巧
等元素出現
waitForTimeout()
,但粗暴地用秒數等待大法還是有可能遇到時間到了,fetch 還沒收到資料的問題,我們可以改用另一個 waitForFunction()
來自定一個等待函式,等到元素出現才往下走:await page.waitForFunction(() => document.querySelectorAll('.data-grid row').length >= 1);
watiForFunction()
才結束等待。使用心得
結語