Update (Apr 9, 2012): A new version of Angular is much easier to test with Selenium2 now. Read about that in the follow up blog entry: Testing Knockout and Angular with Selenium2

Synopsis: Using Selenium with Angular’s not so easy right now.

Refer to my previous JBehave ‘Story Navigator’ app again. Here’s the URL to the site, if you wanted to play with it, or look at it with Firebug:

https://paul-hammant.github.io/StoryNavigator/

What I want to be able to do is use the model references to locate elements within the page. The most ‘sophisticated’ example is the “Showing 3 Of 3 Stories”

That looks like this in the source:

<th colspan="2" class="section"> Stories<br/>
  <p id="stories-total">Showing {‌{data.xref.stories.$filter(search).$size()}} of {‌{data.xref.stories.$size()}} stories</p>
</th>

And in the rendered DOM, that looks like:

Getting to the first number should be as simple as:

//span[@ng:bind = 'data.xref.stories.$filter(search).$size()']

The problem is that $ is an illegal char in respect of a xpath lookup inside Firefox. I’ve tried a couple of escape sequences for it, but have not been able to overcome that. Simpler model references like “search.description” should work though.

The second, and perhaps more serious problem is that using xpath to find attributes “ng:bind” is impossible too. The colon is the issue, and despite there being advice on the StackOverflow as to how to search for namespace-centric things using xpath, none seem to work in Firefox. This one should work, but does not:

//span[local-name()='bind' and namespace-uri()='http://angularjs.org']

OK, I think the AngularJS team needs to get inventive here. Perhaps, for a certain mode of operation it should generate legal IDs for each element that are data-bound. The IDs could be as is, munged somewhat to replace ‘illegal’ chars, or a hash function of the string (or part of it). It’d only be used by Selenium, and the mode would not be turned on for production deployments of the app. This dogged us previously with GWT applications where the XHTML was very sopisticated and locators for widgets required a PhD to make elegant. For a client today, I’m writing Selnium2 scripts for an app that has duplicate IDs, so I don’t partocularly care whether, for testing, IDs are duplicated. Selenium’s Firefox or InternetExplorer driving can handle that.

Anyway, I can get Selenium working with the Story Navigator app. Here’s some groovy:

@‌Grapes([
 @‌Grab("org.seleniumhq.selenium:selenium-java:2.20.0"),
 @‌Grab("org.seleniumhq.selenium.fluent:fluent-selenium:1.0"),
 @‌Grab("org.hamcrest:hamcrest-all:1.1"),
 @‌GrabExclude('xml-apis:xml-apis'),
])

import org.openqa.selenium.firefox.FirefoxDriver
import org.openqa.selenium.StaleElementReferenceException
import static org.openqa.selenium.By.*
import org.seleniumhq.selenium.fluent.FluentWebDriverImpl
import static org.seleniumhq.selenium.fluent.Period.*

def driver = new FirefoxDriver()
def fluent = new FluentWebDriverImpl(driver);

driver.get "https://paul-hammant.github.io/StoryNavigator/"

fluent.within(secs(1)).p(id("stories-total")).text.shouldBe "Showing 3 Of 3 Stories"

fluent.input(name("search.description")).sendKeys "Hat"

fluent.within(secs(1)).p(id("stories-total")).within(secs(1)).text.shouldBe "Showing 1 Of 3 Stories"

driver.quit()

Here’s the resulting video, which is too quick to see (less than half a second for the actual browser interaction):

You might have to advance the video frame by frame in order to see it working - sorry!

One more small fact - Angular has about a 50ms delay between events in the page (I type “hat”) and the model-view-controller results of those changes. Humans cannot see that, but Selenium can, hence the build in delays.



Published

March 15th, 2012
Reads:

Tags

Categories