I recently listened to a Twitter Space on Page Objects v Screenplay. It was a good discussion, and many good points and ideas were shared. Subsequently, I tweeted that the session had reinforced my belief that an automation stack is appropriate for many automation implementations. It is worth checkout that Spaces session and the tweet thread.

If you make it far enough in the thread, you’ll see my friend and colleague Titus Fortner tweeted that he didn’t understand my distinction between behaviors and actions. He then lays out some framework needs. After reading that, I realized that my original blog post omitted a concept that I discuss in my conference talk about an automation stack and that we are using terminology differently. I decided to write this follow-up to my previous automation stack post to perhaps fill in some blanks.

Why Talk About a Stack Instead of a Framework?

Something I noticed in many of the framework descriptions from the Twitter thread, was that things like “assertion library” and “Rspec” were mentioned. My conceptual stack doesn’t include these things; I intentionally leave them out.

Why Do I leave them out? Mainly, for portability. Though I’m a consultant, a large part of my client interactions are implementations (yes, I do still write code and I still enjoy it). As such, I work with many clients in many different contexts. I need a model that facilitates specific implementations but still guides the structure of those implementations. By not prescribing specific technologies, I have the flexibility to make a context-aware recommendations to clients. By not binding the conceptual stack to a specific “testing framework”, say, NUnit, XUnit, or Rspec, I have more latitude regarding framework specifics and choosing an appropriate programming language.

Additionally, not binding the stack to a framework at all, at least not at the lower levels of the stack, we can then use appropriate layers of the stack to create automation that is not based on test cases or test scripts; I label this kind of “non-traditional automation” automation assist. I’ve created tools such as an “e-comm order pumper” and “random link clicker” that provided value to testers even though none of the tasks these tools performed could be considered “test cases”, at least not in the traditional sense. Because I had independence from the framework part, i.e., Rspec, MSTest, etc., I could reuse the appropriate layers of the stack without being bound by, and perhaps limited to, the capability of those frameworks.

From a practical standpoint, some part of an implemented (as opposed to conceptual) framework typically gets bound to a testing framework. Generally, we’re not creating reference implementations; we are trying to provide value to real people and binding to an appropriate technology often provides value. Delaying that binding until implementation time can give greater portability to the stack implementation. Further portability can be achieved by implementing the binding behind a shim, façade, interface, or other appropriate feature of the chosen programming language.

Onto The Clarification: Actions and Behaviors

To attempt to increase the understanding of the differentiation I make between actions and behaviors, I want to refer to what I call the core of automation: stimulus, response, and check(s). In the conceptual stack the actions layer provides the building blocks for this core:

  • Locate the thing with which you want to interact, if necessary.
  • Issue a stimulus to that thing (click, type, POST, etc.).
  • Obtain a response to that stimulus; this may require waiting.
  • Perform one or more checks, assertions, etc. of the response.

In the conceptual stack, this level of granularity is the Actions Layer; each of “locate”, “stimulate”, and “check” is an action. As mentioned in the original stack blog post, working at the Action Layer might look like this


or this

HttpClient.GetAsync(requestUri).ConfigureAwait(continueOnCapturedContext: false);

Note that these are rather granular activities for test automation and programming in general. Since automation is programming, we should follow appropriate programming paradigms such as encapsulating code for reusability. That’s where the Behavior Layer comes in.

Behaviors are reusable sequences of actions and other behaviors. Going from the previous browser-based example, two possible behavior layer methods could be

void LogMeIn(string email, string pw)

void GoToLoginPageAndLogMeIn(string email, string pw)
    LogMeIn(email, pw);

Notice in the code above that LogMeIn() is using capabilities from the action layer; behaviors are made up of reusable sets of actions. Also, notice that GoToLoginPageAndLogMeIn() calls into both the actions layer and reuses a previously defined behavior. Essentially, we are applying basic encapsulation and abstraction principles to test code.

To reiterate, I’m being conceptual with this automation stack. I have a conference talk that explains this stack concept and gives real-world instances where it was used and provided value across multiple different application contexts, where it was used in traditional test automation, and where it was used for automation assist. In most of my real-world cases, the implementations of the stack differed, but the layers were either the same or similar. Differences in layers were influenced by the specific organization’s context such as the application being tested or the “raw tool” being used. Context must influence the definition and implementation of each layer of a stack. Some organizations may need only four stack layers, others might need seven layers. If each layer has a valuable purpose and the stack has appropriate stewardship, a specific stack concept can provide value in a specific context. The stack I present has served me well across multiple contexts and can be a great starting point for other teams.

Like this? Catch me at an upcoming event!