Evolving Automation with Symbiont

Symbiont is my open source test framework, distributed under the MIT license. This post will provide a quick tutorial on how you can evolve your script from a pure watir-webdriver implementation to one that is driven by Symbiont.

To follow along, make sure you have installed the Symbiont gem. You might want to make sure you have a local copy of Symbiote, which is a sample web site that you can use when testing Symbiont. Once you have everything you need, create a script called test-symbiont.rb.

To start, let’s use the Symbiote stardate calculator page. On this page, you can enable a stardate form which will let you convert a valid stardate for a given “era” in Star Trek history. This page has a lot of elements on it. First I’ll show you can use watir-webdriver directly to test this page. I’ll then gradually evolve this example to utilize more of Symbiont. Here’s the script to start with, which does not use Symbiont at all:

Notice how the test conditions, the data conditions, and the page element details all swirl together in one script? You could, of course, construct a few helper methods to make things a little cleaner but a profusion of such methods does not necessarily help as you deal with more aspects of your application. Another way you could handle this lack of separation of concerns is to utilize a design pattern. A common, and effective one, is the page object pattern. The idea here is that pages provide the context for a web site, just as activities would for a web service. A page definition is meant to represent an actual page that will show up in a browser.

What Symbiont does is provide a minimal framework around watir-webdriver that provides convenience methods if you adhere to certain patterns. Those patterns are page object, data builder, and factory. This is all done without removing your direct access to watir-webdriver, should you need it. Further, Symbiont proxies its convenience methods to the watir-webdriver API so that any familiarity you have with watir-webdriver can be leveraged in Symbiont.

Let’s apply the patterns and see how they work.

Page Object Pattern with Symbiont

Here is a revised version of the above script, which uses Symbiont and the page object pattern:

Instead of requiring watir-webdriver, the script now requires symbiont, which provides a wrapper around watir-webdriver.

The Stardate class is a page definition. A page definition provides the specification for that page so that Symbiont understands how to work with the page itself and anything contained on or in it. Symbiont is attached to the class (line 6), which makes the class a page definition rather than just a Ruby class.

Each page definition will have declarations. These declarations will describe the contents of the page or aspects of the page.

The page definition contains assertions declarations: url_is, url_matches, and title_is.

  • The url_is allows a script to know how to navigate to a page.
  • The url_matches allows a script to check if a page (once rendered) contains an expected URL. (This can be a literal string or a pattern.)
  • The title_is allows a script to check if a page (once rendered) contains the expected title. (This can be a literal string or a pattern.)

The page definition also contains element declarations. Element declarations say what elements exist on a page, how they will be referred to in a script, and how they will be found on the page. The above definition says that I’m declaring the Stardate page will have a checkbox, a radio, a button, and two text fields. There may well be more elements on the page, but I have declared the ones I will be working with. Taking one of those text fields as an example, I have indicated that I want to call it ‘calendar’ in my test scripts. When I refer to that “friendly name”, I want the element to be found by looking for the element with an id attribute of ‘calendarValue’.

Creating the Page Object

The page definition is turned into a page object in line 24. Prior to that, in line 19, the call to symbiont_browser establishes a @browser instance variable. That browser instance is passed to the page definition instance. The page definition itself is stored in the @page instance variable.

The @browser instance would be more obvious if you created it like this (which you can do):

That is essentially what symbiont_browser is doing for you behind the scenes. The default browser is Firefox but you could specify another one, like this:

The @browser and the @page variables can tell you a lot about what is available to you.

This will tell you what is immediately available on your page:

What this shows you is that methods that you can call correspond to the element declarations you made. You can see, for example, ‘calendar’ and ‘calendar=’ methods, which correspond to one of the elements I declared on the page definition.

The following will tell you the above along with everything that Symbiont provides to a page instance:

Finally, this will show you what the browser driver exposes for your use.

This is specifically showing you how the methods that the underlying watir-webdriver is providing.

This distinction between @browser and @page can matter. As an example of using the browser directly, you can see that two statements are used control the size of the browser and where it is located (lines 21 and 22). The final line of the script uses the @browser to close the browser instance. So, in general, the @browser controls aspects that deal with the browser itself, whereas the @page is used to deal with pages in the browser.

Using the Page Object

A view method is then called on the page definition instance. The view method works by looking at the url_is assertion defined on the page definition and going to that url in the browser. Calling view without a url_is assertion would lead to an error.

Lines 29 to 32 show the @page being used to refer to elements on the page. Consider how this works with the checkbox element declaration:

Symbiont creates a method called enable. In other words, Symbiont creates a method on the page object for each friendly element name you provide. Those will return WebDriver element objects. Consider this call:

Here calling the enable method on the @page object returns the following instance:

<Watir::CheckBox selector={:id=>"enableForm", :tag_name=>"input", :type=>"checkbox"}>

This means the element declaration is proxied to the Watir-WebDriver element object that corresponds to the element indicated by the declaration. This allows you to call all Watir-WebDriver elements for that particular object (Watir::CheckBox) on your friendly name (enable).

Compare and contrast the two approaches to interacting with the checkbox:

In fact, compare the entire set of actions that manipulates the elements on the page in a side-by-side comparison:

You can see how Symbiont is allowing the tests to be a bit more expressive, removing implementation details and storing those details in a common place (the page object) to concentrate aspects that might need changes in one location.

There is a way to even further encapsulate logic in the page definition.

Action Methods on Page Objects

So far the page object holds information about the state of the page: the elements that it contains and how those elements can be found. It’s left to the script to then use those elements in various ways. You can, however, put methods on the page object. Here’s an example of the page definition from above but with the addition of some action methods:

You can see the addition of methods convert_tng_date, date_should_contain, and date_should_be. With these action methods in place, the script portion then becomes this:

Here you can see that the specific element interactions, such as setting a checkbox, getting the value of text fields, is relegated to the action methods of the page definition. What you do in your script is call those action methods as appropriate. This frees up the test script to simply call those actions, passing them data when necessary. This also allows the script to be more expressive of what it is doing without worrying about how. What this means is that if the implementation details of how to “convert a tng date” change, the scripts will remain as-is; the only change will be to the convert_tng_date action method.

To compare the difference, let’s just look at the executable portions of the previous script with this one:

There’s still the issue of having to use that @page instance variable. There’s also the case that when you create a page object you have to pass it the @browser. There is a way to remove some of those details as well by using the factory pattern.

Factory Pattern with Symbiont

To use the Symbiont Factory you have to make it available to your scripts. This is a one line addition at the top:

The only addition here is line 4, which is the inclusion of the Symbiont Factory module. The page definition does not change at all. What does change is the script portion. Here is the full executable part of the script:

Here the on_view and on calls are factory methods. They create an instance of, in this case, the Stardate page definition. The @browser is automatically passed to the definition and a @page instance variable is being used behind the scenes. So functionally this script is exactly what you were doing previously.

One thing I want to make sure is clear is that this one line:

now accounts for the previous three lines:

In other words, the call to on_view instantiates the page (Stardate) and assigns it to @page, automatically passes the @browser to that @page, and calls the view() method on the @page instance. The call to verified? is simply called on the return value of on_view which is in fact the @page instance.

Here’s a comparison of just the executable portions of the previous script and the current one:

What Have You Learned?

What this post should have shown you is the gradual evolution from a pure watir-webdriver script to one that is using Symbiont. You may in fact feel a bit underwhelmed in that it may not feel like Symbiont is adding much. In some ways, there is truth to that. Symbiont is not trying to replace Watir-WebDriver or make it harder to use it. Symbiont is simply providing a convenience layer on top of it, allowing test scripts to be more expressive.

Beyond being more expressive, Symbiont is attempting to ensure that certain patterns are easy to use (page object, factory) so that you will in fact use them in your scripts. These patterns tend to make for more robust automation. One goal of Symbiont is to make it seem like those patterns are almost built in to watir-webdriver.


This article was written by Jeff Nyman

Anything I put here is an approximation of the truth. You're getting a particular view of myself ... and it's the view I'm choosing to present to you. If you've never met me before in person, please realize I'm not the same in person as I am in writing. That's because I can only put part of myself down into words. If you have met me before in person then I'd ask you to consider that the view you've formed that way and the view you come to by reading what I say here may, in fact, both be true. I'd advise that you not automatically discard either viewpoint when they conflict or accept either as truth when they agree.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.