Blog post: Applying the "page object" pattern with stb-tester

82 views
Skip to first unread message

Will Manley

unread,
Sep 2, 2015, 8:01:50 AM9/2/15
to stb-t...@googlegroups.com
 
I'm reposting to the mailing list to facilitate commentary and conversation.
 

Applying the "page object" pattern with stb-tester

By William Manley. 02 Sep 2015

Often you will hear us recommend that you should structure your test-packs as a set of library functions, plus test-cases that use those library functions. In this blog post we attempt to make this recommendation more concrete by demonstrating how to apply the “page object” pattern when writing stb-tester test-cases.

Here’s what a test-case can look like when applying the page-object pattern:

def test_that_bbc_one_shows_the_one_show_at_7pm():
    guide = open_guide()
    guide.enter_channel(101)
    guide.scroll_to(time='19:00')
    assert guide.find_selected_programme() == "The One Show"

and this is what it can look like without:

def test_that_bbc_one_shows_the_one_show_at_7pm():
    stbt.press('KEY_GUIDE')
    assert stbt.wait_until(lambda: stbt.match('guide-header.png'))

    stbt.press('KEY_1')
    stbt.press('KEY_0')
    stbt.press('KEY_1')
    time.sleep(3)

    while stbt.ocr(region=stbt.Region(213, 10, 200, 36)) < "19:00":
        stbt.press('KEY_RIGHT')
        time.sleep(1)

    assert stbt.ocr(region=stbt.Region(213, 23, 200, 36)) == "The One Show"

The improvement in understandability is clear. Without the page-object, the meaning of your test-case is buried in the forest of calls to stbt.matchocrpress and wait_until. Using the page-object pattern means that your test-case consists of high level concepts that expose the intent of the test-case. Notice that with the page-object pattern calls to stbt functions almost never appear in test-cases directly.


Based on Martin Fowler’s diagram for Page Objects in HTML UI testing

In this example the page-object is guide. This is an instance of a class that knows how to understand and navigate your set-top-box’s programme guide.

The class might look like this:

class Guide(object):
    def find_selected_programme(self):
        return stbt.ocr(region=stbt.Region(213, 23, 200, 36))

    def read_lcn(self):
        return stbt.ocr(region=stbt.Region(100, 20, 40, 20))

    def enter_channel(self, lcn):
        for x in "%03d" % lcn:
            stbt.press('KEY_%s' % x)
        assert stbt.wait_until(lambda: int(self.read_lcn()) == lcn)

    def read_programme_time(self):
        return stbt.ocr(region=stbt.Region(213, 10, 200, 36))

    def scroll_to(self, time):
        for _ in range(100):
            orig_time = self.read_programme_time()
            if orig_time == time:
                return
            stbt.press('KEY_RIGHT')
            wait_until(lambda: self.read_programme_time() != orig_time)
        else:
            assert False


def open_guide():
    stbt.press('KEY_GUIDE')
    assert stbt.wait_until(lambda: stbt.match('guide-header.png'))
    return Guide()

The class is used by many test-cases, which increases code reuse. It serves as a central place to handle any known quirks of your UI, and as a single place to update if your UI changes.

If you need to test multiple similar (but slightly different) variants of your UI with a single test-pack, this class is the place to abstract over those differences.

Note that I made open_guide a standalone function rather than a method or static method of class Guide. I don’t consider navigating to the guide to belong to the Guide page-object. Rather it belongs to the app as a whole. The rule of thumb is that the methods on the Guide class are only valid to call if the guide is currently visible.

So, using the page-object pattern:

  • Improves the readability of your test-cases.
  • Reduces the maintenance cost of your test-pack by sharing (and thus reducing) test code and providing a central place to apply fixes and updates.
  • Assists with reusing test-cases for testing multiple variants of a single UI.

In [next week’s blog post] we will discuss an extension to this approach called the “Frame Object Pattern” which can improve the maintainability of your test scripts even further.

Thanks
 
Will
---
William Manley
Reply all
Reply to author
Forward
0 new messages