Robot Tutorial¶
This tutorial will step you through writing your first test, then enhancing that test with a custom keyword implemented as a page object. It is not a comprehensive tutorial on using Robot Framework. For Robot Framework documentation see the Robot Framework User Guide
It is assumed you’ve worked through the CumulusCI Tutorial at least up to the
point where you’ve called cci project init
. It is also assumed
that you’ve read the Robot Framework section of this document, which gives
an overview of CumulusCI / Robot Framework integration.
Part 1: Folder Structure¶
We recommend that all robot tests, keywords, data, and log and report files live under a folder named robot, at the root of your repository. If you worked through the CumulusCI Tutorial, the following folders will have been created under MyProject/robot/MyProject:
doc - a place to put documentation for your tests
resources - a place to put robot libraries and keyword files that are unique to your project
results - a place for robot to write its log and report files
tests - a place for all of your tests.
Part 2: Creating a custom object¶
For this tutorial we’re going to use a custom object named
MyObject
(e.g. MyObject__c
). In addition, we need a custom tab that is associated
with that object.
If you want to run the tests and keywords in this tutorial verbatim, you will need to go to Setup and create the following:
A custom object with the name
MyObject
.A custom tab associated with this object.
Part 3: Creating and running your first robot test¶
The first thing we want to do is create a test that verifies we can get to the listing page of the custom object. This will let us know that everything is configured properly.
Open up your favorite editor and create a file named MyObject.robot
in the folder robot/MyProject/tests
. Copy and paste the
following into this file, and then save it.
*** Settings ***
Resource cumulusci/robotframework/Salesforce.robot
Library cumulusci.robotframework.PageObjects
Suite Setup Open test browser
Suite Teardown Delete records and close browser
*** Test Cases ***
Test the MyObject listing page
Go to page Listing MyObject__c
Current page should be Listing MyObject__c
Note
The above code uses Go to page
and Current page should be
which accept a page type (Listing
) and object name
(MyObject__c
). Even though we have yet to create that page object,
the keywords will work by using a generic implementation. Later,
once we’ve created the page object, the test will start using our
implementation.
To run just this test, run the following command at the prompt:
$ cci task run robot -o suites robot/MyProject/tests/MyObject.robot --org dev
If everything is set up correctly, you should see the output that looks similar to this:
$ cci task run robot -o suites robot/MyProject/tests/MyObject.robot --org dev
2019-05-21 17:29:25: Getting scratch org info from Salesforce DX
2019-05-21 17:29:29: Beginning task: Robot
2019-05-21 17:29:29: As user: test-wftmq9afc3ud@example.com
2019-05-21 17:29:29: In org: 00Df0000003cuDx
2019-05-21 17:29:29:
==============================================================================
MyObject
==============================================================================
Test the MyObject listing page | PASS |
------------------------------------------------------------------------------
MyObject | PASS |
1 critical test, 1 passed, 0 failed
1 test total, 1 passed, 0 failed
==============================================================================
Output: /Users/boakley/dev/MyProject/robot/MyProject/results/output.xml
Log: /Users/boakley/dev/MyProject/robot/MyProject/results/log.html
Report: /Users/boakley/dev/MyProject/robot/MyProject/results/report.html
Part 4: Creating a page object¶
Most projects are going to need to write custom keywords that are unique to that project. For example, NPSP has a keyword for filling in a batch gift entry form, EDA has a keyword with some custom logic for validating and affiliated contact, and so on.
The best way to create and organize these keywords is to place them in
page object libraries. These libraries contain normal Python classes
and methods which have been decorated with the pageobjects
decorator provided by CumulusCI. By using page objects, you can write
keywords that are unique to a given page, making them easier to find
and easier to manage.
Defining the class¶
CumulusCI provides the base classes that are a good starting point for
your page object (see Page Object Base Classes). In this case
we’re writing a keyword that works on the listing page, so we want our
class to inherit from the ListingPage
class.
Note
Our class also needs to use the pageobject
decorator, so we must
import that along with the ListingPage
class.
To get started, create a new file named MyObjectPages.py in the
folder robot/MyProject/resources
. At the top of the new keyword
file, add the following import statement:
from cumulusci.robotframework.pageobjects import pageobject, ListingPage
Next we can create the class definition by adding the following two lines:
@pageobject(page_type="Listing", object_name="MyObject__c")
class MyObjectListingPage(ListingPage):
The first line registers this class as a page object for a listing page
for the object MyObject__c
. The second line begins the class
definition.
Creating the keyword¶
At this point, all we need to do to create the keyword is to create a method on this object. The method name should be all lowercase, with underscores instead of spaces. When called from a robot test, the case is ignored and all spaces are converted to underscores.
In this case we want to create a method named
click_on_the_row_with_name
. All we want it to do is to find a
link with the given name, click on the link, and then wait for the new
page to load. To make the code more bulletproof, it will use a keyword
from SeleniumLibrary to wait until the page contains the link before
clicking on it. While probably not strictly necessary on this page,
waiting for elements before interacting with them is a good habit to
get into.
Add the following under the class definition:
def click_on_the_row_with_name(self, name):
xpath='xpath://a[@title="{}"]'.format(name)
self.selenium.wait_until_page_contains_element(xpath)
self.selenium.click_link(xpath)
self.salesforce.wait_until_loading_is_complete()
Notice that the above code is able to use the built-in properties
self.selenium
and self.salesforce
to directly call keywords in
the SeleniumLibrary
and Salesforce
keyword libraries.
Putting it all together¶
After adding all of the above code, our file should now look like this:
from cumulusci.robotframework.pageobjects import pageobject, ListingPage
@pageobject(page_type="Listing", object_name="MyObject__c")
class MyObjectListingPage(ListingPage):
def click_on_the_row_with_name(self, name):
xpath='xpath://a[@title="{}"]'.format(name)
self.selenium.wait_until_page_contains_element(xpath)
self.selenium.click_link(xpath)
self.salesforce.wait_until_loading_is_complete()
We now need to import this page object into our tests. In the first
iteration of the test, we imported
cumulusci.robotframework.PageObjects
, which provided our test with
keywords such as Go to page
and Current page should be
. In
addition to being the source of these keywords, it is also the way to
import page object files into a test case.
To import a file with one or more page objects you need to supply the
path to the page object file as an argument when importing
PageObjects
. The easiest way is to use robot’s continuation
characters ...
on a separate line.
Modify the import statements at the top of MyObject.robot
to look
like the following:
*** Settings ***
Resource cumulusci/robotframework/Salesforce.robot
Library cumulusci.robotframework.PageObjects
... robot/MyProject/resources/MyObjectPages.py
This will import the page object definitions into the test case, but
the keywords won’t be available until the page object is loaded. Page
objects are loaded automatically when you call Go to page
, or you
can explicitly load them with Load page object
. In both cases, the
first argument is the page type (eg: Listing, Home, etc) and the
second argument is the object name (eg: MyObject__c
).
Our test is already using Go to page
, so our keyword should
already be available to us once we’ve gone to that page.
Part 5: Adding test data¶
We want to be able to test that when we click on one of our custom objects on the listing page that it will take us to the detail page for that object. To do that, our test needs some test data. While that can be very complicated in a real-world scenario, for simple tests we can use the Salesforce API to create test data when the suite first starts up.
To create the data when the suite starts, we can add a Suite Setup
in the settings section of the test. This takes as an argument the
name of a keyword. In our case we’re going to create a custom keyword
right in the test to add some test data for us.
It is not necessary to do it in a setup. It could be a step in an
individual test case, for example. However, putting it in the Suite
Setup
guarantees it will run before any tests in the same file are
run.
Open up MyObject.robot
and add the following just before ***
Test Cases ***
:
*** Keywords ***
Create test data
[Documentation]
... Creates a MyObject record named "Leeroy Jenkins"
... if one doesn't exist
# Check to see if the record is already in the database,
# and return if it already exists
${status} ${result}= Run keyword and ignore error Salesforce get MyObject__c Name=Leeroy Jenkins
Return from keyword if '${status}'=='PASS'
# The record didn't exist, so create it
Log creating MyObject object with name 'Leeroy Jenkins' DEBUG
Salesforce Insert MyObject__c Name=Leeroy Jenkins
We also need to modify our Suite Setup
to call this keyword in
addition to calling the Open Test Browser
keyword. Since Suite
Setup
only accepts a single keyword, we can use the built-in keyword
Run keywords
to run more than one keyword in the setup.
Change the suite setup to look like the following, again using robot’s continuation characters to spread the code across multipe rows for readability.
Note
It is critical that you use all caps for AND
, as
that’s the way robot knows where one keyword ends and the next
begins.
Suite Setup Run keywords
... Create test data
... AND Open test browser
Notice that our Suite Teardown
calls Delete records and
close browser
. The _records_ in that keyword refers to any data
records created by Salesforce Insert
. This makes it possible to
both create and later clean up temporary data used for a test.
It is important to note that the suite teardown isn’t guaranteed to run
if you forcibly kill a running robot test. For that reason, we added a
step in Create test data
to check for an existing record
before adding it. If a previous test was interrupted and the record
already exists, there’s no reason to create a new record.
Part 6: Using the new keyword¶
We are now ready to modify our test to use our new keyword, since we now have some test data in our database, and the keyword definition in our page object file.
Once again, edit MyObject.robot
to add the following two
statements at the end of our test:
Click on the row with name Leeroy Jenkins
Current page should be Detail MyObject__c
The complete test should now look like this:
*** Settings ***
Resource cumulusci/robotframework/Salesforce.robot
Library cumulusci.robotframework.PageObjects
... robot/MyProject/resources/MyObjectPages.py
Suite Setup Run keywords
... Create test data
... AND Open test browser
Suite Teardown Delete records and close browser
*** Keywords ***
Create test data
[Documentation] Creates a MyObject record named "Leeroy Jenkins" if one doesn't exist
# Check to see if the record is already in the database,
# and do nothing if it already exists
${status} ${result}= Run keyword and ignore error Salesforce get MyObject__c Name=Leeroy Jenkins
Return from keyword if '${status}'=='PASS'
# The record didn't exist, so create it
Log creating MyObject object with name 'Leeroy Jenkins' DEBUG
Salesforce Insert MyObject__c Name=Leeroy Jenkins
*** Test Cases ***
Test the MyObject listing page
Go to page Listing MyObject__c
Current page should be Listing MyObject__c
Click on the row with name Leeroy Jenkins
Current page should be Detail MyObject__c
With everything in place, we should be able to run the test using the same command as before:
$ cci task run robot -o suites robot/MyProject/tests/MyObject.robot --org dev
2019-05-21 22:02:27: Getting scratch org info from Salesforce DX
2019-05-21 22:02:31: Beginning task: Robot
2019-05-21 22:02:31: As user: test-wftmq9afc3ud@example.com
2019-05-21 22:02:31: In org: 00Df0000003cuDx
2019-05-21 22:02:31:
==============================================================================
MyObject
==============================================================================
Test the MyObject listing page | PASS |
------------------------------------------------------------------------------
MyObject | PASS |
1 critical test, 1 passed, 0 failed
1 test total, 1 passed, 0 failed
==============================================================================
Output: /Users/boakley/dev/MyProject/robot/MyProject/results/output.xml
Log: /Users/boakley/dev/MyProject/robot/MyProject/results/log.html
Report: /Users/boakley/dev/MyProject/robot/MyProject/results/report.html