Attaching the application’s state to PageObjects
This is the first in a series of articles on the “PageObject”, which is a software design pattern used in automated testing of graphical user interfaces. See the full series here.
7 years ago William Manley & I created Stb-tester, an open-source tool for testing TV GUIs by looking at the pixels. Stb-tester uses video-capture hardware and image-processing to find particular images on the screen, read the text with OCR, ensure that content is playing without hiccups, and so on. In these articles I hope to share some of the problems we’ve faced and the solutions we have built (or are still building) as we maintain Stb-tester and write tests for our customers. Some of these should be applicable to web or mobile testing frameworks like Selenium, too (with some work).
By the way “STB” stands for “set-top box”, which is your cable- or satellite-TV decoder, or a streaming device like a Roku, Apple TV, or Chromecast. Back in the days of cathode-ray tubes, your decoder would sit on top of your TV set. TVs are too thin for that now, but in the industry we still call these boxes “STBs”.
What is a PageObject?
When you’re testing a web-app with Selenium, a PageObject is a class that uses Selenium APIs like find_element_by_css_selector or send_keys to extract information from the page or to interact with the page, respectively; and it provides (to the test scripts) a higher-level API in the vocabulary and user-facing concepts of the application that you’re testing.
Figure 1: Stb-tester PageObjects (based on Martin Fowler’s diagram)
Similarly, when you’re using Stb-tester to test an app like Netflix on a TV, a PageObject will encapsulate the low-level Stb-tester APIs like image-matching, OCR, and remote-control key-presses.
Stb-tester’s PageObjects attach the application state to each instance
In Stb-tester, when you create an instance of a PageObject, the constructor provided by our PageObject base-class automatically grabs a screenshot from the device-under-test and attaches that screenshot to your PageObject instance.
The properties you’ve defined in your PageObject class (to read information from the page) act on that screenshot. This means that each PageObject instance describes the state of the device-under-test at the time the instance was created.
Figure 2: Each instance is a separate object;
the class defines how the objects behave (what properties they have, etc.)
When I’ve said “state” I mean what page the application is currently showing on the screen, what elements are visible on the page. In Selenium the equivalent state would be a snapshot of the full DOM, rather than a screenshot. There is other state, too, that isn’t visible on the screen (for example: What movies or subscriptions has the logged-in user purchased already, when do they expire, etc). This other state still needs to be handled explicitly by your test scripts.
To recap what’s novel about Stb-tester’s PageObjects:
The constructor automatically attaches the current state of the device-under-test (a screenshot) to the PageObject instance.
Inside a PageObject’s properties (read-only getter methods) most Stb-tester APIs (image-matching, OCR, etc) operate on the PageObject’s screenshot, instead of the live device-under-test.
These properties are evaluated lazily (that is, only when your test script uses them) and cached so that expensive operations like OCR will only be done once per screenshot.
Stb-tester’s PageObjects are immutable
Since each PageObject instance represents the state of the device-under-test at a specific point in time, the instance is immutable. This means that you can’t change the instance’s screenshot; if you want to find out what the latest state is you have to create a new instance (see the example test script in figure 2 above).
If your PageObject has methods that interact with the device-under-test (by clicking, entering text, pressing buttons on a remote-control, etc) then those methods should return a new instance instead of modifying “self” (“this”).
In Stb-tester we called our PageObject base class “FrameObject” because each instance is tied to a single frame of video, but I wish we had just called it “PageObject”.
This way of thinking about PageObjects enables some very useful tooling that I will describe in future blog posts in this series:
- Attaching the application’s state to PageObjects (this article)
- Characterisation tests for faster PageObject development
- Live documentation: Show me the relevant PageObjects for the page I’m looking at (coming soon)
- PageObject-aware record/playback tools (coming soon)
- Re-usable navigation functions parameterised by PageObjects (coming soon)
- Generating test-cases automatically from your PageObjects (coming soon)