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

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.

 

 

Robot Framework Page Objects

Robotframework Hub 0.9

I just released version 0.9 of the robot framework hub. It always feels good to push out a new release of an open source tool. This is a pretty tiny release, though, and long overdue. Thank goodness others submitted pull requests to motivate me to make a new release.

I switched jobs about six months ago, and with that job came a new set of responsibilities which has left me much less time to work on my open source projects. I’m still committed to them, it’s just that I don’t get to work on them as part of my day job so I’m falling a bit behind. If you’re a user of robotframework-hub I want you to know I’m still working on it, It’s just that the work isn’t part of my day job and I have a lot of irons in the fire.

–bryan

Robotframework Hub 0.9

Support for space-separated plain text robotframework files in brackets

I finally pushed the bits that allow the robot framework extension for the brackets editor to support the space-separated format. That format seems to be the preferred format for a lot of people, so hopefully a few more people will start using brackets. This feature is available starting with version 1.2.0 of the extension.

Here is a screenshot showing the robot framework acceptance tests. The screenshots are from OSX, but it works just fine on windows too.

space-format-1

My team doesn’t use the space-separated format, so this code hasn’t gone through very must testing. If you use the space separated format and notice some bugs or unexpected behavior, please submit a bug report here: https://github.com/boakley/brackets-robotframework/issues

Support for space-separated plain text robotframework files in brackets

Introducing robotframework-lint support in brackets

I’m happy to announce that the robot framework extension to brackets now includes integration with robotframework-lint.

brackets-rflint-integration-1

The robot extension for brackets now has a very robust set of features:

  • syntax highlighting
  • code folding
  • smart tab key for inserting pipes
  • run tests from within the editor
  • quickly select contents of a single cell
  • autocomplete and inline documentation via robotframework-hub
  • search for keywords
  • lint support via robotframework-lint

Of course, many of the advanced features of brackets are also available when editing robot files, including:

  • multiple cursors
  • split screens
  • search and replace across files
  • customizable themes

Using the linting feature

The use of robotframework-lint is hooked into the standard linting mechanism of brackets, so you can control whether the window of items will appear or not. In all cases, one or more violations will cause a yellow triangle to appear on the status line.

To use the linting feature you simply need to define how to run robotframework-lint. Do this by selecting “Robot Settings…” from the “Robot” menu, which will display a dialog allowing you define how to call rflint.

You can include any rflint arguments that you want. The current file being edited will be added at the end of the command. The easiest way to do this is to put all of your options in a file, and reference that file with the -A/–argumentfile option.

For example, if you have an argument file named /Users/bryan/rflint.args, you can change the settings to be rflint -A /Users/bryan/rflint.args (note: you cannot use ~ to represent your home directory; you need to use an absolute file name)

Once it has been configured, robotframework-lint will be called every time you save a .robot file. If rule violations are detected they will appear at the bottom of the window. Clicking on an item will move the cursor at or near where the violation was detected.

More information on robotframework-lint

For more information about robotframework-lint see robotframework-lint wiki

Acknowledgements

A huge thank-you to my current employer, Echo Global Logistics who generously allows me to work on this project as part of my day job.

Introducing robotframework-lint support in brackets

Lint for Robot Framework

I’d like to introduce a preview release of a new tool I’m developing called robotframework-lint.

Robotframework-lint, or rflint for short, is a lint-like static analysis tool for robot framework plain text files. For a given test suite or resource file it will run a series of rules against the suite, its test cases and keywords.

The purpose is to automatically detect certain anti-patterns in your test assets. For example, it can catch duplicate test case or keyword names, flag deviance from local standards such as disallowing spaces in tag names.

A brief example

Consider the following test suite file:

*** Test Cases ***
| Hello, rflint!
| | [Tags] | example tag
| | log | hello, rflint!

This is what happens when you run rflint against this file:

$ python -m rflint hello.robot
+ hello.robot
W: 1, 0: No suite documentation (RequireSuiteDocumentation)
E: 2, 0: space not allowed in tag name: 'example tag' (TagWithSpaces)
E: 3, 0: No testcase documentation (RequireTestDocumentation)

Rflint caught three problems:

  • A warning that there is missing suite documentation on line 1
  • An error saying that there is a tag with a space on line 2
  • An error that there is missing test case documentation on line 3

If you want to ignore the TagWithSpaces error you can control that from the command line:

$ python -m rflint --ignore TagWithSpaces hello.robot
W: 1, 0: No suite documentation (RequireSuiteDocumentation)
E: 3, 0: No testcase documentation (RequireTestDocumentation)

rflint will exit with a zero exit code if no errors were found. Otherwise the exit code represents the number of errors. You can use this in a CI server or commit hook to prevent a test from being accepted if it has any errors.

Interactive help

You can get a list of all of the command line options by using the --help option:

$ python -m rflint --help
usage: python -m rflint [-h] [--error ] [--ignore ] [--warn ] [--list]
[--no-filenames] [--format FORMAT]
...

A style checker for robot framework plain text files
...

You can get a list of all of the currently installed rules with the --list option:

$ python -m rflint --list
'E DuplicateKeywordNames'
'E DuplicateTestNames'
'E RequireKeywordDocumentation'
'W RequireSuiteDocumentation'
'E RequireTestDocumentation'
'E TagWithSpaces'

A simple rule

Rules are python classes that implement an apply method. Depending on the inherited class, a rule may be given a reference to a suite object, a testcase object, or a keyword object.

Here is an example rule that checks for duplicate test case names within a suite:

import rflint.rule as rule

class DuplicateTestNames(rule.SuiteRule):
    '''Verify that no tests have a name of an existing test 
       in the same suite
    '''
    severity = rule.ERROR

    def apply(self, suite):
        cache = []
        for testcase in suite.testcases:
            if name in cache:
                self.report(suite, testcase.linenumber, "Duplicate testcase name")
                cache.append(name)

Adding custom rules

Built-in rules can be found in the rules folder inside the rflint module. Rules are also looked for in a folder named site-rules, which is where your custom rules should go.

Where can I find robotframework-lint?

rflint is available on github: https://github.com/boakley/robotframework-lint

You can also install with pip, which will install the module “rflint” in the standard place:

$ pip install robotframework-lint

Online documentation

Being a preview release, documentation isn’t complete. However, what
documentation there is can be found on the github wiki for the
project, here:

https://github.com/boakley/robotframework-lint/wiki

Want to contribute?

Would you like to contribute? I would love to have help growing rflint
into an indispensable part of the robot framework tool chain. There is
an opportunity to work on the base code, or to contribute
rules. Simply fork the repository and submit pull requests. You can
also ask questions on the robotframework-users mailing list.

Acknowledgements

A huge thank-you to my current employer, Echo Global Logistics
(http://www.echo.com) who generously allows me to work on this
project as part of my day job.

Lint for Robot Framework

Robot framework extension for brackets version 1.0.0

After a bit too long of a delay, version 1.0 of the robot framework extension for brackets is available. Download and install it in the usual way, through the extension manager.

This version brings two significant new features: keyword search, and the ability to run tests right from within brackets. Both of these features are available via hotkey, but can also be started from the new Robot menu on the menubar.

Keyword search

If you are running the robot framework hub, you can search through all of the keywords right from within brackets. From the Robot menu select “Show keyword search window” and it will display a list of keywords along with their synopsis. You can filter the list, and there is a link that allows you to paste the highlighted keyword into the editor.

brackets-keyword-search

Test runner

Version 1.0.0 now includes the ability to run robot right from within the editor. When you run a test, a window will slide up from the bottom with the output of the pybot/jybot command.

The command to be run is configurable. If you include the string %SUITE in the command, it will be replaced with the name of the suite that is currently being edited. For example, if you are editing “smoke.robot”, “%SUITE” will be replaced with “smoke”. This would typically be used with the –suite option.

brackets-runner

Robot Menu

This version adds a “Robot” menu to the menubar, which provides a way to learn the shortcuts associated with the test runner and keyword search.

Bug fixes

In addition to the new features, a few small bugs have been ironed out as well, such as better support for dark themes.

Robot framework extension for brackets version 1.0.0