The Dialect Diary – Evaluators

Dialect supports the idea of evaluator methods. These will admittedly be some low-hanging fruit in terms of framework development, but they seemed like a good place to start. This post explains a bit about what an “evaluator” means and how they are used.

Evaluators are methods that can be called on a page or activity definition. That method is passed down to the platform, which contains a driver instance. The driver utilizes the driver specific functionality to handle the action. There are two kinds of evaluators: browser-level and page-level. Browser-level evaluators refer to actions that deal with the state or behavior of the browser rather than a page in the browser. Page-level evaluators refer to actions that deal with the state or behavior of a page in the browser rather than something specific to the browser itself.

All of the logic talked about here is in the feature/page-evaluators branch. (This branch contains both the browser and page evaluators.) So let’s consider how this works. First, let’s start with a basic script:

As a reminder from previous posts, I’m going to be using my slowly evolving Dialogic web app to test these things out. So any scripts are relevant to that application running locally.

I covered most of the details of the above script in the last post. The addition here is the last line, which now has the script visit the URL specified. Consider evaluator.rb, which contains the Evaluator module. This module gets included into the Dialect module which, in turn, means that it gets included into any page or activity definition. You will see the following method in the Evaluator module:

That is the method that is called by the script above. Notice that this method delegates to the platform. The platform is tied to a driver. In this case, the driver is watir-webdriver and so the platform is an instance of Dialect::Platform::WatirWebDriver::PlatformObject. In platform_object.rb, you’ll see:

Now there’s something to notice here: I could have just done the above in the evaluators.rb module. The platform object actually adds a layer of indirection. It sits in front of the actual library calls. Is that good design or simply unnecessary complexity? As I think on it, with the current design goal of Dialect to simply support watir-webdriver, it’s clearly additional complexity. But is it unnecessary? I feel like the platform object concept gives me a bit more freedom to move in the future without restricting any freedom now and, perhaps most importantly, without over-designing around this particular solution.

This ‘visit’ method actually mirrors what method Capybara uses to navigate to pages. Selenium expects you to use a ‘get’ method. The visit() method in Dialect is aliased so that the last statement above could be done like this:

Watir uses goto() to navigate to pages. You can do that as well:

So notice here that I can employ methods that work similar to what you might expect using most popular frameworks. For example, Selenium also lets you use a call and I can (roughly) simulate that with:

With this approach, you can access the underlying Watir driver if you didn’t want to use the platform object:

Note with that last example that the difference is I’m calling a method on “driver”, not on “page”. Also, in the previous post, I mentioned that you can actually access the the underlying driver of Watir itself, which is Selenium. So you could do this:

This perhaps leads to a question: Why would you access the underlying drivers when Dialect provides its own methods? For this simple action, you probably wouldn’t. What these examples show, however, is that the drivers are available if you don’t or can’t use the platform object. For example, let’s say Dialect doesn’t support some aspect of Selenium or Watir operation that it probably should. Rather than wait for that to be included, you can call the underlying drivers to do what you need until such support is added.

Now, a design question raises its head here: is this a good way to do things? Consider all the ways you can get to a page:

  • page.visit(‘http://localhost:9292’)
  • page.navigate_to(‘http://localhost:9292’)
  • page.goto(‘http://localhost:9292’)
  • page.get(‘http://localhost:9292’)
  • driver.goto(‘http://localhost:9292’)
  • driver.driver.get(‘http://localhost:9292’)

Is that perhaps defocusing things a bit? I indicated that part of the reason for allowing the above aliases was to match what popular tool libraries provide. So if you are used to Capybara, for example, and like using a visit() method, you can do that. Similar for Selenium’s get() or Watir’s goto(). Yet, with this, the core API call is visit() because navigate_to(), get(), and goto() are all simply aliases for it.

The other methods in evaluators.rb work pretty much identically to this, from a conceptual point of view. The following is a full script that you can practice with.

Here I require and include RSpec so some elements can be tested. On line 12 I go to a particular page. On line 19 I go to another page. In between those actions, I use the evaluators as part of some tests to make sure things are working. For example, I get the url (line 14). I do that again using the current_url (line 21), which is an alias to accommodate people familiar with Selenium versus Watir. You can see that I also navigate with the browser functionality, such as the “back” logic (line 24) and the “forward” logic (line 30).

I also grab some screenshots, in one case specifying a file name (line 28) and in another allowing a default name to be used (line 33). I should note that the screenshot functionality requires dropping down to the low-level driver and that doesn’t always work the best. For example, with this script, you are likely to see that the entire stardate page was rendered as part of the screenshot. That is not what the page looks like when you view it, since you have to click a checkbox to see some of the screen. Also, the footer on the home page may appear to be misaligned as well. I’ve never been a big fan of screenshots. Your test should have only one reason to fail and if it failed, you should know immediately what that one reason is. The screenshot (should) do nothing more than confirm what you already know. That said, there are times when they are needed so Dialect has to support it.

What the evaluators do is show you the use of the platform object in action. Calls are made to specific actions (methods) that Dialect makes visible to you and then redirects calls to those actions to the platform object, which handles the underlying details.

One thing to consider is that if the underlying driver, say Watir or Selenium, were to change their API, you would not have to worry about that in your tests; if, that is, your tests were using the Dialect frameworks actions. In other words, if Dialect always has you call visit() in order to go to a page, then that would be the case regardless of what Watir or Selenium currently support or support in the future. The onus is on Dialect to make sure that it can always communicate with the underlying driver via the actions that it exposes.

This is really the first experiment to make sure the concept of delegating to the platform object makes sense. While these examples are somewhat simple, they do seem to prove out the concept without adding too much extra complexity.

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 *