Vuex 4源码学习笔记 - 通过Vuex源码学习E2E测试(十一)
在上一篇筆記中:Vuex 4源碼學習筆記 - 做好changelog更新日志很重要(十)
我們學到了通過conventional-changelog來生成項目的Changelog更新日志,通過更新日志我們可以更好記錄版本的更新信息,以及每一個更新所對應的代碼有哪些變更。這樣項目的可維護性會變得更好。
今天我們要看的是E2E測試,也是Vuex源碼中所集成的,和單元測試有所區別,單元測試主要測試每個函數,一個小的單元。而E2E測試主要是測試整個端到端,也就是實際的前端展示是否是正確的,符合預期。
我們通過Vuex的源碼真的可以學到很多的東西,雖然項目的代碼不是很多,才1000多行,但項目的各方面質量保證很好,NPM上每周下載量為159萬,通過學習優秀的開源項目,我們把這些點都應用到自己的項目里。
老規矩,還是從package.json看起,我們可以找到一個test:e2e的命令
"scripts": {//..."dev": "node examples/server.js","test:e2e": "start-server-and-test dev http://localhost:8080 \"jest --testPathIgnorePatterns test/unit\"",//... }要運行E2E測試,由于是要測試項目真實的展示情況,所以需要跑起項目,這里Vuex用到start-server-and-test這個依賴工具,這里不禁的感嘆Node.js的NPM生態真是太強大了,什么工具都有,不知道還有什么其他語言的生態能做到嗎?除了Java。
這個start-server-and-test所做的事情就是,啟動服務器,等待URL加載,然后運行測試命令,當測試結束時,再關閉服務器。
上面命令的前面部分start-server-and-test dev,就相當于運行了npm run dev命令來啟動webpack開發服務器,然后接下來等待http://localhost:8080加載,加載完成后執行jest --testPathIgnorePatterns test/unit這個jest測試命令來使用jest來運行我們的整個E2E測試。
我們可以看到e2e下面有4個測試文件,以cart.spec.js為例
代碼如下:
import { setupPuppeteer, E2E_TIMEOUT } from 'test/helpers'describe('e2e/cart', () => {const { page, text, count, click, sleep } = setupPuppeteer()async function testCart (url) {await page().goto(url)await sleep(120) // api simulationexpect(await count('li')).toBe(3)expect(await count('.cart button[disabled]')).toBe(1)expect(await text('li:nth-child(1)')).toContain('iPad 4 Mini')expect(await text('.cart')).toContain('Please add some products to cart')expect(await text('.cart')).toContain('Total: $0.00')await click('li:nth-child(1) button')expect(await text('.cart')).toContain('iPad 4 Mini - $500.01 x 1')expect(await text('.cart')).toContain('Total: $500.01')await click('li:nth-child(1) button')expect(await text('.cart')).toContain('iPad 4 Mini - $500.01 x 2')expect(await text('.cart')).toContain('Total: $1,000.02')expect(await count('li:nth-child(1) button[disabled]')).toBe(1)await click('li:nth-child(2) button')expect(await text('.cart')).toContain('H&M T-Shirt White - $10.99 x 1')expect(await text('.cart')).toContain('Total: $1,011.01')await click('.cart button')await sleep(200)expect(await text('.cart')).toContain('Please add some products to cart')expect(await text('.cart')).toContain('Total: $0.00')expect(await text('.cart')).toContain('Checkout successful')expect(await count('.cart button[disabled]')).toBe(1)}test('classic', async () => {await testCart('http://localhost:8080/classic/shopping-cart/')}, E2E_TIMEOUT)test('composition', async () => {await testCart('http://localhost:8080/composition/shopping-cart/')}, E2E_TIMEOUT) })在代碼頂部,引入了test/helpers.js中的工具函數setupPuppeteer和常量E2E_TIMEOUT。
import puppeteer from 'puppeteer'// 每個測試的超時時間 export const E2E_TIMEOUT = 30 * 1000// puppeteer啟動參數 const puppeteerOptions = process.env.CI? { args: ['--no-sandbox', '--disable-setuid-sandbox'] }: {}export function setupPuppeteer () {let browserlet page// 運行每條測試前要執行的函數,可以理解為jest的生命周期beforeEach(async () => {browser = await puppeteer.launch(puppeteerOptions)page = await browser.newPage()page.on('console', (e) => {if (e.type() === 'error') {const err = e.args()[0]console.error(`Error from Puppeteer-loaded page:\n`,err._remoteObject.description)}})})// 同理,運行每條測試后要執行的函數afterEach(async () => {await browser.close()})// 點擊元素async function click (selector, options) {await page.click(selector, options)}// 經過元素async function hover (selector) {await page.hover(selector)}// 按鍵抬起async function keyUp (key) {await page.keyboard.up(key)}// 統計元素數量async function count (selector) {return (await page.$$(selector)).length}// 返回元素的文本async function text (selector) {return await page.$eval(selector, (node) => node.textContent)}// 返回元素的valueasync function value (selector) {return await page.$eval(selector, (node) => node.value)}// 返回元素的htmlasync function html (selector) {return await page.$eval(selector, (node) => node.innerHTML)}// 返回元素的所有classasync function classList (selector) {return await page.$eval(selector, (node) => {const list = []for (const index in node.classList) {list.push(node.classList[index])}return list})}// 判斷元素是否有某個classasync function hasClass (selector, name) {return (await classList(selector)).find(c => c === name) !== undefined}// 是否是隱藏狀態async function isVisible (selector) {const display = await page.$eval(selector, (node) => {return window.getComputedStyle(node).display})return display !== 'none'}// 是否為選中狀態async function isChecked (selector) {return await page.$eval(selector, (node) => node.checked)}// 是否是焦點狀態async function isFocused (selector) {return await page.$eval(selector, (node) => node === document.activeElement)}// 設置valueasync function setValue (selector, value) {const el = (await page.$(selector))await el.evaluate((node) => { node.value = '' })await el.type(value)}// 設置value,并按下回撤async function enterValue (selector, value) {const el = (await page.$(selector))await el.evaluate((node) => { node.value = '' })await el.type(value)await el.press('Enter')}// 清空valueasync function clearValue (selector) {return await page.$eval(selector, (node) => { node.value = '' })}// 等待多少毫秒async function sleep (ms = 0) {return new Promise((resolve) => {setTimeout(resolve, ms)})}// 返回這些工具函數return {page: () => page,click,hover,keyUp,count,text,value,html,classList,hasClass,isVisible,isChecked,isFocused,setValue,enterValue,clearValue,sleep} }從依賴我們可以看到,實際我們是使用puppeteer這個庫來實現訪問頁面,像瀏覽器一樣去操作頁面。
puppeteer是由Google開源的一個 Node 庫,它提供了各種高級 API 來通過 DevTools 協議控制 Chrome 或 Chromium。 Puppeteer 默認無頭運行,但可以配置為運行完整(非無頭)Chrome 或 Chromium。
現在大多數E2E測試都會使用到puppeteer。
現在,我們結合頁面的HTML在回來看這些測試用例就很好理解了
訪問:http://localhost:8080/classic/shopping-cart/,可以看到HTML結構
<div id="app"> <h1> Shopping Cart Example </h1> <hr /> <h2> Products </h2> <ul> <li> iPad 4 Mini-$500.01 <br /> <button> Add to cart </button> </li> <li> H&M T-Shirt White-$10.99 <br /> <button> Add to cart </button> </li> <li> Charli XCX-Sucker CD-$19.99 <br /> <button> Add to cart </button> </li> </ul> <hr /> <div class="cart"> <h2> Your Cart </h2> <p> <i> Please add some products to cart. </i> </p> <ul> </ul> <p> Total:$0.00 </p> <p> <button disabled=""> Checkout </button> </p> <p style="display: none;"> Checkout. </p> </div> </div> async function testCart (url) {// 進入http://localhost:8080/classic/shopping-cart/頁面await page().goto(url)// 等待120毫秒await sleep(120) // api simulation// li元素是否為3個expect(await count('li')).toBe(3)// 禁用的Checkout元素的數量expect(await count('.cart button[disabled]')).toBe(1)// 第一個li元素包含的內容是否有:iPad 4 Miniexpect(await text('li:nth-child(1)')).toContain('iPad 4 Mini')// cart元素是否包含文字:Please add some products to cartexpect(await text('.cart')).toContain('Please add some products to cart')// cart元素是否包含文字:Total: $0.00expect(await text('.cart')).toContain('Total: $0.00')// 點擊第一個元素的 Add to cart按鈕await click('li:nth-child(1) button')// cart元素是否包含文字:iPad 4 Mini - $500.01 x 1expect(await text('.cart')).toContain('iPad 4 Mini - $500.01 x 1')// cart元素是否包含文字:Total: $500.01expect(await text('.cart')).toContain('Total: $500.01')// 點擊第一個元素的 Add to cart按鈕await click('li:nth-child(1) button')// cart元素是否包含文字:iPad 4 Mini - $500.01 x 2expect(await text('.cart')).toContain('iPad 4 Mini - $500.01 x 2')// cart元素是否包含文字:Total: $1,000.02expect(await text('.cart')).toContain('Total: $1,000.02')// 第一個元素的 Add to cart按鈕 是否為禁用狀態expect(await count('li:nth-child(1) button[disabled]')).toBe(1)// 點擊第二個元素的 Add to cart按鈕await click('li:nth-child(2) button')// cart元素是否包含文字:H&M T-Shirt White - $10.99 x 1expect(await text('.cart')).toContain('H&M T-Shirt White - $10.99 x 1')// cart元素是否包含文字:Total: $1,011.01expect(await text('.cart')).toContain('Total: $1,011.01')// 點擊Checkout按鈕await click('.cart button')// 等待200毫秒await sleep(200)// cart元素是否包含文字:Please add some products to cartexpect(await text('.cart')).toContain('Please add some products to cart')// cart元素是否包含文字:Total: $0.00expect(await text('.cart')).toContain('Total: $0.00')// cart元素是否包含文字:Checkout successfulexpect(await text('.cart')).toContain('Checkout successful')// Checkout按鈕是否是禁用狀態expect(await count('.cart button[disabled]')).toBe(1) }這是cart.spec.js這一個測試,其他的測試也是同理,我們可以通過修改測試代碼來查看測試結果
比如修改最后一行:
expect(await count('.cart button[disabled]')).toBe(2)可以看到測試并沒有通過,并且展示出是哪條測試沒有通過
Test Suites: 0 of 4 total● e2e/cart ? classicexpect(received).toBe(expected) // Object.is equalityExpected: 2Received: 133 | expect(await text('.cart')).toContain('Total: $0.00')34 | expect(await text('.cart')).toContain('Checkout successful')> 35 | expect(await count('.cart button[disabled]')).toBe(2)| ^36 | }37 | 38 | test('classic', async () => {我們可以修改后,再次執行測試,全部通過。
PASS test/e2e/cart.spec.jsPASS test/e2e/todomvc.spec.jsPASS test/e2e/counter.spec.jsPASS test/e2e/chat.spec.js (5.219 s)Test Suites: 4 passed, 4 total Tests: 8 passed, 8 total Snapshots: 0 total Time: 6.106 s, estimated 7 s Ran all test suites.感覺大家的閱讀
一起學習更多前端知識,微信搜索【小帥的編程筆記】,每天更新
總結
以上是生活随笔為你收集整理的Vuex 4源码学习笔记 - 通过Vuex源码学习E2E测试(十一)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: pdf页眉页脚
- 下一篇: html5倒计时秒杀怎么做,vue 设