基于像素對(duì)比的前端E2E測(cè)試探索

一浦旱,背景

近期我們做了一版新的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é)果如下圖:

image.png

一個(gè)成功一個(gè)失敗,然后我們分別看一下圖形對(duì)比:圖片依次為expect碰声,result诡蜓,diff)*

登錄頁面測(cè)試對(duì)比圖:


image.png

首頁面(展開菜單)測(cè)試對(duì)比圖:

image.png

通過圖形對(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è)好的方向击奶。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市责掏,隨后出現(xiàn)的幾起案子柜砾,更是在濱河造成了極大的恐慌,老刑警劉巖换衬,帶你破解...
    沈念sama閱讀 216,496評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件痰驱,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡瞳浦,警方通過查閱死者的電腦和手機(jī)担映,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來叫潦,“玉大人另萤,你說我怎么就攤上這事∽缣簦” “怎么了?”我有些...
    開封第一講書人閱讀 162,632評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵泛源,是天一觀的道長(zhǎng)拔妥。 經(jīng)常有香客問我,道長(zhǎng)达箍,這世上最難降的妖魔是什么没龙? 我笑而不...
    開封第一講書人閱讀 58,180評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮缎玫,結(jié)果婚禮上硬纤,老公的妹妹穿的比我還像新娘。我一直安慰自己赃磨,他們只是感情好筝家,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著邻辉,像睡著了一般溪王。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上值骇,一...
    開封第一講書人閱讀 51,165評(píng)論 1 299
  • 那天莹菱,我揣著相機(jī)與錄音,去河邊找鬼吱瘩。 笑死道伟,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的使碾。 我是一名探鬼主播蜜徽,決...
    沈念sama閱讀 40,052評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼祝懂,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了娜汁?” 一聲冷哼從身側(cè)響起嫂易,我...
    開封第一講書人閱讀 38,910評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎掐禁,沒想到半個(gè)月后怜械,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,324評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡傅事,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評(píng)論 2 332
  • 正文 我和宋清朗相戀三年缕允,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蹭越。...
    茶點(diǎn)故事閱讀 39,711評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡障本,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出响鹃,到底是詐尸還是另有隱情驾霜,我是刑警寧澤,帶...
    沈念sama閱讀 35,424評(píng)論 5 343
  • 正文 年R本政府宣布买置,位于F島的核電站粪糙,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏忿项。R本人自食惡果不足惜蓉冈,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望轩触。 院中可真熱鬧寞酿,春花似錦、人聲如沸脱柱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽榨为。三九已至掸茅,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間柠逞,已是汗流浹背昧狮。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留板壮,地道東北人逗鸣。 一個(gè)月前我還...
    沈念sama閱讀 47,722評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親撒璧。 傳聞我的和親對(duì)象是個(gè)殘疾皇子透葛,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評(píng)論 2 353

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