之前負責一個案子,是做客戶的官方網站,且該網站有要求 RWD,而在專案後期的時候,常常一個 CSS 沒調整好,導致其他頁面跑版,於是乎我就順手寫了跑全部頁面的 E2E 跑版測試,且可以輕易擴充要測試的解析度。 今天就來說明如何建立一個這樣的 E2E 測試專案。

專案使用的套件說明

除了使用 Protractor 以外,還有使用兩個補助套件

  1. blue-harvest: 此套件用於圖片比較的測試。
  2. protractor-screenshot-utils: 使用 browser.takeScreenshot() 只會截到畫面有顯示的部分,並不會截未顯示的部分,所以要使用 protractor-screenshot-utils 這個套件幫助我們整頁截圖。

專案範例說明

我這邊會以 Protractor 官方網站的Protractor Setup menu 來當作測試案例。

menumenu

而完成這個專案最主要有三個步驟

  1. 建立基底 protractor.conf.js
  2. 建立測試案例
  3. 新增其他解析度的 protractor.conf.js

這篇文章的範例專案放在 RWD-Test-Sample-by-Protractor,讀者可以邊參考專案邊看文章。

建立基底 protractor.conf.js

protractor.conf.js
  • js
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
const { SpecReporter } = require('jasmine-spec-reporter');
process.env['UPDATE_GOLDENS'] = "true";

exports.config = {
params: {
width: 1440,
height: 568,
imagePath: 'goldens/chrome',
device: 'desktop_1440'
},
allScriptsTimeout: 999000,
specs: [
'./src/**/*.e2e-spec.ts'
],
SELENIUM_PROMISE_MANAGER: false,
capabilities: {
'browserName': 'chrome',
shardTestFiles: true,
maxInstances: 2,
},
directConnect: true,
baseUrl: 'https://www.protractortest.org/#/',
framework: 'jasmine',
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 999000,
print: function() {}
},
async onPrepare() {
require('ts-node').register({
project: require('path').join(__dirname, './tsconfig.json')
});
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));

await browser.manage().window().setSize(browser.params.width, browser.params.height);
}
};

上敘述的 protractor.conf.js 有五個重點:

第一個重點UPDATE_GOLDENS 的環境變數,用來設定是否更新 golden 圖, 1true 是更新 golden 圖,反之是比對 golden 圖。

1
process.env['UPDATE_GOLDENS'] = "true";

第二個重點是用來設定解析度跟儲存 golden 圖路徑的參數

1
2
3
4
5
6
params: { 
width: 1440,
height: 568,
imagePath: 'goldens/chrome',
device: 'desktop_1440'
}

第三個重點是,SELENIUM_PROMISE_MANAGER 一定要設定為 false ,否則無法使用 blue-harvest
第四個重點capabilities 可以設定 shardTestFiles 和 maxInstances ,這可以讓 Protractor 平行跑測試,效率上會比較好。

1
2
3
4
5
6
SELENIUM_PROMISE_MANAGER: false,
capabilities: {
'browserName': 'chrome',
shardTestFiles: true,
maxInstances: 2,
},

第五個重點是,在 onPrepare 的時候,讀取重點二設定的解析度的參數,整條畫面的大小。

1
await browser.manage().window().setSize(browser.params.width, browser.params.height);

建立測試案例

我建立了一個 Protractor-Setup.e2e-spec.ts 的測試案例,用來巡覽 Protractor Setup 底下的所有 menu。

Protractor-Setup.e2e-spec.ts
  • ts
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
import { browser } from 'protractor';
import { go, compareScreenshot } from 'blue-harvest'
import { ProtractorScreenShotUtils } from 'protractor-screenshot-utils'

describe('Protractor Setup Pages', function() {

const urls = [
'protractor-setup',
'server-setup',
'browser-setup',
'frameworks'
]

it('should valid Protractor Setup', async function() {

// ProtractorScreenShotUtils 的使用方式
const screenShotUtils = new ProtractorScreenShotUtils({
browserInstance : browser
});

for (let index = 0; index < urls.length; index++) {
const url = urls[index];
await go(browser.baseUrl + url);
const fileName = url;
const golden = './src/' + browser.params.imagePath + `/${browser.params.device}/${fileName}.png`;
const diffDir = './src/' + browser.params.imagePath + `/${browser.params.device}/`;
const actual = await screenShotUtils.takeScreenshot();
const result = await compareScreenshot(actual, golden, diffDir);
expect(result).toBeTruthy();
}

});

});

這段程式碼三個重點:

第一個重點,我建立一個 array ,用來儲存要瀏覽的頁面路徑,所以以後只要有新增頁面,我在這邊新增選項就好了。

1
2
3
4
5
6
const urls = [
'protractor-setup',
'server-setup',
'browser-setup',
'frameworks'
]

第二個重點,使用 protractor-screenshot-utils 做整頁的截圖。

1
2
3
4
5
const screenShotUtils = new ProtractorScreenShotUtils({
browserInstance : browser
});

const actual = await screenShotUtils.takeScreenshot();

第三個重點,用 for 迴圈巡覽全部頁面,並且讀取剛剛在 protractor.conf.js 設定的 golden 路徑。

1
2
3
4
5
6
7
8
9
10
for (let index = 0; index < urls.length; index++) {
const url = urls[index];
await go(browser.baseUrl + url);
const fileName = url;
const golden = './src/' + browser.params.imagePath + `/${browser.params.device}/${fileName}.png`;
const diffDir = './src/' + browser.params.imagePath + `/${browser.params.device}/`;
const actual = await screenShotUtils.takeScreenshot();
const result = await compareScreenshot(actual, golden, diffDir);
expect(result).toBeTruthy();
}

執行 E2E 測試

上敘步驟完成後,我們可以將測試執行起來 npm run protractor,執行後就會看到 golden 圖儲存到設定的路徑上。

desktop 1440 goldensdesktop 1440 goldens

這樣未來只要把 UPDATE_GOLDENS 的環境變數清空或者設定成 false ,就可以跟 golden 做比對了

1
2
//in protractor.conf.js
process.env['UPDATE_GOLDENS'] = "false";

新增其他解析度的 protractor.conf.js

因為畫面的大小是寫在 browser.params , 所以只要新增 protractor.conf.js 覆寫掉 browser.params 就可以快速產生不同解析度的測試,而且程式碼都不需要調整。 我這邊以新增一個 pad 解析度為例 protractor.conf.pad_768.js

protractor.conf.pad_768.js
  • js
1
2
3
4
5
6
7
8
9
10
let config = require('./protractor.conf.js').config;

config.params = {
width: 768,
height: 1024,
imagePath: 'goldens/chrome',
device: 'pad_768'
};

exports.config = config;

然後在 package.json 新增 protractor.conf.pad_768.js 的 E2E 測試指令

1
2
3
4
"scripts": {
"protractor": "protractor",
"chrome:768": "protractor protractor.conf.pad_768.js",
},

執行 npm run chrome:768 後就會看到 golden 圖儲存到設定的路徑上。

pad 768 goldenspad 768 goldens

小結

透過這樣的方式,就可以寫少少的程式碼,快速把網站所有的頁面跑過一次跑版測試,並且保有擴充性,可以隨意新增要測試的解析度,甚至是不同的瀏覽器,只要新增 protractor.conf.js 並覆寫設定就可以了。

另外,如果需要有更新 golden 圖的時候,只要使用 configuring-test-suites 或者是 fdescribe 來部分更新 golden 圖就可以了。