Robot Framework supports numerous libraries which can be used out-of-the-box. But what if we need functionality which is custom to our application? In that case, we can write our own custom library. In this post, we will take a look at how we can share keywords between Test Suites and how we can create our own custom Robot Framework library.
1. Introduction
In our previous posts Automated Acceptance Testing With Robot Framework and How to Write Data Driven Tests With Robot Framework, we learned the basics of Robot Framework. We also learned how to write keywords in order to reuse commonly used functionality between test cases. All of this remained in one Test Suite, but what if you have created keywords which you also want to use in other Test Suites? We will explain how to make use of resource files and how to create custom Robot Framework Python libraries in order to share common keywords between Test Suites.
We will continue using the examples of our previous posts, it is therefore advised to take a look at those posts but it is not required if you already have knowledge of the basic Robot Framework concepts. The source code can be found at GitHub.
2. Share Keywords With Resource Files
In order to share keywords between Test Suites, we can make use of resource files. We will start from the demo application we used in our previous posts and transform the Robot Framework test script in order to make use of a resource file. For the demo application (the application under test), we created an employee.py
Python script which allows us to add an employee to a CSV file, to retrieve the list of employees and to remove the entire list of employees. We also created a Robot Framework test script employee.robot
for this.
First of all, we are going to create the resource file with all the keywords of the employee.robot
file. We copy all the keywords from the employee.robot
file to an employee.resource
file which is located in the data
directory. A resource file consists of an identical structure as a test script, but without the Test Cases section and Test Suite/Test Case related items like Setup, Teardown, etc.
| *** Settings *** | | Documentation | Resource for Employee interactions | Library | OperatingSystem | *** Variables *** | | ${APPLICATION} | python3 ../employee.py | *** Keywords *** | | Clear Employees List | [Documentation] | Clears the list of employees | | ${rc} | ${output} = | Run and Return RC and Output | ${APPLICATION} remove_all_employees | | Should Be Equal As Integers | ${rc} | 0 | Retrieve Employees List | [Documentation] | Return the list of employees | | ${rc} | ${output} = | Run and Return RC and Output | ${APPLICATION} list_employees | | Should Be Equal As Integers | ${rc} | 0 | | [Return] | ${output} | Add Employee | [Documentation] | Add an employee to the list of employees | | [Arguments] | ${first_name} | | ... | ${last_name} | | ${rc} | ${output} = | Run and Return RC and Output | ${APPLICATION} add_employee ${first_name} ${last_name} | | Should Be Equal As Integers | ${rc} | 0
In a next step, we copy the contents of the employee.robot
file to a resource_example.robot
file in order to make the necessary changes to the test script for using the resource file:
- We import the resource file in the Settings section by means of the Resource keyword
- We remove all the other keyword related items which have been copied to the resource file like the Variables section and the Keywords section.
| *** Settings *** | | Documentation | Test the employee Python script | Library | OperatingSystem | Resource | data/employee.resource | *** Test Cases *** | | | Empty Employees List | [Documentation] | Verify the output of an empty employees list | | [Setup] | Clear Employees List | | ${output} = | Retrieve Employees List | | Should Be Equal | ${output} | [] | Add Employee | [Documentation] | Verify adding an employee | | [Setup] | Clear Employees List | | Add Employee | first_name=John | last_name=Doe | | ${output} = | Retrieve Employees List | | Should Be Equal | ${output} | ['John Doe'] | | [Teardown] | Clear Employees List
Execution of the test is identical as before.
$ robot resource_example.robot
From the test execution point of view, nothing has changed. It is merely an amelioration for the maintainability of your Robot Framework test scripts and for re-usability of keywords which have been developed by your fellow colleagues. It is therefore also important to generate documentation for the resource files in order to be able to search for existing keywords more easily. This can be done by means of libdoc
:
$ python3 -m robot.libdoc -f html employee.resource employee.html
Several options are available for libdoc
, we only specify the output format to html
by means of the -f
option, followed by the source and target file.
The employee.html
file is the following:
It is important to provide good documentation and examples. This will make it easier to understand the purpose of a keyword and how to use it. It is clear that the documentation in this example needs some attention, we will do a better job when creating the custom library in the next section of this post.
Just as we can generate documentation for our resource files, so we can generate documentation for our Test Suites. We can use the testdoc
utility for that purpose.
$ python3 -m robot.testdoc employee.robot employee.html
Again, this creates an html
file for us with the generated test documentation:
3. Share Keywords With Custom Libraries
Another approach for sharing keywords, is by developing them in a custom Python library. This requires more developer skills than creating keywords in a Test Suite or resource file. Again, we copy the contents of the employee.robot
file, this time to a custom_library_example.robot
file in order to make the changes for using a custom Python library for the keywords. The Python library is located in the library/employee_lib.py
file.
First of all, we are going to remove the Clear Employees List
keyword from the custom_library_example.robot
file and replace the keyword with its counterpart in the library file. We import the library file by means of the Library keyword in the Settings section and add a variable APPLICATION_PATH
containing the path to our application. We will use this variable as an argument for the new library keyword. The test script is changed as follows:
| *** Settings *** | | Documentation | Test the employee Python script | Library | OperatingSystem | Library | library/employee_lib.py | *** Variables *** | | ${APPLICATION} | python3 ../employee.py | ${APPLICATION_PATH} | ../employee.py ... | | [Setup] | Clear Employees List | ${APPLICATION_PATH} ...
The employee_lib.py
is just a regular Python script. The keyword Clear Employees List
is defined as function clear_employees_list
. The keyword can be used in the test script without the underscores. In the function, we just execute the application. There are other and probably better ways for invoking a Python script, but we want to keep it simple and straightforward in this example. The argument Check=True
of the subprocess
will ensure that an exception is thrown when the underlying process fails. In its turn, this will ensure that the Robot Framework test will fail, the exception will be propagated to the test case execution and the test will fail. And take a look at it: it is just plain Python code!
from robot.api.deco import keyword import subprocess ROBOT_AUTO_KEYWORDS = False @keyword def clear_employees_list(application_path): subprocess.run(['python3', application_path, 'remove_all_employees'], check=True)
Also notice that we have set the variable ROBOT_AUTO_KEYWORDS
to False
. This will ensure that only functions annotated with the @keyword
annotation will be available as a keyword in the test script. Otherwise, all functions will be visible from within the test script and we do not want that all of the time (e.g. a helper function which is only intended to be used within the Python library). It is also possible to make use of classes, but we refer to the official Robot Framework documentation for more information on that topic.
Now execute the test:
$ robot custom_library_example.robot ============================================================================== Custom Library Example :: Test the employee Python script ============================================================================== Empty Employees List :: Verify the output of an empty employees list The file does not exist Empty Employees List :: Verify the output of an empty employees list | PASS | ------------------------------------------------------------------------------ Add Employee :: Verify adding an employee The file does not exist Add Employee :: Verify adding an employee | PASS | ------------------------------------------------------------------------------ Custom Library Example :: Test the employee Python script | PASS | 2 critical tests, 2 passed, 0 failed 2 tests total, 2 passed, 0 failed
Our test cases pass. The only difference is that we have an extra console output line containing a message ‘The file does not exist’. This is caused by the Setup where we clear the employees list and the file does not exist. The print
statement in the application under test employee.py
is the originator of this message.
Next, we are going to move the Retrieve Employees List
to the Python library. We remove it from the Keywords section from the test script and change the calls to the Retrieve Employees List
by adding the APPLICATION_PATH
as an argument, just as we did for the Clear Employees List
.
| | ${output} = | Retrieve Employees List | ${APPLICATION_PATH}
The corresponding library keyword is the function retrieve_employees_list
. Notice that we do have a return value here. Only the last line of the subprocess
output is returned, that is why we needed to strip off the line feed. If we do not strip it off, an empty line is returned.
@keyword def retrieve_employees_list(application_path): process = subprocess.run(['python3', application_path, 'list_employees'], check=True, stdout=subprocess.PIPE, text=True) result = process.stdout.rstrip('\n') return result
Leaves us to change the Add Employee
keyword, which also takes the first_name
and last_name
as arguments besides the APPLICATION_PATH
.
| | Add Employee | ${APPLICATION_PATH} | first_name=John | last_name=Doe
The corresponding library keyword becomes the following:
@keyword def add_employee(application_path, first_name, last_name): subprocess.run(['python3', application_path, 'add_employee', first_name, last_name], check=True)
We also add the variable ROBOT_LIBRARY_VERSION
which can be used for versioning the library.
ROBOT_LIBRARY_VERSION = '0.1'
As mentioned before, it is very important to document the keywords with a meaningful description, specifying the arguments and some good examples of how to use the keyword. An example for the Clear Employees List
keyword is the following:
@keyword def clear_employees_list(application_path): """ Clear the list of employees. Arguments: - ''application_path:'' The relative (or absolute) path where the Employee script resides\n Examples: | Clear Employees List | ../employee.py """ subprocess.run(['python3', application_path, 'remove_all_employees'], check=True)
Just like we did for the resource files, we can generate the corresponding documentation for the library:
$ python3 -m robot.libdoc -f html employee_lib.py employee_lib.html
The generated documentation is the following and contains some meaningful information this time:
4. Conclusion
Making use of resource files and custom Python libraries is definitely something you need to consider when using Robot Framework. Sharing keywords instead of copying them will make your Robot Framework Test Suites more maintainable and it will get everyone up to speed. You have the choice between resource files and custom Python libraries for doing so. Custom Python libraries have the advantage that you have accessibility to the complete Python language, whereas keywords are more limited in there capabilities. Besides that, creating a Python library requires developer skills whereas keywords are more non-developer friendly.