Selenium Option Locators: Deciphering “@see #doSelect for details of option locators”

I like Selenium. It makes it really easy to bring automated web testing to the masses. But their documentation practices can be frustrating. I want to talk about a feature called Option Locators not because there is anything particularly sexy about them, but because I had such a hard time figuring out what they are.

But just in case I’m getting ahead of myself, let me start over by introducing the tool I’m talking about.

Selenium: Automating Web Browsers

If you do a lot of web development, this is a great tool to put in your toolbox. It is a web browser automation tool and what makes it really stand out for me is how easy it is to rapidly develop an automation macro (using a simple record and playback interface) and then export the script into a whole slew of other languages including Ruby, PHP, Java, or C#.

Imagine receiving a Selenium macro as an attachment to a bug report. You use the macro to resolve the issue and verify it’s been fixed, then you can save the macro away in your repository to test for possible regressions later. This can become the foundation for a fully featured automated testing system when you’re ready to make the leap to Selenium WebDriver.

Option Locators

Yesterday I was putting together a macro to populate a form that I had to repeatedly fill out as part of an issue I was working on. One of the steps was to select all of the option elements inside a select element called “ddlGeographyValue”. Now, as I’ve mentioned, you can create these macros using a record-and-playback style IDE, but unfortunately it takes a rather literal interpretation of this command.

Selenium IDE Screenshot

You can click on the picture to expand the size. It creates an individual addSelection command for each option. This wasn’t really what I wanted, not that it mattered much for a one-off throwaway script, but some of the selections were nested, so a static macro wasn’t going to do a lot of good. I was also curious if there was a way to make the addSelection command choose every option without coding them all in. Luckily the Selenium IDE comes with a built-in command reference, visible in the bottom of the image.

addSelection ( locator,optionLocator )
Arguments:
locator - an element locator identifying a multi-select box
optionLocator - an option locator (a label by default)
Add a selection to the set of selected options in a multi-select element using an option locator. @see #doSelect for details of option locators


From the IDE, the value of the Target column becomes the locator, and the Value column is the optionLocator. The documentation mentions that the default locator is label, but what other choices are there? I’ve highlighted the sentence @see #doSelect for details of option locators because that was the first thing I pasted into Google when I wanted to see what other option locators were available.

Your results may vary (they should include this post) but when I submitted that search to Google, every result for at least the first 4 pages was just a repeat of the command reference text, which has apparently been screen scraped by all of the internet. Seriously? Where are you, #doSelect? From Stack Overflow I got some partial answers on other option locators, but it was clear if I wanted a complete listing, I would need to go to the source. Source code, that is!

In case you’re wondering, the source code is available here.

Here’s the source code for the addSelection command:

Selenium.prototype.doAddSelection = function(locator, optionLocator) {
/**
* Add a selection to the set of selected options in a multi-select element using an option locator.
*
* @see #doSelect for details of option locators
*
* @param locator an element locator identifying a multi-select box
* @param optionLocator an option locator (a label by default)
*/
var element = this.browserbot.findElement(locator);
if (!("options" in element)) {
throw new SeleniumError("Specified element is not a Select (has no options)");
}
var locator = this.optionLocatorFactory.fromLocatorString(optionLocator);
var option = locator.findOption(element);
this.browserbot.addSelection(element, option);
};

And this brings us to our first real clue about what the optionLocator is. That would be whatever optionLocatorFactory makes it. Let’s look-up that next.

/**
* Factory for creating "Option Locators".
* An OptionLocator is an object for dealing with Select options (e.g. for
* finding a specified option, or asserting that the selected option of
* Select element matches some condition.
* The type of locator returned by the factory depends on the locator string:
* label=<exp> (OptionLocatorByLabel)
* value=<exp> (OptionLocatorByValue)
* index=<exp> (OptionLocatorByIndex)
* id=<exp> (OptionLocatorById)
* <exp> (default is OptionLocatorByLabel).
*/
function OptionLocatorFactory() {
}

And there we have it. I’m not going to post the factory functions, this comment gives you a pretty good idea what those are going to look like. Now why is a hopeful comment like this hidden back here in the source and not made available through the command reference in the Selenium IDE?

The irony is that those are precisely the locators I would have expected to support. OK, I probably wouldn’t have guessed “id” because I rarely give my options ids individually, but the rest were fairly obvious now that I know what they are! So why was I looking this up again?

Selecting All option elements using a Selenium macro

It turns out, none of these optionLocators allow you to define an addSelection command that selects more than one option at a time. I ended up using a separate command, getEval(script) to select the options. Now, getEval is actually hidden from the command drop down list in the Selenium IDE, the only reason I thought to use it was because it’s mentioned in the reference text for the command verifyEval which is listed.

getEval lets you run a snippet of javascript during your macro, which gives you an incredible amount of flexibility for performing actions that aren’t supported by the IDE. Here’s what I wrote to select all of the options elements for the ddlGeographyValues select element.

Command: getEval
Target: window.$("#ddlGeographyValue option").attr('selected','selected')
Value: (Empty)

That’s all I’ve got. If you made it this far, thanks for reading!


Is your Javascript waiting on multiple Backbone fetches?

I’ve been working with Backbone for approximately a month now. I’m a fan of using a structured coding practice to untangle your objects and the DOM.
In Backbone, you get the latest state of your model by fetching it from the server. You can set a callback on the fetch function directly or by binding to the collection’s reset event.

Tracking fetched collections manually

I needed to fetching more than one collection at the same time and call a callback when all of the fetches were finished. My first attempt wasn’t very good. I’ll give you a largely trivialized sample of what I had going on.

This is an example of a view written in CoffeeScript. I want to fetch 3 different collections, render them using Handlebar templates, and call a function when all 3 collections are rendered:

initialize: =>
  @apples = new MyModels.Apples
  @oranges = new MyModels.Oranges
  @bananas = new MyModels.Bananas
  @bindTo(@apples, 'reset', @renderApples)
  @bindTo(@oranges, 'reset', @renderOranges)
  @bindTo(@bananas, 'reset', @renderBananas)

render: =>
  @apples.fetch()
  @oranges.fetch()
  @bananas.fetch()

This actually isn’t bad so far. We create our models and bind an event so that when they are fetched, their respective render functions will be called. The real mess came later in the same file.

renderBananas: =>
  @$el.html(HandlebarsTemplates["bananas"](
    bananas:@bananas.toJSON()))
  Backbone.ModelBinding.bind(this)

  @bananasLoaded = true
  @finishLoading()

finishLoading: =>
  return unless @applesLoaded
  return unless @orangesLoaded
  return unless @bananasLoaded

  alert("All collections loaded!")

For brevity, I left off renderApples and renderOranges. Those functions are identical to renderBananas with their respective fruits substituted in. This solution isn’t very scalable, as we need to create more state variables to track any new collections that get added in the future and make sure we account for those variables when we finish loading.
It would be better if we could define a callback that would wait for all of my fetch calls to finish. It would be even cooler if these things chained together.

Making promises

What I needed to be using are jQuery Deferred Objects. See, Backbone server calls (like fetch and save) are just $.ajax() calls under the hood, and since jQuery 1.5, $.ajax() calls implement an immutable version of Deferred Object called a Promise. As it happens, everything in that code sample is already defer-able.
I’ll modify the view to make use of Promises.

initialize: =>
  @apples = new MyModels.Apples
  @oranges = new MyModels.Oranges
  @bananas = new MyModels.Bananas
  # Dropped the bind calls, we won't be needing them!

fetchCollections: =>
  # Combines these individual promises into one.
  $.when(
    @apples.fetch(),
    @oranges.fetch(),
    @bananas.fetch()
  ).promise()

renderCollections: =>
  $.when(
    @renderApples(),
    @renderOranges(),
    @renderBananas()
  ).promise()

renderBananas: =>
  d = @$el.html(HandlebarsTemplates["bananas"](
    bananas:@bananas.toJSON()))
  Backbone.ModelBinding.bind(this)
  d.promise()

finishLoading: =>
  alert("All collections loaded!")

With all of this in place, the render function shown below is remarkably straightforward, and communicates the behavior of the view beautifully!

render: =>
  @fetchCollections()
    .then(@renderCollections)
    .then(@finishLoading)

This function is so much better than before; it tells me everything that’s going to happen in a clean and concise way.
An additional enhancement I’ve snuck in was to return promises for my .html() calls in the renderBananas function. Now I know that my HTML templates will be rendered before finishLoading gets called. This will protect me from making a mistake like attempting to focus() an element that maybe hasn’t been rendered yet.


Git Rebase destroyed your commit? Reflog to the Rescue!

I admit I’m something of a newb when it comes to the distributed source control system, Git. This came as a shock to me because I consider myself fairly competent working with Mercurial. When I got a “rebase already started” error message while preparing to submit my latest changes, I didn’t much know what to do about it.

Thinking the message was a leftover from a particularly ornery merge I had just performed on a prior issue, I tried running

$ git rebase --abort

and submitted the command. The local changes, the revisions I pulled from the server, and the commit for my most recent issue were all gone. Notice that last bit? I didn’t want that. Wonderful. Ensue frantic Googling.

That’s how I discovered git reflog. A command whose help text summary suffers from Git’s notorious documentation:

$ git help reflog
git-reflog - Manage reflog information

As I understand it, the reflog is like an activity stream of changes that happen to your repository. Fortunately, this means that in almost every instance, you can use it as a way to recover commits that you’ve lost while navigating the myriad of history manipulating commands that see common usage in Git, such as rebase. In this case, I found my commit:

$ git reflog show
...
483f7d0 HEAD@{18}: commit: DEV-1297 - Expand last viewed widget on home page
...

Now what’s cool about this listing, is that you can checkout these refs as if they were branches. If you want, you can even give it a name.

$ git checkout 483f7d0
$ git checkout -b DEV-1297_Recovered_Commit

And from this branch my commit is restored and I can get back to work!