Micro-Framework Context Patterns

In the previous post I talked about communication patterns in terms of the micro-framework and tests. Here I’ll talk about the expressiveness of the tests themselves, showing how Tapestry supports the idea of a context.

This is what we ended up with in the last post:

As I indicated in that last post, one of the challenges that was becoming obvious as we delegated more of the test script to the page definition is that we only had one abstraction or one definition: Veilus. Consider just those “begin_with” and “page_ready” aspects I showed you. Those are relevant when first going to the site, but what about when we get to the Stardate Calculator, as we do at the end of the script?

As with any framework, you eventually run into areas where it can seem like the organizing principle is conflicting with mechanics that the principle itself provides. And it can leave you wondering how to structure your test logic. But a good micro-framework should help you navigate those choppy waters by essentially doing nothing more than giving you options. So let’s paint ourselves into a few of these corners and see how I might handle this with Tapestry.

Modularize Definitions

Rather than me boring you to death with a lengthy explanation of what I’m doing, how about I just trust you to understand the basics? So, first, here’s a breakdown in the script of more definitions.

Take a moment to look that over and orient yourself. Note that these are not all “page definitions” (page objects). The authentication is a form that pops out. The navigation is, similarly, not a page but rather just an element that pulls out from the left side of the screen.

Here you can see how the element definitions are now spread across multiple definitions. This is the way that Tapestry essentially supports the common “page object pattern”, if we want to call it such. But this introduces some challenges to our script. I’ll encourage you to look at the script portion as it is and see why that is. Let’s modify the script like this:

Again, take a moment to orient yourself. Here I’m doing things that I’ve already basically talked about, in terms of how to structure the code. So I’ll count on you to see what I’m doing and why. What should be immediately clear is that I’m simply instantiating pages. Then for each page, I’m calling actions. One thing to note is a slight change to how I’m checking for the “page ready” aspects. That’s shown in these two lines:

Here I’m just appending a “check_if_ready” call to the instantiation of the definition. This is because, right now, I’m not taking any action on those pages. So I can’t use a “when_ready” block like I showed you in the last post. Also note that in the case of Veilus, I’m calling “view” because that’s the scripts first interaction, which means I need to go to the URL. But for the Stardate page, I don’t call “view” because my script will go to the page as a result of its execution.

And that brings up a further point: notice how only the Veilus definition contains a “url_is” attribute. The others do not. You can put a “url_is” attribute on any definition you want if you want to simply go to that page directly. But, in the case of my Veilus application, you actually can’t go directly to the Stardate page because you need to login in first.

Delegate Actions to Definitions

Let’s handle another bit of low-hanging fruit. That’s the idea of wrapping sets of related actions up into a method and putting that method on the relevant definition. Here are some definitions with added methods:

With those in place our test script can become this:

Notice how this is nothing more than a simple abstraction of instantiating one of the definitions and then calling a method on it.

Context Factories

When using page or activity definitions, the context is critical because it’s how your automation script knows where to execute a portion of its logic. In fact, the definition is the context. So the key thing from the script above is that you are always getting a page context that you can operate on.

I purposely reduced the script to these bare essentials to make it clear what a context factory is doing. Since Tapestry encourages you to use a definition pattern (which can be treated as a page object pattern), it accommodates that by providing a factory behind the scenes that will make establishing the context of a page object a little easier and read a little nicer. To do this you have to include the Tapestry factory and then make calls to defined factory methods.

Tapestry provides a factory module for this purpose. Let’s add an inclusion for the factor near the top of the script:

Now let’s change just the test script portion to look as such:

With the Tapestry factory available, you can make calls to a series of “on” context methods. There’s also the “on_view” context method. So let’s break this down a little bit. The “on_view” factory is a context action that says “on viewing the definition that is represented by the Veilus page definition.” The “on_view” part also means that the definition will be called up in the browser, which further means the page definition must have a url_is assertion.

The next lines do not use on_view, but instead use “on”. Why the difference? In those cases, since a page has already been viewed, and thus a browser initialized, the test script just needs to state that the following actions are taking place “on” a specific page. They don’t need to view the page because the previous action is taking the script to the correct spot. For example, after logging in, the navigation becomes available. The on(Navigation) logic is using then going to a Stardate page.

There’s a lot more I could say about Tapestry’s factory pattern but here I just wanted to get you exposed to it.

Expressive Test Scripts

The main point to get out of this is a very simplified test script, that removes a lot of boilerplate, and that allows you to concisely express the intent of your tests by making the context and action being taken very clear. Further, with the delegation of actions to the page objects, your tests become very compressed but without, I would argue, sacrificing clarity. Even further, those elements can, as you see, start to be composed as workflows.

Modular Design Allows Extensions

Tapestry itself does not support workflows directly but, as you can see, its organizing principle is flexible enough to support a variety of expression modes for tests. Further, since Tapestry is a micro-framework, it’s designed to allow other solutions to be used with it. So, for example, if I did want to consider using workflows more directly, I wrote a test_workflow gem that helps with that.

Tapestry is about functional execution in a singular performance context. But it doesn’t report on performance measures at all. Yet if I want to get performance measures while my script is running, my test_performance gem can provide some value there.

Tapestry doesn’t have much to say about data building or providing accessible data. But if I want that, my data_builder or data_accessible gems will provide those mechanisms.

My goal here is not to promote my own tools although it may appear that way. Rather, my goal is to show why I wrote these various tools and, in particular, why those tools can stand on their own but also be incorporated by a micro-framework.

Are We (Finally!) Done?

Well, I think there actually might be value in one more post on this topic. I’d like to do a little breakdown for you of how Tapestry works by referencing specific aspects of its code, more than I have already, and giving you some insight into why the micro-framework was created as it was.


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.