一浦旱,背景
近期我們做了一版新的web運(yùn)行框架,試運(yùn)行后能庆,領(lǐng)導(dǎo)和同事給了很多的建議和反饋施禾,有些是需求有些是Bug。但當(dāng)我們?cè)谛迯?fù)Bug或?qū)崿F(xiàn)需求的時(shí)候搁胆。發(fā)現(xiàn)一個(gè)質(zhì)量問題弥搞,經(jīng)常出現(xiàn)修改1個(gè)問題引入1個(gè)問題的現(xiàn)象,比如調(diào)整了一種場(chǎng)景下的菜單寬度會(huì)導(dǎo)致另一種場(chǎng)景下的菜單顯示偏移不齊渠旁。
隨著新版web運(yùn)行框架內(nèi)容的不斷豐富攀例,每次修改靠人工去判斷對(duì)錯(cuò),不管是在效率方面還是結(jié)果方面顾腊,都不太理想粤铭。所以我們需要用一種新的測(cè)試手段去進(jìn)行可視化效果的E2E驗(yàn)證。期望:每次修改發(fā)布測(cè)試環(huán)境后杂靶,我們跑一遍測(cè)試用例梆惯,就能知道可視化效果有沒有受損酱鸭,有沒有意外影響。
二垛吗,實(shí)現(xiàn)方案
一番搜索后發(fā)現(xiàn)凹髓,其實(shí)很多大廠都已經(jīng)有了解決方案并用于實(shí)踐,我們?cè)谶@方面有點(diǎn)后知后覺了怯屉。其實(shí)現(xiàn)思路是基于圖的像素比對(duì)扁誓,主要流程如下:
對(duì)預(yù)期結(jié)果抓圖并保存
用無頭瀏覽器 訪問,并進(jìn)行動(dòng)作模擬蚀之。最后抓取運(yùn)行結(jié)果圖片并保存
對(duì)比預(yù)期圖片與結(jié)果圖片間的像素差異,大于某個(gè)值就認(rèn)為有錯(cuò)
通過選型最終結(jié)果如下:
puppeteer 提供無頭瀏覽器(Headless browser)
pixelmatch 提供圖片像素級(jí)別的比較
pngjs 提供png的讀寫能力
三捷泞,關(guān)鍵技術(shù)解讀
pixelmatch是該方案的關(guān)鍵足删,所以筆者就學(xué)習(xí)了一下pixelmatch的源碼,粗略學(xué)習(xí)了像素對(duì)比的實(shí)現(xiàn)原理锁右。其過程大致如下:
如果兩張圖片完全相同失受,則返回0。否則咏瑟,它根據(jù)匹配閾值和YIQ差異度量計(jì)算兩種顏色之間的最大可接受平方距離拂到。然后,它將每個(gè)相應(yīng)像素進(jìn)行比較码泞,并使用YIQ差異度量計(jì)算它們之間的顏色差異兄旬。
如果顏色差異超過閾值,則檢查它是否是真正的渲染差異或僅是反鋸齒余寥。
如果是反鋸齒领铐,則將像素繪制為黃色,并將其不計(jì)為差異宋舷。
如果是真正的渲染差異绪撵,則將像素繪制為差異,并增加差異計(jì)數(shù)祝蝠。
如果像素相似音诈,則將背景繪制為與白色混合的灰度圖像。
在這個(gè)過程中有兩個(gè)比較關(guān)鍵的點(diǎn):
1绎狭,顏色差異的比較采用的是YIQ差異度量計(jì)算
YIQ细溅,是NTSC(National Television Standards Committee)電視系統(tǒng)標(biāo)準(zhǔn)。Y是提供黑白電視及彩色電視的亮度信號(hào)(Luminance)坟岔,即亮度(Brightness)谒兄,I代表In-phase,色彩從橙色到青色社付,Q代表Quadrature-phase承疲,色彩從紫色到黃綠色
這里實(shí)現(xiàn)分兩步:
第一步是RGBA轉(zhuǎn)YIQ邻耕,其標(biāo)準(zhǔn)公式如下:
function rgb2y(r, g, b) { return r * 0.29889531 + g * 0.58662247 + b * 0.11448223; }
如果是有透明度的:會(huì)按一定的公式與白色進(jìn)行混合,其公式如下:
255 + (c - 255) * a;
第二步就是差異計(jì)算的理論基于YIQ NTSC transmission color space in mobile applications燕鸽,計(jì)算公式如下:
delta = 0.5053 * (Y1 - Y2) ^ 2 + 0.299 * (I1 - I2) ^ 2 + 0.1957 * (Q1 - Q2) ^ 2
2兄世,區(qū)分渲染差異和反鋸齒
什么是反鋸齒?
反鋸齒(英語:anti-aliasing啊研,簡(jiǎn)稱AA)御滩,也譯為抗鋸齒或邊緣柔化、消除混疊党远、抗圖像折疊有損等削解。它是一種消除顯示器輸出的畫面中圖物邊緣出現(xiàn)凹凸鋸齒的技術(shù)。
當(dāng)我們計(jì)算出像素差異后沟娱,需要區(qū)分一下是真的差異還是反鋸齒引起氛驮。其判斷依據(jù)是通過檢查像素是否具有3個(gè)或更多相鄰的相同顏色像素來進(jìn)行判定。如果周圍像素顏色一致济似,那么就認(rèn)為是反鋸齒矫废。反之則認(rèn)為是真正的像素差異。
四砰蠢,實(shí)踐情況
我們先實(shí)現(xiàn)了一個(gè)基礎(chǔ)版蓖扑,然后通過邊實(shí)踐邊豐富方式來漸進(jìn)完善。
其代碼主要部分如下台舱,無頭瀏覽器部分:
/**
* 執(zhí)行測(cè)試case
* @param {} caseInfo 測(cè)試case信息
*/
async function runCase(caseInfo) {
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
await page.setViewport({
width: caseInfo.imgWidth,
height: caseInfo.imgHeight,
deviceScaleFactor: 1,
});
await page.goto(caseInfo.caseUrl);
if(caseInfo.autoLogin){
let username = caseInfo.username
let password = caseInfo.password
await page.evaluate(async (username,password) => {
window.autoLogin(username,password)
},username,password);
await page.waitForNavigation()
}
await page.waitForTimeout(5000);
if(caseInfo.operators){
await caseInfo.operators(page)
await page.waitForTimeout(1000);
}
let _p = path.join(caseInfo.baseDir,'result.png')
await page.screenshot({ path: _p });
await browser.close();
const diff = await compareImages(_p, path.join(caseInfo.baseDir,caseInfo.expectImg),caseInfo);
if (diff > 0.005) {
console.log(caseInfo.caseTitle +'\x1b[31m%s\x1b[0m', '?');
} else {
console.log(caseInfo.caseTitle +':\x1b[32m%s\x1b[0m', '?');
}
}
圖片像素比對(duì)部分:
/**
* 圖片比較
* @param {*} img1Path 圖1
* @param {*} img2Path 圖2
* @param {*} caseInfo 測(cè)試case信息
* @returns
*/
async function compareImages(img1Path, img2Path,caseInfo) {
const img1 = PNG.sync.read(fs.readFileSync(img1Path));
const img2 = PNG.sync.read(fs.readFileSync(img2Path));
const {width, height} = img1;
const diff = new PNG({width, height});
const numDiffPixels = pixelmatch(img1.data, img2.data, diff.data, width, height, {threshold: 0.1});
fs.writeFileSync(path.join(caseInfo.baseDir,'diff.png'), PNG.sync.write(diff));
const deviationRate = numDiffPixels / (width * height);
return deviationRate
}
邏輯上我們把圖片分為三個(gè):
預(yù)期的叫expect
執(zhí)行結(jié)果叫result
差異叫diff
最后判斷偏差是否大于0.005律杠,大于就認(rèn)為有問題。
我們做了兩個(gè)demoCase進(jìn)行了效果驗(yàn)證柿赊,分別是
登錄頁面測(cè)試
首頁面(展開菜單)測(cè)試
執(zhí)行測(cè)試后俩功,結(jié)果如下圖:
一個(gè)成功一個(gè)失敗,然后我們分別看一下圖形對(duì)比:(圖片依次為expect碰声,result诡蜓,diff)*
登錄頁面測(cè)試對(duì)比圖:
首頁面(展開菜單)測(cè)試對(duì)比圖:
通過圖形對(duì)比我們可以看到,判斷結(jié)果是準(zhǔn)確的胰挑。
五蔓罚,總結(jié)
目前為止,我們還只是快速實(shí)現(xiàn)了一個(gè)測(cè)試的原型瞻颂,大量case還在補(bǔ)充驗(yàn)證中豺谈。至于是否可以解決開篇描述的問題,還需要進(jìn)一步觀察和實(shí)踐贡这。同時(shí)一些細(xì)節(jié)以及測(cè)試Case的功能完備性方面也會(huì)不斷加強(qiáng)完善茬末。比如結(jié)果對(duì)比可視化,執(zhí)行腳本可錄制化等等
前端代碼質(zhì)量可以通過單元測(cè)試解決,在可視化效果驗(yàn)證方面丽惭,像素對(duì)比應(yīng)該是一個(gè)好的方向击奶。