Cinnamon News

Cinnamon Spring '15 (v1.1) Released!

Hi there!

The Cinnamon managed release package is now available. This means that from now on, updating to the latest version of Cinnamon will be seamless and carefree. Since we've changed the way our releases work, our version numbers moving forward will correspond with the current Salesforce release.

This new package is built on top of the WebDriver JSON protocol, allowing you to take advantage of more service providers (like BrowserStack). Selenium Webdriver now serves as the provider for Cinnamon, allowing you to use any service that supports the WebDriver protocol, including self-hosted instances of Selenium WebDriver. This version is built on top of the WebDriver API so you can easily convert existing WebDriver tests to Cinnamon tests.

Finally, this version introduces concurrent browser testing. You now have the capability to run your Cinnamon tests on multiple browsers in a single test suite.

If you see issues or bugs, please continue to inform us via our GitHub Issues page.

Happy testing!

Running Cinnamon Using an AWS Selenium WebDriver Server

If you want to run your own Selenium servers for Cinnamon testing, we recommend using products like SauceLabs and BrowserStack that primarily host Selenium virtual machines. However, Cinnamon doesn't require a magic Selenium WebDriver server. In this post we'll show you how to run Cinnamon tests using a Selenium WebDriver server hosted in AWS.

We'll assume you're familiar with running VMs in an AWS environment and that your environment satisfies the following prerequisites:

  • AWS access key ID
  • AWS secret access key
  • AWS PEM file
  • A security group with ports 22 and 4444 open

Tom Romano wrote this excellent post explaining how to set up a server using Vagrant. Our examples borrow heavily from his script but with software updated to the most recent versions (you might need to perform these updates as well). After reading the above post, you can use the following Vagrantfile and setup.sh:

  
    # Vagrantfile

    Vagrant.configure("2") do |config|
      config.vm.box = "precise64"
      config.vm.box_url = "http://files.vagrantup.com/precise64.box"
      config.ssh.forward_agent = true

      config.vm.provider :aws do |aws, override|
        # Overrides VirtualBox configuration to use empty box as will load AMI below
        override.vm.box = "dummy"
        override.vm.box_url = "https://github.com/mitchellh/vagrant-aws/raw/master/dummy.box"
        # Export as an environment variable or replace this with yours
        aws.access_key_id = ENV['AWS_ACCESS_KEY_ID']
        # Export as an environment variable or replace this with yours
        aws.secret_acces_key = ENV['AWS_SECRET_ACCESS_KEY']
        # The name of the Keypair instance to use
        aws.keypair_name = 'webdriver'
        # Ubuntu 12.04 64bit precise instance backed
        aws.ami = 'ami-aa941e9a'
        # Override default AWS region
        aws.region = 'us-west-2'
        # Path to my amazon .pem file
        override.ssh.private_key_path = ENV['AWS_PEM_FILEPATH']
        # The name of the security group preconfigured in AWS with port 22 and 4444 open.
        aws.security_groups = ['webdriver']

        # Tags that help you easily identify these elements in the test console.
        aws.tags = {
          'Name' => 'vagrant-webdriver',
          'webdriver' => true
        }
      end

      # Tells Vagrant to run setup.sh when the shell is available
      config.vm.provision :shell, :path => "setup.sh"
      # Tells vagrant to make the VM port 4444 accessible on host port 4444 (only works for VirtualBox provider)
      config.vm.network :forwarded_port, guest:4444, host:4444

    end
  
  
  
  # setup.sh

  #!/bin/sh
  set -e

  if [ -e /.installed ]; then
    echo 'Already installed.'
  else
    echo ''
    echo 'INSTALLING'
    echo '----------'

    # Add Google public key to apt
    wget -q -O - "https://dl-ssl.google.com/linux/linux_signing_key.pub" | sudo apt-key add -

    # Add Google to the apt-get source list
    echo 'deb http://dl.google.com/linux/chrome/deb/ stable main' >> /etc/apt/sources.list

    # Update apt-get
    apt-get update

    # Install Java, Chrome, Xvfb, and unzip
    apt-get -y install openjdk-7-jre google-chrome-stable xvfb unzip firefox

    # Download and copy the ChromeDriver to /usr/local/bin
    cd /tmp
    wget "http://chromedriver.storage.googleapis.com/2.12/chromedriver_linux64.zip"
    wget "http://selenium-release.storage.googleapis.com/2.44/selenium-server-standalone-2.44.0.jar"
    unzip chromedriver_linux64.zip
    mv chromedriver /usr/local/bin
    mv selenium-server-standalone-2.44.0.jar /usr/local/bin

    # Ensure that running 'vagrant provision' doesn't redownload everything
    touch /.installed
  fi

  # Start Xvfb, Chrome, and Selenium in the background
  export DISPLAY =:10
  cd /vagrant

  echo "Starting Xvfb..."
  Xvfb :10 -screen 0 1366x768x24 -ac &

  echo "Starting Google Chrome..."
  google-chrome --remote-debugging-port=9222 &

  echo "Starting Selenium..."
  nohup java -jar ./selenium-server-standalone-2.44.0.jar &
  
  

After issuing vagrant up --provider=aws, you can use the public name of your instance to hit the WebDriver server (e.g. http://ec2-54-69-194-100.us-west-2.compute.amazonaws.com:4444/wd/hub) and see the following hub page:

The hub page.

As the last step on the server, click Create Session to ensure everything is running smoothly. Using the above setup.sh, you can create sessions for Google Chrome and Firefox.

Now that the server is up and running, it's time to configure Cinnamon. We'll implement the RemoteWebDriverServiceProvider interface.


global interface RemoteWebDriverServiceProvider {
    /**
     * Implementors return a list of strings representing the supported OS/browser
     * combinations. These strings allow the tester to specify the environment.
     * This list does not contain duplicate entries.
     * @return The list of os-browser combination keys the provider supports 
     *         (e.g. "Win 7 IE 9", "IOS8 iPad").
     **/
    List<String> getAvailableBrowsers();

    /**
     * Implementors return a Capabilities instance with the minimal set of required
     * capabilities (browserName, browserVersion, platformName, platformVersion).
     * The Capabilities class provides a constructor with these parameters for
     * conveninence. This Capabilities instance is sent to the remote WebDriver service
     * as the desiredCapabilities parameter.
     * @return An instance of Capabilities with minimal OS and browser capabilities
     **/
    Capabilities getCapabilitiesFor(String environmentKey);

    /**
     * ServiceProviders may need additional settings for full functionality.
     * Implementors must provide a Visualforce page where these additional
     * settings are configured. It's the implementor's responsibility to store
     * this information in the appropriate format.
     * @return A Visualforce page reference for use in the Settings page. This page
     *         loads inside an iframe with a maximum height of 200px
     **/
    PageReference getSettingsPage();

    /**
     * Retrieves the URL for test execution details.
     * @return The URL for test execution details.
     **/
    String getSessionDetailsUrl();

    /**
     * Returns whether or not the provider is configured for use.
     * @return A boolean indicating if the provider is configured for use.
     **/
    boolean isConfigured();
}

We'll implement this interface in a class called AwsSeleniumProvider. Ending a class name with "Provider" is a naming convention that allows Cinnamon to recognize custom providers, so it's important that you include this when you're naming classes. Here's what the class looks like:


global with sharing class AwsSeleniumProvider implements cinnamon.RemoteWebDriverServiceProvider {
    public List<String> getAvailableBrowsers() {
      return new String[] { 'googlechrome' };
    }

    public cinnamon.Capabilities getCapabilitiesFor(String environmentKey) {
      cinnamon.Capabilities caps = new cinnamon.Capabilities();
      caps.set('browserName', 'chrome');
      return caps;
    }

    public String getRemoteWebDriverServiceUrl() {
      return 'http://ec2-54-69-194-100.us-west-2.compute.amazonaws.com:4444/wd/hub';
    }

    public PageReference getSettingsPage() {
      return null;
    }

    public String getSessionDetailsUrl(String sessionId) {
      return null;
    }

    public boolean isConfigured() {
      return true;
    }
}

Once you create this class in your Cinnamon organization, navigate to the Cinnamon settings tab to ensure your new provider shows up in the list of WebDriver service providers.

Cinnamon settings page.

You're ready to run tests on your newly configured server! When you navigate to the Cinnamon Test Console, you'll see the browser you specified in the provider in the browser selection menu. In this example we only included Google Chrome, but you can also set up your environment to run tests using Firefox.

Cinnamon test console.

Happy testing!

Cinnamon v.61 is released!

Hi there!

Cinnamon Package Version 61 is now available.

This version contains a bug fix for the TableWebElement component.

Please click here to install this version.

If you see issues or bugs, please let us know via our Github Issues page.

Happy testing!

Cinnamon v.60 is released!

Hi there!

Cinnamon Package Version 60 is now available.

Starting with this release, PageObjects are initialized automatically so you no longer have to explicitly call PageObject.initializePageObject(). Now, simply call Context.getPageObject(<PageObject Class>) and use the pre-initialized returned instance.

There is also a new ExpectedCondition called TitleToContain. This ExpectedCondition tests whether the title of the Web browser contains the text passed in as a parameter.

Please click here to install this version.

If you see issues or bugs, please let us know via our Github Issues page.

Happy testing!

Getting Started with Cinnamon Testing

Now that you've set up your Cinnamon environment, it's time to start writing some tests! This page provides an overview of important concepts in Cinnamon testing and an example of a basic Cinnamon test.

The first step in writing a Cinnamon test is identifying particular pages and objects in the user interface that you need to test. Cinnamon tests the functionality of elements on individual pages, so knowing which page and which elements you want to test is important.

After you decide which page you're testing, you need to design and create a PageObject class to represent the page. A PageObject is an interface between your custom page and your test classes that provides a set of methods or services to manipulate UI components.

Diagram of the interaction between Cinnamon tests and UI pages

Every UI page needs an associated PageObject but a single PageObject can be used in multiple tests, so you'll never need to duplicate the code that controls the UI. Additionally, if the UI changes, you only need to edit the PageObject class instead of every test class that's associated with that page in the UI. See the Selenium PageObject reference and PageObject in Cinnamon for more details.

Let's set up an example to get a better idea of how Cinnamon works. You'll first need to create a custom Visualforce page. If you need help with this step, see the Visualforce topic page in the Salesforce developer library. Use the following markup for this example:


<apex:page standardController="Account">
<apex:form >
    <apex:pageBlock title="Edit Account for {!$User.FirstName}">
        <apex:pageMessages />
        <apex:pageBlockButtons >
            <apex:commandButton id="saveBtn" value="Save" 
                                   action="{!save}"/>
        </apex:pageBlockButtons>
        <apex:pageBlockSection >
            <apex:pageBlockSectionItem >
                <apex:outputLabel value="Account Name" for="name"/>
                <apex:inputField id="name" value="{!account.name}"/>
            </apex:pageBlockSectionItem>
            <apex:pageBlockSectionItem >
                <apex:outputLabel value="Account Site" for="site"/>
                <apex:inputField id="site" value="{!account.site}"/>
            </apex:pageBlockSectionItem>
        </apex:pageBlockSection>
    </apex:pageBlock>
</apex:form>
</apex:page>
    
This definition creates this custom page:

Custom Visualforce Account Edit page.

For this page, you might want to test whether entering a value in the Site field and then clicking Save actually changes the site value for that Account object. To do this, you need to use PageObjects.

To create a PageObject for our example page, you need to create an Apex class.

  1. Log in to your Salesforce organization and select the Cinnamon app from the AppPicker.
  2. Click Setup.
  3. Go to Develop > Apex Classes.
  4. Click New.
  5. Copy and paste the following source code into the text area:
    
    public class EditAccountPageObject extends cinnamon.PageObject {
    
        cinnamon.WebElement saveBtn,
                accountName,
                site,
                ratings;
    
        public override void initializePageObject() {
            saveBtn = getElement(new cinnamon.VisualforceLocator('apex:inputField', 'saveBtn'));
            accountName = getElement(new cinnamon.VisualforceLocator('apex:inputField', 'name'));
            site = getElement(new cinnamon.VisualforceLocator('apex:inputField', 'site'));
            ratings = getElement(new cinnamon.VisualforceLocator('apex:inputField', 'rating'));
        }
    
        public EditAccountPageObject clickSave() {
            saveBtn.click();
            selenium.waitForPageToLoad('3000');
            return this;
        }
    
        public EditAccountPageObject typeAccountSite(String data) {
            site.sendKeys(data);
            clickSave();
            return this;
        }
    }
            
  6. Click Save.
Let's quickly explore this class. First, notice that it extends the abstract cinnamon.PageObject class that provides methods for interacting with a page's UI. There are fields for each element on the page. The values of these fields are assigned in the initializePageObject() method by calling getElement() and using Cinnamon's built-in VisualforceLocator to determine the position of the elements on your page. The remaining methods in this class define different actions that a user might perform such as clickSave() and typeAccountSite(). Notice that typeAccountSite() includes a call to clickSave, so you don't have to worry about calling the save operation manually in your tests. The return type of the method must simulate the page that the user sees after performing that action. For this example, any action that a user performs keeps the user on the Account's Edit page, so all of the methods return an EditAccountPageObject.

Now we're ready to get started with the test. There are three stages in every Cinnamon test.

  1. Setup: During setup, you create a test fixture that can be used throughout all phases of testing. For our example, we'll need to set up a test Account object.
  2. Testing: The testing stage is when you actually manipulate the UI and make sure that it's performing as intended.
  3. Tear down: The final stage of testing is when the objects that you created throughout your tests are removed and your environment is returned to its original state.

Here is the full source code for a test for the custom Account Edit page:


public class TestEditAccountPage extends cinnamon.BaseTest {

    public override void setup(cinnamon.Context context) {
        Account acc = new Account();
        acc.name = 'Account' + System.currentTimeMillis();
        insert acc;
        context.put('accId', acc.Id);
    }

    public override String getStartingPath(cinnamon.Context context) {
        return '/apex/editAccount?id=' + (String) context.get('accId');
    }

    public override void test(cinnamon.Context context) {
        String accId = (String) context.get('accId');

        EditAccountPageObject page = (EditAccountPageObject) context.getPageObject(EditAccountPageObject.class);

        page.typeAccountSite('San Francisco');

        Account a = [select name, site, rating from Account where Id = :accId];
        System.assert(a != null);
        System.assertEquals(a.site, 'San Francisco');
    }

    public override void tearDown(cinnamon.Context context) {
        List<ID> Ids = new List<ID>();
        Ids.add((String) context.get('accId'));
        Database.delete(Ids);
    }
}
You can add this test to your Cinnamon Test Suite by following the instructions for creating an Apex class. Cinnamon will automatically add your new test to the Test Console.
  1. Setup
    
    public class TestEditAccountPage extends cinnamon.BaseTest {
        public override void setup (cinnamon.Context context) {
            Account acc = new Account();
            acc.name = 'Account' + System.currentTimeMillis();
            insert acc;
            context.put('accId', acc.Id);
        }
            

    • The first thing to notice is that, like in the EditAccountPageObject class, you need to extend cinnamon.BaseTest. Take a look at the method signature for setup(). It overrides the setup() method from the BaseTest class, and has a parameter of type Context. The Context object carries your test objects throughout the testing phase by storing them in a map.
    • Inside the method, you create an Account object, assign it a name, and then insert it into your organization's database.
    • The final piece of the setup is to put your new account into the context map so that it can be used later in the test.

    After your test fixture is setup, the Cinnamon testing framework obtains the URL for the UI page that you're testing using the getStartingPath() method.

    
    public override String getStartingPath(cinnamon.Context context) {
         return '/apex/editAccount?id=' + (String) context.get('accId');
    }
            
  2. Testing
    
    public override void test(cinnamon.Context context) {
        String accId = (String) context.get('accId');
        EditAccountPageObject page = (EditAccountPageObject) context.getPageObject(EditAccountPageObject.class);
                

    This is a long method, so let's take a breather. Again, this method overrides the test() method in the BaseTest class and takes a Context object as a parameter. Inside the method, you retrieve the Account Id from the context map and cast it to a String because the get() method returns a generic object. Now you'll need to use the EditAccountPageObject that you created earlier, instantiating it by calling context.getPageObject(). Note that the initializePageObject() method is called automatically by the Cinnamon framework. Let's keep moving.

    
        page.typeAccountSite('San Francisco');
                

    Finally, you call methods from your PageObject to execute the UI's services. Here, we are changing the Account's site to "San Francisco," a change that is saved by the call to clickSave() in the typeAccountSite() method in your PageObject class. Remember that any action that you want to perform in the UI needs to be defined in the associated PageObject class. Now it's time to check whether performing these actions worked the way that you expected.

    
        Account a = [SELECT name, site rating FROM Account WHERE Id = :accId];
        System.assert(a !=null);
        System.assertEquals(a.site, 'San Francisco');
    }
                

    You first need to create an Account by retrieving the Account that you created during setup from the database by using the Account's Ids. The last two assert() lines ensure that this new object isn't null and that its site field equals "San Francisco." That concludes the testing stage for this example. Easy!

  3. Tear down
    
    public override void tearDown(cinnamon.Context context) {
        List<ID> Ids = new List<ID>();
        Ids.add((String) context.get('accId'));
        Database.delete(Ids);
    }
            

    You've made it to the last step of writing a Cinnamon test. Here, you simply create a list to store all of the objects that you created during the setup and testing stages (again, by getting them from the context map) and then delete the objects in the list from the database.


    • To execute your new test, all you need to do is navigate to the Test Console, then select the checkbox next to TestEditAccountPage, and select the browser and operating system combination on which you want your tests to run.
    • Finally, click Execute Tests. You'll see that the execution status immediately changes to "Scheduled."
      Test scheduled view
      The status changes to Running, and terminates with a status of Passed, Failed, or Error.
      Test passed view
      You can view details about the test by clicking on its name. At the bottom of the details page under Test Executions, you can see information about individual executions of the test.
      Test execution detail page
    • You've successfully written and executed your first Cinnamon test! This was a simple example, but Cinnamon provides functionality for more complex testing. See the Cinnamon Documentation to see what else you can do.

Cinnamon v.56 is released!

Hi there!

Cinnamon Package Version 56 is now available. Please click here if you want to install this version.

With this version, Cinnamon runs the tests that failed due to timeout error automatically once more before reporting it as a failure.

Please note that this setting is enabled by default. If you don't want this behavior, you can disable it via Setting tab by adding advanced=true parameter and unchecking Should Rerun Failures.

If you see some issues or bugs, please let us know via our Github Issues

Happy testing!

Cinnamon v.53 is released!

Hi there!

Just wanted to let you know we've released a new version of Cinnamon (v.53).

Click here to install this version and let us know what you think.

Happy testing!

Cinnamon v.52 is released!

Hi there!

A new vesion of Cinnamon package (v.51) has been released. In this release, we added a new batchSendKey method.

Also, the following classes have been deprecated and removed from Cinnamon package:

  • CN_Context
  • CN_OrgUnderTest

As always, click here to install this version and let us know if you run into problems.

Happy testing!

Cinnamon v.51 is released!

A new version of Cinnamon package (v.51) has been released. This version contains fixes to the following issues:

  • Fix Click method issue with Firefox (Issue 1)
  • Fix issue with "My Domain"(Issue 2)
  • Update Assert and VisualforceLocator javadoc

Click here to install this version and let us know what you think.

Happy testing!

Cinnamon v.50 is released!

A new version of Cinnamon (v.50) has been released. In this release, we enhanced Implicit Waits by adding WebElement.getText and WebElement.sendKeys methods.

Click here to install this version and let us know what you think.

Happy testing!