一、robot簡介
robot是dojo框架中用來進行前端自動化測試的工具,doh主要目的在於單元測試,而robot可以用來模仿用戶操作來測試UI。總所周知,Selenium也是一款比較流行的前端自動化測試工具,與Selenium相比robot的優點在於robot觸發的瀏覽器事件是真正的用戶操作事件,而Selenium中的事件屬於“合成事件”。打個比方,用戶在一個textbox元素上觸發了mousedown事件,但是在觸發mousedown事件之前,肯定會觸發mouseover等事件。如果使用Selenium的工具的“合成事件”則不會觸發mouseover等事件。對於使用dojo框架的開發者來說,robot更有利於同dijit交互,包括自定義dijit;當然最重要的一點是,在研究selenium的過程中,我發現Selenium有嚴重的瀏覽器兼容性問題!於是果斷放棄。
言歸正傳,下面來具體介紹robot。robot主要有三部分組成:
doh/robot,這里面包含的是robot的核心內容,與dojo框架沒有耦合關系,可以單獨拿出去作為一個自動化測試工具。
1 require(["doh/robot"], function(doh){ 2 ... 3 });
dojo/robot,該模塊使用dojo核心技術,支持doh/robot的全部方法,主要在doh/robot的基礎上增加了兩個方法:mouseMoveAt和scrollIntoView。
1 require(["dojo/robot"], function(doh){ 2 ... 3 });
dojox/robot,該模塊在dojo/robot的基礎上增加了兩個重要方法:initRobot和waitForPageLoad。為什么說他們重要,因為他們可以再一個測試案例文件中控制其他的測試文件,后文將詳細。
1 require(["dojox/robot"], function(doh){ 2 ... 3 });
二、基本步驟
doh/robot測試主要分為以下四步:
1、實例化一個doh.Deferred對象
2、執行交互命令
3、設置一個超時函數來驗證交互結果
4、在runTest中返回deferred對象
這個事例中頁面上一個文本輸入框,包含hi兩個字符,然后模仿用戶輸入“ again”動作。示例,robot實際上是一個applet第一次訪問的話會提示安裝。
1 require(["doh/runner", "doh/robot"], function(doh, robot){ 2 doh.register("doh/robot", 3 { 4 name: "dojorobot1", 5 timeout: 6900, 6 setUp: function(){ 7 document.getElementById('textbox').value="hi"; 8 }, 9 runTest: function(){ 10 var d = new doh.Deferred(); 11 robot.mouseMove(30, 30, 500); 12 robot.mouseClick({left:true}, 500); 13 robot.typeKeys(" again", 500, 2500); 14 robot.sequence(d.getTestCallback(function(){ 15 doh.is("hi again", document.getElementById('textbox').value); 16 }), 900); 17 return d; 18 } 19 }); 20 doh.run(); 21 });
<form> <input type="text" value="hi" id="textbox" style="position:absolute; left:0px; top:20px; font-family:system;"></input> </form>
這里要說以下robot.sequence和d.getTestCallback這兩個函數,sequence是robot中的一種同步方式書寫的setTimeout,下文delay中會講到這類方法的優勢。doh.Deferred是doh中對dojo/Deferred的擴展,加入了許多方法,getTestCallback就是其中之一,該函數作用就在於交互命令執行完成后驗證交互結果,並將結果告知測試框架從而決定顯示綠條還是紅條,通常一個測試案例中只有一處調用該方法。一下是該函數的源碼:
1 getTestCallback: function(cb, scope){ 2 var _this = this; 3 return function(){// 該函數返回一個閉包給sequence函數,從而在一定時間后調用該閉包 4 try{// 執行驗證函數,監控結果,並將結果通知測試框架 5 cb.apply(scope||doh.global||_this, arguments); 6 }catch(e){ 7 _this.reject(e);// 紅條 8 return; 9 } 10 _this.resolve(true);// 綠條 11 }; 12 },
三、API介紹
1、基本參數
下面以doh/Robot中typeKeys為例介紹一下robot API的基本參數,具體提供了哪些API可以在這里查看:API
1 typeKeys: function(/*String||Number*/ chars, /*Integer, optional*/ delay, /*Integer, optional*/ duration){ 2 // summary: 3 // Types a string of characters in order, or types a dojo.keys.* constant. 4 // 5 // description: 6 // Types a string of characters in order, or types a dojo.keys.* constant. 7 // Example: robot.typeKeys("dijit.ed", 500); 8 // 9 // chars: 10 // String of characters to type, or a dojo.keys.* constant 11 // 12 // delay: 13 // Delay, in milliseconds, to wait before firing. 14 // The delay is a delta with respect to the previous automation call. 15 // For example, the following code ends after 600ms: 16 // robot.mouseClick({left:true}, 100) // first call; wait 100ms 17 // robot.typeKeys("dij", 500) // 500ms AFTER previous call; 600ms in all 18 // 19 // duration: 20 // Time, in milliseconds, to spend pressing all of the keys. 21 // 22 }
delay,顧名思義目的在於延遲,在webAPP中經常會有動畫效果,等待動畫結束后在執行click等事件,所以每一個API中與UI交互的函數都會提供這個方法。該方法內部使用setTimeout方法實現,既然這樣,為什么不讓用戶直接使用setTimeout方法?原因在於dojo可以為我們自動調整一系列動作的執行順序及間隔。舉個例子,如果使用setTimeout,動作f1需要在300ms后執行,動作f2需要在f1之后100ms執行那就需要設置f2的timeout時間為400ms,這時候f1之前要加一個動作f3在f1之前200ms執行,
現在我們就需要挨個該f1跟f2的時間參數,如果f1之后有100個動作要執行。。。。God bless you。使用robot API提供的函數,我們只需要設置每個動作之間的時間間隔即可。
1 setTimeout(f1, 300); 2 setTimeout(f2, 400); 3 =>> 4 setTimeout(f3, 300); 5 setTimeout(f1, 500); 6 setTimeout(f2, 600);
1 var mask = query("div.ovwHighlight")[0]; 2 robot.mouseMoveAt(mask, 200, 200); 3 robot.mousePress({left: true}, 100); 4 robot.mouseMoveAt(mask, 200, 200, 10, 10); 5 robot.mouseRelease({left: true}, 100);
duration便是的是執行這個動作所需要的時間,以typeKeys為例,假設duration設置為1800ms,char為“abc”,則輸入a、b、c這三個字符各占600ms。
2、mouseMoveAt
dojo/Robot中提供了一個mouseMoveAt函數,在上文的示例中,我們可以看到鼠標移動函數用的是mouseMove函數,該函數與mouseMoveto函數都是通過相對於當前瀏覽器document的x、y坐標來定位元素,而mouseMoveAt則是通過選擇符或者node節點來定位元素。
1 mouseMoveAt : function(/*String||DOMNode||Function*/ node, /*Integer, optional*/ delay, /*Integer, optional*/ duration, /*Number, optional*/ offsetX, /*Number, optional*/ offsetY){ 2 // summary: 3 // Moves the mouse over the specified node at the specified relative x,y offset. 4 // 5 // description: 6 // Moves the mouse over the specified node at the specified relative x,y offset. 7 // You should manually scroll off-screen nodes into view; use dijit.robot for automatic scrolling support. 8 // If you do not specify an offset, mouseMove will default to move to the middle of the node. 9 // Example: to move the mouse over a ComboBox's down arrow node, call doh.mouseMoveAt(dijit.byId('setvaluetest').downArrowNode); 10 // 11 // node: 12 // The id of the node, or the node itself, to move the mouse to. 13 // If you pass an id or a function that returns a node, the node will not be evaluated until the movement executes. 14 // This is useful if you need to move the mouse to an node that is not yet present. 15 // 16 // delay: 17 // Delay, in milliseconds, to wait before firing. 18 // The delay is a delta with respect to the previous automation call. 19 // For example, the following code ends after 600ms: 20 // doh.mouseClick({left:true}, 100) // first call; wait 100ms 21 // doh.typeKeys("dij", 500) // 500ms AFTER previous call; 600ms in all 22 // 23 // duration: 24 // Approximate time Robot will spend moving the mouse. 25 // The default is 100ms. 26 // 27 // offsetX: 28 // x offset relative to the node, in pixels, to move the mouse. The default is half the node's width. 29 // 30 // offsetY: 31 // y offset relative to the node, in pixels, to move the mouse. The default is half the node's height. 32 //
所以上文中的實例可以這樣寫:
1 require(["doh/runner", "dojo/robot"], function(doh, robot){ 2 doh.register("doh/robot", 3 { 4 name: "dojorobot1", 5 timeout: 6900, 6 setUp: function(){ 7 document.getElementById('textbox').value="hi"; 8 }, 9 runTest: function(){ 10 var d = new doh.Deferred(); 11 robot.mouseMoveAt(document.getElementById('textbox'), 500); 12 robot.mouseClick({left:true}, 500); 13 robot.typeKeys(" again", 500, 2500); 14 robot.sequence(d.getTestCallback(function(){ 15 doh.is("hi again", document.getElementById('textbox').value); 16 }), 900); 17 return d; 18 } 19 }); 20 doh.run(); 21 });
3、initRobot
對於WebAPP來說,都需要將測試代碼與主程序代碼分離。但測試文件執行時需要與主程序中的UI交互,這時輪到initRobot閃亮登場了。該函數位於dojox/Robot中,所以我們需要引入dojox/Robot。initRobot主要的作用就在於將當前測試文件的執行環境轉變為主程序環境,這時robot.doc、robot.window代表的則是主程序中的document和window。比如說你想使用document.getElementById(‘login')來獲取主程序中的login元素,那么你需要這樣來寫robot.doc.getElementById('login'),官方文檔中提到使用dojo模塊中的方法的執行環境都是主程序環境,遺憾的是我在實際使用中發現並非如此,有的模塊方法的確是主程序環境比如query,而有的則並非主程序環境比如registry。
具體示例可以訪問這里,查看源碼你會發現這里面並沒有robot的測試代碼,該文件位於dojo-release-1.9.1-src\dijit\tests目錄下,robot測試代碼位於dojo-release-1.9.1-src\dijit\tests\robot目錄下。
參考文檔:https://dojotoolkit.org/reference-guide/1.9/util/dohrobot.html#doh-robot
下一篇我們將具體看一個完整robot示例,以及將robot與selenium聯合使用進而將他與CI工具集成。同時我們還會討論robot異步函數的美中不足。