The Dialect Diary – Platform Objects

Dialect will use the concept of a platform object. Here I’ll explain what that means.

Everything I talk about here is stored in the feature/platform-object branch. Do note that I won’t be releasing a gem every time I cut a branch, so if you want to play along, the easiest way to do so is to clone the particular branch I’m talking about and then from within its root directory execute the following command:

rake install

Perhaps the first thing to note, but not dwell on, is that a unit test framework is established. I don’t necessarily believe that unit testing is the be-all, end-all and I certainly don’t believe that 100% unit test coverage is necessary. However I do believe that any framework should have some provision for testing itself. Dialect will be tested with RSpec. Hooks are in place to provide code coverage measures (using SimpleCov) as well as continuous integration (using Travis).

This is obviously not necessary for the test framework to function but it does follow some good development practices and if you want someone to use your framework, it doesn’t hurt to show that you test it and can deploy it fairly easily.

Design Approach for Platforms

My goal for automated test design with this framework is that you will define a simple test harness that utilizes Dialect. You can use Dialect directly as an automated testing solution or you can use it with other tools such as RSpec, Cucumber, or my own Lucid tool.

To actually perform any automated testing, of course, you will have to use a driver library. The initial focus will be to hook into browser driver libraries in order to test browser-based applications. The browser driver libraries that are currently supported by Dialect will be one: Watir-WebDriver. For the most part, Watir-WebDriver is just a friendly API around Selenium-WebDriver, so, for right now, I plan to support Watir-WebDriver directly and Selenium-WebDriver indirectly.

A design goal for the Dialect framework is that it will allow you to describe your application in terms of activity and/or page definitions. Those definitions can be referenced by test libraries using the DSL that Dialect will ultimately provide. The concept of “activity definitions” and “page definitions” are conceptually similar if not identical to the concept of page objects, which, as a concept, was formerly known as the window driver pattern.

Platforms and Definitions

The operating logic of Dialect is going to be “a definition using (or associated with) a driver.” What does that mean? Dialect will exist as a module that is incorporated into the aforementioned page definitions and/or activity definitions.

“Page definitions” are essentially going to be classes that provide the template for the elements that exist on a given web page. The page definition is turned into a page object by Dialect. “Activity definitions” are classes that not only provide the template for elements on a page but also actions that take place within the context of that page using those elements. I should note that page and activity definitions can be utilized regardless of your front-end. So, for example, they have relevance even during headless testing. They have relevance as well if the actions you are taking are in the context of an API or web service.

The reason for a focus on these definition concepts is that when “page” and “activity” definitions work together, then as a framework designer you have the potential to support workflow definitions. Put another way, one goal of Dialect is going to be to allow you to compose pages and activities to make up workflows.

The Platform Implementation

Before I get too far into my design I want make sure that my notion of a platform object makes sense. So here’s the idea (and this is in the commit message for this branch): platform objects hold a reference to the execution driver. More specifically, a driver library can be sent to an instance of a class that is including Dialect. The class that Dialect is mixed in to is, remember, the page or activity definition. That particular definition can then reference the driver via the platform object. The platform object is what essentially links the framework itself to definitions.

I mentioned that the operating logic is “a definition using (or associated with) a browser driver.” You will have a page definition (a class) that represents a facet of the application that can be interacted with. A “facet” here can refer to a complete web page or just part of a page or even an abstract concept, like a workflow. Whatever that class represents, it will incorporate the Dialect framework. Here’s a very simple example:

Here I have defined a page definition (or page class, if you prefer). This definition, DialectTest, indicates that it will incorporate the Dialect framework by including it. This means any standard Ruby classes can “mix in” the Dialect framework. Now let’s add a little more:

Here I’ve established a driver by instantiating Watir-WebDriver. Note that I’m not requiring watir-webdriver in the test script. Since Dialect supports it “out of the box,” as it were, the watir-webdriver library is already included for you when you require the Dialect gem. That also means the selenium-webdriver library is included as well, since Watir uses Selenium as an underlying driver.

I then pass this driver instance to a new instance of the page definition. Admittedly this is pretty simple stuff at this point, but it’s key to a design direction that I believe is viable: with the above in place, when any test scripts want to work with this page definition, a specific “page instance” will be created and associated with the browser instance (and thus the browser driver). And remember the browser and the driver are wrapped up in a platform object. The platform is what Dialect uses as its interface to test scripts.

What you end up with is a situation where “web elements on a given page are used by Selenium running against Firefox” or “web elements during a certain activity are used by Watir running against Internet Explorer”. The platform object is ultimately what makes those statements a reality by providing the context necessary. What does “used by” mean in those phrases? It means those elements are being referenced or accessed by Selenium or Watir. But what’s telling Selenium or Watir how to reference or access those objects? Well, ultimately this will be your test script. And your test script — which says what to do — is delegating the responsibility of how to do it to the Dialect framework, which communicates with the driver libraries on your behalf.

Dialect does have the ability to trace some of its execution. This is currently handled as an environment variable called DIALECT_TRACE. If you make sure that environment variable is set in your command line instance, and you run the above script, you will see output like this:

* The driver watir_webdriver refers to platform Dialect::Platform::WatirWebDriver.
* Class DialectTest is now using the Dialect.
* Dialect attached to driver:
    #<Watir::Browser:0x..fcd62c462 url="about:blank" title="">
* Dialect platform object:
    #<Dialect::Platform::WatirWebDriver::PlatformObject:0x38c1f30
      @driver=#<Watir::Browser:0x..fcd62c462 url="about:blank" title="">>

You can see that a driver identifier called “watir_webdriver” is associcated with a particular module. If you look at the platform.rb file, you’ll see at the bottom that it requires platform_watir.rb. That module self registers with Dialect when it is executed. You can see that, theoretically anyway, this would make it easy for me to incorporate different platforms in the future. The platform.rb would simply require a specific “platform_*.rb” module. That module would in turn register itself.

Do note that each platform module is responsible for creating a platform object (via the appropriately named create_platform_object method) and is also responsible for providing a works_with? predicate method to make sure the appropriate driver is the one that is used with that platform.

You can see from the trace output above that Dialect has attached to the particular dirver and the platform object contains that driver as an instance variable called, appropriately enough, @driver.

Handling is in place to make sure that any driver that Dialect does not support cannot be used. For example, you could change the above script driver as such:

In this case, were you to execute the above, you would be told via an exception that Dialect cannot create a platform object for that driver. That’s essentially because there is no platform_selenium.rb module available that registers itself. Given that earlier I said watir-webdriver utilizies selenium-webdriver, the fact that the above errors out may surprise you. The reason is that Selenium is an underlying driver for Watir, as it is for Capybara. In order to use Selenium via Watir — in other words, to drop down to that layer — you have to reference the driver of the browser driver. For example, consider this:

You would find that the output would be:

<Selenium::WebDriver::Driver:0x38cb6b0>

And here’s where I come to a design decision, but not one I have to make right now. I could support Selenium directly by including a platform_selenium.rb module. Alternatively, since I’m focusing on Watir, I could make the driver.driver more exposed as part of the Dialect API. I’m not sure which approach is better yet and, since I’m not, I’ll just go with what I know.

In any event, since platform objects are the basis for how everything in Dialect is ultimately going to work, what I have provided here is the simplest possible solution that showcases the idea.

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 Dialect. Bookmark the permalink.

Leave a Reply

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