Robot Framework Page Objects

I just released a new keyword library for Robot Framework that lets you use the page object pattern with robot framework keyword driven tests and the Selenium2Library. This isn’t the first page object library for robot framework, but I think it might be the lightest weight and easiest to use page object implementation.

Because robot framework is a keyword driven test framework, this might seem like an impedance mismatch. Keywords and objects are two different approaches to testing. I think, however, that this might actually be robot framework’s “killer app”. I’ve been using one form or another of page objects with robot framework for a couple years now and I’m convinced it’s the way to realize the full potential of keyword driven testing with robot framework.

Instant gratification

Before I get into describing how it works, if you want to skip this blog post and go directly to the source, you can find get the code in several ways:

Your tests, without page objects:

If you’re writing a traditional robot framework test against a website using Selenium2Library, your test might look something like this:

*** Test Cases ***
| Login with valid credentials
| | Go to | ${ROOT}/Login.html
| | Wait for page to contain | id=id_username
| | Input text | id=id_username | ${USERNAME}
| | Input text | id=id_password | ${PASSWORD}
| | Click button | id=id_form_submit
| | Wait for page to contain | Your Dashboard
| | Location should be | ${ROOT}/dashboard.html

Notice how there is a lot of overhead and technical details in that test URLs, element ids, waiting for elements to appear, that sort of thing.  It makes it a bit hard to see the logic in that test. The rose is pretty, but there are a lot of thorns.

Your tests, with page objects

Using this page object library, the same test might be expressed like this:

*** Test Cases ***
| Login with valid credentials
| | Go to page | LoginPage
| | Login as a normal user
| | The current page should be | DashboardPage

The fog clears, and what we’re left with looks almost exactly like the acceptance criteria from an agile user story. After all, isn’t that the end goal of using a high level keyword-based test library?

The definition of a page object

Robot framework supports creating keyword libraries as python classes. Even though robot framework tests don’t typically work with objects, it is very convenient to implement keywords as methods on an object. Robot does a nice job of instantiating the class and exposing the methods as keywords when the library is imported.

With this library, a page object is simply a  robot framework keyword library that inherits from a special base class.  This base class is only about 50 lines of actual code, and about that many more lines of documentation, so the implementation is extremely light weight.

Here is the page object definition for the LoginPage from the previous example:

from PageObjectLibrary import PageObject
from robot.libraries.BuiltIn import BuiltIn

class LoginPage(PageObject):
    PAGE_TITLE = "Login - PageObjectLibrary Demo"
    PAGE_URL = "/"

    _locators = {
        "username": "id=id_username",
        "password": "id=id_password",
        "submit_button": "id=id_submit",
    }

    def login_as_a_normal_user(self):
        username = BuiltIn().get_variable_value("${USERNAME}"}
        password = BuiltIn().get_variable_value("${PASSWORD}"}
        self.se2lib.input_text(self.locator.username, username)
        self.se2lib.input_text(self.locator.password, password)

        with self._wait_for_page_refresh():
            self.click_the_submit_button()

The important thing to notice here is that we can encapsulate all of the implementation details of the page (page URL, element ids, etc) within a single class definition, rather than scattering the knowledge across different resource files, test case files, and so on. For example, if an element id on a page changes, you only have to change it in this one class, rather than in all of the test cases that touch that page.

The secret sauce

What makes this more than just a standard keyword library is the keywords provided by the library itself. When you include PageObjectLibrary into your robot framework test you get two additional keywords: Go to page and The current page should be

These two keywords take the name of a page object as an argument. The Go to page keyword will automatically load the LoginPage definition (assuming it’s in a spot where robot can find it), and then use the underlying selenium library to go to the url defined by the page’s PAGE_URL. The current page should be will call a special method to validate the assertion, and fail the test if it discovers the wrong page is displayed in the browser.

Both keywords do one more thing, and this is what make the whole thing work: it adds the given page object to the front of robot’s library search order. That means that once you call Go to page, all of the keywords in that page will automatically be available to your test.

Of course, you can explicitly import the library yourself, but if you have multiple pages with the same keyword (for example, submit the form), robot will typically complain since it doesn’t know how which of the several submit the form keywords you mean, and you certainly don’t want to have to worry about duplicate names when defining keywords.

Putting power in the hands of your technical testers

Robot framework is a terrific testing tool. It is one of the most flexible tools out there, and also one of the most powerful. However, writing complex tests purely with the keywords that come out of the box can be difficult. For example, looping and complex logic is rather cumbersome. Give a tool like this to the developers on your team (they write tests too, right? RIGHT?) and they will quickly grow frustrated.

One of the great features of robot is that you can implement libraries in python for those times when you need to add a lot of logic to your tests. You can bury the technical details in python code, and expose what is in effect an API to your test writers.

When using this page object library, you not only get the power that comes with writing keywords in python, but you also get lower level access to the actual python selenium api via the self.browser attribute of the PageObject superclass, which is a reference to the actual webdriver object. PageObject also gives you references to the Selenium2Library keywords (self.se2lib) and the robot logging api (self.logger). This gives your developers a remarkable amount of power when implementing keywords.

Other page object implementations for robot

When I first started using page objects with robot framework, I was using a library named robotframework-pageobjects. This was a really interesting library that works quite well. I used it at my previous job with great success. It’s what convinced me that the page object pattern can be very powerful even when using a keyword driven test tool.

When I started a new job this past year I wanted something a bit less complex. This other pageobjects library is relatively large, weighing in at around 2000 lines of code.  It also has a slight learning curve because of how it integrated with Selenium2Library, and because of how it was implemented the generated documentation was not to my liking. The nail in the coffin, though, was that the developer abandoned the project. It’s open source, so that’s not a particularly big problem, but because the code is complex it’s somewhat difficult to modify.

In developing this library I decided to take a fresh look at the problem, and came up with the idea of the “secret sauce” as a way to cut down considerably on the complexity of the final solution. I wanted a solution that was simple to implement and simple to maintain, and I think I achieved that goal. Instead of 2000 lines of python code, my library is around 400.

My new team is using a slight variation of this library with tremendous success. The developers find it very easy to use their python skills to write the keywords for each page, and the tests themselves read very much like final acceptance criteria.

Final thoughts

If you’re looking for a way to easily organize your keywords on a page by page basis, want to give power to your technical testers, and give powerful keywords to your less technical testers, you should give page objects with robot framework a try. Try my library, try the other one mentioned in this post, or write your own and let your testers soar.

 

 

Advertisements
Robot Framework Page Objects

4 thoughts on “Robot Framework Page Objects

  1. I am one of the developers on the lib that influenced you. Thanks for the post. We abandoned the project because of the need for python3 support and the fact that many of our devs refused to use the extra layer that RF requires. That said you should check out holmium: https://holmiumcore.readthedocs.io/en/latest/. It’s really nice. We now use this with pure python tests and are very happy with it. Note especially their solution to encapsulating selectors where they grab hold (just-in-time) of the actual WebElement. Also Element hashes are really useful.

    Like

  2. Arun says:

    Hi i have seen your article in stackoverflow.
    I wanted to implement the below code in my project. Can you please provide detailed steps
    for this.

    def click_login_button(self):
    with self._wait_for_page_refresh():
    WebDriverWait(self.browser, 10).until(
    lambda *args: button_element.element_to_be_clickable((By.ID, locator.login_button))
    )
    self.browser.find_element_by_id(locator.login_button).click()

    stacklink : https://stackoverflow.com/questions/40920112/extending-selenium2library-webelement-in-robotframework/40922165#40922165

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s