原文:https://nicolas.perriault.net/code/2012/introducing-casperjs-toolkit-phantomjs/
一段時間之前,我發表過一篇關於PhantomJS的文章,PhantomJS是一個無界面的,包含了WebKit瀏覽器引擎和JavaScript API的腳本解釋器.
於此同時,我開始編寫一個用來簡化PhantomJS腳本編寫尤其是導航操作腳本的輕量級庫.
六個月過去了,這個庫有了更多的功能,現在已經是一個獨立的項目了,CasperJS就這樣誕生了,目前,在GitHub上,該代碼庫已經有超過180個關注者和32個分支.
Ariya Hidayat — PhantomJS的作者 — 甚至說:
如果你還不知道CasperJS,快去看一看吧,它是一個非常有用的PhantomJS伴侶. (原文)
CasperJS有什么作用呢?
我太懶了,所以直接引用CasperJS網站的介紹:
CasperJS是一個開源的,用JavaScript編寫的,基於PhantomJS的導航腳本和測試工具 ,它簡化了定義一個完成的導航操作所需的步驟,還提供了很有用的函數封裝,方法,和語法糖,它可以完成下面這些常見任務:
- 定義 & 排序瀏覽器導航步驟
- 填充 & 提交表單
- 點擊 & 跟蹤鏈接
- 捕獲網頁截圖 (還可以截取某一區域)
- 在遠程DOM上進行斷言測試
- 記錄事件
- 下載資源,包括二進制文件
- 編寫功能測試套件,結果保存為JUnit XML文件
- 抓取網頁內容
進行導航操作
如果你在PhantomJS腳本中使用鏈式回調來進行導航操作,那是相當痛苦的.這種代碼無論寫起來,讀起來,理解起來還是維護起來,都是噩夢:
var page = require('webpage').create(); //新建一個頁面
page.open(url1, function(status) { //導航到第一個URL
if (status == "fail") phantom.exit(); //如果發生錯誤,退出程序
page.open(url2, function(status) { //否則在頁面加載完成的回調函數中繼續導航到第二個URL,依次類推
if (status == "fail") phantom.exit();
page.open(url3, function(status) {
if (status == "fail") phantom.exit();
page.open(url4, function(status) {
if (status == "fail") phantom.exit();
// 我可以停下來了嗎?
});
});
});
});
CasperJS使用更方便的API解決了這種異步操作的問題:
var casper = require('casper').create(); //新建一個頁面
casper.start(url1); //添加第一個URL
casper.thenOpen(url2); //添加第二個URL,依次類推
casper.thenOpen(url3);
casper.thenOpen(url4);
casper.run(); //開始導航
想要模擬用戶通過點擊鏈接來進行導航操作嗎?沒問題:
var casper = require("casper").create() //新建一個頁面
casper.start('http://my.blog.tld/'); //添加第一個URL
casper.thenClick('nav#menu a.blog'); //在頁面加載完成后,點擊選擇器指定的鏈接,進入一個新頁面
casper.thenClick('.posts li a'); //在新頁面加載完成后,再次點擊一個選擇器指定的鏈接
casper.then(function() { //在第二個新頁面加載完成后,輸出一些信息到控制台中
this.echo('Page url is ' + this.getCurrentUrl());
this.echo('Page title is ' + this.getTitle());
});
casper.run(); //開始導航
你還可以使用coffeescript來編寫如上功能的腳本:
var casper = require("casper").create()
casper.start "http://my.blog.tld/"
casper.thenClick "nav#menu a.blog"
casper.thenClick ".posts li a"
casper.then ->
@echo "Page url is #{@getCurrentUrl()}"
@echo "Page title is #{@getTitle()}"
casper.run()
填充和處理表單
填充和提交表單也並不難:
casper.start('http://admin.domain.tld/login/', function() { //打開頁面,並指定一個回調函數
this.fill('form[id="login-form"]', { //定位到一個form中
'username': 'chuck', //給name為username的表單控件填充值'chuck'
'password': 'n0rr1s' //給name為password的表單控件填充值'n0rr1s'
}, true); //參數true,表示填充完畢后,立刻提交表單
});
casper.then(function() {
this.echo(this.getTitle()); //新頁面加載完成后,在控制台輸出頁面標題
});
casper.run(); //開始導航
頁面截圖
給頁面上指定的區域截圖非常簡單:
casper.start('http://domain.tld/page.html', function() {
this.captureSelector('capture.png', '.article-content'); //給頁面中'.article-content'選擇器匹配的元素截圖,輸出圖片文件名為cpature.png,目錄為當前目錄
});
casper.run();
異步渲染頁面
有時(好吧,其實是經常),很多頁面內容是通過Ajax或者其他的手段異步加載的.你可以等到某個元素出現時再執行想要的操作:
casper.start('https://twitter.com/casperjs_org', function() {
this.waitForSelector('.tweet-row', function() { //等到'.tweet-row'選擇器匹配的元素出現時再執行回調函數
this.captureSelector('twitter.png', 'html'); //成功時調用的函數,給整個頁面截圖
}, function() {
this.die('Timeout reached. Fail whale?').exit(); //失敗時調用的函數,輸出一個消息,並退出
}, 2000); //超時時間,兩秒鍾后指定的選擇器還沒出現,就算失敗
});
測試
除了上面講的這些功能之外,CasperJS真正的威力是它提供了功能測試的能力.例如,測試谷歌的搜索操作可以這樣完成:
casper.start('http://www.google.fr/', function() { //打開谷歌主頁,添加頁面加載完成時的回調函數
this.test.assertTitle('Google', 'google homepage title is the one expected'); //檢測頁面標題是否是'Google',如果是,輸出第二個參數指定的字符串
this.test.assertExists('form[action="/search"]', 'main form is found'); //檢測頁面中是否存在選擇器指定的元素,如果存在輸出第二個參數指定的字符串
this.fill('form[action="/search"]', { //填充表單並提交,執行搜索操作
q: 'foo'
}, true);
});
casper.then(function() {
this.test.assertTitle('foo - Recherche Google', 'google title is ok'); //檢測搜索結果頁的頁面標題是否正確
this.test.assertUrlMatch(/q=foo/, 'search term has been submitted'); //檢測搜索結果頁的網址是否匹配指定的正則表達式
this.test.assertEval(function() {
return __utils__.findAll('h3.r').length >= 10; //自定義一個檢測函數
}, 'google search for "foo" retrieves 10 or more results');
});
casper.run(function() {
this.test.renderResults(true); //輸出檢測結果
});
運行上面的腳本會產生這樣的結果:
輸出結果還能導出為XUnit XML文件,可以用在持續集成服務器中,比如Jenkins.
對於記錄來說,整個CasperJS測試套件使用自己的API寫成,結果在visible on Travis-CI.