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 Get Started section at least up to the point where you’ve called cci project init. It is also assumed that you’ve read the Acceptance Testing with Robot Framework section of this document, which gives an overview of CumulusCI and 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 Get Started section, 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:

  1. A Custom Object with the name MyObject.

  2. 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]{.title-ref}, [Home]{.title-ref}, 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 multiple 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