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 Page Objects#

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 easily define and use keywords in multiple classes within a single file.

To create a page object class, start by inheriting from one of the provided base classes. You need to use the pageobject decorator to designate the class as a page object, and to describe the type of page (Listing, Detail, etc) and the associated salesfore object. From within a test, these page objects are referenced using both the type and object name (eg: Go to page  Listing  CustomObject__c).

The following example illustrates how to create a Listing page object for CustomObject__c.

from cumulusci.robotframework.pageobjects import ListingPage, pageobject

@pageobject(page_type='Listing', object_name='CustomObject__c')
class CustomObjectListingPage(ListingPage):
    ...

Using object aliases#

Within a test, if you want to refer to the page object with a more human-readable name such as Custom Object rather than CustomObject__c you can do so by setting object_name to Custom Object and then defining _object_name in the class, as in the following example.

from cumulusci.robotframework.pageobjects import ListingPage, pageobject

@pageobject(page_type = 'Listing', object_name = 'My Object')
class CustomObjectListingPage(ListingPage):
    _object_name = 'MyObject__c'
    ...

By using an alias, you can reference the page object with either the alias or the actual object name. For example, if object_name is set as described above, the following two uses of Go to page are identical:

Go to page  Listing  My Object
Go to page  Listing  MyObject__c

Page Object Base Classes#

CumulusCI provides the following base classes, which should be used for all classes that use the pageobject decorator. You can import these base classes from cumulusci.robotframework.pageobjects.

  • cumulusci.robotframework.pageobjects.BasePage A generic base class used by the other pageobject 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 information about the currently loaded page object to the log file generated when the test runs.

  • 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.

Common page object attributes#

When using the decorator and inheriting from one of the page object base classes, your class inherits the following attributes and properties.

  • self._object_name: The name of the object related to the class. If the class does not define this property, it is set to the value provided as the object_name parameter to the pageobject decorator. Note: do not add the namespace prefix in the decorator. This attribute automatically adds the prefix from the cumulusci.yml file when necessary.

  • self.object_name: A property that combines the _object_name attribute with the namespace returned by the get namespace prefix keyword from the CumulusCI library. If there is no namespace, this returns the value of the _object_name attribute.

  • self.builtin: A reference to the Robot Framework BuiltIn library that you can use to directly call built-in keywords. You can call any built-in keyword 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. You can 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. You can 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. You can 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).

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()

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#

As mentioned in the previous section, you must 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__c. 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  Island__c

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

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