Write your keywords for Robot Framework with Python
Tags: python, testing, programming
Published: 2021-06-24 15:45:04

Here is a story based on my professionnal life:

Me :

> "The Bluetooth communication between our connected frige and our PC-based software is gettting better and better. I have a Python library that handles everything! Now, I would like to write tests for it."

People from my team :

> "You should try Robot Framework, it seems promising!"

Me :

> "OK, let's do this!"

There are a lot of tutorials for Robot Framework on the Internet, but most of them teach you how to connect to a web page to enter your login and password, or to use a REST API to perform 2+2=4 (and I am not exagerating that much 😀). Needless to say that I was far from connecting to my frige over Bluetooth.

In this article, I will explain how you can interface Robot Framework and a custom Python library in order to write automated tests for a connected frige (which is fictitious, I hope you got it). 

## Robot Framework in short

I will let Robot Framework introduce [itself](https://robotframework.org/):

> Robot Framework is a generic open source automation framework. It can be used for test automation and robotic process automation (RPA).
>
> [...]
>
> Robot Framework itself is open source software released under [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0.html), and most of the libraries and tools in the ecosystem are also open source. The framework was initially developed at Nokia Networks and was open sourced in 2008.

Robot Framework is mainly written in Python and the source code is available on [GitHub](https://github.com/robotframework/robotframework).

`pip` is the easiest way to install it as all the packages are available on [PyPi](https://pypi.org/project/robotframework/):

```bash
$ pip install robotframework
```

![rf logo](https://upload.wikimedia.org/wikipedia/commons/e/e4/Robot-framework-logo.png)

## Keywords are everything

With Robot Framework, tests are written with "_keywords_". Keywords (which often look like _keyphrases_) perform actions or checks.

Here is a _test case_ example from the [official website](https://robotframework.org/#examples) :

```robot
*** Test Cases ***
Valid Login
   Open Browser To Login Page
   Input Username    demo
   Input Password    mode
   Submit Credentials
   Welcome Page Should Be Open
   [Teardown]    Close Browser
```

Each line starts with a keyword and is optionally followed by parameters. It is possible to write your own keywords using existing keywords:

```robot
*** Keywords ***
Input Credentials
   [Arguments]    ${username}    ${password}
   Input Username    ${username}
   Input Password    ${password}
```

Thanks to this new keyword, we can write a simpler version of the previous test case:

```robot
*** Test Cases ***
Valid Login
   Open Browser To Login Page
   Input Credentials    demo    mode
   Submit Credentials
   Welcome Page Should Be Open
   [Teardown]    Close Browser
```

Here, I am simply reusing existing keywords. How I can create _trully_ new keywords, like keywords to establish a Bluetooth connection to a fridge and send it a new temperature setpoint?

On the Robot Framework's [official page](https://robotframework.org/), we can read:

> Its capabilities can be extended by libraries implemented with Python or Java.

Great. How do we do that?

## Create your keyword library using Python

I must admit that my first attempts to use my existing Python library in Robot Framework were quite laborious...

 [The official documentation](http://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#creating-test-libraries) is very detailed, but may be hard to comprehend at first sight. Clearly, it's more a _Reference Manual_ than a _Getting Started_. I ended watching [YouTube videos of live coding from an Indian consulting company](https://www.youtube.com/watch?v=pvSWEXzs3Bs&list=PLIMhDiITmNrLD-gOfSN10LuIsq_fXbTjd&index=11&t=0s).

![_True story_](https://media.tenor.com/images/24805223bc5f729f16fe658893de5c54/tenor.gif)

In fact, there are several ways to call Python code from Robot Framework. The simplest solution is sufficient in most cases. It is the one I used for my actual project and the one I will present here. It's called "_Static API_" in the documentation:

> The simplest approach is having a module (in Python) or a class (in Python or Java) with methods which map directly to keyword names. Keywords also take the same arguments as the methods implementing them. Keywords report failures with exceptions, log by writing to standard output and can return values using the return statement.

Few notes:

- Python functions with a leading `_` in their names are hidden in Robot Framework.
- A Python function named `input_credentials()` can be used in Robot Framework as `Input Credentials`, `input credentials`, `Input credentials` or even `input_credentials` (it's up to you).
- if you already have a custom Python library (like me), you probably don't want to directly import it in Robot Framework. Instead, you should write a wrapper library to expose nice keywords, as suggested in this [discussion on stackoverflow](https://stackoverflow.com/questions/27039016/how-to-create-a-custom-python-code-library-for-the-robot-framework).

## A simple example

Here is an example of a Python library that is ready to be imported in Robot Framework. Let's call it `fridge.py`:

```python
import robot.api.logger
import robot.utils.asserts

# Variable that is supposed to be in the fridge firmware
_temperature_setpoint = 5


def _ble_send_setpoint(value):
    """Pretend to send data to the fridge with the new temperature setpoint.

    :param value: the setpoint value
    """
    global _temperature_setpoint
    _temperature_setpoint = value
    print('Sending setpoint to the fridge: ', _temperature_setpoint)


def _ble_read_setpoint():
    """Pretend to read data to the fridge to get the temperature setpoint.

    :return: the setpoint value
    """
    global _temperature_setpoint
    print('Reading setpoint to the fridge: ', _temperature_setpoint)
    return _temperature_setpoint


def change_temperature_setpoint(value):
    """Function exposed as a keyword to change the temperature setpoint.

    :param value: the setpoint value
    """
    value = float(value)
    robot.api.logger.info('Change temperature setpoint to %f' % value)
    _ble_send_setpoint(value)


def check_temperature_setpoint(expected):
    """Function exposed as a keyword to check the temperature setpoint.

    :param expected: the expected setpoint value
    """
    expected = float(expected)
    actual = _ble_read_setpoint()
    robot.utils.asserts.assert_equal(expected, actual)
```

The first two functions simulate a communication with the fridge to read and write the temperature setpoint. A locale variable is used to store the value, simulating the memory of the fridge's processor. As their names start with an underscore, they won't be exposed as keywords.

On the contrary, the last two functions can be used as keywords.

Note how function arguments are converted with the function `float()`. Indeed, arguments are passed from Robot Framework to Python [as strings by default](http://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#argument-types) and this is not what we want. Hence we simply convert those strings to numbers.

Now, let's write a little test case to use our brand new keywords!

```robot
*** Settings ***
Library  fridge.py

*** Test Cases ***
It is possible to change the temperature setpoint of the fridge
    change_temperature_setpoint  4
    check_temperature_setpoint   4

    change temperature setpoint  8
    Check temperature setpoint   8

    Change_Temperature SETPOINT  3.5
    CHECK temperature_SETpoint   3.5
```

As I told you : underscores and letter case are handled very smoothly 😅

Let's save this test to file named `tests.robot` (which will be considered as a _test suite_ named _Tests_) and execute them in the terminal:

```bash
$ robot tests.robot
==============================================================================
Tests
==============================================================================
It is possible to change the temperature setpoint of the fridge       | PASS |
------------------------------------------------------------------------------
Tests                                                                 | PASS |
1 critical test, 1 passed, 0 failed
1 test total, 1 passed, 0 failed
==============================================================================
Output:  C:\...\output.xml
Log:     C:\...\log.html
Report:  C:\...\report.html
```

Robot Framework generates a beautiful HTML report for us:

![report summary](https://dev-to-uploads.s3.amazonaws.com/i/39cp2cgayi4d77edkm6j.png)

For each test, we have all the steps that have been performed, the logs from the Python code, the functions' documentation thanks to docstring, etc:

![report details](https://dev-to-uploads.s3.amazonaws.com/i/ekqwblmnka5xjvof9icn.png)

## The world is yours now

From now on, everything is just about Robot Framework, Python, documentation reading and your imagination. You know the basics to write your own automated tests.

As an example of these countless possibilities, I will show you how you can "automatically" connect to the fridge when a test case starts and disconnect when it ends. Indeed, you must open a connection before exchanging data and you don't want to it manually in each and every test case. It would be painful and error prone.

To do this, I add 2 new functions to `fridge.py`:

```python
def connect():
    """Pretend to connect to the fridge."""
    robot.api.logger.info('Connect to the frige')


def disconnect():
    """Pretend to disconnect from the fridge."""
    robot.api.logger.info('Disconnect from the frige')
```

Then, I configure my test suite so that the setup of each test case opens a new connection and that the teardown closes this connection:

```robot
*** Settings ***
Library  fridge.py

Test Setup      Connect
Test Teardown   Disconnect

*** Test Cases ***
It is possible to change the temperature setpoint of the fridge
    Change temperature setpoint  6
    Check temperature setpoint   6
```

In the test report, I can see that the functions are correctly called:

![report setup teardown](https://dev-to-uploads.s3.amazonaws.com/i/02fv8draofy1rpki98ck.png)

## That's it!

You know how to write Python functions and import them in Robot Framework to use them as keywords.

The easiest solution is to write `my_module.py` and to import it with the command `Library  my_module.py` in the section `*** Settings ***` of your test file. All functions in `my_module.py` which names don't start with an underscore will be available as keywords.

OK, I have to go now: there a fridge asking for its automated tests! 😎