Using Mixins with Page Objects

Recently I wrote about an implementation of the Page Object pattern for Robot Framework test cases (see https://github.com/boakley/robotframework-pageobjectlibrary). In short, a page object allows you to encapsulate all of the behaviors of a web page within a class, and then to expose these behaviors as keywords to the test writer.

Consider  a simple “Hello, world” website with a home page that has a “Hello, world” button. A page object definition for this page might expose a “Press the hello button” keyword like so:

from PageObjectLibrary import Pageobject

class HelloWorldPage(PageObject):
    def press_the_hello_button(self):
        button = self.browser.find_element_by_id("hello_btn")
        button.click()

You might use it in a test case like this:

*** Settings ***
Library        HelloWorldPage
Library        GreetingPage
Suite Setup    Login to the website

*** Test Cases ***
Verify the hello button works
    Press the hello button
    The current page should be    GreetingPage
    

This works great, and as you add more pages to your website, each class has the definition of keywords that work for that specific page. It’s all very tidy.

The real world is a messy place

Unfortunately, the real world isn’t that simple. In the real world we don’t have simple pages within just a handful of controls. In the real world a website might have a common header and footer, and it might have some complex controls (rich text editor, calendar widget, etc) that show up on some pages and not on others. It seems that with the page object model there’s going to be a lot of code duplication, since multiple pages might need an “enter a date” keyword.

The solution is to use a “Mixin” class for common keywords and web page components.

Using a Mixin class for shared keywords

Strictly speaking, python doesn’t support mixins. At least, not formally. Python does support multiple inheritance however, and a mixin is really just a special case of multiple inheritance.

Consider the case where our “Hello, world” web app has a header with some common controls. For example, it might have a logout button, and maybe a search field. We could certainly create “logout” and “search the site” keywords in every page that has the header, but we don’t want to have to duplicate code like that.

The solution? A simple class that inherits from object rather than PageObject, and defines the keywords that will need to be shared. Because python doesn’t distinguish mixin classes from normal classes, it’s best to include the word “Mixin” in the class definition so that it’s clear that this class shouldn’t be used on its own, but rather “mixed in” with other classes.

A Mixin Example

Given our “HelloWorldPage” example from earlier in the blog post, we want this page to include keywords for searching and for logging out. We do this by including the mixin before the base class, since the method resolution order in python goes left-to-right and we want python to find keywords in the mixin before it finds them in the base class:

class HelloWorldPage(HeaderMixin, PageObject):
    ...

Now all we need is to define HeaderMixin. Because this is a mixin and not a full-blown class definition, we can inherit from object rather than PageObject or some other class, which helps eliminate some of the complexity when doing multiple inheritance.

We can still use all of the attributes available in the base class, however, since this class will never be used on its own. We know that any objects that are created from classes that use the mixin will ultimately inherit from PageObject.

For example, since HelloWorldPage inherits from PageObject it will have attributes such as self.se2lib, self.browser, and others. We can use those in our mixin since the new keywords are “mixed in” with the actual page object:

class HeaderMixin(object):
    def search_the_site(self, search_string):
        """Search the website for the given string"""

        search_element = self.browser.find_element_by_id("search")
        search_element.send_keys(search_string)
        search_element.submit_form()

That’s all there is to it! Now, for every page on the website that includes the search widget, you can use the HeaderMixin to give the tester access to that keyword for that page. It really is just that simple.

Summary

The page object library was designed to be lightweight, simple to use, and simple to learn. Just because it is simple doesn’t mean it’s not powerful. By organizing your code as base classes for each page, and using mixin classes for shared components, it becomes very simple to build powerful keyword libraries without writing a lot of duplicate code.

Advertisements
Using Mixins with Page Objects