Pages

Saturday, February 8, 2014

  • Parameterization of Selenium IDE using XML 
  • Download the flowcontrol extension for IDE.  (“Download” is a bit of a misnomer here, as the download link takes one to an HTML page.  Simply copy/paste only the JavaScript contents of that page into a file named goto_sel_ide.js.)
  • Download the 1.1 version of the includeCommand4IDE extension.  (The just-released 1.2 version appears to have a serious bug.)
  • Download the most recent version (0.2) of the datadriven.js extension.
  • Install these 3 extensions in IDE via the Selenium Core extensions field (accessible via Options=>Options=>General). They must be specified in the order above!!!
  • Re-start IDE so that all 3 extensions will get read in.
  • Create an .xml file:

Copy and paste below code , save as .js file, copy all three code in different file and add to ide extension


goto_sel_ide.js

//+++++++++++++++++++++++++++++++++++++++

var gotoLabels= {};
var whileLabels = {};

// overload the original Selenium reset function
Selenium.prototype.reset = function() {
    // reset the labels
    this.initialiseLabels();
    // proceed with original reset code
    this.defaultTimeout = Selenium.DEFAULT_TIMEOUT;
    this.browserbot.selectWindow("null");
    this.browserbot.resetPopups();
}

Selenium.prototype.initialiseLabels = function()
{
    gotoLabels = {};
    whileLabels = { ends: {}, whiles: {} };
    var command_rows = [];
    var numCommands = testCase.commands.length;
    for (var i = 0; i < numCommands; ++i) {
        var x = testCase.commands[i];
        command_rows.push(x);
    }
    var cycles = [];
    for( var i = 0; i < command_rows.length; i++ ) {
        if (command_rows[i].type == 'command')
        switch( command_rows[i].command.toLowerCase() ) {
            case "label":
                gotoLabels[ command_rows[i].target ] = i;
                break;
            case "while":
            case "endwhile":
                cycles.push( [command_rows[i].command.toLowerCase(), i] )
                break;
        }
    }  
    var i = 0;
    while( cycles.length ) {
        if( i >= cycles.length ) {
            throw new Error( "non-matching while/endWhile found" );
        }
        switch( cycles[i][0] ) {
            case "while":
                if( ( i+1 < cycles.length ) && ( "endwhile" == cycles[i+1][0] ) ) {
                    // pair found
                    whileLabels.ends[ cycles[i+1][1] ] = cycles[i][1];
                    whileLabels.whiles[ cycles[i][1] ] = cycles[i+1][1];
                    cycles.splice( i, 2 );
                    i = 0;
                } else ++i;
                break;
            case "endwhile":
                ++i;
                break;
        }
    }
}

Selenium.prototype.continueFromRow = function( row_num )
{
    if(row_num == undefined || row_num == null || row_num < 0) {
        throw new Error( "Invalid row_num specified." );
    }
    testCase.debugContext.debugIndex = row_num;
}

// do nothing. simple label
Selenium.prototype.doLabel = function(){};

Selenium.prototype.doGotolabel = function( label )
{
    if( undefined == gotoLabels[label] ) {
        throw new Error( "Specified label '" + label + "' is not found." );
    }
    this.continueFromRow( gotoLabels[ label ] );
};

Selenium.prototype.doGoto = Selenium.prototype.doGotolabel;

Selenium.prototype.doGotoIf = function( condition, label )
{
    if( eval(condition) ) this.doGotolabel( label );
}

Selenium.prototype.doWhile = function( condition )
{
    if( !eval(condition) ) {
        var last_row = testCase.debugContext.debugIndex;
        var end_while_row = whileLabels.whiles[ last_row ];
        if( undefined == end_while_row ) throw new Error( "Corresponding 'endWhile' is not found." );
        this.continueFromRow( end_while_row );
    }
}

Selenium.prototype.doEndWhile = function()
{
    var last_row = testCase.debugContext.debugIndex;
    var while_row = whileLabels.ends[ last_row ] - 1;
    if( undefined == while_row ) throw new Error( "Corresponding 'While' is not found." );
    this.continueFromRow( while_row );
}

//++++++++++++++++++++++++++++++++++++++++++++++++++






/************************************ DATADRIVEN EXTENSION START ********************************************/
/*
NAME:
datadriven
Licensed under Apache License v2
http://www.apache.org/licenses/LICENSE-2.0

PURPOSE:
Basic data driven testing.
Full documentation at http://wiki.openqa.org/display/SEL/datadriven
EXAMPLE USE:
The structure of your data driven test case will be;
-------------------------------------------
COMMAND       |TARGET          |VALUE
-------------------------------------------
loadTestData  |<file path>     |
while         |!testdata.EOF() |
testcommand1  |                |
testcommand...|                |
testcommandn  |                |
endWhile      |                |
-------------------------------------------

AUTHOR:
Jonathan McBrien
jonathan@mcbrien.org
2008-10-22: v0.1: Initial version.
2009-01-16: v0.2: Updated for Firefox 3.
xmlTestData.prototype.load now uses the include extension's getIncludeDocumentBySynchronRequest method for better portability.
(Why reinvent the wheel? :) - with appreciation to the include extension's authors.)
*/

XML.serialize = function(node) {
if (typeof XMLSerializer != "undefined")
return (new XMLSerializer()).serializeToString(node) ;
else if (node.xml) return node.xml;
else throw "XML.serialize is not supported or can't serialize " + node;
}

function xmlTestData() {
this.xmlDoc = null;
this.testdata = null;
this.dataIndex = null;
}


xmlTestData.prototype.loadTestCase = function(xmlloc,testcase) {
loader = new IDEIncludeCommand();
var xmlHttpReq = loader.getIncludeDocumentBySynchronRequest(xmlloc);
this.xmlDoc = xmlHttpReq.responseXML;

this.dataIndex = 0;
this.testdata = this.xmlDoc.getElementsByTagName(testcase)[0].getElementsByTagName("testdata");
if (this.testdata == null || this.testdata.length == 0) {
throw new Error("Test Case couldn't be loaded or test case was empty.");
}
}


xmlTestData.prototype.EOF = function() {
if (this.dataIndex != null && this.dataIndex < this.testdata.length) return false;
return true;
}

xmlTestData.prototype.more = function() {
return !this.EOF();
}


xmlTestData.prototype.nextTestData = function() {
if (this.EOF()) {
LOG.error("No test data.");
return;
}
LOG.info(XML.serialize(this.testdata.item(this.dataIndex))); // Log should anything go wrong while testing with this data.
if (this.testdata.item(this.dataIndex).length != this.testdata.item(0).length) {
LOG.error("Inconsistent data length in test case.");
return;
}
for (i=0; i<this.testdata.item(this.dataIndex).childNodes.length; i++){
if (null == this.testdata.item(0).getElementsByTagName(this.testdata.item(this.dataIndex).childNodes[i].nodeName)) {
LOG.error("Inconsistent data tag names in test data.");
return;
}
if(this.testdata.item(this.dataIndex).childNodes[i].nodeType == 1) {
selenium.doStore(this.testdata.item(this.dataIndex).childNodes[i].firstChild.nodeValue, this.testdata.item(this.dataIndex).childNodes[i].nodeName);
}
}

this.dataIndex++;
}


Selenium.prototype.testdata = null;

Selenium.prototype.doLoadTestCase = function(xmlloc,testcase) {
testdata = new xmlTestData();
testdata.loadTestCase(xmlloc,testcase);
};

Selenium.prototype.doNextTestData = function() {
testdata.nextTestData();
};




/************************************ DATADRIVEN EXTENSION START ********************************************/
/*
NAME:
datadriven
Licensed under Apache License v2
http://www.apache.org/licenses/LICENSE-2.0

PURPOSE:
Basic data driven testing.
Full documentation at http://wiki.openqa.org/display/SEL/datadriven
EXAMPLE USE:
The structure of your data driven test case will be;
-------------------------------------------
COMMAND       |TARGET          |VALUE
-------------------------------------------
loadTestData  |<file path>     |
while         |!testdata.EOF() |
testcommand1  |                |
testcommand...|                |
testcommandn  |                |
endWhile      |                |
-------------------------------------------

AUTHOR:
Jonathan McBrien
jonathan@mcbrien.org
2008-10-22: v0.1: Initial version.
2009-01-16: v0.2: Updated for Firefox 3.
xmlTestData.prototype.load now uses the include extension's getIncludeDocumentBySynchronRequest method for better portability.
(Why reinvent the wheel? :) - with appreciation to the include extension's authors.)
*/

XML.serialize = function(node) {
if (typeof XMLSerializer != "undefined")
return (new XMLSerializer()).serializeToString(node) ;
else if (node.xml) return node.xml;
else throw "XML.serialize is not supported or can't serialize " + node;
}

function xmlTestData() {
this.xmlDoc = null;
this.testdata = null;
this.dataIndex = null;
}


xmlTestData.prototype.loadTestCase = function(xmlloc,testcase) {
loader = new IDEIncludeCommand();
var xmlHttpReq = loader.getIncludeDocumentBySynchronRequest(xmlloc);
this.xmlDoc = xmlHttpReq.responseXML;

this.dataIndex = 0;
this.testdata = this.xmlDoc.getElementsByTagName(testcase)[0].getElementsByTagName("testdata");
if (this.testdata == null || this.testdata.length == 0) {
throw new Error("Test Case couldn't be loaded or test case was empty.");
}
}


xmlTestData.prototype.EOF = function() {
if (this.dataIndex != null && this.dataIndex < this.testdata.length) return false;
return true;
}

xmlTestData.prototype.more = function() {
return !this.EOF();
}


xmlTestData.prototype.nextTestData = function() {
if (this.EOF()) {
LOG.error("No test data.");
return;
}
LOG.info(XML.serialize(this.testdata.item(this.dataIndex))); // Log should anything go wrong while testing with this data.
if (this.testdata.item(this.dataIndex).length != this.testdata.item(0).length) {
LOG.error("Inconsistent data length in test case.");
return;
}
for (i=0; i<this.testdata.item(this.dataIndex).childNodes.length; i++){
if (null == this.testdata.item(0).getElementsByTagName(this.testdata.item(this.dataIndex).childNodes[i].nodeName)) {
LOG.error("Inconsistent data tag names in test data.");
return;
}
if(this.testdata.item(this.dataIndex).childNodes[i].nodeType == 1) {
selenium.doStore(this.testdata.item(this.dataIndex).childNodes[i].firstChild.nodeValue, this.testdata.item(this.dataIndex).childNodes[i].nodeName);
}
}

this.dataIndex++;
}


Selenium.prototype.testdata = null;

Selenium.prototype.doLoadTestCase = function(xmlloc,testcase) {
testdata = new xmlTestData();
testdata.loadTestCase(xmlloc,testcase);
};

Selenium.prototype.doNextTestData = function() {
testdata.nextTestData();
};

/************************************ DATADRIVEN EXTENSION END **********************************************/

//------------UserExtension-------------

/**
 * Jerry Qian(qqqiansjtucs@hotmail.com)
 * include extension for Selenium-IDE edition
 * refer to includeCommand_2.1.3 for Selenium-Core edition
 * @version 1.1
 *
 */
function IDEIncludeCommand() {}
IDEIncludeCommand.LOG_PREFIX = "IDEIncludeCommand: ";
IDEIncludeCommand.BEGIN_TEMPLATE = "begin$Template$";
IDEIncludeCommand.END_TEMPLATE = "end$Template$";
IDEIncludeCommand.VERSION = "1.1";
   
IDEIncludeCommand.prototype.prepareTestCaseAsText = function(responseAsText, paramsArray) {
    /**
     * Prepare the HTML to be included in as text into the current testcase-HTML
     * Strip all but the testrows (tr)
     * Stripped will be:
     *  - whitespace (also new lines and tabs, so be careful wirt parameters relying on this),
     *  - comments (xml comments)                 
     * Replace variable according to include-parameters 
     * note: the include-variables are replaced literally. selenium does it at execution time
     * also note: all selenium-variables are available to the included commands, so mostly no include-parameters are necessary
     *
     * @param responseAsText table to be included as text (string)
     * @return testRows array of tr elements (as string!) containing the commands to be included
     * 
     * TODO:
     *  - selenium already can handle testcase-html. use selenium methods or functions instead
     *  - find better name for requester
     */
    // removing new lines, carret return and tabs from response in order to work with regexp
    var pageText = responseAsText.replace(/\r|\n|\t/g,"");
    // remove comments
    // begin comment, not a dash or if it's a dash it may not be followed by -> repeated, end comment
    pageText = pageText.replace(/<!--(?:[^-]|-(?!->))*-->/g,"");
    // find the content of the test table = <[spaces]table[char but not >]>....< /[spaces]table[chars but not >]>
    var testText = pageText.match(/<\s*table[^>]*>(.*)<\/\s*table[^>]*>/i)[1];

    // Replace <td></td> with <td>&nbsp;</td> for iE - credits Chris Astall
    // rz: somehow in my IE 7 this is not needed but is not bad as well
    testText = testText.replace(/<\s*td[^>]*>\s*<\s*\/td[^>]*>/ig,"<td></td>");// jq: no space

    // replace vars with their values in testText
    for ( var k = 0 ; k < paramsArray.length ; k++ ) {
        var pair = paramsArray[k];
        testText = testText.replace(pair[0],pair[1]);
    }

    // removes all  < /tr> 
    // in order to split on < tr>
    testText = testText.replace(/<\/\s*tr[^>]*>/ig,"");
    // split on <tr>
    var testRows = testText.split(/<\s*tr[^>]*>/i);
    return testRows;
};

IDEIncludeCommand.prototype.getIncludeDocumentBySynchronRequest = function(includeUrl) {
    /**
     * Prepare and do the XMLHttp Request synchronous as selenium should not continue execution meanwhile
     *
     * note: the XMLHttp requester is returned (instead of e.g. its text) to let the caller decide to use xml or text
     *
     * selenium-dependency: uses extended String from htmlutils
     *
     *  TODO use Ajax from prototype like this:
     *   var sjaxRequest = new Ajax.Request(url, {asynchronous:false});
     *   there is discussion about getting rid of prototype.js in developer forum.
     *   the ajax impl in xmlutils.js is not active by default in 0.8.2
     *
     * @param includeUrl URI to the include-document (document has to be from the same domain)
     * @return XMLHttp requester after receiving the response
     */
    var url = this.prepareUrl(includeUrl);
    // the xml http requester to fetch the page to include
    var requester = this.newXMLHttpRequest();
    if (!requester) {
        throw new Error("XMLHttp requester object not initialized");
    }
    requester.open("GET", url, false); // synchron mode ! (we don't want selenium to go ahead)
    try {
        requester.send(null);
    } catch(e) {
      throw new Error("Error while fetching url '" + url + "' details: " + e);
    }
    if ( requester.status != 200 && requester.status !== 0 ) {
        throw new Error("Error while fetching " + url + " server response has status = " + requester.status + ", " + requester.statusText );
    }
    return requester;
};

IDEIncludeCommand.prototype.prepareUrl = function(includeUrl) {
    /** Construct absolute URL to get include document
     * using selenium-core handling of urls (see absolutify in htmlutils.js)
     */
    var prepareUrl;
    // htmlSuite mode of SRC? TODO is there a better way to decide whether in SRC mode?
    if (window.location.href.indexOf("selenium-server") >= 0) {
        LOG.debug(IDEIncludeCommand.LOG_PREFIX + "we seem to run in SRC, do we?");
        preparedUrl = absolutify(includeUrl, htmlTestRunner.controlPanel.getTestSuiteName());
    } else {
        preparedUrl = absolutify(includeUrl, selenium.browserbot.baseUrl);
    }
    LOG.debug(IDEIncludeCommand.LOG_PREFIX + "using url to get include '" + preparedUrl + "'");
    return preparedUrl;
};

IDEIncludeCommand.prototype.newXMLHttpRequest = function() {
    // TODO should be replaced by impl. in prototype.js or xmlextras.js
    //     but: there is discussion of getting rid of prototype.js
    //     and: currently xmlextras.js is not activated in testrunner of 0.8.2 release
    var requester = 0;
    var exception = '';
    // see http://developer.apple.com/internet/webcontent/xmlhttpreq.html
    // changed order of native and activeX to get it working with native
    //  xmlhttp in IE 7. credits dhwang
    try {
        // for IE/ActiveX
        if(window.ActiveXObject) {
            try {
                requester = new ActiveXObject("Msxml2.XMLHTTP");
            }
            catch(e) {
                requester = new ActiveXObject("Microsoft.XMLHTTP");
            }
        }
        // Native XMLHttp
        else if(window.XMLHttpRequest) {
            requester = new XMLHttpRequest();
        }
    }
    catch(e) {
        throw new Error("Your browser has to support XMLHttpRequest in order to use include \n" + e);
    }
    return requester;
};

IDEIncludeCommand.prototype.splitParamStrIntoVariables = function(paramString) {
    /**
     * Split include Parameters-String into an 2-dim array containing Variable-Name and -Value
     *
     * selenium-dependency: uses extended String from htmlutils
     *
     * TODO: write jsunit tests - this could be easy (if there were not the new RegExp)
     *
     * @param includeParameters string the parameters from include call
     * @return new 2-dim Array containing regExpName (to find a matching variablename) and value to be substituted for
     */
    var newParamsArray = new Array();
    // paramString shall contains a list of var_name=value
    var paramListPattern = /([^=,]+=[^=,]*,)*([^=,]+=[^=,]*)/;
    if (! paramString || paramString === "") {
        return newParamsArray;
    } else if (paramString.match( paramListPattern )) {
        // parse parameters to fill newParamsArray
        var pairs = paramString.split(",");
        for ( var i = 0 ; i < pairs.length ; i++ ) {
            var pair = pairs[i];
            var nameValue = pair.split("=");
            //rz: use String.trim from htmlutils.js of selenium to get rid of whitespace in variable-name(s)
            var trimmedNameValue = new String(nameValue[0]).trim();
            // the pattern to substitute is ${var_name}
            var regExpName = new RegExp("\\$\\{" + trimmedNameValue + "\\}", "g");
            
            if (nameValue.length < 3) {
               newParamsArray.push(new Array(regExpName,nameValue[1]));
            } else {
                var varValue = new String(nameValue[1]);
                for (var j = 2; j < nameValue.length; j++) {
                    varValue=varValue.concat("="+nameValue[j]);
                }
                newParamsArray.push(new Array(regExpName,varValue));
            }
        }
    } else {
        throw new Error("Bad format for parameters list : '" + paramString + "'");
    }
    return newParamsArray;
};

IDEIncludeCommand.prototype.doInclude = function(locator, paramString) { 
    // Rewrite logic for Selenium IDE by Jerry Qian
    var currentSelHtmlTestcase = testCase; 
     
    var includeCmdRow = testCase.debugContext.currentCommand();

    if (!includeCmdRow) {
        throw new Error("IDEIncludeCommand: failed to find include-row in source testtable");
    }

    var paramsArray = this.splitParamStrIntoVariables(paramString);
    var inclDoc = this.getIncludeDocumentBySynchronRequest(locator);
    var includedTestCaseHtml = this.prepareTestCaseAsText(inclDoc.responseText, paramsArray);
    
    this.injectIncludeTestCommands(locator,includeCmdRow,includedTestCaseHtml); 
};


IDEIncludeCommand.prototype.injectIncludeTestCommands = function(locator,includeCmdRow,  testRows) { 
    // Rewrite logic for Selenium IDE by Jerry Qian
    var newCommands = new Array(); 
    // skip first element as it is empty or <tbody>
    for (var i = 1 ; i < testRows.length; i++) {
     if(i == 1){// add BEGIN-END block
       var beginCommand = new Command(IDEIncludeCommand.BEGIN_TEMPLATE,locator,"");
       newCommands.push(beginCommand);
     } 
     
          var newText = testRows[i];  
     if(newText.match(/<\s*td.*colspan=.*>(.*)<\/\s*td[^>]*>/i)){//delete comment step
             continue;
          }
          
          // removes all  < /td> 
          // in order to split on <td>
          newText = newText.replace(/<\/\s*td[^>]*>\s*<\/\s*tbody[^>]*>/ig,""); //remove </tbody>first
          newText = newText.replace(/<\/\s*td[^>]*>/ig,""); 
     var newCols = newText.split(/<\s*td[^>]*>/i);
     var new_cmd,new_target,new_value; 
     
          for (var j = 1 ; j < newCols.length; j++) {//skip 0
      if(j == 1) {
      new_cmd = newCols[j].replace(/\s/g,"");//trim \s
      }else if(j == 2) {
       new_target = newCols[j].replace(/\s+$/g,"");//trim end \s
      }else if(j == 3) {
       new_value = newCols[j].replace(/\s+$/g,"");//trim end \s
     
     } 
     
      
          var newCommand =  new Command(new_cmd,new_target,new_value); 
          newCommands.push(newCommand); //correct all steps
    } 
    var endCommand = new Command(IDEIncludeCommand.END_TEMPLATE,locator,"");
    newCommands.push(endCommand);//add BEGIN-END block
   
   
    var cmsBefore = testCase.commands.slice(0,testCase.debugContext.debugIndex + 1);     
    var cmdsBehind = testCase.commands.slice(testCase.debugContext.debugIndex + 1, testCase.commands.length);     
    testCase.commands = cmsBefore.concat(newCommands).concat(cmdsBehind);//Injection
     
     
    editor.view.refresh();//Must refresh to syncup UI
};


Selenium.prototype.doInclude = function(locator, paramString) {
    LOG.debug(IDEIncludeCommand.LOG_PREFIX + " Version " + IDEIncludeCommand.VERSION);
    var ideIncludeCommand = new IDEIncludeCommand();
    ideIncludeCommand.doInclude(locator, paramString);
};

Selenium.prototype.doBegin$Template$ = function(locator){
    LOG.info("Begin Template " + locator);
};

Selenium.prototype.doEnd$Template$ = function(locator){
    LOG.info("End Template " + locator);
};