Page objects in Ruby

Your UI is a domain, too.

Because “clicking a button” should not exist in your domain language.

When we write tests, we want to be able to write them in a way which is relevant to the problem the application (or sub-component) solves, rather than how it is implemented. So, if you get an urge to reimplement natural numbers, then you want to avoid testing them in terms of the objects under the hood for several reasons. A big one being that the tests will be coupled to the implementation, meaning that they will likely break if you change how the code works.

Rather than that, it’s almost always best to test a module in terms of how it interacts with the outside world, and preferably in terms of how it is used by the client code. No doubt this is old hat for a lot of readers, but let’s see how this applies to system tests for a web-based application.

During the course of writing an experimental project, I needed to write some tests to describe importing a deck of cards:

So, this tries to tell us a story about what a user would do, albeit with a few concessions to the fact we only expect developers to read it. In this case, we import an flashcard deck called hangul_anki (Anki being the source of the data), we verify we have enough cards, and that we find the data we expect in each of the fields for the card.

This kind of approach has a few benefits, such as looser coupling between the tests and the markup (so it’s harder for UI changes to break your tests). Also, because you’ve defined an abstraction over your application, It is a lot easier to re-use code between tests of different features.

On the other hand, it’s still a bit more code that you need to maintain, and you need to have confidence that your driver code isn’t faking results, which we can obtain by making the driver code do as little as possible.

For the driver code, I’ve chosen a design which slightly resembles the repository pattern, but the returned objects (importer and card in this case) are stateless, and tie back to a central capybara driven browser session.

The main code for the page objects is to be found in spec/review_browser.rb. Every time you request a page from the top-level ReviewBrowser object, we tell the browser to do something, and then parse the resulting page:

And so, when we visit the page containing the list of cards, and we want to verify how many items there are; we use expect(card_list).to have(41).cards where CardEditorListPage#cards (apparently, naming isn’t my strongest skill) is defined as such:

Where the Page class is mostly just:

So, really, we’re just using plain old-fashioned ruby to write an API wrapper for our application, that allows us to keep our tests isolated from the details of the implementation, and hence help make our code easier to change.