webui-aria2/js/libs/dojox/robot/recorder.js.uncompressed.js
2012-05-01 19:52:07 +08:00

507 lines
16 KiB
JavaScript

//>>built
// wrapped by build app
define("dojox/robot/recorder", ["dijit","dojo","dojox"], function(dijit,dojo,dojox){
dojo.provide("dojox.robot.recorder");
dojo.experimental("dojox.robot.recorder");
// summary:
// Generates a doh test as you interact with a Web page.
// To record a test, click inside the document body and press CTRL-ALT-ENTER.
// To finish recording a test and to display the autogenerated code, press CTRL-ALT-ENTER again.
//
(function(){
// CONSTANTS
// consolidate keypresses into one typeKeys if they occur within 1 second of each other
var KEYPRESS_MAXIMUM_DELAY = 1000;
// consolidate mouse movements if they occur within .5 seconds of each other
var MOUSEMOVE_MAXIMUM_DELAY = 500;
// absolute longest wait between commands
// anything longer gets chopped to 10
var MAXIMUM_DELAY = 10000;
// stack of commands recorded from dojo.connects
var commands = [];
// number to write next to test name
// goes up after each recording
var testNumber = 0;
// time user started test
var startTime = null;
// time since last user input
// robot commands work on deltas
var prevTime = null;
var start = function(){
// summary:
// Starts recording the user's input.
//
alert("Started recording.");
commands = [];
startTime = new Date();
prevTime = new Date();
}
var addCommand = function(name, args){
// summary:
// Add a command to the stack.
//
// name:
// doh.robot function to call.
//
// args:
// arguments array to pass to the doh.robot
//
// omit start/stop record
if(startTime == null
|| name=="doh.robot.keyPress"
&& args[0]==dojo.keys.ENTER
&& eval("("+args[2]+")").ctrl
&& eval("("+args[2]+")").alt){ return; }
var dt = Math.max(Math.min(Math.round((new Date()).getTime() - prevTime.getTime()),MAXIMUM_DELAY),1);
// add in dt
// is usually args[1] but there are exceptions
if(name == "doh.robot.mouseMove"){
args[2]=dt;
}else{
args[1]=dt;
}
commands.push({name:name,args:args});
prevTime = new Date();
}
var _optimize = function(){
// make the stack more human-readable and remove any odditites
var c = commands;
// INITIAL OPTIMIZATIONS
// remove starting ENTER press
if(c[0].name == "doh.robot.keyPress"
&& (c[0].args[0] == dojo.keys.ENTER || c[0].args[0] == 77)){
c.splice(0,1);
}
// remove ending CTRL + ALT keypresses in IE
for(var i = c.length-1; (i >= c.length-2) && (i>=0); i-- ){
if(c[i].name == "doh.robot.keyPress"
&& c[i].args[0]==dojo.keys.ALT || c[i].args[0]==dojo.keys.CTRL){
c.splice(i,1);
}
}
// ITERATIVE OPTIMIZATIONS
for(i = 0; i<c.length; i++){
var next, nextdt;
if(c[i+1]
&& c[i].name=="doh.robot.mouseMove"
&& c[i+1].name==c[i].name
&& c[i+1].args[2]<MOUSEMOVE_MAXIMUM_DELAY){
// mouse movement optimization
// if the movement is temporally close, collapse it
// example: mouseMove(a,b,delay 5)+mouseMove(x,y,delay 10)+mousePress(delay 1) becomes mouseMove(x,y,delay 5)+mousePress(delay 11)
// expected pattern:
// c[i]: mouseMove
// c[i+1]: mouseMove
// ...
// c[i+n-1]: last mouseMove
// c[i+n]: something else
// result:
// c[i]: last mouseMove
// c[i+1]: something else
// the c[i] mouse location changes to move to c[i+n-1]'s location, c[i+n] gains c[i+1]+c[i+2]+...c[i+n-1] delay so the timing is the same
next = c[i+1];
nextdt = 0;
while(next
&& next.name==c[i].name
&& next.args[2]<MOUSEMOVE_MAXIMUM_DELAY){
// cut out next
c.splice(i + 1,1);
// add next's delay to the total
nextdt += next.args[2];
// move to next mouse position
c[i].args[0]=next.args[0];
c[i].args[1]=next.args[1];
next = c[i+1];
}
// make the total delay the duration
c[i].args[3]=nextdt;
}else if(c[i+1]
&& c[i].name=="doh.robot.mouseWheel"
&& c[i+1].name==c[i].name
&& c[i+1].args[1]<MOUSEMOVE_MAXIMUM_DELAY){
// mouse wheel optimization
// if the movement is temporally close, collapse it
// example: mouseWheel(1,delay 5)+mouseWheel(-2,delay 10) becomes mouseWheel(-1,delay 5, speed 10)
// expected pattern:
// c[i]: mouseWheel
// c[i+1]: mouseWheel
// ...
// c[i+n-1]: last mouseWheel
// c[i+n]: something else
// result:
// c[i]: mouseWheel
// c[i+1]: something else
next = c[i+1];
nextdt = 0;
while(next
&& next.name==c[i].name
&& next.args[1]<MOUSEMOVE_MAXIMUM_DELAY){
// cut out next
c.splice(i + 1, 1);
// add next's delay to the total
nextdt += next.args[1];
// add in wheel amount
c[i].args[0]+=next.args[0];
next = c[i+1];
}
// make the total delay the duration
c[i].args[2]=nextdt;
}else if(c[i + 2]
&& c[i].name=="doh.robot.mouseMoveAt"
&& c[i+2].name=="doh.robot.scrollIntoView"){
// swap scrollIntoView of widget and mouseMoveAt
// the recorder traps scrollIntoView after the mouse click registers, but in playback, it is better to go the other way
// expected pattern:
// c[i]: mouseMoveAt
// c[i+1]: mousePress
// c[i+2]: scrollIntoView
// result:
// c[i]: scrollIntoView
// c[i+1]: mouseMoveAt
// c[i+2]: mousePress
var temp = c.splice(i+2,1)[0];
c.splice(i,0,temp);
}else if(c[i + 1]
&& c[i].name=="doh.robot.mousePress"
&& c[i+1].name=="doh.robot.mouseRelease"
&& c[i].args[0]==c[i+1].args[0]){
// convert mousePress+mouseRelease to mouseClick
// expected pattern:
// c[i]: mousePress
// c[i+1]: mouseRelease
// mouse buttons are the same
c[i].name = "doh.robot.mouseClick";
// delete extra mouseRelease
c.splice(i + 1,1);
// if this was already a mouse click, get rid of the next (dup) one
if(c[i+1] && c[i+1].name == "doh.robot.mouseClick" && c[i].args[0] == c[i+1].args[0]){
c.splice(i + 1, 1);
}
}else if(c[i + 1]
&& c[i - 1]
&& c[i - 1].name=="doh.robot.mouseMoveAt"
&& c[i].name=="doh.robot.mousePress"
&& c[i+1].name=="doh.robot.mouseMove"){
// convert mouseMoveAt+mousePress+mouseMove to mouseMoveAt+mousePress+mouseMoveAt+mouseMove
// this is to kick off dojo.dnd by moving the mouse 1 px
// expected pattern:
// c[i-1]: mouseMoveAt
// c[i]: mousePress
// c[i+1]: mouseMove
// insert new mouseMoveAt, 1px to the right
var cmd={name:"doh.robot.mouseMoveAt",args:[c[i-1].args[0], 1, 100, c[i-1].args[3]+1,c[i-1].args[4]]};
c.splice(i+1,0,cmd);
}else if(c[i + 1]
&& ((c[i].name=="doh.robot.keyPress"
&& typeof c[i].args[0] =="string")
|| c[i].name=="doh.robot.typeKeys")
&& c[i+1].name=="doh.robot.keyPress"
&& typeof c[i+1].args[0] =="string"
&& c[i+1].args[1]<=KEYPRESS_MAXIMUM_DELAY
&& !eval("("+c[i].args[2]+")").ctrl
&& !eval("("+c[i].args[2]+")").alt
&& !eval("("+c[i+1].args[2]+")").ctrl
&& !eval("("+c[i+1].args[2]+")").alt){
// convert keyPress+keyPress+... to typeKeys
// expected pattern:
// c[i]: keyPress(a)
// c[i+1]: keyPress(b)
// ...
// c[i+n-1]: last keyPress(z)
// c[i+n]: something else
// result:
// c[i]: typeKeys(ab...z)
// c[i+1]: something else
// note: does not convert alt or ctrl keypresses, and does not convert non-character keypresses like enter
c[i].name = "doh.robot.typeKeys";
c[i].args.splice(3,1);
next = c[i+1];
var typeTime = 0;
while(next
&& next.name == "doh.robot.keyPress"
&& typeof next.args[0] =="string"
&& next.args[1]<=KEYPRESS_MAXIMUM_DELAY
&& !eval("("+next.args[2]+")").ctrl
&& !eval("("+next.args[2]+")").alt){
c.splice(i + 1,1);
c[i].args[0] += next.args[0];
typeTime += next.args[1];
next = c[i+1];
}
// set the duration to the total type time
c[i].args[2] = typeTime;
// wrap string in quotes
c[i].args[0] = "'"+c[i].args[0]+"'";
}else if(c[i].name == "doh.robot.keyPress"){
// take care of standalone keypresses
// characters should be wrapped in quotes.
// non-characters should be replaced with their corresponding dojo.keys constant
if(typeof c[i].args[0] == "string"){
// one keypress of a character by itself should be wrapped in quotes
c[i].args[0] = "'"+c[i].args[0]+"'";
}else{
if(c[i].args[0]==0){
// toss null keypresses
c.splice(i,1);
}else{
// look up dojo.keys.constant if possible
for(var j in dojo.keys){
if(dojo.keys[j] == c[i].args[0]){
c[i].args[0] = "dojo.keys."+j;
break;
}
}
}
}
}
}
}
var toggle = function(){
// summary:
// Toggles recording the user's input.
// Hotkey: CTRL- ALT-ENTER
//
if(!startTime){ start(); }
else{ stop(); }
}
var stop = function(){
// summary:
// Stops recording the user's input,
// and displays the generated code.
//
var dt = Math.round((new Date()).getTime() - startTime.getTime());
startTime = null;
_optimize();
var c = commands;
console.log("Stop called. Commands: " + c.length);
if(c.length){
var s = "doh.register('dojox.robot.AutoGeneratedTestGroup',{\n";
s += " name: 'autotest" + (testNumber++)+"',\n";
s += " timeout: " + (dt+2000)+",\n";
s += " runTest: function(){\n";
s += " var d = new doh.Deferred();\n";
for(var i = 0; i<c.length; i++){
s += " "+c[i].name+"(";
for(var j = 0; j<c[i].args.length; j++){
var arg = c[i].args[j];
s += arg;
if(j != c[i].args.length-1){ s += ", "; }
}
s += ");\n";
}
s += " doh.robot.sequence(function(){\n";
s += " if(/*Your condition here*/){\n";
s += " d.callback(true);\n";
s += " }else{\n";
s += " d.errback(new Error('We got a failure'));\n";
s += " }\n";
s += " }, 1000);\n";
s += " return d;\n";
s += " }\n";
s += "});\n";
var div = document.createElement('div');
div.id="dojox.robot.recorder";
div.style.backgroundColor = "white";
div.style.position = "absolute";
var scroll = {y: (window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0),
x: (window.pageXOffset || (window["dojo"]?dojo._fixIeBiDiScrollLeft(document.documentElement.scrollLeft):undefined) || document.body.scrollLeft || 0)};
div.style.left = scroll.x+"px";
div.style.top = scroll.y+"px";
var h1 = document.createElement('h1');
h1.innerHTML = "Your code:";
div.appendChild(h1);
var pre = document.createElement('pre');
if(pre.innerText !== undefined){
pre.innerText = s;
}else{
pre.textContent = s;
}
div.appendChild(pre);
var button = document.createElement('button');
button.innerHTML = "Close";
var connect = dojo.connect(button,'onmouseup',function(e){
dojo.stopEvent(e);
document.body.removeChild(div);
dojo.disconnect(connect);
});
div.appendChild(button);
document.body.appendChild(div);
commands = [];
}
}
var getSelector = function(node){
// Selenium/Windmill recorders have a concept of a "selector."
// The idea is that recorders need some reference to an element that persists even after a page refresh.
// For elements with ids, this is easy; just use the id.
// For other elements, we have to be more sly.
if(typeof node =="string"){
// it's already an id to be interpreted by dojo.byId
return "'" + node+"'";
}else if(node.id){
// it has an id
return "'" + node.id+"'";
}else{
// TODO: need a generic selector, like CSS3/dojo.query, for the default return value
// for now, just do getElementsByTagName
var nodes = document.getElementsByTagName(node.nodeName);
var i;
for(i = 0; i<nodes.length; i++){
if(nodes[i] == node){
break;
}
}
// wrap in a function to defer evaluation
return "function(){ return document.getElementsByTagName('" + node.nodeName+"')["+i+"]; }";
}
}
var getMouseButtonObject = function(b){
// convert native event to doh.robot API
return "{left:" + (b==0)+", middle:" + (b==1)+", right:" + (b==2)+"}";
}
var getModifierObject = function(e){
// convert native event to doh.robot API
return "{'shift':" + (e.shiftKey)+", 'ctrl':" + (e.ctrlKey)+", 'alt':" + (e.altKey)+"}";
}
// dojo.connects
dojo.connect(document,"onkeydown",function(e){
// the CTRL- ALT-ENTER hotkey to activate the recorder
//console.log(e.keyCode + ", " + e.ctrlKey+", " + e.altKey);
if((e.keyCode == dojo.keys.ENTER || e.keyCode==77) && e.ctrlKey && e.altKey){
dojo.stopEvent(e);
toggle();
}
});
var lastEvent = {type:""};
var onmousedown = function(e){
// handler for mouse down
if(!e || lastEvent.type==e.type && lastEvent.button==e.button){ return; }
lastEvent={type:e.type, button:e.button};
var selector = getSelector(e.target);
var coords = dojo.coords(e.target);
addCommand("doh.robot.mouseMoveAt",[selector, 0, 100, e.clientX - coords.x, e.clientY-coords.y]);
addCommand("doh.robot.mousePress",[getMouseButtonObject(e.button-(dojo.isIE?1:0)), 0]);
};
var onclick = function(e){
// handler for mouse up
if(!e || lastEvent.type==e.type && lastEvent.button==e.button){ return; }
lastEvent={type:e.type, button:e.button};
var selector = getSelector(e.target);
var coords = dojo.coords(e.target);
addCommand("doh.robot.mouseClick",[getMouseButtonObject(e.button-(dojo.isIE?1:0)), 0]);
};
var onmouseup = function(e){
// handler for mouse up
if(!e || lastEvent.type==e.type && lastEvent.button==e.button){ return; }
lastEvent={type:e.type, button:e.button};
var selector = getSelector(e.target);
var coords = dojo.coords(e.target);
addCommand("doh.robot.mouseRelease",[getMouseButtonObject(e.button-(dojo.isIE?1:0)), 0]);
};
var onmousemove = function(e){
// handler for mouse move
if(!e || lastEvent.type==e.type && lastEvent.pageX==e.pageX && lastEvent.pageY==e.pageY){ return; }
lastEvent={type:e.type, pageX:e.pageX, pageY:e.pageY};
addCommand("doh.robot.mouseMove",[e.pageX, e.pageY, 0, 100, true]);
};
var onmousewheel = function(e){
// handler for mouse move
if(!e || lastEvent.type==e.type && lastEvent.pageX==e.pageX && lastEvent.pageY==e.pageY){ return; }
lastEvent={type:e.type, detail:(e.detail ? (e.detail) : (-e.wheelDelta / 120))};
addCommand("doh.robot.mouseWheel",[lastEvent.detail]);
};
var onkeypress = function(e){
// handler for key press
if(!e || lastEvent.type==e.type && (lastEvent.charCode == e.charCode && lastEvent.keyCode == e.keyCode)){ return; }
lastEvent={type:e.type, charCode:e.charCode, keyCode:e.keyCode};
addCommand("doh.robot.keyPress",[e.charOrCode==dojo.keys.SPACE?' ':e.charOrCode, 0, getModifierObject(e)]);
};
var onkeyup = function(e){
if(!e || lastEvent.type==e.type && (lastEvent.charCode == e.charCode && lastEvent.keyCode == e.keyCode)){ return; }
lastEvent={type:e.type, charCode:e.charCode, keyCode:e.keyCode};
}
// trap all native elements' events
dojo.connect(document,"onmousedown",onmousedown);
dojo.connect(document,"onmouseup",onmouseup);
dojo.connect(document,"onclick",onclick);
dojo.connect(document,"onkeypress",onkeypress);
dojo.connect(document,"onkeyup",onkeyup);
dojo.connect(document,"onmousemove",onmousemove);
dojo.connect(document,!dojo.isMozilla ? "onmousewheel" : 'DOMMouseScroll',onmousewheel);
dojo.addOnLoad(function(){
// get scrollIntoView for good measure
// catch: dojo.window might not be loaded (yet?) so addonload
if(dojo.window){
dojo.connect(dojo.window,"scrollIntoView",function(node){
addCommand("doh.robot.scrollIntoView",[getSelector(node)]);
});
}
});
// Get Dojo widget events too!
dojo.connect(dojo, "connect",
function(/*dijit._Widget*/ widget, /*String*/ event, /*Function*/ f){
// kill recursion
// check for private variable _mine to make sure this isn't a recursive loop
if(widget && (!f || !f._mine)){
var hitchedf = null;
if(event.toLowerCase() == "onmousedown"){
hitchedf = dojo.hitch(this,onmousedown);
}else if(event.toLowerCase() == (!dojo.isMozilla ? "onmousewheel" : 'dommousescroll')){
hitchedf = dojo.hitch(this,onmousewheel);
}else if(event.toLowerCase() == "onclick"){
hitchedf = dojo.hitch(this,onclick);
}else if(event.toLowerCase() == "onmouseup"){
hitchedf = dojo.hitch(this,onmouseup);
}else if(event.toLowerCase() == "onkeypress"){
hitchedf = dojo.hitch(this,onkeypress);
}else if(event.toLowerCase() == "onkeyup"){
hitchedf = dojo.hitch(this,onkeyup);
}
if(hitchedf == null){ return; }
hitchedf._mine = true;
dojo.connect(widget,event,hitchedf);
}
});
})();
});