起因
目前我們在持續(xù)開發(fā)著一個幾十個頁面,十萬+行代碼的項目,隨著產(chǎn)品的更迭,總會出現(xiàn)這樣的問題。在對某些業(yè)務(wù)邏輯或者功能進行添加或者修改的時候(尤其是通用邏輯),這些通用的邏輯或者組件往往會牽扯到一些其他地方的問題。由于測試人員受限,我們很難在完成一個模塊單元后,對所有功能重新測試一遍。
同時,由于環(huán)境及數(shù)據(jù)的區(qū)別,(以及在開發(fā)過程中對代碼完備性的疏忽),代碼會在某些特殊數(shù)據(jù)的解析和和展示上出現(xiàn)問題,在開發(fā)和測試中很難去發(fā)現(xiàn)。總的來說,我們希望有一個這樣的工具,幫我們解決上述幾個問題:
其中,最重要的問題,就是將測試代碼與功能解耦,避免每次迭代和修改都需要追加新的測試用例。我們?nèi)绾巫龅竭@一點呢?首先我們來梳理下測試平臺的功能。
功能設(shè)定
由于我們的平臺主要是進行數(shù)據(jù)展示,所以我們在測試過程中,主要以日常的展示數(shù)據(jù)為重心即可,針對一些復(fù)雜的表單操作先不予處理。針對上述的幾個問題,我們針對自動化測試工具的功能如下:
根據(jù)以上的梳理,我們可以把整個應(yīng)用分為幾個測試單元
通過這樣的劃分,我們針對各個單元進行具體的測試邏輯書寫用例,這樣就可以避免再添加新功能和頁面時,頻繁對測試用例進行修改了。
Puppeteer
帶著上面我們的需求,我們來看下Puppeteer的功能和特性,是否能夠滿足我們的要求。
文檔地址
Puppeteer是一個Node庫,它提供了一個高級 API 來通過 DevTools 協(xié)議控制 Chromium 或 Chrome。Puppeteer 默認以 headless 模式運行,但是可以通過修改配置文件運行“有頭”模式。
我們可以使用Puppeteer完成以下工作:
我們來通過一些小案例,來介紹他們的基本功能:
訪問一個帶有ba認證的網(wǎng)站
puppeteer可以創(chuàng)建page實例,并使用goto方法進行頁面訪問,page包含一系列方法,可以對頁面進行各種操作。
(async () => { const browser = await puppeteer.launch(); const page = await browser.newPage(); // ba認證 await page.authenticate({ username, password }); // 訪問頁面 await page.goto('https://example.com'); // 進行截圖 await page.screenshot({path: 'example.png'}); await browser.close();})();訪問登陸頁面,并進行登錄
首先,對于SPA(單頁面應(yīng)用),我們都知道,當頁面進入后,客戶端代碼才開始進行渲染工作。我們需要等到頁面內(nèi)容渲染完成后,再進行對應(yīng)的操作。我們有以下幾種方法來使用
waitUntil
puppeteer針對頁面的訪問,切換等,提供了waitUntil參數(shù),來確定滿足什么條件才認為頁面跳轉(zhuǎn)完成。包括以下事件:
通過waitUnitl,我們可以當頁面請求都完成之后,確定頁面已經(jīng)訪問完成。
waitFor
waitFor方法可以在指定動作完成后才進行resolve
// wait for selectorawait page.waitFor('.foo');// wait for 1 secondawait page.waitFor(1000);// wait for predicateawait page.waitFor(() => !!document.querySelector('.foo'));我們可以利用waitForSelector方法,當?shù)卿浛蜾秩境晒螅胚M行登錄操作
// 等待密碼輸入框渲染await page.waitFor('#password');// 輸入用戶名await page.type('input#username', "username");// 輸入密碼await page.type('input#password', "testpass");// 點擊登錄按鈕await Promise.all([ page.waitForNavigation(), // 等跳轉(zhuǎn)完成后resolve page.click('button.login-button'), // 點擊該鏈接將間接導(dǎo)致導(dǎo)航(跳轉(zhuǎn))]);await page.waitFor(2000)// 獲取cookiesconst cookies = await page.cookies()針對列表內(nèi)容里的鏈接進行批量訪問
主要利用到page實例的選擇器功能
const table = await page.$('.table')const links = await table.$$eval('a.link-detail', links => links.map(link => link.href));// 循環(huán)訪問links...進行錯誤和訪問監(jiān)聽
puppeteer可以監(jiān)聽在頁面訪問過程中的報錯,請求等等,這樣我們就可以捕獲到頁面的訪問錯誤并進行上報啦,這也是我們進行測試需要的基本功能~
// 當發(fā)生頁面js代碼沒有捕獲的異常時觸發(fā)。page.on('pagerror', () => {})// 當頁面崩潰時觸發(fā)。page.on('error', () => {})// 當頁面發(fā)送一個請求時觸發(fā)page.on('request')// 當頁面的某個請求接收到對應(yīng)的 response 時觸發(fā)。page.on('response')通過以上的幾個小案例,我們發(fā)現(xiàn)Puppeteer的功能非常強大,完全能夠滿足我們以上的對頁面進行自動訪問的需求。接下來,我們針對我們的測試單元進行個單元用例的書寫
最終功能
通過我們上面對測試單元的規(guī)劃,我們可以規(guī)劃一下我們的測試路徑
訪問網(wǎng)站 -> 登陸 -> 訪問頁面1 -> 進行基本單元測試 -> 獲取詳情頁跳轉(zhuǎn)鏈接 -> 依次訪問詳情頁 -> 進行基本單元測試
-> 訪問頁面2 ...
所以,我們可以拆分出幾個大類,和幾個測試單元,來進行各項測試
// 包含基本的測試方法,log輸出等class Base {}// 詳情頁單元,進行一些基本的單元測試class PageDetal extends Base {}// 頁面單元,進行基本的單元測試,并獲取并依次訪問詳情頁class Page extends PageDetal {}// 進行登錄等操作,并依次訪問頁面單元進行測試class Root extends Base {}同時,我們?nèi)绾卧诠δ茼撁孀兓瘯r,跟蹤到測試的變化呢,我們可以針對我們測試的功能,為其添加自定義標簽test-role,測試時,根據(jù)自定義標簽進行測試邏輯的編寫。
例如針對時間切換單元,我們做一下簡單的介紹:
// 1. 獲取測試單元的元素const timeSwitch = await page.$('[test-role="time-switch"]');// 若頁面沒有timeSwitch, 則不用進行測試if (!timeSwitch) return// 2. time switch的切換按鈕const buttons = timeSwitch.$$('.time-switch-button')// 3. 對按鈕進行循環(huán)點擊for (let i = 0; i < buttons.length; i++) { const button = buttons[i] // 點擊按鈕 await button.click() // 重點! 等待對應(yīng)的內(nèi)容出現(xiàn)時,才認定頁面訪問成功 try { await page.waitFor('[test-role="time-switch-content"]') } catch (error) { reportError (error) } // 截圖 await page.screenshot()}上面只是進行了一個簡單的訪問內(nèi)容測試,我們可以根據(jù)我們的用例單元書寫各自的測試邏輯,在我們?nèi)粘i_發(fā)時,只需要對需要測試的內(nèi)容,加上對應(yīng)的test-role即可。
總結(jié)
根據(jù)以上的功能劃分,我們很好的將一整個應(yīng)用拆分成各個測試單元進行單元測試。需要注意的是,我們目前僅僅是對頁面的可訪問性進行測試,僅僅驗證當用戶進行各種操作,訪問各個頁面單元時頁面是否會出錯。并沒有對頁面的具體展示效果進行測試,這樣會和頁面的功能內(nèi)容耦合起來,就需要單獨的測試用例的編寫了。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持VeVb武林網(wǎng)。
新聞熱點
疑難解答