TestCafe前端E2E自動(dòng)化測(cè)試技術(shù)要點(diǎn)

TestCafe前端E2E自動(dòng)化測(cè)試技術(shù)要點(diǎn)

最近用TestCafe完成了一個(gè)營(yíng)銷活動(dòng)的前端自動(dòng)化測(cè)試眉撵,整個(gè)過程很順利苍日,運(yùn)行也較穩(wěn)定。對(duì)比以前用Selenium作的幾個(gè)Web UI自動(dòng)化項(xiàng)目而言蒲肋,感覺到了新一代的前端E2E自動(dòng)化測(cè)試工具的強(qiáng)大。下面記錄一些遇到的要點(diǎn)和TestCafe獨(dú)有的一些特性钝满。

結(jié)構(gòu)

TestCafe是個(gè)基于代理的自動(dòng)化測(cè)試工具兜粘,下面是它在測(cè)試運(yùn)行過程中所處的位置:


image.png
image.png

正是因?yàn)檫@種結(jié)構(gòu),TestCafe多了一些特有的功能弯蚜。

Docker Image

TestCafe有Docker Image孔轴,可以在遠(yuǎn)程Linux主機(jī)終端下用chrome或firefox的headless方式運(yùn)行。

docker run -v /etc/localtime:/etc/localtime:ro -v `pwd`:/tests -it testcafe/testcafe chromium /tests/*.js

因?yàn)闇y(cè)試腳本中有獲取當(dāng)前時(shí)間的函數(shù)碎捺,導(dǎo)致時(shí)間對(duì)比時(shí)出錯(cuò)路鹰,是因?yàn)槿萜髦袝r(shí)區(qū)和宿主機(jī)不一致造成的。命令行加上-v /etc/localtime:/etc/localtime:ro就解決了收厨。

HTTP Logging和Mock

被測(cè)系統(tǒng)中有一個(gè)銀行的營(yíng)銷活動(dòng)晋柱,是個(gè)轉(zhuǎn)盤抽獎(jiǎng),抽中何種獎(jiǎng)品是后端傳給前端的诵叁,前端的頁(yè)面中根據(jù)獎(jiǎng)品的不同有一些定制的邏輯雁竞。所以測(cè)試腳本需要提前獲取中了何種獎(jiǎng)品才能處理,不能僅僅作個(gè)Mock或配置成全部只中一個(gè)獎(jiǎng)品拧额,那樣覆蓋不到所有場(chǎng)景碑诉。好在TestCafe實(shí)質(zhì)是個(gè)Http代理彪腔,所以它提供了HTTP logging的功能。

const turntableLogger = RequestLogger(/getTurntPrize/, {
// logRequestHeaders: true, logRequestBody: true,
logResponseHeaders: true, logResponseBody: true
})
...
test
  .meta('testID', 'F004-T003')
  .requestHooks(turntableLogger)
  ('xxx', async t => {
...
// 從logger中獲取http請(qǐng)求和響應(yīng)进栽,從resoonse中得到抽中獎(jiǎng)品
const httpLog = turntableLogger.requests[0];
await t.expect(turntableLogger.contains(log => log.response.statusCode === 200)).ok();
const resJSON = JSON.parse(httpLog.response.body.toString());
PFOIL.turntableCouponName = resJSON.data[0].voucherName;
const wonClz = actPage.getCouponClz(PFOIL.turntableCouponName);
...

然后就可以在測(cè)試代碼中根據(jù)抽中的獎(jiǎng)品作檢查漫仆、斷言判斷了。

Mock機(jī)制泪幌,很多現(xiàn)代的前端E2E自動(dòng)化測(cè)試工具比如Cypress也有盲厌,這方面沒什么好說的,看看官方文檔就可以用了祸泪。

Programming Interface

TestCafe提供了Programming Interface吗浩,可以用Node.js寫Running wrapper程序來跑測(cè)試,比如寫CLI或Web的Running Wrapper没隘,使得運(yùn)行測(cè)試的手段豐富了很多懂扼。

const createTestCafe = require('testcafe');
let testcafe = null;

createTestCafe('localhost', 1337, 1338)
    .then(tc => {
        testcafe = tc;
        const runner = testcafe.createRunner();

        return runner
            .src('./*.js')
            .browsers(['chrome:headless'])
            .run({
                skipJsErrors: true,
                speed: 1,
                quarantineMode: true,
                takeScreenshotsOnFails: true,
                stopOnFirstFail: false
            });
    })
    .then(failedCount => {
        console.warn('Tests failed: ' + failedCount);
        testcafe.close();
    })
    .catch(err => { /* ... */ })

Page model中自動(dòng)注入Test Controller

UI自動(dòng)化測(cè)試中肯定是要用Page model的,TestCafe有個(gè)很好特性是會(huì)把Test Controller自動(dòng)注入到Page model的方法中去右蒲。例如:

mport { Selector, t } from 'testcafe';
const PFOIL = require('../lib/context.js');

class ActivityTrunTablePage {
    constructor() {
        this.actLuckUnit0 = Selector('.pf-lottery-box .luck-unit-0');
...
    async start() {
      await t.click(this.actStartBtn);
    }
...

但是想把斷言部分也放到Page model中時(shí)阀湿,Test Controller將不能自動(dòng)注入到Selector中,可以在Selector中用{ boundTestRun: t }選項(xiàng)瑰妄,并手工傳入Test Controller來實(shí)現(xiàn):

async validateTurntableElement(t) {
      const actLuckUnit0 = Selector('.pf-lottery-box .luck-unit-0', { boundTestRun: t });
      ...
      const actStartBtn = Selector('#btn', { boundTestRun: t });
      const actDetail = Selector('.pf-lottery-detail', { boundTestRun: t });
      
      await t
        .expect(actLuckUnit0.exists).ok({ timeout: PFOIL.pfoilAssertionDelayLevel1 })
        .expect(actLuckUnit0.textContent).eql('1元話費(fèi)')
        .expect(actLuckUnit1.exists).ok()
      ...
        .expect(actLuckUnit6.exists).ok()
        .expect(actLuckUnit6.textContent).eql('神州專車')
      ...
        .expect(actStartBtn.exists).ok()
        .expect(actStartBtn.getAttribute('ng-click')).eql('showRecharge()');
      ...
    }

這種自動(dòng)注入的特性陷嘴,在重用自動(dòng)化測(cè)試腳本方面很有用,你可以把公用的動(dòng)作放到Page model中间坐,也可以放到Helper方法中灾挨,不用手工傳遞Test Controller:

import { t } from 'testcafe';
...
export async inputGasNum(gasCardNo) {
  await t
    .typeText('#editboxGasNumber', gasCardNo)
    ...
}

然后在測(cè)試中引用此Helper,調(diào)用時(shí)也要加await竹宋。

豐富的Selector功能

查找界面元素

UI自動(dòng)化工具劳澄,首要的是選擇界面元素,TestCafe的Selector功能很豐富蜈七,建立Selector時(shí)參數(shù)支持:

  • CSS選擇器
  • 普通方法
  • 其它的Selector或DOM Snapshot
  • Promise

Selector還支持函數(shù)過濾秒拔,再次選擇,官網(wǎng)上的例子片段:

// Returns id of the third element in the set
const id = await Selector('ul').find('label').parent('div.someClass').nth(2).id;

// Returns snapshot for the fourth element in the set
const snapshot = await Selector('ul').find('label').parent('div.someClass').nth(4)();

其實(shí)CSS3選擇器已經(jīng)足夠強(qiáng)大飒硅,平時(shí)使用時(shí)僅用它就可以滿足要求了砂缩。TestCafe還為常見的現(xiàn)代前端框架定制了Selcetor,這樣用組件就可以選擇界面元素:

  • React
  • Angular
  • AngularJS
  • Vue
  • Aurelia

獲取界面元素屬性

可以直接調(diào)用Selector的方法獲取元素屬性或狀態(tài)狡相,也支持定制的Timeout:

await t
    .expect(packagePage.packageItem.exists).ok( {timeout: PFOIL.pfoilAssertionDelayLevel3} )
    .expect(packagePage.packageType.textContent).contains(labels.labelTwo);

還能通過addCustomDOMPropertiesaddCustomMethods擴(kuò)展Selector的屬性和方法梯轻。這樣的方法是運(yùn)行在瀏覽器端的食磕。用這樣的方法尽棕,所有DOM元件的屬性和方法,都可以從瀏覽器端獲取和執(zhí)行到彬伦。

要注意的有兩點(diǎn):
第一個(gè)是在代碼中使用Selector的屬性而不是在Assertion方法中時(shí)滔悉,需要手工加上awiat:

// 狀態(tài)變化有個(gè)時(shí)間差:先是鎖定伊诵,等支付成功回調(diào),才會(huì)變成已使用
const stateIsCorrect = (await packagePage.packageStateUsed.exists || await packagePage.packageStateLocked.exists);
await t.expect(stateIsCorrect).ok();

第二個(gè)是在Node.js代碼中使用Selector和Test controller時(shí)回官,需要用到boundTestRun屬性曹宴,官網(wǎng)的例子:

test('Title changed', async t => {
    const boundSelector = elementWithId.with({ boundTestRun: t });

    // Performs an HTTP request that changes the article title on the page.
    // Resolves to a value indicating whether the title has been changed.
    const match = await new Promise(resolve => {
        const req = http.request(/* request options */, res => {
            if(res.statusCode === 200) {
                boundSelector('article-title').then(titleEl => {
                    resolve(titleEl.textContent === 'New title');
                });
            }
        });

        req.write(title)
        req.end();
    });

    await t.expect(match).ok();
});

Smart Assertion Query

Web UI自動(dòng)化以前會(huì)面臨一個(gè)頭痛的問題,就是延時(shí)等待歉提。TestCafe會(huì)在斷言其間笛坦,不斷地去試探,直到超時(shí)才認(rèn)為是失敗苔巨。官網(wǎng)上的示意圖:


image.png
image.png

在我實(shí)戰(zhàn)的過程版扩,只在幾個(gè)慢的節(jié)點(diǎn)增加了超時(shí)處理,延長(zhǎng)了缺省的內(nèi)置超時(shí)時(shí)間侄泽。而且TestCafe還能靈活地指定的Page loading時(shí)間礁芦。幾乎不需過多考慮顯式等待的情況。
只要是Client function或Selector傳入Assertion方法中悼尾,都會(huì)自動(dòng)觸發(fā)Smart Assertion Query機(jī)制柿扣。

Client Function

TestCafe實(shí)際上有兩部分,一部分是代理即TestCafe Server闺魏,一部分是在瀏覽器上運(yùn)行的所謂的Client部分未状。因?yàn)镃lient Function是和被測(cè)系統(tǒng)一起運(yùn)行在一個(gè)瀏覽器中的,這就有了很多的可能性析桥,比如娩践,獲取前面Selector機(jī)制不容易得到的界面元素,調(diào)用被測(cè)系統(tǒng)方法烹骨,實(shí)時(shí)計(jì)算和被測(cè)系統(tǒng)相關(guān)的東西等等翻伺。官網(wǎng)上有個(gè)例子:

import fs from 'fs';
import { ClientFunction } from 'testcafe';
...
const getDataFromClient = ClientFunction(() => getSomeData());

test('Check client data', async t => {
    const boundGetDataFromClient = getDataFromClient.with({ boundTestRun: t });

    const equal = await new Promise(resolve => {
        fs.readFile('./data/clientData.json', (err, data) => {
            boundGetDataFromClient().then(clientData => {
                resolve(JSON.stringify(clientData) === data);
            });
        });
    });

    await t.expect(equal).ok();
});

在Client Function運(yùn)行時(shí),還可以把當(dāng)前測(cè)試代碼上下文中的一些變量或工具方法用注入到其內(nèi)沮焕,使用起來很方便:

const thirdOption = option.nth(2);
const getThirdOptionHTML = ClientFunction(() => option().innerHTML, {
     dependencies: { option: thirdOption }
});
...
const getDocumentURI = require('./utils.js').getDocumentURI;
...
test('My test', async t => {
    const getUri = ClientFunction(() => {
        return getDocumentURI();
    }, { dependencies: { getDocumentURI } });
    const uri = await getUri();
...

其它

一路用下來吨岭,感覺TestCafe的特性非常豐富,也很容易學(xué)習(xí)峦树。用VS Code來寫TestCafe的測(cè)試腳本非常順暢辣辫。限于篇幅,其它一些主要特性就只列舉如下魁巩,可以閱讀官文文檔再實(shí)踐即可熟練掌握:

  • 因?yàn)樗莏avascript急灭,所以用所有的現(xiàn)代瀏覽器來跑兼容性測(cè)試
  • User Roles機(jī)制,抽象了用戶鑒權(quán)谷遂,用于處理有登陸的測(cè)試任務(wù)葬馋,支持cookie和browser storage
  • Accessing Console Messages,可以訪問控制臺(tái)消息,在有些情況下可能很有用
  • 暫停畴嘶、調(diào)試蛋逾,因?yàn)門estCafe Server是個(gè)Node程序,所以支持在VS code和在Chrome中調(diào)試
  • Live mode模式窗悯,修改測(cè)試后区匣,自動(dòng)運(yùn)行
  • 并行測(cè)試,每個(gè)運(yùn)行的瀏覽器都是隔離的
  • 非常方便靈活蒋院,可自定義地處理Native Dialogbox
  • 內(nèi)置了大量的等待機(jī)制亏钩,等待界面元素、動(dòng)作欺旧、斷言铸屉、HXR和Fetch請(qǐng)求、重定向等等
  • 在夾具和測(cè)試上都支持測(cè)試框架通用的Setup切端、Teardown彻坛、Tag機(jī)制,只是和其它框架相比名稱不同而已
  • 失敗后截屏踏枣、錄屏昌屉;用quarantine-mode支持失敗重跑
  • 支持remote browser,可以讓測(cè)試跑在沒有安裝TestCafe和測(cè)試腳本的設(shè)備上茵瀑,比如可在手機(jī)上跑測(cè)試
  • 不同的節(jié)點(diǎn)间驮、層級(jí)上都可以指定加載時(shí)間、運(yùn)行速度
  • 運(yùn)行時(shí)具備命令行马昨、代碼竞帽、配置文件等不同層面的配置項(xiàng)
  • 可以靈活地用不同方式地向被測(cè)應(yīng)用注入第三方模塊或代碼,然后用在測(cè)試代碼中
  • 支持夾具或測(cè)試之間的上下文共享(其實(shí)也可用一個(gè)全局的自定義模塊來作為上下文)
  • ......

TestCafe很強(qiáng)大鸿捧,也很易用屹篓,因?yàn)闇y(cè)試用例都是代碼,所以有強(qiáng)大的表現(xiàn)能力匙奴,表現(xiàn)力對(duì)于復(fù)雜的自動(dòng)化測(cè)試來說非常重要堆巧。TestCafe支持用現(xiàn)代的JavaScript(ES7)、TypeScript 和 CoffeeScript來寫測(cè)試泼菌。如有興趣谍肤,可以從官方文檔上學(xué)習(xí)更細(xì)節(jié)的內(nèi)容。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末哗伯,一起剝皮案震驚了整個(gè)濱河市荒揣,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌焊刹,老刑警劉巖系任,帶你破解...
    沈念sama閱讀 218,858評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件恳蹲,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡赋除,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門非凌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來举农,“玉大人,你說我怎么就攤上這事敞嗡“湓悖” “怎么了?”我有些...
    開封第一講書人閱讀 165,282評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵喉悴,是天一觀的道長(zhǎng)棱貌。 經(jīng)常有香客問我,道長(zhǎng)箕肃,這世上最難降的妖魔是什么婚脱? 我笑而不...
    開封第一講書人閱讀 58,842評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮勺像,結(jié)果婚禮上障贸,老公的妹妹穿的比我還像新娘。我一直安慰自己吟宦,他們只是感情好篮洁,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著殃姓,像睡著了一般袁波。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蜗侈,一...
    開封第一講書人閱讀 51,679評(píng)論 1 305
  • 那天篷牌,我揣著相機(jī)與錄音,去河邊找鬼踏幻。 笑死娃磺,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的叫倍。 我是一名探鬼主播偷卧,決...
    沈念sama閱讀 40,406評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼吆倦!你這毒婦竟也來了听诸?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,311評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤蚕泽,失蹤者是張志新(化名)和其女友劉穎晌梨,沒想到半個(gè)月后桥嗤,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,767評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡仔蝌,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年泛领,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片敛惊。...
    茶點(diǎn)故事閱讀 40,090評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡渊鞋,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出瞧挤,到底是詐尸還是另有隱情锡宋,我是刑警寧澤,帶...
    沈念sama閱讀 35,785評(píng)論 5 346
  • 正文 年R本政府宣布特恬,位于F島的核電站执俩,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏癌刽。R本人自食惡果不足惜役首,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望显拜。 院中可真熱鬧宋税,春花似錦、人聲如沸讼油。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)矮台。三九已至乏屯,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間瘦赫,已是汗流浹背辰晕。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評(píng)論 1 271
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留确虱,地道東北人含友。 一個(gè)月前我還...
    沈念sama閱讀 48,298評(píng)論 3 372
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像校辩,于是被迫代替她去往敵國(guó)和親窘问。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容