本文內容涉及ES6 async
、jest
的相關知識,對於以上內容不太瞭解的讀者可以先了解相關內容。
Puppeteer是什麼
它由Chrome官方團隊提供,通過Devtools協議在Node層提供了一系列API來控制chrome或者chromium,也就是說我們能夠編寫Node環境的程式碼即可對瀏覽器的行為進行控制。通過它我們可以做到以下行為:
-
生成頁面快照:圖片、pdf
-
抓取spa應用生成預渲染頁面
-
自動化表單提交、UI測試、鍵盤輸入
-
抓取應用的效能資料(chrome performance timeline)
-
測試chrome擴充套件
當然以上行為都是puppeteer能力中極小的一部分,更多的讀者可以閱讀Puppeteer的文件來了解,本文將介紹Puppeteer在E2E測試的實踐
E2E測試
簡單來說,就是模擬真實使用者使用場景進行測試,預期應用能夠正常響應使用者的操作,其關鍵點在於模擬使用者使用環境,模擬使用者操作。
那對於Web應用來說,使用者環境就是瀏覽器,使用者操作主要是移動、點選,這些就是我們需要模擬的部分,下面就直接進入環境和實踐部分。
環境部分
本文的例子採用puppeteer
jest
jest-puppeteer
在mac環境實現
首先需要安裝以上依賴,這裡需要注意以下問題:
-
puppeteer會預設下載chromium,這裡可以通過
export PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
跳過chromium下載 -
Node版本最好使用大於8的版本
接下來說明一下相關的配置
首先是jest相關的配置,在根目錄建立jest.config.js
//jest.config.js
const config = require('config');
const _ = require('lodash');
module.exports = {
preset: 'jest-puppeteer', //呼叫preset
globals: _.assign({}, config.get('e2e.variable'), { //這裡可以注入全域性變數
ENV_URL: config.get('baseUrl')
}),
testMatch: ['**/__e2e__/**/*.test.js?(x)'] //指定需要進行測試的檔案
};
複製程式碼
接下來就是配置puppeteer,在根目錄建立jest-puppeteer.config.js
//jest-puppeteer.config.js
module.exports = {
launch: {
headless: true, //設定執行模式,false的情況下將會工作在有GUI介面的模式,true則不開啟GUI介面
executablePath: //設定本地Chrome路徑,官方推薦使用Chrome Canary
'/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary'
}
};
複製程式碼
實踐部分
環境已經配置好了,是時候進入實踐環節了。對於絕大部分系統尤其是管理系統來說,第一步要做的肯定是登入系統,因此,我們的第一個實踐就是用puppeteer指令碼模擬登入,這裡的例子基於作者這邊的一個系統的登入流程。
const loginFunc = page => async () => {
await page.setViewport({ width: 1280, height: 720 }); //設定視窗大小
await page.goto(`${ENV_URL}/login`); //前往登入地址
const loginIframe = page.frames().find(f => f.name() === 'login-iframe'); //找到登入iframe
await loginIframe.waitForSelector(
'#login-tab-container > div.tab-item.tab-right'
);
await loginIframe.click('#login-tab-container > div.tab-item.tab-right'); //切換登入TAB
await page.waitFor(1000); //特殊用途,之後說明
const username = await loginIframe.waitForSelector('#username'); //找到輸入框
await username.type(USERNAME); //輸入使用者名稱
const pass = await loginIframe.waitForSelector('#password');
await pass.type(PASSWORD); //輸入密碼
await loginIframe.click('#login-btn'); //點選登入按鈕
await page.waitForNavigation(); //等待跳轉導航完成
};
module.exports = {
loginFunc: loginFunc
};
複製程式碼
在這個例子中,展示了開啟登入頁面,輸入使用者名稱、密碼並進行登入的流程,主要涉及到了setViewport
goto
frames
waitForSelector
click
waitFor
type
waitForNavigation
這些API,下面說說其中幾個比較重要的API,更多的可以參考官方文件;
setViewport
設定視窗大小,返回一個promise
waitForSelector
引數為CSS Selector,返回一個promise,直到指定的元素出現才會resolve,超時後會reject
click
點選某個元素,返回一個promise
type
向某個元素輸入內容,返回一個promise
waitForNavigation
URL改變時觸發,返回promise,導航結束時resolve,通過history api進行URL更改時也可以通過該方法等待導航結束
frames
可以獲取頁面中所有的iframe
複製程式碼
相信讀者看完這段例子後也能感受到puppeteer的API設計是十分語意化的,非常好懂。但是實際上這個例子是存在幾個踩坑點的,這裡說一下筆者從這個例子中收穫的經驗:
-
waitForSelector只有當目標Selector原本不存在DOM中才會生效,如果目標元素是通過
display: none;
visibility: hidden
這類方式進行切換的話,這個方法是直接resolve的。 -
如果這個切換過程中還存在動畫效果,那在這個動畫效果的過程中,無論是click還是type動畫過程中的元素,操作都是不會生效的,必須等到動畫結束,因此在本例子中還增加了
waitFor(1000)
這句程式碼來等待動畫的結束
至此我們已經完成了登入,接下來就可以和jest結合進行自動化測試了,先放出本部分的例子:
const utils = require('./utils');
jest.setTimeout(10000);
describe('e2e test', () => {
beforeAll(utils.loginFunc(page));
it('e2e test-1', async () => {
const el = await page.waitForSelector(
'#root > .services_C2FC97 > .ant-row > .ant-col-8:nth-child(1) > .service-card_C2FC97'
);
expect(await el.$eval('p', node => node.innerText)).toBe('test'); //獲取指定元素的innerText
});
});
複製程式碼
在這個例子裡,我們先引入了上面編寫的loginFunc來作為每一個測試的前置條件,由於使用了jest-puppeteer,執行環境中會自動注入puppeteer的page和browser物件,因此可以直接呼叫;這個例子中測試的是首頁的一張功能卡片的標題是否為test
;這個例子是很基礎的,不過相信通過上面那個登入的例子,讀者已經瞭解到編寫E2E測試指令碼的套路了。
不過這裡還有一點需要說明,由於puppeteer啟動時間和開啟網頁的耗時比較難以估計,需要通過jest.setTimeout(ms)
來延長一個測試執行的時間,否則有可能因為執行超時導致失敗,下面是這個例子最終的執行結果:
yarn e2e
yarn run v1.9.4
$ cross-env NODE_ENV=test jest -c jest.config.js
Determining test suites to run...
DevTools listening on ws://127.0.0.1:54751/devtools/browser/7d8d289a-5335-4ffc-a65c-f7e98cb34de0
[1129/154120.584773:WARNING:spdy_session.cc(3152)] Received HEADERS for invalid stream 25
PASS __e2e__/demo.test.js (6.931s)
e2e test
✓ e2e test-1 (1540ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 7.046s
Ran all test suites.
✨ Done in 10.72s.
複製程式碼
至此,入門教程就結束了,是否感覺到很像按鍵精靈?但實際上通過puppeteer我們可以實現更強大的一些控制,包括監聽網路請求,監聽頁面建立等等,基本上我們能在瀏覽器手動做到的,通過它提供的Node API也能夠做到,這部分內容歡迎讀者去閱讀文件進一步的進行探索;
那E2E測試究竟能為我們帶來什麼呢?在筆者看來,對於一個長期迭代的專案,隨著專案規模的擴大,功能的迴歸測試耗費的時間會不可避免的增加,在這種情況下,如果有自動化測試能夠幫我們進行部分測試,將大大提高我們的效率;同時通過定時測試任務可以更早的發現產品功能上存在的問題,從而保證產品能夠按時交付,這是它帶來的最大的價值。
最後,推薦給大家一個小工具puppeteer-recorder
,這是一個Chrome外掛,可以方便的錄製測試指令碼,從而擺脫手動編寫指令碼的苦惱?(由於同源策略,該工具不支援iframe內的操作錄製)
@author: Monado