Selenium IDE Extensions and Mapping

20131120 Lets capture these now

First is utilities and second is locators & UIMaps

diaExtensions.js

/*************************************************************************************/
/*************************************************************************************/
// Selenium extensions for GDAP project
// Version: 20121217-19:00
/*************************************************************************************/


// xPathCount functions don't work with UI-Elements so we will make our own here..
// Selenium.prototype.getCountNodes = function(locator, target) {
    // var container = this.page().findElement(locator);
    // /*
    // var count = 0;
    // for(var prop in element) {
        // alert(element.prop);
        // if(element.hasOwnProperty(prop))
            // ++count;
    // }
    // storedVars[target] = count;
    // */
    
    // // from http://help.dottoro.com/ljsfamht.php per childElementCount
    // // and http://blog.tegneblokken.net/2009/08/counting-childnodes-with-javascript-the-whitespace-incident/
    // //var container = document.getElementById ("container");
    // var childCount = 0;
    
    // /*
    // if ('childElementCount' in container) {
        // alert("1");
        // childCount = container.childElementCount;
    // }
    // else {
        // if (container.children) {
            // alert("2");
            // childCount = container.children.length;
        // }
        // else
            // {  // Firefox before version 3.5
            // alert("3");
            
            // */
            
        // var childNodes = container.childNodes
        // for(var i=0; i<childNodes.length; i++) {
            // alert(childNodes[i])
            // }

        // alert("Part1 done");
        
        // var p2count = 0;
        // var childNodes = container.childNodes
        // for(var i=0; i<childNodes.length; i++) {
            // if (childNodes[i].nodeType == 1) {
                // alert(childNodes[i])
                // p2count++;
                // }
            // }
        // alert("Part2 count" + p2count);
        
        // var p3count = 0;
        // for(var i=0; i<container.length; i++) {
            // //if (container[i].nodeType == 1) {
                // alert(container[i])
                // p3count++;
            // //    }
            // }
        // alert("Part3 count" + p3count);
        
        
            // var child = container.firstChild;
            // while (child) {
                // if (child.nodeType == 1) {
                    // alert (child.nodeName + ", " + child.innerHTML + ", " + child.nodeType);
                    // childCount++;
                // }
                // child = child.nextSibling;
            // }
            
            // /*
        // }
    // }
    
    // */

     // alert ("The number of child elements is " + childCount);
    // return childCount;
// }

// Selenium.prototype.getCountObjects = function(locator) {
    // var container = this.page().findElement(locator);
    // var childCount = 0;
    // var log = 0;
    
    // for (var i in container) {
        // var obj = container[i];
        // log += "here it is: " + obj.nodeType + ", " + i + "\r\n";    
        // childCount++;
        // }
    // alert (log);
    // return childCount;
    // }
    
// Selenium.prototype.getCountObjects = function(locator) {
    // var container = this.page().findElement(locator);
    // var childCount = 0;
    // var log = 0;
    
    // for (var i in container) {
        // var obj = container[i];
        // log += "here it is: " + obj.nodeType + ", " + i + "\r\n";    
        // childCount++;
        // }
    // alert (log);
    // return childCount;
    // }

    

// Selenium.prototype.getLocator = function(locator, target) {
    // storedVars[target] = map.getLocator(locator);
// }

/*************************************************************************************/
/*  Random values generator  */
/*************************************************************************************/

Selenium.prototype.doStoreRandomValue = function(options, varName ) {
/* Options: type-length */

    var length = 8;
    var type   = 'alphanumeric';
    var o = options.split( '|' );
    for ( var i = 0 ; i < 2 ; i ++ ) {
        if ( o[i] && o[i].match( /^\d+$/ ) )
            length = o[i];

        if ( o[i] && o[i].match( /^(?:alpha)?(?:numeric)?$/ ) )
            type = o[i];
    }

    switch( type ) {
        case 'alpha'        : storedVars[ varName ] = randomAlpha( length ); break;
        case 'numeric'      : storedVars[ varName ] = randomNumeric( length ); break;
        case 'alphanumeric' : storedVars[ varName ] = randomAlphaNumeric( length ); break;
        default             : storedVars[ varName ] = randomAlphaNumeric( length );
    };
};

function randomNumeric (length) {
    return generateRandomString( length, '0123456789'.split( '' ) );
}

function randomAlpha (length) {
    var alpha = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.split( '' );
    return generateRandomString( length, alpha );
}

function randomAlphaNumeric (length) {
    var alphanumeric = '01234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.split( '' );
    return generateRandomString( length, alphanumeric );
}

function generateRandomString(length, chars) {
    var string = '';
    for ( var i = 0 ; i < length ; i++ )
        string += chars[ Math.floor( Math.random() * chars.length ) ];
    return string;
}

/*************************************************************************************/
/* Date functions */
/*************************************************************************************/

// Date helpers (could be merged but no time..)
diaGetCurrentTimeHHcolonMMcolonSS = function(seconds) {
//    param: seconds = if true output seconds
    var d = new Date();
    var curr_hour = d.getHours();
    var curr_min = d.getMinutes();
    var curr_sec = d.getSeconds();

    var result = String("0" + curr_hour).slice(-2)+':' + String("0" + curr_min).slice(-2);
    if (seconds) result += ':'+ String("0" + curr_sec).slice(-2);
    return result
};

diaGetCurrentDateDDslashMMslashYY = function(date) {
    //var date=new Date();
    var year=date.getFullYear();
    var day=date.getDay();
    var month=date.getMonth()+1;

    if (month<10)
       month='0'+month;
    
      var monthDate=date.getDate();
    
    if (monthDate<10)
       monthDate='0'+monthDate;

     //this will contain the final date
        return monthDate+'/'+month+'/'+String(year).slice(-2);
}

diaGetCurrentDateDDslashMMslashYYYY = function(date) {
    //var date=new Date();
    var year=date.getFullYear();
    var day=date.getDay();
    var month=date.getMonth()+1;

    if (month<10)
       month='0'+month;
    
      var monthDate=date.getDate();
    
    if (monthDate<10)
       monthDate='0'+monthDate;

     //this will contain the final date
        return monthDate+'/'+month+'/'+year.slice;
}

diaGetCurrentDateDDslashMMslashYYYY = function(date) {
    //var date=new Date();
    var year=date.getFullYear();
    var day=date.getDay();
    var month=date.getMonth()+1;

    if (month<10)
       month='0'+month;
    
      var monthDate=date.getDate();
    
    if (monthDate<10)
       monthDate='0'+monthDate;

     //this will contain the final date
        return monthDate+'/'+month+'/'+year;
}

diaGetCurrentDateDD_MMM_YYYY = function(date) {
       
    var fullYear=date.getFullYear();
    var month=date.getMonth();
    var monthDate=date.getDate();

    if (monthDate<10)
        monthDate='0'+monthDate;

    var monthArr=new Array('Jan','Feb','Mar','April','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec');
    return monthDate+' '+monthArr[month]+' '+fullYear;    
};       

diaGetCurrentDateFull = function(date) {
/**
* This function return system full date like "Saturday, September 12, 2009" format
**/
       
    // var fifteenth=new Date();
    var fifteenth=date;
    var fullYear=fifteenth.getFullYear();
    var seventeenth=fifteenth.getDay();
    var month=fifteenth.getMonth();
    var monthDate=fifteenth.getDate();

    if (monthDate<10)
        monthDate='0'+monthDate;

    var dayArr=new Array('Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday');
    var monthArr=new Array('January','February','March','April','May','June','July','August','September','October','November','December');

    return dayArr[seventeenth]+', '+monthArr[month]+' '+monthDate+', '+fullYear;    
};       


// date/time assemblers
diaGetDateFormatted = function(options) {
    // Options: format|dayDifference = days to add/subtract from today
    var dayDifference = 0;
    var type = "";
    var o = options.toUpperCase().split( '|' );
    
    // Lindsay: this probably isn't necessary but it does allow optional params options
    // in either order so is an interesting example of param parsing
    for ( var i = 0 ; i < 2 ; i ++ ) {
        // if ( o[i] && o[i].match( /^\d+$/ ) )
        if ( o[i] && o[i].match( /^[-+]?[0-9]*\.?[0-9]+$/ ) )     // positive & negative
            dayDifference = o[i];

        if ( o[i] && o[i].match( /\b(DDMMYY|DDMMYYYY|DDMMMYYYY|FULL)\b/ ) )
            type = o[i];
    }
    var today = new Date();
    var requiredDate = new Date();
    requiredDate.setDate(today.getDate()+ parseInt(dayDifference));
    switch( type) {
        case 'DDMMYY'        : return diaGetCurrentDateDDslashMMslashYY(requiredDate); break;
        case 'DDMMYYYY'        : return diaGetCurrentDateDDslashMMslashYYYY(requiredDate); break;
        case 'DDMMMYYYY'    : return diaGetCurrentDateDD_MMM_YYYY(requiredDate); break
        case 'FULL'            : return diaGetCurrentDateFull(requiredDate); break
        default             : return "Invalid date format: " + options;
    };
}

 diaGetTimeCurrentFormatted = function(options){
 // Options: format
    var type = "";
    var o = options.toUpperCase().split( '|' );
    // Lindsay: this probably isn't necessary but it does allow optional params options
    // in either order so is an interesting example of param parsing
    for ( var i = 0 ; i < 2 ; i ++ ) {
    if ( o[i] && o[i].match( /\b(HHMM|HHMMSS)\b/ ) )
        type = o[i];
    }
    switch( type) {
        case 'HHMM'            : return diaGetCurrentTimeHHcolonMMcolonSS(false); break;
        case 'HHMMSS'        : return diaGetCurrentTimeHHcolonMMcolonSS(true); break;
        default             : return "Invalid time format: " + options;
    };
};

// date/time extensions
// Use match function for directly comparing against locators..
// i.e. don't use

 Selenium.prototype.doStoreTime = function(options, varName){
 // Options: format
    storedVars[ varName ] = diaGetTimeCurrentFormatted(options);
};

 Selenium.prototype.isTimeMatch = function(locator, options) {
    var element = this.browserbot.findElement(locator);
    return element.innerHTML == diaGetTimeCurrentFormatted(options);
};

 Selenium.prototype.doStoreDate = function(options, varName){
 // Options: format|dayDifference
    storedVars[ varName ] = diaGetDateFormatted(options);
};

 Selenium.prototype.isDateMatch = function(locator, options){
    var element = this.browserbot.findElement(locator);
    return element.innerHTML == diaGetDateFormatted(options);
};

Selenium.prototype.doAppendDateTime = function(strval, varName ) {
    // Options: strval = prefix for appending date time to
    storedVars[varName] = strval + ' ' + diaGetDateFormatted('ddmmyyyy|0') + '-' + diaGetTimeCurrentFormatted('hhmmss');
}

user-extensions.js

/*************************************************************************************/
// GDAP Selenium testing user extensions
/*************************************************************************************/

// XPath mapping

// Note that grid item columns are assumed to be in fixed columns at this time,  however Matt/Andrew
// have agreed in principle to add identifiers to each cell most likely as classes

var testData = {
    "url":"http://test.xxxxxxx",
    "users":{
        "editor1": {
            "username":"Tester1 - xxxx",
            "password":"xxxx",
            "logoutExisting":true,
            "office":"xxxxxxxxx",
            "failIfLoginInUse": false
        }
    }
};

var testSharedOptions = {
    "jobStates":"['In Draft', 'For Review', 'Reviewed' , 'To Finish']",
    "jobListJobElements":"['row', 'title', 'created', 'arrived', 'entitycount', 'itemcount', 'flag', 'flagonlyifset']",
    "jobListDetailDisplayFields":"['Number', 'Owner', 'Office', 'State', 'Arrived', 'Created']",
    "jobListDetailCreateEntityOptions":"[\
         'Access Authority'\
        ,'Accession'\
        ,'Agency'\
        ,'Disposal Authority'\
        ,'Function'\
        ,'Item group'\
        ,'Jurisdiction'\
        ,'Organisation'\
        ,'Series']"
};

var testGDAPLocators = {
    "login": {
        "username":"//input[@id='username']",
        "password":"//input[@id='password']",
        "logoutExisting":"//input[@id='exceptionIfMaximumSessionsExceeded']",
        "btnLogin":"//button[@id='login']"
    },
    "common": {
        "headerLinks": {
            "root":                                                         "//div[@id='primary-links']",
            "selected":                    function(){return this.root +         "//li[contains(@class,'selected')]/child::";},
            "lJobs":                    function(){return this.root +         "//a[span[.='Jobs']]";},
            "lSearch":                    function(){return this.root +         "//a[contains(.,'Search')]";},
            "lThesaurus":                function(){return this.root +         "//a[span[.='Thesaurus']]";},
            "lLegislation":                function(){return this.root +         "//a[span[.='Legislation']]";},
            "lJobsSelected":            function(){return this.selected() + "a[span[.='Jobs']]";},
            "lSearchSelected":            function(){return this.selected() + "a[contains(.,'Search')]";},
            "lThesaurusSelected":        function(){return this.selected() + "a[span[.='Thesaurus']]";},
            "lLegislationSelected":     function(){return this.selected() + "a[span[.='Legislation']]";},
            "lLogout": function(){return                                     "//a[.='Logout']";}
        }
    },
    "jobs": {
        "root": "//div[@class='jobs']",
        "headerLinks": {
            "root":                                                     "//div[@class='jobs']//div[@class='subnav']",
            "selected":                function(){return this.root +         "//li[contains(@class,'selected')]/child::";},
            "lMyJobs":                function(){return this.root +         "//a[span[.='My jobs']]";},
            "lForReview":            function(){return this.root +         "//a[span[.='For review']]";},
            "lJobSearch":            function(){return this.root +         "//a[span[.='Job search']]";},
            "lMyJobsSelected":        function(){return this.selected() +    "a[span[.='My jobs']]";},
            "lForReviewSelected":    function(){return this.selected() +    "a[span[.='For review']]";},
            "lJobSearchSelected":    function(){return this.selected() +    "a[span[.='Job search']]";}
        },
        "actions": {
            "root":                                         "//div[@class='jobs']//div[@class='actions']",
            "btnCreateJob": function(){return this.root +    "//a[span[.=' Create job']";}
        },
        "jobList": {
            "root":                                                         "//div[contains(@class,'jobListHead')]",
            "headerByState":    function(){return                             "//div[contains(@class,'jobListHead') and descendant::span[.='STATE']]/h4";},
            "jobListByState":    function(){return                             "//div[contains(@class,'jobListHead') and descendant::span[.='STATE']]/following-sibling::*";},
            "jobListJobRows":    function(){return this.jobListByState() +     "//tbody/tr";},
            "jobSelected":        function(){return                             "contains(@class,'selected') and ";},
            "jobByTitle":        function(){return this.jobListJobRows() +     "[SELECTED td[@class='title' and descendant::span[text()='TITLE']]]";},
            "jobByRow":            function(){return this.jobListJobRows() +     "[ROW]";},
            "jobAttribute":{
                "title":         "//td[@class='title']/span",
                "created":         "//td[@class='created']/span",
                "arrived":         "//td[@class='arrived']/span",
                "entitycount":     "//td[@class='entities']/span/span[1]",
                "itemcount":     "//td[@class='entities']/span/span[2]",
                "flag":         "//td[@class='flag']/a",
                "flagonlyifset":         "//td[@class='flag']/a/img[not(contains(@src,'unset'))]",
                "suffix":        function(column) {
                                    switch (column.toUpperCase())
                                    {
                                        case "ROW":
                                            return "";
                                            break;
                                        case 'TITLE':
                                            return this.title;
                                            break;
                                        case 'CREATED':
                                            return this.created;
                                            break;
                                        case 'ARRIVED':
                                            return this.arrived;
                                            break;
                                        case 'ENTITYCOUNT':
                                            return this.entitycount;
                                            break;
                                        case 'ITEMCOUNT':
                                            return this.itemcount;
                                            break;
                                        case 'FLAG':
                                            return this.flag;
                                            break;
                                        case 'FLAGONLYIFSET':
                                            return this.flagonlyifset;
                                            break;
                                    }
                                }
            },
        },
        "jobDetail": {
            "root": "//div[contains(@class,'selectedJob')]",
            "titleRead":        function(){return this.root +     "//div[@class='job-title']//span";},
            "titleEdit":        function(){return this.root +     "//div[@class='job-title']//input";},
            "titleEditIcon":    function(){return this.root +     "//div[@class='job-title']/descendant::a";},
            "toolbar": {
                "root":                         "//div[contains(@class,'selectedJob')]//div[contains(@class,'btn-toolbar')]",
                "btnCreateEntityPartText":        function(){return this.root +                         "//*[@id='create-entity']";},
                "btnCreateEntityPartDownArrow":    function(){return this.btnCreateEntityPartText() +     "/following-sibling::*";},
                "btnCreateEntityOptionByLabel": function(){return this.root +    "//ul[preceding-sibling::a[@id='create-entity']]//a[span[text()='ENTITY']]";},
                "btnSendForReview":                function(){return this.root +                         "//*[contains(text(),'Send for review')]";},
                "btnDelete":                    function(){return this.root +                         "//*[contains(text(),'Delete')]";},
                "btnUnassign":                    function(){return this.root +                         "//*[contains(text(),'Unassign')]";},
            },
            "displayField":     function(){return this.root +    "//dd[preceding-sibling::dt[text()='FIELD']][1]/span";},
            "chkPriority":         function(){return this.root +    "//dt/input[@id='priority']";},
            "selJobType":         function(){return this.root +    "//dd[preceding-sibling::dt[text()='Type']][1]/select";},
            "txtNote":            function() { return this.root +    "//dd[preceding-sibling::dt[text()='Notes']][1]//div";},
            "entities": {
            
            },
            "itemgroup": {
                "root" :     "//*[@class='sectionHead' and text()='Item Groups']",
                "list" :     function() {return this.root +    "/following::*[1][contains(@class,'entities')]";},
                "byTitle":    function(){return this.list +     "//tbody/tr[td[@class='title' and descendant::span[text()='TITLE']]]";},
                "byRow":    function(){return this.root +     "//tbody//tr[ROW]";},
            }
        }
    },
    "itemGroup": {
        "titleRead": "//div[contains(@class,'item-group-title')]/descendant::span",
        "titleEdit": "//div[contains(@class,'item-group-title')]/descendant::input",
        "titleEditIcon": "//div[contains(@class,'item-group-title')]/descendant::a",
        "toolbar": {
            "root":                                                 "//div[contains(@class,'toolbar-buttons')]",
            "btnInfo":                function(){return this.root +     "//a[.=' Info']";},
            "btnColumns":            function(){return this.root +     "//a[.=' Columns']";},
            "btnSort":                function(){return this.root +     "//a[.=' Sort']";},
            "btnCreate":            function(){return this.root +     "//a[.=' Create']";},
            "btnImport":            function(){return this.root +     "//a[.=' Import']";},
            "btnExport":            function(){return this.root +     "//a[.=' Export']";},
            "btnFill":                function(){return this.root +     "//a[.=' Fill']";},
            "btnMove":                function(){return this.root +     "//a[.=' Move']";},
            "btnReady":                function(){return this.root +     "//a[.=' Ready']";},
            "btnSelectAll":            function(){return this.root +     "//a[.=' Select all']";},
            "btnSelectBy":            function(){return this.root +     "//a[.=' Select by...']";},
            "btnClearSelection":    function(){return this.root +     "//a[.=' Clear selection']";},
            "btnFilter":            function(){return this.root +     "//a[.=' Filter']";},
            "btnFilterBy":            function(){return this.root +     "//a[.=' Filter by...']";},
            "btnClearFilter":        function(){return this.root +     "//a[.=' Clear filter']";},
            "btnComments":            function(){return this.root +     "//a[.=' Comments']";}
        },
        "count": {
            "root":                                     "//div[@class='item-summary-info']",
            "total":    function(){return this.root +     "//span[1]";},
            "filtered":    function(){return this.root +     "//span[2]";},
            "selected":    function(){return this.root +     "//span[3]";}
        },
        "pagination": {
            "root":                                     "//div[@class='navigator']",
            "lFirst":    function(){return this.root +     "//a[@class='first']";},
            "lPrev":    function(){return this.root +     "//a[@class='prev']";},
            "lNext":    function(){return this.root +     "//a[@class='next']";},
            "lLast":    function(){return this.root +     "//a[@class='last']";},
            "lPage":    function(){return this.root +     "//a[contains(@href,'navigator-PAGE']";}
        },
        "grid": {
            "root":                                 "//div[@class='results']",
            "header": function(){return this.root +    "//tr[contains(@class,'headers')]";},
            "item": {
                "summary": {
                    "root":                                         "//div[@class='results']//tr[contains(@class,'summary')][INDEX]",
                    "lChevron":        function(){return this.root +     "//span[contains(@class,'chevron')]";},
                    "lChevronUp":    function(){return this.root +     "//span[contains(@class,'chevron-right')]";},
                    "lChevronDown":    function(){return this.root +     "//span[contains(@class,'chevron-down')]";},
                    "chkSelect":    function(){return this.root +     "//input[@class='selectionCheckBox')]";},
                    "agencyRecID":    function(){return this.root +     "/td[3]";},
                    "code":            function(){return this.root +     "/td[4]";},
                    "type":            function(){return this.root +     "/td[5]/span";},
                    "title":        function(){return this.root +     "/td[6]";},
                    "years":        function(){return this.root +     "/td[7]";},
                    "series":        function(){return this.root +     "/td[8]";},
                    "restriction":    function(){return this.root +     "/td[9]";},
                    "description":    function(){return this.root +     "/td[10]";},
                    "accession":    function(){return this.root +     "/td[11]";},
                    "state":        function(){return this.root +     "/td[12]";}
                },
                "detail": {
                    "root":                                                 "//tr[contains(@class,'detail')][INDEX]",
                    "tab": {
                        "root":                                             "//tr[contains(@class,'detail')][INDEX]",
                        "General":            function(){return this.root +     "//span[.='General']";},
                        "Relationships":    function(){return this.root +     "//span[.='Relationships']";},
                        "Repository":        function(){return this.root +     "//span[.='Repository']";},
                        "restrictions":        function(){return this.root +     "//span[.='Restrictions']";},
                        "documentation":    function(){return this.root +     "//span[.='Documentation']";}
                    },
                    "content": {
                        "root":                                     "//tr[contains(@class,'detail')][INDEX]//div[contains(@class,'tab-content')",
                        "code":        function(){return this.root +     "//li[label[.='Code']]/span";},
                        "type":        function(){return this.root +     "//li[label[.='Type']]/span";},
                        "years":    function(){return this.root +     "//li[label[.='Years']]/span";}
                    }
                }
            }
        }
    }
};


/*************************************************************************************/
/*************************************************************************************/
// UI-Elements Maps
/*************************************************************************************/

// Q. Why use UI-elements and not just a JS object?
// A. Because maps integrate into the IDE and can also be used in Web driver..

var map = new UIMap();

map.addPageset({name: 'login_page', description: 'login page elements', pathRegexp: '.*' });
map.addPageset({name: 'common_page', description: 'common page elements', pathRegexp: '.*' });
map.addPageset({name: 'jobs_page', description: 'jobs page elements', pathRegexp: '.*'});
map.addPageset({name: 'itemGroup_page', description: 'itemGroup', pathRegexp: '.*'});

/***************************************************/
// Login page

map.addElement('login_page', {name: 'username', description: 'userid input', locator: testGDAPLocators.login.username});
map.addElement('login_page', {name: 'password', description: 'password input', locator: testGDAPLocators.login.password});
map.addElement('login_page', {name: 'logoutExisting', description: 'Logout existing sessions', locator: testGDAPLocators.login.logoutExisting});
map.addElement('login_page', {name: 'btnLogin', description: 'Login button', locator: testGDAPLocators.login.btnLogin});
        
/***************************************************/
// Common to every page except login

// This one just to give something harmless to click off edits where there is no 'done' type button, ie job title..
map.addElement('common_page', {name: 'banner', description: 'common bannr for harmless click to finish edits', locator: testGDAPLocators.common.headerLinks.root});

map.addElement('common_page', {name: 'lJobs', description: 'jobs link', locator: testGDAPLocators.common.headerLinks.lJobs()});
map.addElement('common_page', {name: 'lSearch', description: 'seach link', locator: testGDAPLocators.common.headerLinks.lSearch()});
map.addElement('common_page', {name: 'lThesaurus', description: 'thesaurus link', locator: testGDAPLocators.common.headerLinks.lThesaurus()});
map.addElement('common_page', {name: 'lLegisation', description: 'legisation link', locator: testGDAPLocators.common.headerLinks.lLegislation()});
map.addElement('common_page', {name: 'lJobsSelected', description: 'jobs link', locator: testGDAPLocators.common.headerLinks.lJobsSelected()});
map.addElement('common_page', {name: 'lSearchSelected', description: 'seach link', locator: testGDAPLocators.common.headerLinks.lSearchSelected()});
map.addElement('common_page', {name: 'lThesaurusSelected', description: 'thesaurus link', locator: testGDAPLocators.common.headerLinks.lThesaurusSelected()});
map.addElement('common_page', {name: 'lLegisationSelected', description: 'legisation link', locator: testGDAPLocators.common.headerLinks.lLegislationSelected()});
map.addElement('common_page', {name: 'lLogout', description: 'legisation link', locator: testGDAPLocators.common.headerLinks.lLogout()});

/***************************************************/
// Jobs page

// Header toolbar
map.addElement('jobs_page', {name: 'lMyJobs', description: 'My Jobs link', locator: testGDAPLocators.jobs.headerLinks.lMyJobs()});
map.addElement('jobs_page', {name: 'lMyJobsSelected', description: 'My Jobs link selected', locator: testGDAPLocators.jobs.headerLinks.lMyJobsSelected()});
map.addElement('jobs_page', {name: 'lForReview', description: 'For review link', locator: testGDAPLocators.jobs.headerLinks.lForReview()});
map.addElement('jobs_page', {name: 'lForReviewSelected', description: 'For review link selected', locator: testGDAPLocators.jobs.headerLinks.lForReviewSelected()});
map.addElement('jobs_page', {name: 'lJobSearch', description: 'Job search link', locator: testGDAPLocators.jobs.headerLinks.lJobSearch()});
map.addElement('jobs_page', {name: 'lJobSearchSelected', description: 'Job search link selected', locator: testGDAPLocators.jobs.headerLinks.lJobSearchSelected()});

// Job list by state

map.addElement('jobs_page', {name: 'btnCreateJob', description: 'Create job button', locator: testGDAPLocators.jobs.actions.btnCreateJob()});

map.addElement('jobs_page', {
    name: 'jobListHeaderByState'
  , description: 'Job list header by state'
  , args: [
            {
                name: 'state',
                description: 'Target job states',
                defaultValues: eval(testSharedOptions.jobStates)
            }
        ]
  , getLocator: function(args) {
        return testGDAPLocators.jobs.jobList.headerByState().replace(/STATE/g, args.state);
    }
});

map.addElement('jobs_page', {
    name: 'jobListByState'
  , description: 'Job list by state'
  , args: [
            {
                name: 'state',
                description: 'Target job states',
                defaultValues: eval(testSharedOptions.jobStates)
            }
        ]
  , getLocator: function(args) {
        return testGDAPLocators.jobs.jobList.jobListByState().replace(/STATE/g, args.state);
    }
});

map.addElement('jobs_page', {
    name: 'jobListJobRows'
  , description: 'Job list rows, use with Selenese storeXpathCount statement'
  , args: [
            {
                name: 'state',
                description: 'Target job states',
                defaultValues: eval(testSharedOptions.jobStates)
            }
        ]
  , getLocator: function(args) {
        return testGDAPLocators.jobs.jobList.jobListJobRows().replace(/STATE/g, args.state);
    }
});

map.addElement('jobs_page', {name: 'test', description: 'TEST FOR HARDCODED JOB ROWS', locator: "//div[contains(@class,'jobListHead') and descendant::span[.='In Draft']]/following-sibling::*//tbody/tr"});

map.addElement('jobs_page', {
    name: 'jobListJobByTitle'
  , description: 'Job in job list by state/title'
  , args: [
            {
                name: 'state',
                description: 'Target job state',
                defaultValues: eval(testSharedOptions.jobStates)
            },
            {
                name: 'title',
                description: 'Target job title',
                defaultValues: []
                /*
                getDefaultValues: function(inDocument) {
                    var defaultValues = [];
                    var elements = this.browserbot.findElement(testGDAPLocators.jobs.jobList.jobListJobRows.jobAttribute.title);
                    for (var i = 0; i < titles.length; i++)
                    {
                        var element = elements[i];
                        defaultvalues.push(element.innerHTML);
                    }
                    return defaultValues;
                }
                */
            },
            {
                name: 'selected',
                description: 'true if job must be currently selected, defaults to false',
                defaultValues: [true, false]
            },
            {
                name: 'column',
                description: 'Target job list column, defaults to entire (clickable) row',
                defaultValues: eval(testSharedOptions.jobListJobElements)
            }
        ]
  , getLocator: function(args) {
          if (!args.column) args.column = 'row';
        if (!args.selected) args.selected = 'false';
        var locator = testGDAPLocators.jobs.jobList.jobByTitle().replace(/STATE/g, args.state).replace(/TITLE/g, args.title);
        locator = locator.replace(/SELECTED/g, (args.selected.toUpperCase()=='TRUE') ? testGDAPLocators.jobs.jobList.jobSelected(): '');
        return locator += testGDAPLocators.jobs.jobList.jobAttribute.suffix(args.column);
    }
});

map.addElement('jobs_page', {
    name: 'jobListJobByRow'
  , description: 'Job row in job list vertically (starting at 1)'
  , args: [    {
                name: 'state',
                description: 'Target job state',
                defaultValues: eval(testSharedOptions.jobStates)
            },
            {
                name: 'row',
                description: 'Target job row no',
                defaultValues: range(1, 100)
            },
            {
                name: 'selected',
                description: 'true if job must be currently selected, defaults to false',
                defaultValues: [true, false]
            },
            {
                name: 'column',
                description: 'Target job list column, defaults to entire (clickable) row',
                defaultValues: eval(testSharedOptions.jobListJobElements)
            }
        ]
  , getLocator: function(args) {
          if (!args.column) args.column = 'row';
        if (!args.selected) args.selected = 'false';
          var locator = testGDAPLocators.jobs.jobList.jobByRow().replace(/STATE/g, args.state).replace(/ROW/g, args.row);
        locator = locator.replace(/SELECTED/g, (args.selected.toUpperCase()=='TRUE') ? testGDAPLocators.jobs.jobList.jobSelected(): '');
        return locator += testGDAPLocators.jobs.jobList.jobAttribute.suffix(args.column);
    }
});

// Job detail title

map.addElement('jobs_page', {name: 'jobTitleDisplay', description: 'Job title not editable', locator: testGDAPLocators.jobs.jobDetail.titleRead()});
map.addElement('jobs_page', {name: 'jobTitleEdit', description: 'Job title editable', locator: testGDAPLocators.jobs.jobDetail.titleEdit()});
map.addElement('jobs_page', {name: 'jobTitleEditIcon', description: 'Job title edit icon', locator: testGDAPLocators.jobs.jobDetail.titleEditIcon()});

// Job detail toolbar

map.addElement('jobs_page', {name: 'btnCreateEntityPartText', description: 'Create entity button text part', locator: testGDAPLocators.jobs.jobDetail.toolbar.btnCreateEntityPartText()});
map.addElement('jobs_page', {name: 'btnCreateEntityPartDownArrow', description: 'Create entity button down arrow part', locator: testGDAPLocators.jobs.jobDetail.toolbar.btnCreateEntityPartDownArrow()});
map.addElement('jobs_page', {name: 'btnCreateEntityOptionByLabel'
  , description: 'Create entity selection'
  , args: [
            {
                name: 'label',
                description: 'target entity label (case sensitive as it appears on the page)',
                defaultValues: eval(testSharedOptions.jobListDetailCreateEntityOptions)
            }
        ]
  , getLocator: function(args) {  
        return testGDAPLocators.jobs.jobDetail.toolbar.btnCreateEntityOptionByLabel().replace(/ENTITY/g, args.label);
    }
});
map.addElement('jobs_page', {name: 'btnSendForReview', description: 'Send for review button', locator: testGDAPLocators.jobs.jobDetail.toolbar.btnSendForReview()});
map.addElement('jobs_page', {name: 'btnDelete', description: 'Send for review button', locator: testGDAPLocators.jobs.jobDetail.toolbar.btnDelete()});
map.addElement('jobs_page', {name: 'btnUnassign', description: 'Send for review button', locator: testGDAPLocators.jobs.jobDetail.toolbar.btnUnassign()});

// Job detail fields
map.addElement('jobs_page', {name: 'detailFieldBylabel'
  , description: 'Job detail, fields by label'
  , args: [
            {
                name: 'label',
                description: 'target field label (case sensitive as it appears on the page)',
                defaultValues: eval(testSharedOptions.jobListDetailDisplayFields)
            }
        ]
  , getLocator: function(args) {  
        return testGDAPLocators.jobs.jobDetail.displayField().replace(/FIELD/g, args.label);
    }
});

map.addElement('jobs_page', {name: 'detailChkPriority', description: 'Job detail, priority checkbox', locator: testGDAPLocators.jobs.jobDetail.chkPriority()});
map.addElement('jobs_page', {name: 'detailJobTypeSelector', description: 'Job detail, job type selector', locator: testGDAPLocators.jobs.jobDetail.selJobType()});
map.addElement('jobs_page', {name: 'detailNoteClickable', description: 'Job detail, note', locator: testGDAPLocators.jobs.jobDetail.txtNote()});

// Job detail item group list

map.addElement('jobs_page', {
    name: 'jobItemGroupByTitle'
  , description: 'Item group in job by item group title'
  , args: [
            {
                name: 'title',
                description: 'Target item group title',
                defaultValues: ['']
            },
            {
                name: 'column',
                description: 'Target job list column (use row for clickable element)',
                defaultValues: eval(testSharedOptions.jobListJobElements)
            }
        ]
  , getLocator: function(args) {
        var locator = testGDAPLocators.jobs.jobList.jobByTitle().replace(/STATE/g, args.state).replace(/TITLE/g, args.title);
        return locator += testGDAPLocators.jobs.jobList.jobAttribute.suffix(args.column);
    }
});

/***************************************************/
// Item group page

// Item title
map.addElement('itemGroup_page', {name: 'titleDisplay', description: 'Item group title', locator: testGDAPLocators.itemGroup.titleRead});
map.addElement('itemGroup_page', {name: 'titleEdit', description: 'Item group title', locator: testGDAPLocators.itemGroup.titleEdit});
map.addElement('itemGroup_page', {name: 'titleEditIcon', description: 'Item group title edit link', locator: testGDAPLocators.itemGroup.titleEditIcon});

// Tool bar
map.addElement('itemGroup_page', {name: 'btnInfo', description: 'Item group info button', locator: testGDAPLocators.itemGroup.toolbar.btnInfo()});
map.addElement('itemGroup_page', {name: 'btnColumns', description: 'Item group Columns button', locator: testGDAPLocators.itemGroup.toolbar.btnColumns()});
map.addElement('itemGroup_page', {name: 'btnSort', description: 'Item group Sort button', locator: testGDAPLocators.itemGroup.toolbar.btnSort()});
map.addElement('itemGroup_page', {name: 'btnCreate', description: 'Item group Create button', locator: testGDAPLocators.itemGroup.toolbar.btnCreate()});
map.addElement('itemGroup_page', {name: 'btnImport', description: 'Item group Import button', locator: testGDAPLocators.itemGroup.toolbar.btnImport()});
map.addElement('itemGroup_page', {name: 'btnExport', description: 'Item group Export button', locator: testGDAPLocators.itemGroup.toolbar.btnExport()});
map.addElement('itemGroup_page', {name: 'btnFill', description: 'Item group Fill button', locator: testGDAPLocators.itemGroup.toolbar.btnFill()});
map.addElement('itemGroup_page', {name: 'btnMove', description: 'Item group Move button', locator: testGDAPLocators.itemGroup.toolbar.btnMove()});
map.addElement('itemGroup_page', {name: 'btnReady', description: 'Item group Ready button', locator: testGDAPLocators.itemGroup.toolbar.btnReady()});
map.addElement('itemGroup_page', {name: 'btnSelectAll', description: 'Item group Select all button', locator: testGDAPLocators.itemGroup.toolbar.btnSelectAll()});
map.addElement('itemGroup_page', {name: 'btnSelectBy', description: 'Item group Select by button', locator: testGDAPLocators.itemGroup.toolbar.btnSelectBy()});
map.addElement('itemGroup_page', {name: 'btnClearSelection', description: 'Item group Clear selection button', locator: testGDAPLocators.itemGroup.toolbar.btnClearSelection()});
map.addElement('itemGroup_page', {name: 'btnFilter', description: 'Item group Filter button', locator: testGDAPLocators.itemGroup.toolbar.btnFilter()});
map.addElement('itemGroup_page', {name: 'btnFilterBy', description: 'Item group Filter by button', locator: testGDAPLocators.itemGroup.toolbar.btnFilterBy()});
map.addElement('itemGroup_page', {name: 'btnClearFilter', description: 'Item group Clear filter button', locator: testGDAPLocators.itemGroup.toolbar.btnClearFilter()});
map.addElement('itemGroup_page', {name: 'btnComments', description: 'Item group Comments button', locator: testGDAPLocators.itemGroup.toolbar.btnComments()});

// Item counters (at least 1 item in itemGroup)
map.addElement('itemGroup_page', {name: 'count', description: 'items count', locator: testGDAPLocators.itemGroup.count.total()});
map.addElement('itemGroup_page', {name: 'itemsFiltered', description: 'items filter count', locator: testGDAPLocators.itemGroup.count.filtered()});
map.addElement('itemGroup_page', {name: 'itemsSelected', description: 'items selected count', locator: testGDAPLocators.itemGroup.count.selected()});

// Pagination controls (> 50 items in itemGroup)
map.addElement('itemGroup_page', {name: 'pagination', description: 'container for item pagination', locator: testGDAPLocators.itemGroup.pagination.root});
map.addElement('itemGroup_page', {name: 'lFirst', description: 'First page navigator', locator: testGDAPLocators.itemGroup.pagination.lFirst});
map.addElement('itemGroup_page', {name: 'lPrev', description: 'Prev page navigator', locator: testGDAPLocators.itemGroup.pagination.lPrev});
map.addElement('itemGroup_page', {name: 'lNext', description: 'Next page navigator', locator: testGDAPLocators.itemGroup.pagination.lNext});
map.addElement('itemGroup_page', {name: 'lLast', description: 'Last page navigator', locator: testGDAPLocators.itemGroup.pagination.lLast});

map.addElement('itemGroup_page', {
    name: 'lPage'
  , description: 'Specific page navigator'
  , args: [
            {
            name: 'page',
            description: 'the page no',
            defaultValues: range(1, 50)
            }
        ]
  , getLocator: function(args) {
        var page = args.page - 1;
        return testGDAPLocators.itemGroup.pagination.lPage().replace(/PAGE/g, page);
    }
});

// Item grid (at least 1 item in itemGroup)
map.addElement('itemGroup_page', {name: 'gridHeader', description: 'Item grid header', locator: testGDAPLocators.itemGroup.grid.header()});
map.addElement('itemGroup_page', {name: 'grid', description: 'container for items', locator: "//div[@class='results']"});
map.addElement('itemGroup_page', {name: 'gridheader', description: 'grid header', locator: "//div[@class='results']//tr[contains(@class,'headers')]"});
map.addElement('itemGroup_page', {
    name: 'gridItemSummary'
  , description: 'item summary relative entry'
  , args: [{
            name: 'index',
            description: 'the relative item index',
            defaultValues: range(1, 50)
        }]
  , getLocator: function(args) {
        var index = args.index;
        return "//div[@class='results']//tr[contains(@class,'summary')][" + index + "]";
        }
});

map.addElement('itemGroup_page', {
    name: 'gridItemDetail'
  , description: 'item detail relative entry'
  , args: [{
        name: 'index',
        description: 'item summary element',
        defaultValues: range(1, 50)
        }]
  , getLocator: function(args) {
        var index = args.index;
        return "//div[@class='results']//tr[contains(@class,'detail')][" + index + "]";
        }
});

var rollups = new RollupManager();

rollups.addRollupRule({
    name: 'jobsPageCountJobs'
    , description: 'Count how many jobs in the list for the target status, param status=In Draft'
    , pre: 'Jobs page is displayed'
    , post: 'variable jobsPageCountJobs is populated'
    , args: [
        {
          name: 'status'
        , description: "the job state ie 'In Draft'"
        , exampleValues: eval(testSharedOptions.jobStates)
        }
    ]
    , commandMatchers: []
    , getExpandedCommands: function(args) {
        var commands = [
                {    command:'store',                    target: '0',                    value: 'jobsPageCountJobs'},
                    
                // In Draft header?
                
                {    command: 'storeElementPresent',
                    target: 'ui=jobs_page::jobListHeaderByState(state=' + args.status + ')',
                    value: 'jobsPageCountJobs_present'},
                    
                {    command:'gotoIf',                    target: '!${jobsPageCountJobs_present}',            value: 'DONECOUNTING'},
                    
                // In Draft header so we have jobs, are they displayed? look for row 1
                
                {    command:'storeVisible',
                    target: 'ui=jobs_page::jobListJobByRow(state=' + args.status + ', row=1, column=row)',
                    value: 'jobsPageCountJobs_present'},
                    
                {    command:'gotoIf',                    target: '${jobsPageCountJobs_present}',            value: 'PRIMECOUNT'},
                
                // Jobs not visible so click to expand header
                
                {    command:'click',
                    target: 'ui=jobs_page::jobListHeaderByState(state=In Draft)',
                    value: 'jobsPageCountJobs'},
                    
                {    command:'label',                    target: 'PRIMECOUNT'},
                
                {    command:'store',                    target: '1',                    value: 'jobsPageCountJobs'},
                
                {    command:'label',                    target: 'DOCOUNT'},
                
                {    command:'storeElementPresent',
                    target: 'ui=jobs_page::jobListJobByRow(state=In Draft, row=${jobsPageCountJobs} ,column=row)',
                    value: 'jobsPageCountJobs_present'},
                    
                {    command:'gotoIf',                    target: '!${jobsPageCountJobs_present}',            value: 'DONECOUNTING'},
                
                {    command:'storeEval',                target: '${jobsPageCountJobs} + 1',        value: 'jobsPageCountJobs'},
                
                {    command:'goto',                        target: 'DOCOUNT'},
                
                {    command:'label',                    target: 'DONECOUNTING'},
                
                {    command:'storeEval',                target: '${jobsPageCountJobs} - 1',            value: 'jobsPageCountJobs'},

            ]
            
        return commands;
    }
});

xxx