Robot Advanced Topics

In the previous section we gave a broad overview of how Robot Framework is integrated with CumulsCI. In this section we’ll take a deeper dive into some advanced topics.

Running CumulusCI Tasks

CumulusCI provides two keywords for running a task from within a robot test case: Run Task and Run Task Class.

Run Task can be used to run any CumulusCI tasks configured for the project. Tasks run can be any of CumulusCI’s standard tasks as well as project-specific custom tasks from the project’s cumulusci.yml file. Run Task accepts a single argument, the task name, along with any arguments required by the task.

Run Task Class works in a similar fashion, but the task can be specified as a python class rather than a task name. For example, you can use this keyword to run logic from CumulusCI tasks which have not been configured in the project’s cumulusci.yml file. This is most useful in cases where a test needs to use task logic for logic unique to the test and thus not worth making into a named task for the project.

Performance Testing

The Salesforce keyword library somes with several keywords to aid in performance testing.

Setting the elapsed time

Normally, the full execution time of a test is recorded in the robot framework log. This includes the time spent in both test setup and teardown. Sometimes it is preferable to report only the time spent in the test case itself.

The Set Test Elapsed Time keyword allows you to record a computed elapsed time. For example, when performance testing a Salesforce batch process, you have the option to store the Salesforce-measured elapsed time of the batch process instead of the time measured in the CumulusCI client process.

The Set Test Elapsed Time keyword takes a single optional argument, either a number of seconds or a Robot time string.

When using this keyword, the tag cci_metric_elapsed_time will automatically be added to the test case.

When the test is run via MetaCI, the computed time will be retrieve and stored inside MetaCI instead of the total elapsed time as measured by Robot Framework.

Start and End Performance Time

A time can be recorded for any group of keywords by calling Start Performance Timer and Stop Performance Timer. The latter will automatically call the Set Test Elapsed Time keyword.

The Start Performance Timer keyword starts a timer. The Stop Performance Timer keyword stops the timer and stores the result with Set Test Elapsed Time.

Setting Test Metrics

The Set Test Metric keyword retrieves any metric for performance monitoring, such as number of queries, rows processed, CPU usage, and more.

The keyword takes a metric name, which can be any string, and a value, which can be any number.

Using this keyword will automatically add the tag cci_metric to the test case and ${cci_metric_<metric_name>} to the test’s variables. These permit downstream processing in tools like CCI and MetaCI.

Note: cci_metric is not included in Robot’s html statistical roll-ups.

Set Test Metric    Max_CPU_Percent    30

Performance test metrics are output in the CCI logs, log.html and output.xml. MetaCI captures them but does not currently have a user interface for displaying them.

Elapsed Time for Last Record

The Elapsed Time For Last Record keyword queries Salesforce for its recorded log of a job.

For example, to query an Apex bulk job:

${time_in_seconds} =    Elapsed Time For Last Record
...             obj_name=AsyncApexJob
...             where=ApexClass.Name='BlahBlah'
...             start_field=CreatedDate
...             end_field=CompletedDate
...             order_by=CompletedDate

Browser Testing

Testing salesforce from within a browser presents some unique challenges. This section covers some Salesforce-specific features of our keyword libraries.

Waiting for Lightning UI

A common challenge when writing end-to-end UI tests is waiting for asynchronous actions to complete before proceeding to run the next interaction. The Salesforce Library is aware of the Lightning UI and can handle waiting automatically. After each click, the Salesforce Library waits for any pending requests to the server to complete. (Manually waiting using “sleep”, or waiting for a particular element to appear, can still be necessary after specific interactions, and when interacting with pages that don’t use the Lightning UI.)

API Keywords

In addition to browser interactions, the Salesforce Library also provides keywords for interacting with the Salesforce REST API. Here are the keywords we provide which talk directly to Salesforce via an API rather than through the UI:

Using PageObjects

The PageObjects library provides support for page objects, Robot Framework-style. Even though Robot is a keyword-driven framework, it’s also possible to dynamically load in keywords unique to a page or an object on the page.

With the PageObjects library, you can define classes that represent page objects. Each class provides keywords that are unique to a page or a component. These classes can be imported on demand only for tests that use these pages or components.

The pageobject Decorator

Page objects are normal Python classes that use the pageobject decorator provided by CumulusCI. Unlike traditional Robot Framework keyword libraries, you can define multiple sets of keywords in a single file.

To create a page object class, start by inheriting from one of the provided base classes. No matter which class you inherit from, your page object class gets these predefined properties.

  • self.object_name: The name of the object related to the class. This is defined via the object_name parameter to the pageobject decorator. Do not add the namespace prefix in the decorator. This attribute will automatically add the prefix from the cumulusci.yml file when necessary.

  • self.builtin: A reference to the Robot Framework BuiltIn library that can be used to directly call built-in keywords. Any built-in keyword can be called by converting the name to all lowercase, and replacing all spaces with underscores (such as self.builtin.log and self.builtin.get_variable_value).

  • self.cumulusci: A reference to the CumulusCI keyword library. Call any keyword in this library by converting the name to all lowercase, and replacing all spaces with underscores (such as self.cumulusci.get_org_info).

  • self.salesforce: A reference to the Salesforce keyword library. Call any keyword in this library by converting the name to all lowercase, and replacing all spaces with underscores (such as self.salesforce.wait_until_loading_is_complete).

  • self.selenium: A reference to SeleniumLibrary. Call any keyword in this library by converting the name to all lowercase, and replacing all spaces with underscores (such as self.selenim.wait_until_page_contains_element).

Page Object Base Classes

Presently, CumulusCI provides the following base classes, which should be used for all classes that use the pageobject decorator:

  • cumulusci.robotframework.pageobjects.BasePage A generic base class used by the other base classes. Use the BasePage class for creating custom page objects when none of the other base classes make sense.
    • The BasePage adds the Log current page object keyword to every page object. This keyword is most useful when debugging tests. It will add to the log information about the currently loaded page object.

  • cumulusci.robotframework.pageobjects.DetailPage: A class for a page object that represents a detail page.

  • cumulusci.robotframework.pageobjects.HomePage: A class for a page object that represents a home page.

  • cumulusci.robotframework.pageobjects.ListingPage: A class for a page object that represents a listing page.

  • cumulusci.robotframework.pageobject.NewModal: A class for a page object that represents the “new object” modal.

  • cumulusci.robotframework.pageobject.ObjectManagerPage: A class for interacting with the object manager.

Example Page Object

This example shows the definition of a page object for the listing page of custom object MyObject__c wherein a new custom keyword, Click on the row with name, is added.

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):
        self.selenium.click_link('xpath://a[@title="{}"]'.format(name))
        self.salesforce.wait_until_loading_is_complete()

The pageobject decorator takes two arguments: page_type and object_name. These two arguments are used to identify the page object (`Go to page Listing Contact`_). The values can be any arbitrary string, but ordinarily should represent standard page types (such as “Detail”, “Home”, “Listing”, or “New”) and standard object names.

Importing the Page Object Library Into a Test

The PageObjects library is not only a keyword library, but also the mechanism to import files that contain page object classes. You can import these files by providing the paths to one or more Python files that implement page objects. You can also import PageObjects without passing any files to it to take advantage of general purpose page objects.

For example, consider a case where you create two files that each have one or more page object definitions: PageObjects.py and MorePageObjects.py, both located in the robot/MyProject/resources folder. You can import these page objects from these files into a test suite.

*** Settings ***
Library         cumulusci.robotframework.PageObjects
...  robot/MyProject/resources/PageObjects.py
...  robot/MyProject/resources/MorePageObjects.py

Using Page Objects

To use the keywords in a page object:

As mentioned in the previous section, first import the PageObjects library and any custom page object files you wish to use.

Next, either explicitly load the keywords for a page object, or reference a page object with one of the generic page object keywords provided by the PageObjects library.

To explicitly load the keywords for a page object, use the Load Page Object keyword provided by the PageObjects library. If successful, the PageObjects library will automatically import the keywords.

For example, call the Go To Page keyword followed by a page object reference. If the keyword (or page object reference?) navigates you to the proper page, its keywords will automatically be loaded.

Current Page Should Be

Example: Current Page Should Be  Listing Contact

This keyword attempts to validate that the given page object represents the current page. Each page object may use its own method for making the determination, but the built-in page objects all compare the page location to an expected pattern (such as .../lightning/o/...). If the assertion passes, the keywords for that page object automatically load.

This keyword is useful if you get to a page via a button or some other form of navigation because it lets you assert that you are on the page you think you should be on, and load the keywords for that page, with a single statement.

Get Page Object

Example: Get page object  Listing  Contact

This keyword is most often used to get the reference to a keyword from another keyword. It is similar in function to robot’s built-in Get Library Instance keyword. It is rarely used in a test.

Go To Page

Example: Go to page  Listing  Contact

This keyword attempts to go to the listing page for the Contact object, and then load the keywords for that page.

Log Page Object Keywords

Example: Log Page Object Keywords

This keyword is primarily used as a debugging tool. When called, it will log each of the keywords for the current page object.

Load Page Object

Example: Load page object  Listing  Contact

This keyword loads the page object for the given page_type and object_name. It is useful when you want to use keywords from a page object without first navigating to that page (for example, when you are already on the page and don’t want to navigate away).

Wait for Modal

Example: Wait for modal  New  Contact

This keyword can be used to wait for a modal, such as the one that pops up when creating a new object. The keyword returns once a modal appears, and has a title of New <object_name> (such as “New Contact”).

Wait for Page Object

Example: Wait for page object  Popup  ActivityManager

Page objects don’t have to represent entire pages. You can use the Wait for page object keyword to wait for a page object representing a single element on a page, such as a popup window.

Generic Page Objects

You don’t need to create a page object in order to take advantage of page object keywords. If you use one of the page object keywords for a page that does not have its own page object, the PageObjects library attempts to find a generic page.

For example, if you use Current page should be  Home  Event and there is no page object by that name, a generic Home page object will be loaded, and its object name will be set to Event.

Or let’s say your project has created a custom object named Island. You don’t have a home page, but the object does have a standard listing page. Without creating any page objects, this test works by using generic implementations of the Home and Listing page objects:

*** Test Cases ***
Example test which uses generic page objects
    # Go to the custom object home page, which should
    # redirect to the listing page
    Go To Page  Home  Islands

    # Verify that the redirect happened
    Current Page Should Be  Listing  Islands

CumulusCI provides these generic page objects.

Detail

Example: Go to page  Detail  Contact  ${contact id}

Detail pages refer to pages with a URL that matches the pattern <host>/lightning/r/<object name>/<object id>/view.

Home

Example: Go to page  Home  Contact

Home pages refer to pages with a URL that matches the pattern “<host>/lightning/o/<object name>/home”

Listing

Example: Go to  page  Listing  Contact

Listing pages refer to pages with a URL that matches the pattern “<host>b/lightning/o/<object name>/list”

New

Example: Wait for modal  New  Contact

The New page object refers to the modal that pops up when creating a new object.

Of course, the real power comes when you create your own page object class that implements keywords that can be used with your custom objects.

Configuring the robot_libdoc Task

If you define a robot resource file named MyProject.resource and place it in the resources folder, you can add this configuration to the cumulusci.yml file to enable the robot_libdoc task to generate documentation.

tasks:
   robot_libdoc:
       description: Generates HTML documentation for the MyProject Robot Framework Keywords
       options:
           path: robot/MyProject/resources/MyProject.resource
           output: robot/MyProject/doc/MyProject_Library.html

Normally this task will generate HTML output. If the output file ends with “.csv”, a csv file will be generated instead.

To generate documentation for more than one keyword file or library, give a comma-separated list of files for the path option, or define path as a list under tasks__robot_libdoc in the cumulusci.yml file.

For example, generate documentation for MyLibrary.py and MyLibrary.resource.

tasks:
   robot_libdoc:
       description: Generates HTML documentation for the MyProject Robot Framework Keywords
       options:
           path:
             - robot/MyProject/resources/MyProject.resource
             - robot/MyProject/resources/MyProject.py
           output: robot/MyProject/doc/MyProject_Library.html

You can also use basic filesystem wildcards.

For example, to document all Robot files in robot/MyProject/resources, configure the path option under tasks__robot_libdoc in the cumulusci.yml file.

tasks:
   robot_libdoc:
       description: Generates HTML documentation for the MyProject Robot Framework Keywords
       options:
           path: robot/MyProject/resources/*.resource
           output: robot/MyProject/doc/MyProject_Library.html

Using Keywords and Tests from a Different Project

Much like you can use tasks and flows from a different project<sources>`<TODO>, you can also use keywords and tests from other projects. The keywords are brought into your repository the same way as with tasks and flows, via the ``sources` configuration option in the cumulusci.yml file. However, keywords and tests require extra configuration before they can be used.

Note

This feature isn’t for general purpose sharing of keywords between multiple projects. It was designed specifically for the case where a product is being built on top of another project and needs access to product-specific keywords.

Using Keywords

In order to use the resources from another project, you must first configure the robot task to use one of the sources that have been defined for the project. To do this, add a sources option under the robot task, and add to it the name of an imported source.

For exmple, if your project is built on top of NPSP, and you want to use keywords from the NPSP project, first add the NPSP repository as a source in the project’s cumulusci.yml file:

sources:
    npsp:
        github: https://github.com/SalesforceFoundation/NPSP
        release: latest_beta

Then add npsp under the sources option for the robot task. This is because the project as a whole can use tasks or flows from multiple projects, but robot only needs keywords from a single project.

tasks:
   robot:
     options:
        sources:
          - npsp

When the robot task runs, it adds the directory that contains the code for the other repository to PYTHONPATH, which Robot uses when resolving references to libraries and keyword files.

Once this configuration has been saved, you can import the resources as if you were in the NPSP repository.

For example, in a project which has been configured to use NPSP as a source, the NPSP.robot file can be imported into a test suite.

*** Settings ***
Resource   robot/Cumulus/resources/NPSP.robot

Note

Even with proper configuration, some keywords or keyword libraries might not be usable. Be careful to avoid using files that have the exact same name in multiple repositories.

Running Tests

Running a test from another project requires prefixing the path to the test with the source name. The path needs to be relative to the root of the other repo.

For example, starting from the previous example, to run the create_organization.robot test suite from NPSP:

$ cci task run robot --suites npsp:robot/Cumulus/tests/browser/contacts_accounts/create_organization.robot