# Selenium UI-Element Reference

## Introduction

UI-Element is a Selenium feature that makes it possible to define a mapping between semantically meaningful names of elements on webpages, and the elements themselves. The mapping is defined using JavaScript Object Notation, and may be shared both by the IDE and tests run via Selenium RC. It also offers a single point of update should the user interface of the application under test change.

## Terminology

Page
  A unique URL, and the contents available by accessing that URL. A page typically consists of several interactive page elements. A page may also be considered a DOM document object, complete with URL information.
Page element
  An element on the actual webpage. Generally speaking, an element is anything the user might interact with, or anything that contains meaningful content. More specifically, an element is realized as a Document Object Model (DOM) node and its contents. So when we refer to a page element, we mean both of the following, at the same time:
  - something on the page
  - its DOM representation, including its relationship with other page elements
Pageset
  A set of pages that share some set of common page elements. For example, I might be able to log into my application from several different pages. If certain page elements on each of those pages appear similarly (i.e. their DOM representations are identical), those pages can be grouped into a pageset with respect to these page elements. There is no restriction on how many pagesets a given page can be a member of. Similarly, a UI element belong to multiple pagesets. A pageset is commonly represented by a regular expression which matches the URL's that uniquely identify pages; however, there are cases when the page content must be considered to determine pageset membership. A pageset also has a name.
UI element
  A mapping between a meaningful name for a page element, and the means to locate that page element's DOM node. The page element is located via a locator. UI elements belong to pagesets.
UI argument
  An optional piece of logic that determines how the locator is generated by a UI element. Typically used when similar page elements appear multiple times on the same page, and you want to address them all with a single UI element. For example, if a page presents 20 clickable search results, the index of the search result might be a UI argument.
UI map
  A collection of pagesets, which in turn contain UI elements. The UI map is the medium for translating between UI specifier strings, page elements, and UI elements.
UI specifier string
  A bit of text containing a pageset name, a UI element name, and optionally arguments that modify the way a locator is constructed by the UI element. UI specifier strings are intended to be the human-readable identifier for page elements.
Rollup rule
  Logic that describes how one or more Selenium commands can be grouped into a single command, and how that single command may be expanded into its component Selenium commands. The single command is referred to simply as a "rollup".
Command matcher
  Typically folded into a rollup rule, it matches one or more Selenium commands and optionally sets values for rollup arguments based on the matched commands. A rollup rule usually has one or more command matchers.
Rollup argument
  An optional piece of logic that modifies the command expansion of a rollup.

## The Basics

### Getting Motivated

Question: Why use UI-Element? Answer: So your testcases can look like this (boilerplate code omitted):

    <tr>
        <td>open</td>
        <td>/</td>
        <td></td>
    </tr>
    <tr>
        <td>clickAndWait</td>
        <td>ui=allPages::section(section=topics)</td>
        <td></td>
    </tr>
    <tr>
        <td>clickAndWait</td>
        <td>ui=topicListingPages::topic(topic=Process)</td>
        <td></td>
    </tr>
    <tr>
        <td>clickAndWait</td>
        <td>ui=subtopicListingPages::subtopic(subtopic=Creativity)</td>
        <td></td>
    </tr>
    <tr>
        <td>click</td>
        <td>ui=subtopicArticleListingPages::article(index=2)</td>
        <td></td>
    </tr>

### Including the Right Files

UI-Element is now fully integrated with Selenium. The only additional file that needs to be specified is your map definitions file. In the IDE, add it to the comma-delimited Selenium Core extensions field of the IDE options. A sample definition file created for the website alistapart.com is included in the distribution and is available here:

    chrome://selenium-ide/content/selenium/scripts/ui-map-sample.js

You might want to experiment with the sample map to get a feel for UI-Element. For the Selenium RC, you have two options. The map file may be included in the `user-extensions.js` file specified at startup with the `-userExtensions` switch. Or, you may load it dynamically with the variant of the `setUserExtensionJs` command in your driver language, before the browser is started.

### Map Definitions File Syntax

This is the general format of a map file:

    var map = new UIMap();

    map.addPageset({
        name: 'aPageset'
        , ...
    });
    map.addElement('aPageset', { ... });
    map.addElement('aPageset', { ... });
    ...

    map.addPageset({
        name: 'anotherPageset'
        , ...
    });
    ...

The map object is initialized by creating a new `UIMap` object. Next, a pageset is defined. Then one or more UI elements are defined for that pageset. More pagesets are defined, each with corresponding UI elements. That's it!

### Pageset Shorthand

The method signature of `addPageset()` is (pagesetShorthand). pagesetShorthand is a JSON description of the pageset. Here's a minimal example:

    map.addPageset({
        name: 'allPages'
        , description: 'contains elements common to all pages'
        , pathRegexp: '.*'
    });

Here's a table containing information about the attributes of the Pageset object. The conditionally required or unrequired items are for IDE recording support only.

Name | Required? | Description | Example
--- | --- | --- | ---
name | Yes | (String) the name of the pageset. This should be unique within the map. |
    name: 'shopPages'
description | Yes | (String) a description of the pageset. Ideally, this will give the reader an idea of what types of UI elements the pageset will have. |
    description: 'all pages displaying product'
pathPrefix | No | (String) the path of the URL of all included pages in this pageset will contain this prefix. For example, if all pages are of the form http://www.example.com/gallery/light-show/, the page prefix might be `gallery/` . |
    pathPrefix: 'gallery/'
paths pathRegexp | Conditional | (Array | String) either a list of path strings, or a string that represents a regular expression. One or the other should be defined, but not both. If an array, it enumerates pages that are included in the pageset. If a regular expression, any pages whose URL paths match the expression are considered part of the pageset. In either case, the part of the URL being matched (called the path) is the part following the domain, less any training slash, and not including the CGI parameters. For example: http://www.example.com/articles/index.php http://www.example.com/articles/2579?lang=en_US http://www.example.com/articles/selenium/ The entire path must match (however, `pathPrefix` is taken into account if specified). If specified as a regular expression, the two regular expression characters `^` and `$` marking the start and end of the matched string are included implicitly, and should not be specified in this string. Please notice too that backslashes must be backslash-escaped in javascript strings. |
    paths: [
        'gotoHome.do'
        , 'gotoAbout.do'
        , 'gotoFaq.do'
    ]
    pathRegexp: 'goto(Home|About|Faq)\\.do'
paramRegexps | No | (Object) a mapping from URL parameter names to regular expression strings which must match their values. If specified, the set of pages potentially included in this pageset will be further filtered by URL parameter values. There is no filtering by parameter value by default. |
    paramRegexps: {
        dept: '^[abcd]$'
        , team: 'marketing'
    }
pageContent | Conditional | (Function) a function that tests whether a page, represented by its document object, is contained in the pageset, and returns true if and only if this is the case. If specified, the set of pages potentially included in this pageset will be further filtered by content, after URL and URL parameter filtering. Since the URL is available from the document object (`document.location.href`), you may encode the logic used for the `paths` and `pathRegexp` attributes all into the definition of `pageContent`. Thus, you may choose to omit the former if and only if using `pageContent`. Of course, you may continue to use them for clarity. |
    pageContent: function(doc) {
        var id = 'address-tab';
        return doc.getElementById(id) != null;
    }

### UI-Element Shorthand

The method signature of `addElement()` is (pagesetName, uiElementShorthand). pagesetName is the name of the pageset the UI element is being added to. uiElementShorthand is a complete JSON description of the UI element object in shorthand notation.

In its simplest form, a UI element object looks like this:

    map.addElement('allPages', {
        name: 'about_link'
        , description: 'link to the about page'
        , locator: "//a[contains(@href, 'about.php')]"
    });

Here's a table containing information about the attributes of the UI element object. The asterisk (`*`) means any string:

Name | Required? | Description | Example
--- | --- | --- | ---
name | Yes | (String) the name of the UI element |
    name: 'article'
description | Yes | (String) a description of the UI element. This is the main documentation for this UI element, so the more detailed, the better. |
    description: 'front or issue page link to article'
args | No | (Array) a list of arguments that modify the `getLocator()` method. If unspecified, it will be treated as an empty list. |
    [
        { name: 'index'
        , description: 'the index of the author, by article'
        , defaultValues: range(1, 5) }
    ]
See section below elaborating on attributes of argument objects.
locator getLocator() xpath getXPath() | Yes | (String | Function) either a fixed locator string, or a function that returns a locator string given a set of arguments. One or the other should be defined, but not both. Under the sheets, the `locator` attribute eventually gets transcripted as a `getLocator()` function. As of ui0.7, `xpath` and `getXPath()` have been deprecated. They are still supported for backward compatibility. |
    locator: 'submit'
    getLocator: function(args) {
        return 'css=div.item:nth-child(' + args.index + ')'
            + ' > h5 > a';
    }
    getLocator: function(args) {
        var label = args.label;
        var id = this._idMap[label];
        return '//input[@id=' + id.quoteForXPath() + ']';
    }
genericLocator getGenericLocator | No | (String | Function) either a fixed locator string, or a function that returns a locator string. If a function, it should take no arguments. You may experience some slowdown when recording on pages where individual UI elements have many default locators (due to many permutations of default values over multiple arguments). This is because each default locator is potentially evaluated and matched against the interacted page element. This becomes especially problematic if several UI elements have this characteristic. By specifying a generic locator, you give the matching engine a chance to skip over UI elements that definitely don't match. The default locators for skipped elements will not be evaluated unless the generic locator matches the interacted page element.. |
    genericLocator: "//table[@class='ctrl']"
        + "/descendant::input"
    getGenericLocator: function() {
        return this._xpathPrefix + '/descendant::a';
    }
getOffsetLocator | No | (Function) a function that returns an offset locator. The locator is offset from the element identified by a UI specifier string. The function should take this element, and the interacted page element, as arguments, and have the method signature `getOffsetLocator(locatedElement, pageElement)`. If an offset locator can't be found, a value that evaluates to `false` must be returned. A convenient default function `UIElement.defaultOffsetLocatorStrategy` is provided so you don't have to define your own. It uses several typical strategies also employed by the IDE recording when recording normally. See the Advanced Topics section below for more information on offset locators. |
    getOffsetLocator: UIElement.defaultOffsetLocatorStrategy
    getOffsetLocator:
        function(locatedElement, pageElement) {
        if (pageElement.parentNode == locatedElement) {
            return '/child::' + pageElement.nodeName;
        }
        return null;
    }
testcase* | No | (Object) a testcase for testing the implementation of the `getLocator()` method. As many testcases as desired may be defined for each UI element. They must all start with the string "testcase". |
    testcase1: {
        xhtml: '<div class="item"><h5>'
            + '<a expected-result="1" /></h5></div>'
    }
See section below elaborating on testcases.
_* | No | (Any data type) a "local variable" declared for the UI element. This variable will be available both within the `getLocator()` method of the UI element, and any `getDefaultValues()` methods of the arguments via the `this` keyword. They must all start with an underscore "_". |
    _labelMap: {
        'Name': 'user'
        , 'Email': 'em'
        , 'Phone': 'tel'
    }

### UI-Argument Shorthand

UI arguments are defined as part of UI elements, and help determine how an XPath is generated. A list of arguments may be defined within the UI element JSON shorthand. Here's an example of how that might look:

    map.addElement('searchPages', {
        name: 'result'
        , description: 'link to a result page'
        , args: [
            {
                name: 'index'
                , description: 'the index of the search result'
                , defaultValues: range(1, 21)
            }
            , {
                name: 'type'
                , description: 'the type of result page'
                , defaultValues: [ 'summary', 'detail' ]
            }
        ]
        , getLocator: function(args) {
            var index = args['index'];
            var type = args['type'];
            return "//div[@class='result'][" + index + "]"
                + "/descendant::a[@class='" + type + "']";
        }
    });

In the above example, two arguments are defined, `index` and `type`. Metadata is provided to describe them, and default values are also specified. FInally, the `getLocator()` method is defined. The behavior of the method depends on the values of the arguments that are passed in.

Default values come into play when recording tests using the Selenium IDE. When you interact with a page element in recording mode, the IDE uses all the locator strategies at its disposal to deduce an appropriate locator string for that element. UI-Element introduces a new `ui` locator strategy. When applying this strategy using a particular UI element, Selenium generates a list of XPaths to try by permuting the arguments of that UI element over all default values. Here, the default values { 1, 2, 3, 4 .. 20 } are given for the `index` argument using the special `range()` function, and the values { summary, detail } are given for the `type` argument in standard javascript array notation. If you don't intend to use the IDE, go ahead and set the default values to the empty array `[]`.

Here's a table containing information about the attributes of the UI argument object.

Name | Required? | Description | Example
--- | --- | --- | ---
name | Yes | (String) the name of the argument. This will be the name of the property of the object passed into the parent UI element's `getLocator()` method containing the argument value. |
    name: 'index'
description | Yes | (String) a description for the argument. |
    description: 'the index of the article'
defaultValues getDefaultValues() | Yes | (Array | Function) either an array of string or numerical values, or a function that returns an array of string or numerical values. One or the other should be defined, but not both. Under the sheets, the `defaultValues` attribute eventually gets transcripted as a `getDefaultValues()` function. The method signature of the function is `getDefaultValues(inDocument)`. `inDocument` is the current document object of the page at time of recording. In cases where the default values are known a priori, `inDocument` need not be used. If the default values of all arguments of a UI element are known a priori, the list of default locators for the element may be precalculated, resulting in better performance. If `inDocument` is used, in cases where the current document is inspected for valid values, the element's default locators are calculated once for every recordable event. |
    defaultValues: [ 'alpha', 'beta', 'unlimited' ]
    getDefaultValues: function() {
        return keys(this._idMap);
    }
    getDefaultValues: function(inDocument) {
        var defaultValues = [];
        var links = inDocument
            .getElementsByTagName('a');
        for (var i = 0; i < links.length; ++i) {
            var link = links[i];
            if (link.className == 'category') {
                defaultValues.push(link.innerHTML);
            }
        }
        return defaultValues;
    }

### About `this`

You may have noticed usage of the `this` keyword in the examples above, specifically in the `getLocator()` and `getDefaultValues()` methods. Well, what exactly is `this`?

The answer is: it depends. The object referred to by `this` changes depending on the context in which it is being evaluated. At the time of object creation using `addPageset()` or `addElement()`, it refers to the `window` object of the Selenium IDE, which isn't useful at all. However, subsequently any time `getLocator()` is called, its `this` references the UI element object it's attached too. Thus, using `this`, any "local variables" defined for the UI element may be accessed. Similarly, when `getDefaultValues()` is called, its `this` references the UI argument object it's attached too. But ... what "local variables" are accessible by the from `getDefaultValues()`?

There's a little magic here. If you defined your local variables as prescribed in the above UI-Element Shorthand section, starting with an underscore, those variable are automatically made available to the UI argument via its `this` keyword. Note that this isn't standard javascript behavior. It's implemented this way to clear out clutter in the method definitions and to avoid the use of global variables for lists and maps used within the methods. It is sometimes useful, for example, to define an object that maps human-friendly argument values to DOM element `id`'s. In such a case, `getDefaultValues()` can be made to simply return the `keys()` (or property names) of the map, while the `getLocator()` method uses the map to retrieve the associated `id` to involve in the locator.

Also note that `this` only behaves this way in the two mentioned methods, `getLocator()` and `getDefaultValues()`; in other words you can't reference the UI element's local variables using `this` outside of methods.

If you're interested, here's some additional reading on javascript scope.

## Advanced Topics

### Testcases

You can write testcases for your UI element implementations that are run every time the Selenium IDE is started. Any testcases that fail are reported on. The dual purpose of writing testcases is to both validate the `getLocator()` method against a representation of the real page under test, and to give a visual example of what the DOM context of a page element is expected to be.

A testcase is an object with a required `xhtml` property (String), and a required `args` property (Object). An example is due:

    testcase1: {
        args: { line: 2, column: 3 }
        , xhtml: '<table id="scorecard">'
            + '<tr class="line" />'
            + '<tr class="line"><td /><td /><td expected-result="1" /></tr>'
            + '</table>'
    }

The `args` property specifies the object to be passed into the UI element's `getLocator()` method to generate the test locator, which is then applied to the `xhtml`. If evaluating the locator on the XHTML document returns a DOM node with the `expected-result` attribute, the testcase is considered to have passed.

The `xhtml` property must represent a complete XML document, sans `<html>` tags, which are automatically added. The reason this is necessary is that the text is being converted into an XPath evaluable DOM tree via Mozilla's native XML parser. Unfortunately, there is no way to generate a simple HTML document, only XML documents. This means that the content of the `xhtml` must be well-formed. Tags should also be specified in lowercase.

### Fuzzy Matching

Here's a real-world example of where fuzzy matching is important:

    <table>
    <tr onclick="showDetails(0)">
        <td>Brahms</td>
        <td>Viola Quintet</td>
    </tr>
    <tr onclick="showDetails(1)">
        <td>Saegusa</td>
        <td>Cello 88</td>
    </tr>
    </table>

Imagine I'm recording in the IDE. Let's say I click on "Cello 88". The IDE would create locator for this action like `//table/tr[2]/td[2]`. Does that mean that my `getLocator()` method should return the same XPath?

Clearly not. Clicking on either of the table cells for the second row has the same result. I would like my UI element generated XPath to be `//table/tr[2]` . However, when recording, the page element that was identified as being acted upon was the table cell, which doesn't match my UI element XPath, so the `ui` locator strategy will fail to auto-populate. What to do?

Fuzzy matching to the rescue! Fuzzy matching is realized as a fuzzy matcher function that returns true if a target DOM element is considered to be equivalent to a reference DOM element. The reference DOM element would be the element specified by the UI element's generated XPath. Currently, the fuzzy matcher considers it a match if:

- the elements are the same element,
- the reference element is an anchor (`<a>`) element, and the target element is a descendant of it; or
- the reference element has an `onclick` attribute, and the target element is a descendant of it.

This logic may or may not be sufficient for you. The good news is, it's very easy to modify. Look for the definition of `BrowserBot.prototype.locateElementByUIElement.is_fuzzy_match` in `ui-element.js` .

### Offset Locators

Offset locators are locators that are appended to UI specifier strings to form composite locators. They can be automatically deduced by the IDE recorder for UI elements that have specified a `getOffsetLocator` function. This feature may be useful if your pages contain too many elements to write UI elements for. In this case, offset locators allow you to define UI elements that "anchor" other elements. Given the following markup:

    <form name="contact_info">
        <input type="text" name="foo" />
        <textarea name="bar"></textarea>
        <input type="submit" value="baz" />
    </form>

Assume that a UI element has been defined for the form element. Then the following locators containing offset locators and "anchored" off this element would be recorded using the default offset locator function (`UIElement.defaultOffsetLocatorStrategy`):

    ui=contactPages::contact_form()->//input[@name='foo']
    ui=contactPages::contact_form()->//textarea[@name='bar']
    ui=contactPages::contact_form()->//input[@value='baz']

The character sequence `->` serves to delimit the offset locator from the main locator. For this reason, the sequence should not appear in the main locator, or else ambiguity will result.

When recording with the IDE, no preference is given to matching plain vanilla UI specifier strings over ones that have offset locators. In other words, if a page element could be specified both by a UI specifier string for one UI element, and by one augmented by an offset locator for a different UI element, there is no guarantee that one or the other locator will be recorded.

Currently, only XPath is supported as an offset locator type, as it is the only locator for which a context node can be specified at evaluation time. Other locator strategies may be supported in the future.

### Rollup Rules

Question: Why use rollup rules? Answer: Remember the testcase from the "Getting Motivated" section above? With rollups, that testcase can be condensed into this:

    <tr>
        <td>open</td>
        <td>/</td>
        <td></td>
    </tr>
    <tr>
        <td>rollup</td>
        <td>navigate_to_subtopic_article</td>
        <td>index=2, subtopic=Creativity</td>
    </tr>

It's inevitable that certain sequences of Selenium commands will appear in testcases over and over again. When this happens, you might wish to group several fine-grained commands into a single coarser, more semantically meaningful action. In doing so, you would abstract out the execution details for the action, such that if they were to change at some point, you would have a single point of update. In UI-Element, such actions are given their own command, called `rollup`. In a sense, rollups are a natural extension of the `ui` locator.

UI-Element is designed with the belief that the IDE can be a useful tool for writing testcases, and need not be shunned for lack of functionality. A corollary belief is that you should be able to drive your RC test in the language of your choice. The execution of the `rollup` command by the Selenium testrunner produces the component commands, which are executed in a new context, like a function call. The logic of the rollup "expansion" is written in javascript. Corresponding logic for inferring a rollup from a list of commands is also written in javascript. Thus, the logic can be incorporated into any of the family of Selenium products as a user extension. Most notably, the IDE is made viable as a testcase creation tool that understands both how rollups expand to commands, and also how rollup rules can be "applied" to commands to reduce them to rollups.

Rollup rule definitions appear in this general format:

    var manager = new RollupManager();

    manager.addRollupRule({ ... });
    manager.addRollupRule({ ... });
    ...

In a relatively simple form, a rollup rule looks like this:

    manager.addRollupRule({
        name: 'do_search'
        , description: 'performs a search'
        , args: [
            name: 'term'
            , description: 'the search term'
        ]
        , commandMatchers: [
            {
                command: 'type'
                , target: 'ui=searchPages::search_box\\(.+'
                , updateArgs: function(command, args) {
                    var uiSpecifier = new UISpecifier(command.target);
                    args.term = uiSpecifier.args.term;
                    return args;
                }
            }
            , {
                command: 'click.+'
                , target: 'ui=searchPages::search_go\\(.+'
            }
        ]
        , getExpandedCommands: function(args) {
            var commands = [];
            var uiSpecifier = new UISpecifier(
                'searchPages'
                , 'search_box'
                , { term: args.term });
            commands.push({
                command: 'type'
                , target: 'ui=' + uiSpecifier.toString()
            });
            commands.push({
                command: 'clickAndWait'
                , target: 'ui=searchPages::search_go()'
            });
            return commands;
        }
    });

In the above example, a rollup rule is defined for performing a search. The rule can be "applied" to two consecutive commands that match the `commandMatchers`. The rollup takes one argument, `term`, and expands back to the original two commands. One thing to note is that the second command matcher will match all commands starting with `click`. The rollup will expand that command to a `clickAndWait`.

Here's a table containing information about the attributes of the rollup rule object.

Name | Required? | Description | Example
--- | --- | --- | ---
name | Yes | (String) the name of the rollup rule. This will be the target of the resulting `rollup` command. |
    name: 'do_login'
description | Yes | (String) a description for the rollup rule. |
    description: 'logs into the application'
alternateCommand | No | (String) specifies an alternate usage of rollup rules to replace commands. This string is used to replace the command name of the first matched command. |
    alternateCommand: 'clickAndWait'
pre | No | (String) a detailed summary of the preconditions that must be satisfied for the rollup to execute successfully. This metadata is easily viewable in the IDE when the rollup command is selected. |
    pre: 'the page contains login widgets'
post | No | (String) a detailed summary of the postconditions that will exist after the rollup has been executed. |
    post: 'the user is logged in, or is \
    directed to a login error page'
args | Conditional | (Array) a list of arguments that are used to modify the rollup expansion. These are similar to UI arguments, with the exception that `exampleValues` are provided, instead of `defaultValues`. Here, example values are used for reference purposes only; they are displayed in the rollup pane in the IDE. This attribute may be omitted if no `updateArgs()` functions are defined for any command matchers. |
    args: {
        name: 'user'
        , description: 'the username to login as'
        , exampleValues: [
            'John Doe'
            , 'Jane Doe'
        ]
    }
commandMatchers getRollup() | Yes | (Array | Function) a list of command matcher definitions, or a function that, given a list of commands, returns either 1) a rollup command if the rule is considered to match the commands (starting at the first command); or 2) false. If a function, it should have the method signature `getRollup(commands)`, and the returned rollup command (if any) must have the `replacementIndexes` attribute, which is a list of array indexes indicating which commands in the `commands` parameter are to be replaced. If you don't intend on using the IDE with your rollups, go ahead and set this to the empty array `[]`. |
    commandMatchers: [
        {
            command: 'type'
            , target: 'ui=loginPages::user\\(.+'
            , value: '.+'
            , minMatches: 1
            , maxMatches: 1
            , updateArgs:
                function(command, args) {
                args.user = command.value;
                return args;
            }
        }
    ]
    // this is a simplistic example, roughy
    // equivalent to the commandMatchers
    // example above. The to_kwargs() function
    // is used to turn an arguments object into
    // a keyword-arguments string.
    getRollup: function(commands) {
        var command = commands[0];
        var re = /^ui=loginPages::user\(.+/;
        if (command.command == 'type' &&
            re.test(command.target) &&
            command.value) {
            var args = { user: command.value };
            return {
                command: 'rollup'
                , target: this.name
                , value: to_kwargs(args)
                , replacementIndexes: [ 0 ]
            };
        }
        return false;
    }
See section below elaborating on command matcher objects.
expandedCommands getExpandedCommands() | Yes | (Array | Function) a list of commands the rollup command expands into, or a function that, given an argument object mapping argument names to values, returns a list of expanded commands. If a function, it should have the method signature `getExpandedCommands(args)`. Each command in the list of expanded commands should contain a `command` attribute, which is the name of the command, and optionally `target` and `value` attributes, depending on the type of command. It is expected that providing a fixed list of expanded commands will be of limited use, as rollups will typically contain commands that have arguments, requiring more sophisticated logic to expand. |
    expandedCommands: [
        {
            command: 'check'
            , target: 'ui=termsPages::agree\\(.+'
        }
        , {
            command: 'clickAndWait'
            , target: 'ui=termsPages::submit\\(.+'
        }
    ]
    getExpandedCommands: function(args) {
        var commands = [];
        commands.push({
            command: 'type'
            , target: 'ui=loginPages::user()'
            , value: args.user
        });
        commands.push({
            command: 'type'
            , target: 'ui=loginPages::pass()'
            , value: args.pass
        });
        commands.push({
            command: 'clickAndWait'
            , target: 'ui=loginPages::submit()'
        });
        commands.push({
            command: 'verifyLocation'
            , target: 'regexp:.+/home'
        });
        return commands;
    }
    // if using alternateCommand
    expandedCommands: []

The user should be able to freely record commands in the IDE, which can be collapsed into rollups at any point by applying the defined rollup rules. Healthy usage of the `ui` locator makes commands easy to match using command matcher definitions. Command matchers simplify the specification of a command match. In basic usage, for a rollup rule, you might specify 3 command matchers: M1, M2, and M3, that you intend to match 3 corresponding commands, C1, C2, and C3. In more complex usage, a single command matcher might match more than one command. For example, M1 matches C1 and C2, M2 matches C3, and M3 matches C4, C5, and C6. In the latter case, you would want to track the matches by updating argument values in the command matchers' `updateArgs()` methods.

Here are the required and optional fields for command matcher objects:

Name | Required? | Description | Example
--- | --- | --- | ---
command | Yes | (String) a simplified regular expression string that matches the command name of a command. The special regexp characters `^` and `$` are automatically included at the beginning and end of the string, and therefore should not be explicitly provided. |
    command: 'click.+'
    command: 'rollup'
target | Yes | (String) a simplified regular expression string that matches the target of a command. Same rules as for the `command` attribute. |
    target: 'btnG'
    // special regexp characters must be
    // escaped in regexp strings. Backslashes
    // always need to be escaped in javascript
    // strings.
    target: 'ui=loginPages::user\\(.+'
value | No | (String) a simplified regular expression string that matches the value of a command. Same rules as for the `command` attribute. |
    value: '\\d+'
    value: (?:foo|bar)
minMatches | No | (Number) the minimum number of times this command matcher must match consecutive commands for the rollup rule to match a set of commands. If unspecified, the default is 1. If `maxMatches` is also specified, `minMatches` must be less than or equal to it. |
    minMatches: 2
maxMatches | No | (Number) the maximum number of times this command matcher is allowed to match consecutive commands for the rollup rule. If unspecified, the default is 1. |
    maxMatches: 2
updateArgs() | No | (Function) updates an arguments object when a match has been found, and returns the updated arguments object. This method is used to keep track of the way in which one or more commands were matched. When a rollup rule is successfully applied, any argument name-value pairs are stored as the rollup command's value. At time of expanding the rollup command, the command's value is converted back to an arguments object, which is passed to the rollup rule's `getExpandedCommands()` method. This method must have the following method signature: `updateArgs(command, args)`, where `command` is the command object that was just matched, and `args` is the arguments object for the current trial application of the parent rollup rule. |
    // reused from above
    updateArgs: function(command, args) {
        args.user = command.value;
        return args;
    }
    // for multiple matches
    updateArgs: function(command, args) {
        if (!args.clickCount) {
            args.clickCount = 0;
        }
        ++args.clickCount;
        return args;
    }
    // another example from above (modified).
    // If you need to parse a UI specifier,
    // instantiate a new UISpecifier object with the
    // locator. To do it by the book, you should
    // first strip off the "ui=" prefix. If you just
    // want to inspect the UI specifier's args, you
    // can safely skip this step.
    updateArgs: function(command, args) {
        var s = command.target.replace(/^ui=/, '');
        var uiSpecifier = new UISpecifier(s);
        args.term = uiSpecifier.args.term;
        return args;
    }
    // example from sample map file. If you're
    // matching a rollup command that has arguments,
    // you'll want to parse them. The easiest way
    // to do this is with the parse_kwargs()
    // function, which has its roots in python.
    updateArgs: function(command, args) {
        var args1 = parse_kwargs(command.value);
        args.subtopic = args1.subtopic;
        return args;
    }

Too much mumbo jumbo?

To see rollup rules in action in the IDE, use the included sample map with UI-Element (see instructions above in the "Including the Right Files" section), and grab the listing of 4 commands from the "Getting Motivated" section, above. Under the Source tab of the IDE, paste the commands in between the `<tbody>` and `</tbody>` tags. Now switch back to the Table tab, and click the new purple spiral button; this is the "Apply rollup rules" button. If done correctly, you should be prompted when rollup rule matches are found. Go ahead - go to the alistapart.com site and try executing the rollups!

## Release Notes

### Core-1.0

- UI-Element is now completely integrated into Selenium Core.
- Added offset locators. Modified the delimiter to be `->`.
- `getDefaultValues()` can dynamically construct a list of values and assume that a `document` object is being passed in.
- Arguments in UI specifier strings are presented in the same order as they are defined in the mapping file (no longer alphabetically).
- Allow generic locators to be specified, potentially improving recording performance when there are many UI arguments.
- Updated documentation.
- Many other fixes.

### ui0.7

- Changed extensions id and homepage to avoid conflicting with standard Selenium IDE distribution.
- Added rollup button.
- Added UI-Element and Rollup panes, with beautiful colors and formatting.
- Updated referenced version of Selenium Core to 0.8.3, and RC to 0.9.2 .
- Added `quoteForXPath()` to `String` prototype.
- Made XPath uppercasing much more robust. It is now backed by the ajaxslt library. Uppercasing is no longer required by UI-Element. It is used to demonstrate how XPath transformations may be used to improve XPath evaluation performance when using the javascript engine. See this thread on XPath performance.
- Added new `rollup` Selenium command and associated functionality with the RollupManager object.
- Deprecated `getXPath()` and `xpath` in favor of `getLocator()` and `locator` in the UI-Element shorthand. Testcases now work with locator types other than XPath (implicit, CSS, etc.).
- UI element testcases are now truly XHTML. All content is considered inner HTML of the html element, which is automatically generated. This supports the use of alternate locator types described in the above bullet.
- Global variables introduced by UI-Element are now properties of the variable `GLOBAL`. You can now name your map `uiMap` if you wish.
- Updated the sample map, including demonstration of implicit and CSS locators, Rollup Rules, and usage of `quoteForXPath()`.
- Improved auto-population of target dropdown with UI element locators and rollup names. The population logic is as follows: when a command is loaded from a file or inserted without having been recorded, all UI element locators are shown. After a command recorded or executed, only UI element locators for pagesets that match the page at time of recording or execution will be shown.
- Made UI element testcase args mandatory. This reduces the startup time for the IDE, and improves testcase readability.
- Updated documentation. Added this release notes section.

## Final Thoughts

Catch UI-Element news in the Selenium category of my blog. You can also find me on the OpenQA Forums.

- Haw-Bin Chai