1. 程式人生 > >1.前端自動化測試 之 視覺測試

1.前端自動化測試 之 視覺測試

前端自動化測試 之 視覺測試

image

前端測試分類

Dmaic Model.png

前端測試主要分五大方向測試,而這五大方向也分很多小方向測試,首先簡單的介紹每個方向的概念

  1. 介面樣式測試

    • 固定介面樣式測試:主要針對文字內容不變的區域,例如頁面的頁頭,頁尾這類結構、內容不變的區域,而測試一般通過截圖對比解決。

    • 結構不變介面樣式測試:主要針對結構不變的區域,例如新聞區域這類結構不變,內容變化的區域,這類測試一般通過DOM元素對比解決。

    • 計算樣式測試:主要針對計算樣式不變的區域,這類測試一般通過比較計算樣式解決,但是這種測試不推薦,因為測試成本比較大。

  2. 功能測試

    • 伺服器資料預期測試:主要針對使用者在前端介面進行某種操作後,提交資料給後臺後,測試後臺能否返回預期的資料

    • 介面功能測試:主要針對使用者在前端介面進行某種互動性操作後,測試能否獲取預期的功能、介面互動

  3. 多瀏覽器測試

    • 多瀏覽器測試:基於介面樣式測試、功能測試的基礎上來進行不同瀏覽器的的測試,俗稱相容性測試。
  4. 效能測試

    • 白屏時間:使用者瀏覽器輸入網址後至瀏覽器出現至少1px畫面為止。
    • 首屏時間:使用者瀏覽器首屏內所有的元素呈現所花費時間。
    • 頁面迴歸時間:使用者瀏覽器非第一次載入所有的元素呈現所花費時間。
    • 使用者可操作時間(dom ready) :網站某些功能可以使用的時間。
    • 頁面總下載時間(onload):網站中所有資源載入完成並且可用時間。

什麼樣的專案適合自動化測試呢?

什麼樣的專案適合自動化測試呢?

如上圖所示,真正工作中無法全部滿足以上條件,所以需要作出權衡,一般來說,只需要滿足以下幾點,就可以對專案開展自動化測試:

  1. 需求穩定,不會頻繁變更

    自動化測試最大的挑戰就是需求的變化,而自動化指令碼本身就需要修改、擴充套件、debug,去適應新的功能,如果投入產出比太低,那麼自動化測試也失去了其價值和意義;

    折中的做法是選擇相對穩定的模組和功能進行自動化測試,變動較大、需求變更較頻繁的部分用手工測試;

  2. 多平臺執行,組合遍歷型、大量的重複任務

    測試資料、測試用例、自動化指令碼的重用性和移植性較強,降低成本,提高效率和價值;

  3. 軟體維護週期長,有生命力

    自動化測試的需求穩定性要求、自動化框架的設計、指令碼開發與除錯均需要時間,這其實也是一個軟體開發過程,如果專案週期較短,沒有足夠的時間去支援這一過程,那自動化測試也就不需要了;

  4. 被測系統開發較為規範,可測試性強

    主要出於這幾點考慮:被測試系統的架構差異、測試技術和工具的適應性、測試人員的能力能否設計開發出適應差異的自動化測試框架;

什麼是UI測試

對於介面佈局,傳統的測試都是由人工對比設計圖和產品介面。當介面有修改之後,再由人通過肉眼去檢查修改(包括正確的和錯誤的修改),這樣即費時而且測試結果又不穩定,因為人工對比測試存在兩個巨坑:1.效率低;2.人的不確定性。對於擁有大量複雜介面的Web應用,介面佈局的測試的數量巨大,再加上這兩個問題,導致這類應用的介面佈局測試/迴歸測試時間很長,成本很高,所以很多基於Agile(敏捷開發)專案基本不可能在迭代週期內高質量的完成其視覺測試。對於每天做一次,那更是不可能完成的任務。

視覺感知測試/視覺迴歸測試

為了解決上面提到的各種問題,視覺感知測試孕育而生。它使用傳統的對圖片進行二進位制比較的辦法,結合敏捷迭代開發的理念,產生的一種針對介面佈局的自動化測試方法。

視覺感知測試

視覺感知測試就是對第一個版本的所有介面進行第一次測試。

視覺感知測試包含以下幾個主要的測試步驟:

視覺感知測試流程.jpg

需要注意的是!

  1. 配對URL(忽略hostname)

    通過配對URL,對所有的截圖按照相同的URL進行分組。當然有時候會出現新的介面,有時候老的介面會被刪除。對於新的介面就需要人工進行首次驗證測試 。

  2. 畫素級別的圖形比較

    對於分組之後的截圖進行畫素級別的比較並生產差別圖。有時候為了降噪,可以只對區域性關心的元件進行比較。

  3. 人工檢視所有不同

    最後通過人工審查差別圖報告完成測試。

視覺感知測試結果:

預期(expected) 實際(actual) 比較結果(diff)
1diff

視覺迴歸測試

我們認為如果一個介面通過第一次的人工驗證併發布之後,它就是一個正確的標準介面,並且是包含了人工測試價值的資產。當下一次測試的時候,這部分價值就應該被保留並重用起來,用於減少新的一次測試的時間,從而實現介面的快速回歸測試。

視覺迴歸測試包含以下幾個主要的測試步驟:
視覺迴歸測試流程 -.jpg

迴歸和感知測試流程差不多隻是差異值要更小一點,並且只有效果圖需要替換內容。

視覺自動測試怎麼做?

要進行視覺自動測試,有三種方式。
* 第一種是截圖比對(區域性、整頁)。
* 第二種通過JavaScript呼叫window.getComputedStyle()獲取計算後的樣式並進行斷言。
* 第三種dom結構對比加css樣式對比。

這三種各有明顯的優勢和不足。 第二種方式強綁定了實現,從而變得可能比較脆弱。 第一種方式離設計太近了,當頁面中有可變內容時就會有問題。
第三種方式,無法進行視覺感知測試結果只能進行視覺迴歸測試和上一版的dom繼續比較差異。

我更傾向與第一種截圖對比;它的測試基於使用者所見而不是使用者所見的抽象。當然第三種也是非常好的 page-monitor 有興趣的朋友可以自行了解。為什麼第三種那麼好為什麼不使用呢?因為上面這個庫是基於phantomjs並且它的實現方式過於複雜不適合新手玩玩。

畫素對比工具,有哪些?

好了介紹了那麼多,怎麼選一個合適的Headless Browser呢?

Headless Browser???我是視覺測試要無頭瀏覽器幹嘛?

因為有了畫素對比工具我們還需要一個瀏覽器進行截圖和設計圖進行畫素比較。

比較常見出名的幾個Headless Browser,有哪些?

PhantomJS 基於 Webkit 核心,不支援 Flash 的播放;SlimerJS 基於火狐的 Gecko 核心,支援 Flash播放,並且執行過程會有頁面展示。

我們這裡呢就只講Webkit核心的,其他的我就不講了。

PhantomJS簡介:

PhantomJS 是一個基於webkit的JavaScript API。它使用QtWebKit作為它核心瀏覽器的功能,使用webkit來編譯解釋執行JavaScript程式碼。任何你可以在基於webkit瀏覽器做的事情,它都能做到。它不僅是個隱形的瀏覽器,提供了諸如CSS選擇器、支援Web標準、DOM操作、JSON、HTML5、Canvas、SVG等,同時也提供了處理檔案I/O的操作,從而使你可以向作業系統讀寫檔案等。PhantomJS的用處可謂非常廣泛,諸如網路監測、網頁截圖、無需瀏覽器的 Web 測試、頁面訪問自動化等。

但是 PhantomJS 因為畢竟不是真實的使用者瀏覽器環境,使用起來還是有不少的詬病。之前一直在使用 PhantomJS ,功能雖然夠用,不過和在真實的瀏覽器裡面訪問的介面來對比差別還是比較大的。

Puppeteer簡介:

Puppeteer是Chrome團隊開發的一個Node庫。它提供了一個高階API來控制無頭或完整的Chrome。它通過使用Chrome無介面模式 (Headless Chrome )和DevTools協議的組合來實現這一點。它使用一個更上層的API來封裝其功能,讓使用者介面測試自動化變得輕而易舉。
人們基於Chrome DevTools協議開發了一系列Google Chrome工具。你在瀏覽器中點選更多工具 ->開發工具,開啟的就是DevTools。DevTools協議是DevTools的動力基礎,我們現在可以使用Chrome中的DevTools來做更多的事情。

好了簡介講完了,我們來對比一下這兩個Headless Browser的區別。

截圖比較
程式碼:

PhantomJS:

var page = require('webpage').create();

page.viewportSize = { width: 400, height: 400 };
page.open("http://localhost:8899/VS", function(status) {
    if (status === "success") {
        page.render("a.jpg");

    } else {
        console.log("Page failed to load.");
    }
    phantom.exit(0);
});

Puppeteer:

const puppeteer = require('puppeteer');

(async() => {
    const browser = await puppeteer.launch();
    const page = await browser.newPage();
    await page.goto('http://localhost:8899/VS');

    await page.setViewport({ width: 400, height: 400 })

    //儲存圖片
    const images = await page.screenshot({ path: 'VS.jpg', fullPage: true, omitBackground: true });

    //關閉瀏覽器
    await browser.close();

})();
Puppeteer PhantomJS Chrome瀏覽器
a.jsp VS.jsp QQ截圖20180322154826.png

瀏覽器效果(大圖):

image

好了看到這裡已經可以分別出來勝負了。

使用puppeteer進行視覺感知測試

我們來做一個簡單的dome

我們這裡拿掘金來做一個視覺感知測試的例子。

1.首先建立專案名字就叫“PerceptionTest”把。

2.安裝依賴

安裝Puppeteer

npm install puppeteer --save

畫素對比工具我就選我最常用的blink-dif了

npm install blink-diff --save

3.依賴安完了我們來建立一個js檔案“app.js”

4.載入依賴

const puppeteer = require('puppeteer'),//無頭瀏覽器
    BlinkDiff = require('blink-diff'),//畫素對比
    imgUrl = __dirname + "/blink-diff_img/";//圖片目錄

5.使用puppeteer進行截圖

(async () => {
    //建立puppeteer
    const browser = await puppeteer.launch({ headless: true });
    //new 一個新的tab頁面
    const page = await browser.newPage();
    //設定瀏覽器的尺寸
    await page.setViewport({ width: 1920, height: 945 });
    //開啟url
    await page.goto('https://juejin.im/');
    //儲存截圖
   await page.screenshot({ path: imgUrl + 'Screenshots.png', fullPage: true });

    //關閉瀏覽器
    await browser.close();
})();

6.分析頁面找到要替換的內容

為什麼要替換內容呢,因為我們UI測試指的是測試介面樣式而不是去匹配裡面的內容,如果不替換裡面的內容那畫素對比工具比較出來的相似度肯定很低。所以我們要替換掉內容,要讓內容完整統一,我們才好更加精確的去比較差異。

好了我們來分析一下要替換的內容。
替換的內容.png

要替換的內容如下:
1. 標題
2. 標籤
3. 作者
4. 釋出時間
5. 閱讀數
6. 列表圖片

7.替換內容

puppeteer提供了非常豐富的api,其中有個api叫page.evaluate可以向頁面插入一段js。

 await page.evaluate(async () => {

        //列表
        var Lists = document.querySelectorAll("div.feed.welcome__feed > ul > li > div > a > div");
        Lists.forEach(function (element, index, array) {

            element.querySelector("a.title").innerHTML = "測試測試測試測試測試測試測試測試測試測試測試測試測試測試測試測試測試測試測試測試測試測試測試測試測試測試測試測試測試測試測試測試測試測試";

            //替換標籤
            element.querySelector("ul > li.item.category > span").innerHTML = "測試";

            //替換作者
            element.querySelector("ul > li.item.username.clickable > div > a").innerHTML = "測試";

            //替換髮布時間
            element.querySelector("div.info-row.meta-row > ul > li:nth-child(3)").innerHTML = "9999天前";

            //替換髮布時間
            element.querySelector("div.info-row.meta-row > ul > li:nth-child(4)").innerHTML = "99999999999 次閱讀";

            //列表圖片
            if (element.querySelectorAll("div.lazy.thumb.thumb.loaded").length==1) {
                element.querySelector("div.lazy.thumb.thumb.loaded").style.background = "#fdedc9";
            } else {
                var loaded=document.createElement("div");
                loaded.className=" lazy thumb thumb loaded";
                loaded.style.background = "#fdedc9";
                loaded.setAttribute("data-v-b2db8566","");
                loaded.setAttribute("data-v-009ea7bb","");
                loaded.setAttribute("data-v-f2ca14b0","");
                element.appendChild(loaded);
            }
        });

    });

8.使用Blink-Diff進行畫素對比較

const diff = new BlinkDiff({
        imageAPath: imgUrl + 'example.png', // 設計圖
        imageBPath: imgUrl + 'Screenshots.png',//頁面截圖
        //低於其中差異的畫素數/ p(預設值:500) - 百分比閾值:1 = 100%,0.2 = 20%
        threshold: 0.02, // 1% threshold
        imageOutputPath: imgUrl + 'Diff.png'//Diff路徑
    });

    diff.run(function (error, result) {
        if (error) {
            throw error;
        } else {
            console.log(diff.hasPassed(result.code) ? '通過' : '失敗');
            console.log('總畫素:' + result.dimension);
            console.log('發現:' + result.differences + ' 差異.');
        }
    });

完整程式碼:

const puppeteer = require('puppeteer'),
    BlinkDiff = require('blink-diff'),
    imgUrl = __dirname + "/blink-diff_img/";

(async () => {
    const browser = await puppeteer.launch({ headless: true });
    const page = await browser.newPage();
    await page.setViewport({ width: 1920, height: 945 });
    await page.goto('https://juejin.im/');



    await page.evaluate(async () => {

        //列表
        var Lists = document.querySelectorAll("div.feed.welcome__feed > ul > li > div > a > div");
        Lists.forEach(function (element, index, array) {

            element.querySelector("a.title").innerHTML = "測試測試測試測試測試測試測試測試測試測試測試測試測試測試測試測試測試測試測試測試測試測試測試測試測試測試測試測試測試測試測試測試測試測試";

            //替換標籤
            element.querySelector("ul > li.item.category > span").innerHTML = "測試";

            //替換作者
            element.querySelector("ul > li.item.username.clickable > div > a").innerHTML = "測試";

            //替換髮布時間
            element.querySelector("div.info-row.meta-row > ul > li:nth-child(3)").innerHTML = "9999天前";

            //替換髮布時間
            element.querySelector("div.info-row.meta-row > ul > li:nth-child(4)").innerHTML = "99999999999 次閱讀";

            //列表圖片
            if (element.querySelectorAll("div.lazy.thumb.thumb.loaded").length==1) {
                element.querySelector("div.lazy.thumb.thumb.loaded").style.background = "#fdedc9";
            } else {
                var loaded=document.createElement("div");
                loaded.className=" lazy thumb thumb loaded";
                loaded.style.background = "#fdedc9";
                loaded.setAttribute("data-v-b2db8566","");
                loaded.setAttribute("data-v-009ea7bb","");
                loaded.setAttribute("data-v-f2ca14b0","");
                element.appendChild(loaded);
            }
        });

    });

    await page.screenshot({ path: imgUrl + 'Screenshots.png', fullPage: true });

    const diff = new BlinkDiff({
        imageAPath: imgUrl + 'example.png', // 設計圖
        imageBPath: imgUrl + 'Screenshots.png',//頁面截圖
        threshold: 0.02, // 1% threshold
        imageOutputPath: imgUrl + 'Diff.png'//Diff路徑
    });

    diff.run(function (error, result) {
        if (error) {
            throw error;
        } else {
            console.log(diff.hasPassed(result.code) ? '通過' : '失敗');
            console.log('總畫素:' + result.dimension);
            console.log('發現:' + result.differences + ' 差異.');
        }
    });


    //關閉puppeteer
    await browser.close();
})();

差異圖

有差異 無差異
Diff.png Diff.png

git完整程式碼

好了,欲知後事如何,請聽下回分解。

cs.gif

參考: