The API of a Micro-Framework

Here I’ll continue on with the introduction of my Tapestry micro-framework that I started in the first post. This time I’ll focus on a bit on how you want to create an API interface for your micro-framework.

Let’s start with this script:

That’s pretty much what we ended the last post with.

Using the Page Definition

Right now I have code like this:

However, that seems a bit inefficient. If I have many page definitions that become page objects, this means my script has to know the URL and visit it each time. But that means a lot of duplication over my automated logic. It would be better if the page itself knew how to go to the appropriate URL. The automated scripts could then just delegate to the page. Well, that’s certainly possible to do.

Definitions with Attributes

Each page definition can have declarations or assertions on it. Collectively, I tend to refer to these as attributes. These attributes will describe (or declare or assert, if you prefer) the contents of the page or aspects of the page. For this example, you can add a declaration for the URL and then stop using the URL in the test script itself. Here’s the script with those modifications in place:

Notice that the call to visit (which used the URL) is changed to a call to view (which does not require a URL). In truth, neither ‘visit’ nor ‘view’ require a URL. You could change line 21 above to be page = Veilus.visit if you would like.

The url_is declaration allows the page to indicate what its URL is. That’s why you can also think of this as an assertion. The declaration is asserting what the URL of this page is. In fact, you’ll note that I was already checking page.url in one of my expectations on line 24. But I could also check that the actual URL that this action reports matches the URL attribute on the page definition.

Note that the second of those is likely to fail. Why? Because what you will like have his this:

  • page.url = “https://veilus.herokuapp.com/”
  • page.url_attribute = “https://veilus.herokuapp.com”

Notice that lack of an ending forward slash on the url attribute. You could, of course, add that to your page definition but this also shows why I wouldn’t necessarily put too much stock in checking a static URL that completely matches my URL attribute. Rather, I might check for matching elements. I’ll come back to the URL in a bit but, for now, let’s do something similar for the title. Originally I was checking page.title in one of my expectations. But I could also check that the actual title that this action reports matches an asserted title on the page definition. So let’s add the assertion and the check. First the assertion:

Now I can do something similar with the title that I did with the url:

There is a slightly nicer way to have the title checked. There is a has_correct_title? method that can be checked for a true or false value. If this method returns true, that means the page displayed in the browser was found to have the asserted title. Here are some examples of statements you could use:

The last statement there shows an example of how RSpec lets you be flexible with predicate methods, which are methods that end with a question mark. That would not work, of course, if you were using a different expectations library.

You might wonder if you can do the same thing with the URL. You can. But the URL requires an additional declaration to the page definition. The reason for this is because the url_is assertion must be exact. And that’s because it’s used to actually navigate to the page. But sometimes navigating to a page takes you to another page or adds parameters to the URL. In those cases, you may want to match on that specific final URL. So you can add another assertion to your page definition:

With this in place, you can do similar checks for the url that you did for the title, as such:

The url_matches assertion can be any regular expression. So, for a simple but probably unrealistic example, if you want to make sure that your page was always referencing a four digit port number, you could do something like this:

Here’s another example for Amazon:

Amazon has “smile” domain for giving to charities and this allows you to match on that, or on www. or just on amazon.com.

You can also use displayed? as a synonym for has_correct_url? should you want. This check for displayed is really only checking the URL because that’s about the only safe assumption that Tapestry can make. Normally, of course, you would want to be checking for specific elements on a page to determine if what you want is actually displayed.

The Tapestry API Interface

All of what I’m showing you here is part of an interface that Tapestry provides. You can check out the interface source in the Tapestry source to get a feel for how this works. That is essentially a large part of Tapestry’s API. It’s small because, remember, Tapestry is ultimately delegating down to Watir. So I didn’t want Tapestry reimplementing work that was already done.

All of this is admittedly some pretty low-hanging fruit. I had to provide a means to get to a URL and verify some aspects of the page, the url and the title. I also provide some elements to manipulate the browser, such as resizing it, maximizing it, moving it to screen coordinates, and so forth. The interface is relatively well commented and, since it’s an API, I recommend you provide this level of comment when you are creating your own micro-framework.

Unhappy Paths and Situations

I’m taking you somewhat through “happy paths” here but, as a tester, you might wonder what happens if you try to view or visit a page when a url_is declaration does not exist on the definition. What if you try to check the title attribute or the url attribute when those attributes were not specified? Feel free to check that out. For example, in the above script, comment out the url_is assertion and run it.

Tapestry should report a problem message. I wanted to experiment with a situation handler approach. This requires coming up with a standard set of errors that Tapestry will report. This is something I believe micro-frameworks should try to supply: situation handling based on problems during execution. Rather than just a stack trace, do the analysis for the person and provide them with some helpful hints as to what the problem might be.

What’s Next?

A core part of Tapestry’s strategy is the way it gets included in the definitions. I introduced that in the first post and essentially just continued it here. The short explanation can be found by looking at the self.included method in Tapestry. That is essentially what is providing Tapestry’s power to whatever it is included with.

In the first post, you saw we included Tapestry at the top level and then we shifted to including it in the definition. In fact, as the script stands right now, we’ve included at both. So in the next post I’m going to show you how this mechanism works and why it adds value. In particular, I believe this is an important part of micro-frameworks: providing a “mix-in” style approach so that your framework elements are entirely separate from your test logic. Look for that in the next post on this topic.

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 *