Google 上搜尋了下 phantomjs 關鍵詞,展示最多的是測試和截圖相關的內容。phantomjs 提供了大量的 API,讓我們可以操作 webkit 網頁沙箱中的內容,也可以將網頁中的資訊輸出到外層,進行分析處理。而本文著重要講述的是,如何更好的與 webkit 網頁沙箱互動,如何注入指令碼,如何修改請求。
QA 測試最煩惱的是 UI 測試,包括網頁元素的正確呈現,網頁互動之後的元素變化等,人工測試很容易疏忽一些問題,並且 UI 層面的測試用例也不好寫,這讓人很頭痛。為了讓程式能夠很好地分析頁面 UI,也有很多人做了圖片對比工具,通過 phantomjs 提供的 screen capture 功能,定時抓取頁面截圖,或者在不同的場景下抓取同一個頁面的截圖,在時間維度和空間維度上對頁面圖片做對比分析,做監控警報等。技術成本不是很高,但是為了個性需求需要做很多額外的工作。
事件就是精確插入點
為了能夠在精確的時間點注入測試指令碼,我們需要了解下 phantomjs 在請求資源時會發生哪些事件,畢竟它也是一個事件驅動模型。
- onInitialized 類似於我們傳送 ajax 請求,狀態為 0 的時候
- onLoadStarted 準備載入網頁,此時頁面的 page.content 是存在的,內容為 <html><body></body></html>
- onLoadFinished 頁面載入完成,是 DOMContentLoaded 還是 window.onload ,我稍微測試了下,感覺應該是後者
- onResourceRequested 請求資源,如 css、js 等
- onResourceReceived 請求的資源已到達
- onClosing 關閉頁面
- onConsoleMessage 沙箱內的 console 內容是不會出現到外層的,通過這個函式可以輸出
還有很多,具體可以翻閱文件: http://phantomjs.org/api/webpage/。這些事件都是開啟一個頁面之後的例項化物件上的:
1 2 3 4 |
var page = require('webpage').create(); // 事件監聽 page.onxxxx(); page.open(url, function(){}); |
用慣了 nodejs 的同學,可能會很自然的在程式碼裡頭寫
1 2 3 |
var page = require('webpage').create(); // phantomjs API 沒有這些東西 var http = require('http'); |
我們需要搞清楚的是 phantomjs 是一個基於 webkit 核心的 JS API,webkit 包含兩方面,一個是 webCore 解析 html,一個是 V8 解析和執行 javascipt,phantomjs 包含了 webkit 和基於 webkit 的 js 封裝,相比單純的 webkit 它提供了更多的 API。而 nodejs 是對 v8 的封裝,在 v8 上做了一些上層建築。要搞清楚 phantomjs 和 nodejs 之間的界限。
實際上,phantomjs 自己也做了一些類似 nodejs 的 API 包裝,比如 webserver、child_process、FileSystem 等等,這些 API 可以讓我們很方便的操作網路 IO 和系統程式。
注入程式碼測試
嵌入式指的是,將測試程式碼嵌入到 phantomjs 的沙箱之中,然後通過它提供的 API 將測試資料匯出來,那麼,如何將資料插入進去?
1. includeJS/injectJS注入檔案
兩個函式都可以網沙箱內注入程式碼,區別是 includeJS 主要用於注入一個遠端的指令碼,如:
1 2 3 4 5 6 7 8 9 10 |
var webPage = require('webpage'); var page = webPage.create(); page.includeJs('http://code.jquery.com/jquery-1.10.2.min.js', function() { // 模擬登入 var $testForm = $('form#login'); $testForm.find('input[name="username"]').value('barret'); $testForm.find('input[name="password"]').value('1234'); $testForm.submit(); }); |
2. evaluate注入程式碼
而 injectJS 主要用於注入本地的檔案,在做測試的時候,這個函式的使用頻率稍高一些。很多的測試平臺都是線上的,並且支援線上編寫測試用例,最後生成的指令碼地址當然也是網路可訪問的,那麼這個時候用 includeJS 就略微方便一些啦。
上面兩種方式是想容器內注入可執行穩緊啊。除此之外,我們還可以使用 evaluate 直接注入可執行的指令碼內容,如:
1 2 3 4 5 6 7 8 9 10 |
var webPage = require('webpage'); var page = webPage.create(); page.open('http://www.taobao.com', function(status) { var title = page.evaluate(function() { return document.title; }); console.log(title); phantom.exit(); }); |
這種方式有點類似於我們在 chrome devtoos 的控制檯中輸入程式碼進行測試。
搬出資料
注入程式碼拿到輸出之後,我們需要將資料拿出來,在執行的環境中,可以通過如下手段將資料搬出來。
1. 多開幾個 webserver
上面提到,phantomjs 提供了 webserver 方面的 API,我們可以在注入的程式碼中:
1 2 3 4 |
// part of inject.js $.post("http://localhost:10220", data, function(){ // code.. }) |
向外部通訊,而我們在外部也準備好了接駕程式碼:
1 2 3 4 5 6 7 |
// part of server.js var server = require('webserver').create(); var port = 10220; server.listen(port, function(req, res){ console.log(JSON.stringify(req, null, 2)); // coding... }) |
所以 http 是個好東西,我們可以控制 web 的 server(沙箱外) 和 client(沙箱內),那麼資料通訊就不是問題了。
2. callPhantom 函式
phantomjs 的 webpage 物件提供了一個 onCallback 函式,這個函式能夠聽到沙箱內一個叫做 callPhantom 函式的吶喊。 callPhantom 是 phantomjs 在 window 上擴充套件的函式,他的使用:
1 2 3 4 5 6 7 8 9 10 |
var page = require("webpage").create(); page.onCallback = function(msg) { console.log("這是沙箱說的話: " + msg); return "嘿,沙箱,我聽到了!"; }; page.evaluate(function() { // Return-value of the "onCallback" handler arrive here var callbackResponse = window.callPhantom("嘿,外殼,我是沙箱~"); console.log("這是外殼說的話: " + callbackResponse); }); |
所以一切都是那麼簡單!知道了這些之後,還有啥事我們辦不成的呢?!
小結
Jasmine 和 QUnit 是兩套用的比較多的測試框架,我們可以使用上面任一種方式進行測試開發。
自動化測試少不了這些有用的工具,這些工具能夠讓我們在網頁初始化的任何階段注入測試程式碼,所以我們可以寫好一堆測試用例,作為持續整合的測試庫,讓上線的程式碼更安全、更乾淨!