Odoo Automation for Quality Testing

Sometimes, a simple commit can break a full workflow. In this article, I’ll show you how to use browser automation tools to quickly detect regressions in your Odoo application. Here’s a teaser of the results for the impatient:

1. The promise of a fully graphical approach

At Trobz, the Odoo integration company I’m working for, we wanted testers with little programming skills to be able to define, run and update tests cases in total autonomy.

Moreover, we wanted these tests cases to be run in the browser, and not using the Odoo XML-RPC or internal API, in order to be as close as possible to the real usage of the application.

The most well-known tool for browser automation is Selenium. I quickly found out that related tools exist to record and run Selenium tests cases directly in the browser :

That sounded like the perfect approach: point-and-click, no code involved. Unfortunately, it’s not that easy!

Let’s take the example of the Create button in the Sales Order list view:

<button class="oe_button oe_list_add oe_hilight" type="button">Create</button>

When we click on this button while recording a test case, the tool will insert a new “click” command:

Selenium IDE in action

To define the “Target” of the click, it will use its “Locator Builders”, ordered by priority. The goal is to look for the best expression that uniquely identifies our button. In our case, the button has no id nor name attributes. That’s why the tool ends up using this XPath expression:


But what happens if we decide to add another button just before “Create”? The test case is broken. Anyway, even if we don’t, XPath expressions are not very human-friendly.

2. How do others do?

Odoo is not simply an ERP software shipped by Odoo SA. It’s an open source project with a vibrant community of partners, clients, individuals or companies, that believe in the value of sharing: code of course, but also tips and ideas.

Diving in the Community mailing list archives, I found out that Andreas Stauder from Brain-Tech had been working on the same problem and pushed some nice code on Github.

2.1. Add context in HTML code

First, he created the web_selenium Odoo module to generate semantically richer HTML elements. By using Odoo’s template extending mechanism, this module adds the model’s name as an attribute of each related elements.

In the case of our Create button, here’s what happens:

  • on the template side:
    <t t-extend="ListView.buttons">
        <t t-jquery="button.oe_list_add">
            this.attr('t-att-data-bt-testing-model_name', 'widget and widget.getParent and widget.getParent().dataset and widget.getParent().dataset.model');
            this.attr('data-bt-testing-name', 'oe_list_add');
  • which translates in the DOM as:
<button class="oe_button oe_list_add oe_highlight" type="button" data-bt-testing-name="oe_list_add" data-bt-testing-model_name="sale.order">Create</button>

2.2. Abstract the messy part

Second, Andreas created odoo-robot-framework repository, providing us with a set of Keywords to be used with the Robot Framework.

Robot Framework is a Python-based, extensible keyword-driven test automation framework. Once installed with the Selenium2library, you can write web application tests cases expressed as a simple suite of keywords.

Let’s create the Login keyword for example, specially adapted for Odoo:

simple.robot download
*** Settings ***

Library     Selenium2Library

*** Keywords ***

Login      [Arguments]  ${login}  ${password}
    Open Browser                        http://localhost:8069/web/?db=v8
    Maximize Browser Window
    Input Text                          name=login  ${login}
    Input Password                      name=password   ${password}
    Click Button                        xpath=//div[contains(@class,'oe_login_buttons')]/button[@type='submit']

*** Test Cases ***

    Login admin admin

Following the same principle, Andreas created the MainMenu, SubMenu, ChangeView, Button, Many2OneSelect, NewOne2Many and many other keywords to be used in Odoo tests cases.

2.3 Glue things together

Finally, Andreas forked Selenium Builder to:

3. Let’s automate one workflow!

It’s now time to get our hands dirty and use Andreas’ work as a starting point. We’ll auto-magically create a Sale Order, confirm it and deliver it.

3.1 Install and config Odoo

The change I did in Odoo 8.0 code is minor but necessary to support a form view shown in a modal window, such as the “Create order line” one:

Create order line

$ git clone https://github.com/nilshamerlinck/odoo --branch 8.0 --single-branch --depth 1

Install the modules sale and stock, with demo data.

Tick “Product properties on order lines” in Settings > Configuration > Sales > Quotations and Sales.

3.2. web_selenium module

Go to a directory set in the --addons-path of Odoo.

$ git clone https://github.com/nilshamerlinck/web_selenium -b 8.0-add_modal_window_form_view_support

In the application, go to Settings > Users, Edit Administrator and tick the “Enable Technical Features” checkbox. Save and refresh the page: an “Update Modules List” entry should appear in the left menu. Process with the update. You should now be able to find and install the web_selenium module:

web_selenium module installation

To check that it’s working fine, inspect the “Create” button from Sales > Sales Orders List View: it should now have a data-bt-testing-model_name attribute set to “sale.order”.

3.3. Odoo Robot Framework

Install Robot Framework first

We’ll install it in a dedicated virtual env:

$ virtualenv venv-robot
$ source venv-robot/bin/activate
$ pip install robotframework robotframework-selenium2library

Robot Framework start-up script pybot should now be available.

Hacky fix on Firefox profile

We’ll use Firefox as the default browser for our tests. Whenever pybot will execute the Open Browser keyword, Selenium will create a brand new Firefox profile. This creates a small unconvenience: since version 32, Firefox doesn’t allow pasting in the console unless you explicitly activate it.

Warning on pasting

Trust me, when the test fails, it’s very convenient to just open the console and start debugging with copy-pasted XPath expressions. Typing “allow pasting” each time will get you mad. In order not to have to, we’re going to add a new parameter directly in the preferences template venv-robot/lib/python2.7/site-packages/selenium/webdriver/firefox/webdriver_prefs.json:

  "frozen": {
    "devtools.selfxss.count": 100,

(if anybody finds out how to this in a more elegant way, let me know ;)

Get odoo-robot-framework

My fork of odoo-robot-framework includes new keywords: BackInBreadcrumb, NewMany2One, NewMany2Many. It also includes our full workflow:

demo_8_0_full_workflow.robot download
*** Settings ***

Documentation  Full workflow demonstration
Resource       odoo_8_0.robot

*** Test Cases ***
Valid Login

Create Sale Order
  MainMenu   Sales
  SubMenu    Sales Orders
  Button    sale.order    oe_list_add
  Many2OneSelect    sale.order    partner_id  Agrolait, Thomas Passot
  Date    sale.order  date_order  08/30/2015
  Char    sale.order  client_order_ref  AGR001
  NewOne2Many    sale.order    order_line
  Many2OneSelect    sale.order.line    product_id [HDD-SH2] HDD SH-2
  Char    sale.order.line    product_uom_qty    2
  Button  sale.order.line    oe_form_button_save_and_close
  NotebookPage    Other Information
  Select-Option    sale.order picking_policy  Deliver all products at once
  Button  sale.order     oe_form_button_save
  Capture Page Screenshot

Process Sale Order
  Button    sale.order    action_button_confirm
  Button    sale.order    action_view_delivery
  Button    stock.picking    action_assign
  Button    stock.picking    do_enter_transfer_details
  Button    stock.transfer_details    do_detailed_transfer

Crystal clear syntax even for non-coders, right?

Get the repo:

$ git clone https://github.com/nilshamerlinck/odoo-robot-framework -b 8.0-improvements
$ cd odoo-robot-framework

Open config_80.py_template, fill it with the appropriate values and save it as config_80.py.

You’re now ready to run the test suite!

$ pybot demo_8_0_full_workflow.robot