diff options
| author | Tristan Zur <tzur@web.web.ccwn.org> | 2014-03-27 22:27:47 +0100 |
|---|---|---|
| committer | Tristan Zur <tzur@web.web.ccwn.org> | 2014-03-27 22:27:47 +0100 |
| commit | b62676ca5d3d6f6ba3f019ea3f99722e165a98d8 (patch) | |
| tree | 86722cb80f07d4569f90088eeaea2fc2f6e2ef94 /js/dojo/dojox/robot/recorder.js | |
Diffstat (limited to 'js/dojo/dojox/robot/recorder.js')
| -rw-r--r-- | js/dojo/dojox/robot/recorder.js | 506 |
1 files changed, 506 insertions, 0 deletions
diff --git a/js/dojo/dojox/robot/recorder.js b/js/dojo/dojox/robot/recorder.js new file mode 100644 index 0000000..415f248 --- /dev/null +++ b/js/dojo/dojox/robot/recorder.js @@ -0,0 +1,506 @@ +//>>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); + } + }); +})(); + +}); |
