Paul Hammant's Blog: Angular and Selenium
I’m the co-creator of the original Selenium 1.0 (or 0.something as it was then) but I mean Selenium2 (nee WebDriver) of course. As UI’s get more sophisticated, the ability to locate the right part of the page for purposes of a query or interaction, is increasingly hard. I have personal experience with GWT that, on occasion, has driven me nuts and a pathological fear of the the super-advanced frameworks like Cappuccino
Finding Elements for and Angular app is not especially hard. Indeed all of Backbone, Knockout, KnockBack, Spine, Ember, BatMan (and the rest) will paint relatively normal HTML and you could get busy with ID, CSS or XPATH based locators for widgets. This blog entry could end there, but I’d like to do a “what if”.
Model-Centric Angular Locators
Here is the model for from monday’s blog entry again:
scope.cart = {
authenticity_token: "xotITlobeVi6BUnqBIqAd4eFy5+yJf0JlSyog8rK5hQ=",
items: [
{
imgSrc: "index_files/grace-wireless-player.jpg",
link: "grace-wireless-internet-radio",
name: "Grace Wireless Internet Radio",
price: 179.99,
qty: 1
},
{
imgSrc: "index_files/plantronics-headphone.jpg",
link: "plantronics-m40-mobile-in-ear-headset",
name: "Plantronics M40 Mobile in Ear Headset",
price: 9.99,
qty: 2
}
]
};
Here is part of the Angular page:
<tbody id="line_items">
<tr ng:repeat="item in cart.items" class="">
<td><img alt="{{item.name}}" ng:src="{{item.imgSrc}}" /></td>
<td class="description"><h4><a href="http://demo.spreecommerce.com/products/{{item.link}}">{{item.name}}</a></h4></td>
<td class="unit-price"> {{item.price | currency}} </td>
<td class="operator"> X </td>
<td class="quantity"><input style="width: 32px;" name="item.qty" ng:validate="integer:1:5" /></td>
<td class="operator"> = </td>
<td class="total"> {{item.price*item.qty | currency}} </td>
<td class="total"><a ng:click="cart.items.$remove(item)" class="delete button">Remove</a></td>
</tr>
<tr class="totals">
<td colspan="6"> </td>
<td colspan="2" class="totals"> Subtotal: {{cart.items.$sum('price*qty') | currency}} </td>
</tr>
</tbody>
What I would like to be able to do is locate parts of the page via the model that’s presenting them. Please excuse the Java:
WebElement quantityInput = webDriver.findElement(id("cart")).findElement(angularModel("items.$filter('{ name: \"plantronics-m40-mobile-in-ear-headset\"}').qty"));
quantityInput.sendKeys("15");
The initial findElement() is for the DIV that contains the ‘MyController’ Angular reference, and is from the regular Selenium2 API. The ‘angularModel’ method is a Selenium2 locator yet to be written for Angular interop. The expression within is more or less handed over the wire to an Angular enabled Selenium2 controlled browser, and the lookup is against the model of course. The Angular/Selenium integration piece returns the widget that is bound to that model node. If there is no widget bound directly, then an exception is thrown all the way back to the test script. If there is more than one widget bound directly to the model node, then a different exception is thrown. Use of ‘Quantity’ in formulae would be considered secondary.
I wonder if $filter() as opposed to a more desirable $find(). The former will subset an array to all that match. What we want in this instance is a way to find an single matching element.
View-Centric Angular Locators
Let’s look for something less model-centric in the page, using a hypothetical Selenium/Angular locator that traverses view references.
WebElement cost = webDriver.findElement(id("cart")).findElement(angularView("item.price*item.qty"));
assertThat(cost.getText(), equalTo("$123.45"));
Neat huh? We could find anything Angular managed whether output or input. Of course, Angular’s recommended test idiom is via Miško’s other stroke of genius - JsTestDriver.
Command Line: A Freebie
What if we had a custom control for Angular that allows an effective command line interaction with the current model:
.. snip ..
</table>
</div>
<p class="actions"> <a ng:click="todo()" class="continue">Continue shopping</a>
or <a ng:click="todo()" class="button checkout">Checkout</a> </p>
<div ng:commandline style="width: 500px; height: 100px;"/>
</div>
.. snip ..
We could interact with that using the same model traversal techniques as described above. ThoughtWorker colleagues would love for a command line interface to our “time and expenses” app. It is currently a Rails app, and was previously a pre-Django Python app when it was the test bed for the earliest version of Selenium 1.0. Before that it was a Lotus Notes app. Anyway, it could easily be an Angular app with a command line for expedited time and/or expense entering:
Conclusion
In the race for dominance between Backbone, Knockout, KnockBack, Spine, Ember, Batman, Sammy, YUILibrary, JavaScriptMVC, Broke, Fidel and my current favorite Angular, part of the criteria for winning is going to include:
- Elegant left-to-right style model traversal expressions.
- Selenium2 integration for low effort full stack testing.
- A command line interface :)
Jul 2nd 2012 Update
A Command Line for Angular via a Chrome plugin: Introducing the AngularJS Batarang