0%

TestCafe 筆記

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 的自動化端對端測試工具

  1. Write tests with ease
  2. Test in every browser that matters
  3. Deploy without fear
    1. CI/CD-ready
    2. Concurrent test runs
    3. 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. 安裝

1
npm install -g testcafe

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

1
testcafe edge day02.js

7.2.4. 同時執行兩個瀏覽器

1
testcafe chrome,firefox day02.js

7.2.5. 本地端所有瀏覽器執行

1
testcafe all day02.js

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
}

接著就下指令試試囉

註:請在根目錄執行

1
testcafe

就會自動跑在tests資料夾底下所有的腳本囉

image-20210919150241430

10. 測試資料

有規劃可控管的測試資料帶你上天堂,沒控管的測試資料會帶你下地獄。

目前的資料結構

image-20210919165641400

user_account.json

image-20210919170200827

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);
});

})

結果

image-20210919170516212

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
//Selectors
const txtID = Selector('#personal_id')

//Actions
typeText(MemberPage.txtID, "A123456789")

//Assertions
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
//Selectors
const selRole = Selector("#selRole")

//Actions
click(selRole)
click(selRole.find("option").withText("超級使用者"))

//Assertions
//1. get value
expect(selRole.value).eql('super_user');

//2. get text
expect(selRole.find('option:checked').innerText).eql("超級使用者")

11.3. CheckBox

唯一ID

TesCafe

1
2
3
4
5
6
7
8
//Selectors
const checkbox = Selector('#testing-on-remote-devices');

//Actions
click(checkbox)

//Assertions
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
//Selectors
const chkFruit = Selector('#selFruit')

//Actions
chkFruit.withText("香蕉")

//Assertions
expect(chkFruit.withText('香蕉').find('input[type=checkbox]').checked).eql(true)

11.4. RadioButton

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
//Selectors
const rdoDiff = Selector('.form-check-label')

//Actions
click(rdoDiff.withText('娜美'))

//Assertions
未寫

延伸主題:

資料驅動測試 (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-