1. 介紹
官方
A node.js tool to automate end-to-end web testing Write tests in JS or TypeScript, run them and view results
基於 node.js 的自動化端對端測試工具
- Write tests with ease
- Test in every browser that matters
- Deploy without fear
- CI/CD-ready
- Concurrent test runs
- If something goes wrong…
[Day 11] 看見 TestCafe,又簡單又完整的工具鍊
TestCafe 是筆者用過框架裡,支援最多作業系統 和 瀏覽器 的測試框架
官網
https://www.letswrite.tw/testcafe-member-login/
https://partypeopleland.github.io/artblog/2019/06/24/%E4%BD%BF%E7%94%A8testCafe%E5%81%9AE2E%E6%B8%AC%E8%A9%A6/
https://www.youtube.com/watch?v=D7BfL1x5T2M
http://testingpai.com/article/1619681368102
http://testingpai.com/article/1600914326318
https://www.letswrite.tw/testcafe-settings/
2. 安裝
3. 起手式
1 2 3 4 5 6 7 8 9 10 11
| import { Selector } from 'testcafe';
fixture `Getting Started` .page `http://devexpress.github.io/testcafe/example`;
test('HelloWorld test', async t => { await t .typeText('#developer-name', 'HelloWorld') .click('#submit-button') .expect(Selector('#article-header').innerText).eql('Thank you, HelloWorld!'); });
|
4. 安裝後的執行錯誤
執行
1
| testcafe chrome day01.js
|
因為這個系統上已停用指令碼執行,所以無法載入 C:\Users\RESH\AppData\Roaming\npm\testcafe.ps1 檔案。如需詳細資訊,請參閱 about_Execution_Policies,網址為 https:/go.
microsoft.com/fwlink/?LinkID=135170。
開啟powershell ,請使用「系統管理員身份」執行。
1
| Set-ExecutionPolicy RemoteSigned
|
解決 Windows 上輸入指令出現「因為這個系統上已停用指令碼執行,所以無法載入…」的問題
5. 截圖功能
5.1. 全屏截圖
放在你想要截圖的流程上
takeScreenshot()
test_members.js
1 2 3 4 5 6 7 8
| test('測試_登入', async t => { await t .typeText(Member.txtAccount, 'kite') .typeText(Member.txtPassword, '123456') .click(Member.btnLogin) .expect(Selector('.dropdown-toggle').innerText).contains('kite') .takeScreenshot(); });
|
5.2. 局部截圖
takeElementScreenshot()
test_members.js
1 2 3 4 5 6 7 8 9
| test('測試_登入', async t => { await t .typeText(Member.txtAccount, 'kite') .typeText(Member.txtPassword, '123456') .takeElementScreenshot('#divLoginForm') .click(Member.btnLogin) .expect(Selector('.dropdown-toggle').innerText).contains('kite') .takeScreenshot(); });
|
5.3. 錯誤才截圖
這份code 裡面已經沒有takescreenshot()
囉,當發生錯誤的時候,會自動儲存照片
test_members.js
1 2 3 4 5 6 7
| test('測試_登入', async t => { await t .typeText(Member.txtAccount, 'kite') .typeText(Member.txtPassword, '123456') .click(Member.btnLogin) .expect(Selector('.dropdown-toggle').innerText).contains('kite') });
|
指令
1
| testcafe chrome tests/test_members.js -s takeOnFails=true
|
6. 全螢幕功能
maximizeWindow()
1 2 3 4 5
| test('測試_登入', async t => { await t .maximizeWindow() .typeText(Member.txtAccount, 'kite') });
|
7. 參數說明
7.1. 速度參數
1
| testcafe chrome HelloWorld.js --speed 0.3
|
0.01 ~ 1 之間 1最快,0.01最慢
7.2. 瀏覽器參數
對於 Web 自動化測試而言,一個瀏覽器還真解決不了所有事!
為了完成相容性測試和提高測試完整度,通常要在多的瀏覽器上測試過。
7.2.1. 啟用chrome
1
| testcafe chrome day02.js
|
7.2.2. 啟用firefox
1
| testcafe firefox day02.js
|
7.2.3. 啟用edge
7.2.4. 同時執行兩個瀏覽器
1
| testcafe chrome,firefox day02.js
|
7.2.5. 本地端所有瀏覽器執行
7.2.6. 無頭模式
1
| testcafe chrome:headless day02.js
|
7.2.7. 模擬iphone or android device
1 2
| testcafe "chrome:emulation:device=iphone X" day02.js testcafe "chrome:emulation:device=pixel 2" day02.js
|
7.2.8. 模擬器 + headless mode
1 2
| testcafe "chrome:headless:emulation:device=iphone X" day02.js testcafe "chrome:headless:emulation:device=pixel 2" day02.js
|
7.3. concurrent 參數
真正的平行測試,將多個測試案例 分散到 多個瀏覽器跑
下例,若有 2 個測試案例,會開 2 個 Chrome 瀏覽器,各跑 1 個測試案例。
1
| testcafe -c 2 chrome day02.js --speed .5
|
7.4. 遠端跑自動化測試
1 2
| testcafe remote day02.js testcafe remote day02.js --qr-code
|
7.5. 報表類型
7.5.1. spec
1
| testcafe chrome day02.js -r spec
|
7.5.2. list
1
| testcafe chrome day02.js -r list
|
7.5.3. minimal
1
| testcafe chrome day02.js -r minimal
|
7.5.4. xunit:report.xml
1
| testcafe chrome day02.js -r xunit:report.xml
|
output結果
1 2 3 4 5 6 7
| <?xml version="1.0" encoding="UTF-8" ?> <testsuite name="TestCafe Tests: Chrome 92.0.4515.159 / Windows 10" tests="2" failures="0" skipped="0" errors="0" time="6.167" timestamp="Wed, 25 Aug 2021 01:28:00 GMT" > <testcase classname="Getting Started" name="測試案例一" time="3.65"> </testcase> <testcase classname="Getting Started" name="測試案例二" time="2.502"> </testcase> </testsuite>
|
7.5.5. json:report.json
1
| testcafe chrome day02.js -r json:report.json
|
output結果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| { "startTime": "2021-08-25T03:15:25.322Z", "endTime": "2021-08-25T03:15:32.590Z", "userAgents": [ "Chrome 92.0.4515.159 / Windows 10" ], "passed": 2, "total": 2, "skipped": 0, "fixtures": [ { "name": "Getting Started", "path": "E:\\note\\test_cafe_project\\day02\\day02.js", "meta": {}, "tests": [ { "name": "測試案例一", "meta": {}, "errs": [], "durationMs": 4735, "unstable": false, "screenshotPath": null, "skipped": false }, { "name": "測試案例二", "meta": {}, "errs": [], "durationMs": 2513, "unstable": false, "screenshotPath": null, "skipped": false } ] } ], "warnings": [] }
|
8. Debug說明
1 2 3 4 5 6 7
| test('測試_登出', async t => { Member.autoLogin() await t .click(Member.btnLogout) .debug() .expect(Selector('#divLoginForm > div > h2').innerText).contains('...'); });
|
9. TestCafe Config 說明
資料結架構如下:
1 2 3 4 5
| ├── 根目錄 // root ├── .testcaferc.json // TestCafe Configuration File └── tests // 存放測試的資料夾 ├── TestFixture1.js └── TestFixture2.js
|
我們將要測試的腳本都放在tests資料夾
底下
然後在這個的根目錄
建立一個.testcaferc.json
檔,還有另一種方式是建立.js
檔可以參考官網文件
註:記得testcaferc.json 前面有一個.
,踩到這個坑,想說怎麼照設定一直不會自動跑
testcaferc.json
文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| { "browsers": ["chrome"], "speed": 1, "concurrency": 1, "src": ["tests"], "reporter": [ { "name": "spec" }, { "name": "json", "output": "reports/report.json" } ], "screenshotPath": "reports/screenshots", "screenshots": { "fullPage": true }, "takeScreenshotsOnFails": true, "videoPath": "reports/videos", "videoOptions": { "singleFile": false, "failedOnly": false }, "selectorTimeout": 3000, "assertionTimeout": 3000 }
|
接著就下指令試試囉
註:請在根目錄
執行
就會自動跑在tests資料夾底下所有的腳本囉
10. 測試資料
有規劃可控管的測試資料帶你上天堂,沒控管的測試資料會帶你下地獄。
目前的資料結構
user_account.json
test_batch_members.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import { Selector } from 'testcafe'; import Member from './model/Member'; const dataSet = require("./dataset/user_account.json")
fixture `會員中心`.page `fill your website`;
dataSet.forEach(data =>{ const { account, password, name } = data test(`測試_登入_${account}_${name}`, async t => { await t .maximizeWindow() .typeText(Member.txtAccount, account) .typeText(Member.txtPassword, password) .click(Member.btnLogin) .expect(Selector('.dropdown-toggle').innerText).contains(name); }); })
|
結果
11. 技巧整理
三神器 Selectors, Actions, Assertions
在實作過程中,筆者認為在選取elements
時儘量以中文字的方式選取對象。在mockdata階段,填中文字比較直覺。
11.1. InputText
HTML
1
| 身份證:<input type="text" id="personal_id">
|
testcafe js
1 2 3 4 5 6 7 8
| const txtID = Selector('#personal_id')
typeText(MemberPage.txtID, "A123456789")
expect(MemberPage.txtID.value).eql("A123456789")
|
11.2. SelectOption
HTML
1 2 3 4
| <select> <option value="super_user">超級使用者</option> <option value="normal_user">一般用戶</option> </select>
|
TesCafe
1 2 3 4 5 6 7 8 9 10 11 12 13
| const selRole = Selector("#selRole")
click(selRole) click(selRole.find("option").withText("超級使用者"))
expect(selRole.value).eql('super_user');
expect(selRole.find('option:checked').innerText).eql("超級使用者")
|
11.3. CheckBox
唯一ID
TesCafe
1 2 3 4 5 6 7 8
| const checkbox = Selector('#testing-on-remote-devices');
click(checkbox)
expect(checkbox.checked).ok();
|
多個ID
以這個為例取最外層的selFruit
再用withText
方式點擊checkbox
HTML
1 2 3 4 5 6 7 8 9
| <div id="selFruit" name="fruits" class="form-check"> <input class="form-check-input" type="checkbox" name="chkFruit[]" id="chkFruit_A" value="香蕉"> <label class="form-check-label" for="chkFruit_A">香蕉</label> </div>
<div id="selFruit" name="fruits" class="form-check"> <input class="form-check-input" type="checkbox" name="chkFruit[]" id="chkFruit_B" value="蘋果"> <label class="form-check-label" for="chkFruit_B">蘋果</label> </div>
|
TesCafe
1 2 3 4 5 6 7 8
| const chkFruit = Selector('#selFruit')
chkFruit.withText("香蕉")
expect(chkFruit.withText('香蕉').find('input[type=checkbox]').checked).eql(true)
|
HTML
1 2 3 4 5 6 7 8 9 10 11 12
| <div class="form-check-inline"> <input name="difficulty" class="form-check-input" id="role_1" type="radio" value="2"> <label class="form-check-label" for="role_1">娜美</label> </div> <div class="form-check-inline"> <input name="difficulty" class="form-check-input" id="role_2" type="radio" value="2"> <label class="form-check-label" for="role_2">喬巴</label> </div> <div class="form-check-inline"> <input name="difficulty" class="form-check-input" id="role_3" type="radio" value="2"> <label class="form-check-label" for="role_3">魯夫</label> </div>
|
以此為例,選Label
click 等於直接選radiobutton
,主要原因是有寫label for 對應id,這樣的寫法可以很簡單的抽換中文字就好囉。
下一關在mockdata階段就會比較彈性一點。
TestCafe
1 2 3 4 5 6 7 8
| const rdoDiff = Selector('.form-check-label')
click(rdoDiff.withText('娜美'))
未寫
|
延伸主題:
資料驅動測試 (Data-Driven Test)
12. 安裝外掛
1
| npm install --save-dev @ffmpeg-installer/ffmpeg
|
安裝外掛報表
1
| testcafe chrome day02.js -r html:report.html
|
npm 市集
1
| https://www.npmjs.com/search?q=testcafe-reporter-
|