Organizing Principles for Micro-Frameworks

This is a continuation of my exploration into providing insight into micro-framework creation for automation, using my own Tapestry tool by way of example. The first post set the context and the second post focused on exposing an API. Here we’ll dig into exposing the organizing principle.

A quick note: This post will require version 0.5.0 of Tapestry. Tapestry was at 0.3.0 when I started this series. To get the updated version you can just do this:

gem update tapestry

I’m doing this purposely to show that a micro-framework can be in evolution and still be the basis for showing people how it works, as long as you don’t break that which came before.

What I show you here will, conceptually, likely be nothing new. But what I do want to show is how a popular pattern, called the page object pattern, essentially came to be and show why, despite their being possibly better patterns, it has held on for a long time as a solid method of organization.

Our Initial Script

Let’s start out with a fairly large script here:

Up to line 21, this is pretty much what I’ve shown you in the previous posts. But then, after that, I start off querying and manipulating objects on the web pages. The script goes to the home page URL, as before. Then it logs in, ends up on a landing page, clicks a particular link on a navigation list that it expands, and ends up at a Stardate Calculator page. Along the way I check a few things to make sure that the script is ending up at the right place.

You should find this to work. But it’s a little messy. When things are a little messy it’s usually because we don’t have an organizing principle. Before we get into that, let’s take a look at one aspect of the above script that might have struck you.

Waiting For Stuff

You might notice those interesting “wait_until” statements (lines 33 and 35). I purposely designed that element of the web app to be a little tricky to automate because it relies on a DOM change before it becomes visible enough to interact with. Standard Watir may suggest using one of the following statements:

None of those will work in this context. Tapestry does provide a way to handle this but for now I just wanted to call out one way to do this, which would require you checking some styling. You most certainly don’t want to use “sleep” statements here but, as most of us know, that is a go-to solution for many writing automation. The point I’m going to lead to is that your micro-framework should help people abstract away these kind of issues.

Multiple Locators

You’ll also note on line 36 that I use multiple attributes for the locator. This is because there are two “stardate” links on the page and I want to make sure I click the one on the navigation list that gets expanded. I could, however, have done this with a single locator like this:

Notice in that case I’m not using some XPath — like “//a[@href=’../startdate’]” — but instead I’m using “:href” as a valid locator for link. This is one of the nice things about abstracting yourself from Selenium. Watir provides different locators that make it easier to specify what you are looking for. Tapestry simply allows you to leverage that functionality.

In case you didn’t read the first post in this series, I’ll says that Tapestry is wrapping Watir and that implementation is held by the “browser” attribute. You could argue that all of this is really doing not much more than you could do with Watir. The main difference being that you’re putting “Tapestry” in front of everything. That’s true, up to a point. But that’s where we start to get into the organizing principle. That is where micro-frameworks should prove that they are adding a certain value above and beyond what the underlying libraries are providing.

To that end, I also indicated in a previous post that Tapestry is predicated upon the pattern of using definitions. So now what we’ll do is convert the above script to showing what that actually means.

Page Definitions

Let’s expand our Veilus definition as follows:

What’s added here are element definitions. What those definitions get you is a way to specify what the page “looks like” in terms of what is located on it. Your automated scripts will be able to use this information to drive actions against the browser. I’ll show that in action in a bit but let’s consider these definitions first.

Element Definitions

So a definition — a page definition, in this case — is an organizing principle for storing element definitions. And those element definitions are themselves an organizing principle in terms of providing a consistent pattern for how elements should be specified. That pattern is as such:

selector :friendly name locator

First you specify the type of web element. This name will correspond to what are known as selectors in the web development world. These are largely the same names you would use in CSS files and are what tools like Watir and Selenium use as well.

The next part of the element definition is a friendly name for the element. The friendly name, which must be preceded by a colon, is how you will refer to the element in your test logic. This can be as descriptive as you want. Do note that you cannot use spaces, but you can separate words by underscores. Finally, the last part of the element definition is a locator for that element. The locator tells the browser driver how to find the element on the web page. You have to specify a valid locator type (such as ‘id’) and then the value that the locator will have.

But how do you know what Watir provides for selectors? Try this:

Anything in that list is a locator that Watir recognizes and thus which Tapestry will allow you to use for the “selector” part of the element definition. Incidentally, you might remember from the first post, that I had you try this:

At the time that statement showed you nothing, however were you to try that now with your current script you would find:

open
password
test_gorilla
username
login
notice
navigation
stardate_on_nav
stardate_logo

Hmm, does any of that look familiar? Those are showing you the exact names you used for the friendly names of your elements. What’s happening here is that putting element definitions on a page definition automatically converts those declarations into method calls. This is done using a particular pattern.

Proxy Pattern

Selenium is a low-level API. What I think good automation micro-frameworks do is provide wrappers around those kinds of APIs. That’s what I’m doing with Tapestry. Even more specifically, I’m using a proxy pattern to delegate down to Watir’s API. You can see how this works in my elements method, with this key line:

There I’m getting all of the instance methods that Watir itself makes available. If Watir changes those, Tapestry will be updated automatically. Whatever Watir supports, I support. And keep in mind that Watir uses Selenium behind the scenes. So whatever changes happen to either, I get that support automatically.

Element Objects

One thing to note is that I seem to be using some selectors that are specific to an HTML element, like “text_field”, “link”, and so on, but then I have what seem to be some very generically named ones called simply “element”. Why the difference? Well, first let’s talk about how the difference manifests. Let’s take the element I called “navigation”. Behind the scenes this looks like:

#<Watir::HTMLElement:0x1c8c990ba9621aec located=true selector={:id=>"navList"}>

However, if you actually inspect the element, it’s this:

#<Watir::Paragraph: located: true; selector={element: (webdriver element)}

Which is ultimately just this:

#<Watir::Paragraph: located: true; {:id=>"navlist"}>

Watir has a particular method called to_subtype and when a generic element selector is referenced, it is possible to get the subtype of that element. Tapestry handles this for you behind the scenes. (If you’re curious how, check out the access_element method, which does the work.) So what you see here is that the navigation element is actually of type paragraph. Which means I could have done this for the element definition:

So why might I not do that? In other words, why didn’t I just use the “p” selector? Well, in this case, I used the generic “element” simply because I felt the paragraph element might change (say, to a div or whatever) based on the whim of the designer. But a text field is unlikely to change as readily.

Some people don’t like taking the approach where you use Watir-specific selectors (like “link”) because they feel that with this approach they have to tie the implementation detail of the element into the page definition, whereas using the more generic “element” allows them to abstract away that detail. I could have replaced every single one of those above selectors with the generic “element”.

Test Script Uses Definitions

All these definitions are the organizing principle that Tapestry is putting in front of you. So now let’s change the rest of the test script to use those definitions. Here’s that portion:

Lots of changes here so make sure you modify the script accordingly, paying attention to what you change as you do so.

Here I’m simply calling Watir methods (“set”, “click”) on the objects that represent the element declarations. You’ll find that Watir uses set for a lot of actions, even when you might think you are “checking” or “choosing.” You might wonder why I say “set!” rather than just “set”. This has to do with some changes in how low-level browser drivers, like Watir, are starting to treat the entering of text.

A method with an exclamation is called a “bang method” and, in this case, what that does is have Watir use JavaScript to quickly fill the field with the specified text. This is as opposed to using the driver to send each character of text to the field, such as with Selenium’s send_keys approach. In aggregate, you can get a small set of performance improvements with this kind of approach.

Waiting for Stuff: Revisited

Let’s talk a lines 42 and 43. Remember earlier I said examples like the below wouldn’t work?

Well, Tapestry is providing the dom_updated? as an addition to those. You’ll notice here that this entirely removed my need to be checking for some style on the element. To do this, Tapestry provides certain extensions to its base behavior. One of those is the DOM Observer which is, in turn, utilizing a JavaScript implementation of such an observers.

What this does is provide a JavaScript library-agnostic form of looking for mutation events that occur as part of the DOM. There is plenty information out there about this concept. Two articles that I like are
Using Mutation Observers to Watch for Element Availability and How to Track Changes in the DOM Using MutationObserver.

Any automation micro-framework should be considering this approach, in my opinion.

But notice how seamlessly this all fits together. Consider this line:

Here the “page” is the Veilus definition, which includes Tapestry. The “navigation” is the friendly name of one of the element definitions, acting as a method. The “wait_until” is part of the Watir API for waiting for some condition to be true. The “dom_updated?” part is from Tapestry’s API, or specifically an extension to that API by providing a type of wait condition upon the DOM. And, finally, “click” is using the Selenium API for clicking on an element.

Incidentally, in case you’re not familiar with the “&:” notation of Ruby, what this does is provide a shorthand for a method to be called on something. In this case, this:

would, if expanded out, be this:

But, again, implementation details aside, notice how concise this allows us to make our code. It’s even fairly readable even as an English statement. Line 42, for example, is basically saying “On the ‘page’, ‘click’ the ‘navigation’ element, but ‘wait until’ you see that the ‘dom updated’.”

Organizing Principles Matter

This was, I think, a good start to showing the organizing principles of Tapestry. You can have page definitions that hold element definitions. Those aspects, working together, provide a concise way to set up script logic. Further, extensions to those definitions can be used to slot in elements that are structured nearly or even completely identically to that provided by the underlying libraries.

In the next post in the series, I’ll work with these organizing principles a little more to show how the API can interact with the principles to provide some nice abilities that keep the overall code base concise yet still expressive.

Share

About 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.

This entry was posted in Automation, Tapestry. Bookmark the permalink.

Leave a Reply

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