[Article] Introducing UI tests for WSO2 Products

  • By Dimuthu De Lanerolle
  • 13 Oct, 2014

Table of contents

  • Introduction
  • Structure of UI test modules
  • Dependency management for UI tests
  • Scope
  • Plugin and configuration management for UI tests
  • Selenium record and playback
  • Selenium page object pattern
  • Writing the test case
  • Execution of tests
  • Summary

Introduction

Our objective is to present comprehensive guidance on adding UI tests-related modules to WSO2 products and a step-by-step guide describing the execution of tests.

Note: It is assumed that that those reading this article are familiar with TestNG and Selenium WebDriver API for writing UI tests (refer to TestNG documentation and Selenium documentation for further details).

Structure of UI test modules

In order to begin our implementation, we have to define a structure for our project.

For demonstration purposes, we will now consider a use case of introducing UI modules for WSO2 Business Activity Monitor (BAM). To learn more about WSO2 BAM refer to product documentation.

You can clone the WSO2 BAM product source from the following github HTTP clone URL https://github.com/wso2/product-bam.

After cloning the source, we need to add relevant modules to the project structure. Navigate to …./product-bam/modules/integration module. If it does not exist, you would need to add the tests-common module to the integration module. How to add these modules are explained here. Now navigate to the created tests-common module and we then need to create a ui-pages module inside the tests-common module.

Moreover, we need to add a new tests-ui-integration module for the purpose of writing UI tests in our project. To do so, navigate back to the …./product-bam/modules/integration module and create the tests-ui-integration maven module.

Our project structure should look similar to what has been shown below.

tests-common

This module is used to add useful custom common utilities to help write our tests.

ui-pages

This module can be used to store page object model classes that we can use inside our tests.

Page object models are the areas that our tests interact with as objects within the test code. In other words, page objects are java object class that represent a web page, which we need to test. The main advantages of page object models are that they reduce the amount of duplicate code and in case the UI changes, the fix needs to be applied only in one place. If UI changes have taken place, we can waive the burden of modifying every single test class in the suite or Page Object classes, and instead only make changes to the mapper.properties file.

However, note that in a situation where a considerable layout change takes place, we might need to change the page object classes as well depending on the requirement.

tests-ui-integration

UI test classes can be written inside this module.

However, note that in a situation where a considerable layout change takes place, we might need to change the page object classes as well depending on the requirement.

tests-ui-integration

UI test classes can be written inside this module.

Dependency management for UI tests

Maven dependency management is one of the key features of Maven and for our exercise we need to identify required maven dependencies for the project. We will now list-down basic key maven dependencies you need to add to your relevant pom.xml file/s. We will travel through each module and describe the necessary maven dependencies that need to be added to each pom.xml file.

Note the groupId, artifactId, and versions of each pom.xml files. You can replace relevant values in accordance with your module structure. In-order to adhere to best practices, we will define all required dependencies in the root pom.xml with the versions.

product-bam pom.xml

  
………………..   

    
      org.wso2.carbon.automationorg.wso2.carbon.automation.engine${test.framework.version}testorg.wso2.carbon.automationorg.wso2.carbon.automation.extensions${test.framework.version}testorg.wso2.carbon.automationorg.wso2.carbon.automation.test.utils${test.framework.version}testorg.wso2.bamorg.wso2.bam.integration.ui.pages${bam.version}testorg.wso2.carbonorg.wso2.carbon.integration.common.admin.client${carbon.version}testorg.wso2.carbonorg.wso2.carbon.integration.common.extensions${carbon.version}test4.3.0-SNAPSHOT4.3.1-SNAPSHOT2.5.0-SNAPSHOT
…………...

tests-common pom.xml

  
4.0.0org.wso2.bambam-integration-tests-commonpom2.5.0-SNAPSHOTWSO2 BAM Server Integration Test Commonui-pages

ui-pages pom.xml

  
org.wso2.bambam-integration-parent2.5.0-SNAPSHOT../../pom.xml4.0.0WSO2 BAM - Integration Test UI Moduleorg.wso2.bamorg.wso2.bam.integration.ui.pagesorg.wso2.carbon.automationorg.wso2.carbon.automation.extensionscompileorg.testngtestngcompileorg.wso2.carbon.automationorg.wso2.carbon.automation.enginecompileorg.wso2.carbon.automationorg.wso2.carbon.automation.test.utilscompileorg.wso2.carbonorg.wso2.carbon.integration.common.admin.clientcompile

tests-ui-integration pom.xml

  
………
  org.wso2.bambam-integration-parent2.5.0-SNAPSHOT../pom.xml4.0.0BAM Server Integration test UI moduleorg.wso2.bam.ui.integration.testjarmaven-surefire-pluginfalse2.12.3-Xmx1024m -XX:PermSize=256m -XX:MaxPermSize=512msrc/test/resources/testng.xml${skipUiTests}maven.test.haltafterfailurefalsecarbon.zip
                                ${basedir}/../../distribution/target/wso2bam-${project.version}.zip
                            samples.dir${basedir}/../../../samples/productframework.resource.location
                                ${basedir}/src/test/resources/
                            server.list
                                BAM
                            usedefaultlistenersfalse${basedir}/target/security-verifier/${basedir}/target/emma${basedir}/src/test/resources/instrumentation.txt${basedir}/src/test/resources/filters.txt${basedir}/target/emma${basedir}/targetorg.apache.maven.pluginsmaven-clean-plugin2.5${basedir}/src/test/resources/client/modules**/*.marfalsemaven-dependency-plugincopy-emma-dependenciescompilecopy-dependencies${project.build.directory}/emmajaremma
                            copy-jar-dependenciescompilecopy-dependencies${basedir}/src/test/resources/artifacts/BAM/jar
                            jarmysql-connector-java
                            truecopy-secVerifiercompilecopy-dependencies${basedir}/target/security-verifieraarSecVerifiertrueunpack-mar-jkscompileunpackorg.wso2.bamwso2bam${project.version}ziptrue${basedir}/target/tobeCopied/**/*.jks,**/*.marorg.apache.maven.pluginsmaven-jar-plugin2.4test-jarmaven-resources-plugin2.6copy-resources-jkscompilecopy-resources${basedir}/src/test/resources/keystores/products
                            
                                        ${basedir}/target/tobeCopied/wso2bam-${project.version}/repository/resources/security/
                                    **/*.jkscopy-resources-marcompilecopy-resources${basedir}/src/test/resources/client/modules
                            
                                        ${basedir}/target/tobeCopied/wso2bam-${project.version}/repository/deployment/client/modules
                                    **/*.marorg.wso2.carbon.automationorg.wso2.carbon.automation.engineorg.wso2.carbon.automationorg.wso2.carbon.automation.test.utilsorg.wso2.carbonorg.wso2.carbon.integration.common.extensionsorg.wso2.bamorg.wso2.bam.integration.ui.pagescompiletrue

Scope

As you can see, we used maven scope to limit the transitivity of a dependency. Below are the scopes we used in the above pom.xml files.

test

This scope indicates that the dependency is not required for normal use of the application, and is only available for the test compilation and execution phases.

compile

This is the default scope; compile dependencies are available in all classpaths of a project. Furthermore, those dependencies are propagated to dependent projects.

Plugin and configuration management for UI tests

  1. maven-surefire-plugin

    This plugin can be used during the test phase of the build lifecycle to execute our tests. You can define many configurational properties, as mentioned below, which becomes very handy when organizing your maven test project.

    E.g. ${skipUiTests}

    • We use this property tag to skip all Ui tests from regular builds and enable UI tests only in build servers

      carbon.zip

      ${basedir}/../../distribution/target/wso2bam-${project.version}.zip

    • You need to provide the correct location where your product zip file resides. The test suite will extract the zip file found from this place to ${basedir}/target directory and start running your test class.
    • To run the UI tests in you local builds, the following command can be used -

      mvn install -DskipUiTests=false

  2. maven-clean-plugin

    This plugin removes files generated at build-time in a project's directory. Clean plugin assumes that these files are generated inside the target directory.

  3. maven-dependency-plugin

    Basically, this plugin is capable of manipulating artifacts. You can define operations like copying artifacts from local or remote repositories to a specified location.

  4. maven-jar-plugin

    We use this plugin to build and sign jars. Basically, you can define two goals inside this plugin.

    • jar:jar

      - creates a jar file for your project classes inclusive resources.

    • jar:test-jar

      - creates a jar file for your project test classes.

  5. maven-resources-plugin

    Copies project resources to the output director.

WSO2 TAF recommends the following two approaches for writing automated UI tests.

  1. Selenium record and playback
  2. Selenium Page Object Pattern

Selenium record and playback

Selenium IDE is a plugin to firefox to record and playback tests. You can then export the recorded tests. These exported tests can be run in any browser and any platform using "Selenium WebDriver".

For more details on Selenium IDE click here.

We found many interesting tutorials on Selenium IDE and record and playback on the web. Here are some of them that will help you explore more on Selenium and its usages.

  1. http://www.tutorialspoint.com/selenium/index.htm
  2. http://www.guru99.com/selenium-tutorial.html

Note: TAF's main test engine is TestNG. TAF heavily uses the listener based test execution of the TestNG. You can easily integrate Selenium web driver with the TestNG framework for writing automated UI tests. Therefore, the recorded Selenium scripts should be exported as TestNG tests in order to make them compatible with TAF.

Selenium page object pattern

We will now elaborate source codes for each classes categorized under the respective UI modules.

LoginPage.java

Performs UI Login test case scenario, i.e. this class contains methods to login to WSO2 products.

  

package org.wso2.bam.integration.ui.pages.login;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.wso2.bam.integration.ui.pages.UIElementMapper;
import org.wso2.bam.integration.ui.pages.home.HomePage;

import java.io.IOException;

public class LoginPage {
    private static final Log log = LogFactory.getLog(LoginPage.class);
    private WebDriver driver;
    private UIElementMapper uiElementMapper;

    public LoginPage(WebDriver driver) throws IOException {
        this.driver = driver;
        this.uiElementMapper = UIElementMapper.getInstance();
        // Check that we're on the right page.
        if (!(driver.getCurrentUrl().contains("login.jsp"))) {
            // Alternatively, we could navigate to the login page, perhaps logging out first
            throw new IllegalStateException("This is not the login page");
        }
    }

    /**
     * Provide facility to log into the products using user credentials
     *
     * @param userName login user name
     * @param password login password
     * @return reference to Home page
     * @throws java.io.IOException if mapper.properties file not found
     */
    public HomePage loginAs(String userName, String password) throws IOException {
        log.info("Login as " + userName);
        WebElement userNameField = driver.findElement(By.name(uiElementMapper.getElement("login.username")));
        WebElement passwordField = driver.findElement(By.name(uiElementMapper.getElement("login.password")));
        userNameField.sendKeys(userName);
        passwordField.sendKeys(password);
        driver.findElement(By.className(uiElementMapper.getElement("login.sign.in.button"))).click();
        return new HomePage(driver);
    }
}

HomePage.java

Home page class holds the information of product page. It also contains a sign-out method.

  
package org.wso2.bam.integration.ui.pages.home;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.wso2.bam.integration.ui.pages.UIElementMapper;
import org.wso2.bam.integration.ui.pages.login.LoginPage;

import java.io.IOException;

public class HomePage {

    private static final Log log = LogFactory.getLog(HomePage.class);
    private WebDriver driver;
    private UIElementMapper uiElementMapper;

    public HomePage(WebDriver driver) throws IOException {
        this.driver = driver;
        this.uiElementMapper = UIElementMapper.getInstance();
        // Check that we're on the right page.
        if(!driver.findElement(By.id(uiElementMapper.getElement("home.dashboard.middle.text"))).getText().contains("Home")) {
            throw new IllegalStateException("This is not the home page");
        }
    }

    public LoginPage logout() throws IOException {
        driver.findElement(By.xpath(uiElementMapper.getElement("home.greg.sign.out.xpath"))).click();
        return new LoginPage(driver);
    }
}

BAMIntegrationUiBaseTest.java

This is an abstract class that helps us to create custom automation context objects and we can define environment-related methods that can be used regularly inside our test cases. This class can be extended by other test classes which in-turn eliminates code duplication inside the project.

  
package org.wso2.bam.integration.ui.pages;

import org.wso2.carbon.automation.engine.context.AutomationContext;
import org.wso2.carbon.automation.engine.context.TestUserMode;
import org.wso2.carbon.automation.test.utils.common.HomePageGenerator;

import javax.xml.xpath.XPathExpressionException;

public abstract class BAMIntegrationUiBaseTest {

    protected AutomationContext automationContext;

    protected void init() throws Exception {
        automationContext = new AutomationContext("BAM", "bam001", TestUserMode.SUPER_TENANT_ADMIN);
    }


    protected String getServiceUrl() throws XPathExpressionException {
        return automationContext.getContextUrls().getServiceUrl();
    }

    protected String getLoginURL() throws XPathExpressionException {
        return HomePageGenerator.getProductHomeURL(automationContext);
    }
}

UIElementMapper.java

The objective of this class is to read mapper.properties file and load its Selenuim UI Elements into properties object.

  
package org.wso2.bam.integration.ui.pages;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;


public class UIElementMapper {
    public static final Properties uiProperties = new Properties();
    private static final Log log = LogFactory.getLog(UIElementMapper.class);
    private static UIElementMapper instance;

    private UIElementMapper() {
    }

    public static synchronized UIElementMapper getInstance() throws IOException {
        if (instance == null) {
            setStream();
            instance = new UIElementMapper();
        }
        return instance;
    }

    public static Properties setStream() throws IOException {

      InputStream inputStream = UIElementMapper.class.getResourceAsStream("/mapper.properties");

        if (inputStream.available() > 0) {
            uiProperties.load(inputStream);
            inputStream.close();
            return uiProperties;
        }
        return null;
    }

    public String getElement(String key) {
        if (uiProperties != null) {
            return uiProperties.getProperty(key);
        }
        return null;
    }
}

mapper.properties

Includes key-value pair configurational properties relating to UI elements of tests. Should be placed inside …../tests-common/ui-pages/src/main/resources directory. The main purpose of maintaining a mapper.properties file is to define a central location to maintain all UI elements. Accordingly, if a path to an UI element has been changed, you can easily modify the mapper.properties file. Below is an excerpt of a mapper.properties file.

  
….
login.username=username
login.password=password
login.sign.in.button=button
home.dashboard.middle.text=middle
….

To view a structure of a complete mapper.properties file click here.

Writing the test case

Add the following test class (LoginTestCase.java) to the module tests-ui-integration.

The basic objective of this class is to perform and verify UI login to the BAM server. Note how we extended BAMIntegrationUiBaseTest inside our LoginTestCase class.

There are a few TestNG annotations we have used inside our test class.

@BeforeClass

  • The annotated method will be run before the first test method if the current class is invoked, i.e this annotation allows us to execute the setUp() method before the testLogin() method, which is under the @Test annotation.

@Test

  • Test methods are annotated with @Test. In other words, it marks a class or a method as part of the test.

@AfterClass

  • The annotated method will be run after all the test methods in the current class have been run, i.e this annotation allows to execute tearDown() after performing the stated operations inside testLogin() method.

Note:

setUp()

  • super.init() - Initializes the automation context
  • Invokes getWebDriver() of BrowserManager class in-order to derive the Webdriver instance.

tearDown()

  • Quits the driver closing every associated window.
  
package org.wso2.bam.ui.integration.test;

import org.openqa.selenium.WebDriver;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.wso2.carbon.automation.extensions.selenium.BrowserManager;
import org.wso2.bam.integration.ui.pages.BAMIntegrationUiBaseTest;
import org.wso2.bam.integration.ui.pages.home.HomePage;
import org.wso2.bam.integration.ui.pages.login.LoginPage;

public class LoginTestCase extends BAMIntegrationUiBaseTest {

    private WebDriver driver;

    @BeforeClass(alwaysRun = true)
    public void setUp() throws Exception {
        super.init();

        driver = BrowserManager.getWebDriver();
        driver.get(getLoginURL());
    }

    @Test(groups = "wso2.bam", description = "verify login to bam server")
    public void testLogin() throws Exception {
        LoginPage test = new LoginPage(driver);
        HomePage home = test.loginAs(automationContext.getSuperTenant().getTenantAdmin().getUserName(),
                automationContext.getSuperTenant().getTenantAdmin().getPassword());
        home.logout();
        driver.close();
    }

    @AfterClass(alwaysRun = true)
    public void tearDown() throws Exception {
        driver.quit();
    }
}

Note:

Click on BrowserManager to view the source of the BrowserManager class. To identify Selenium locators, you can use the element locator for webdriver firefox plugin.

Execution of tests

In-order to run LoginTestCase follow the steps mentioned below.

  • Configuring automation.xml

    Click automation.xml to learn more about automation.xml configurations. We will now consider relevant segments you need to draw your attention to in order to execute our UI test scenario with a short description under each segment.

  
http://10.100.2.51:4444/wd/hub/firefox/home/test/name/webDriver

Description:

The above configuration will help us to define the browser type in which the test should run and define the web driver path and the choice of enabling/disabling the remote web driver instance.

  
adminadmintestuser11testuser11testuser21testuser21

Description:

To register a set of system-wide users at the test initiation stage. Note the admin super tenant and set of tenant users controlled by the admin super tenant.

  
 localhost97639443

Description:

You can define different product groups for the product category (in our case we can specify this as BAM) together with enable/disable clustering feature (true/false). Note how these configurations help us to initialize the automation context inside the BAMIntegrationUiBaseTest class.

Add the following listeners’ entries to testng.xml file.

  1.   
    
  2.   
    
  3. Description:

    1. Implementing TestNG listener interfaces provide a way to call event handlers inside custom listener classes, thus this makes it possible to do pre-defined operations in the TestNG execution cycle. Click here to learn more on these listener classes.
    2. Provide the test class you need to execute. This will run the mentioned class only in the test suite.

    Note:

    Alternatively, you can execute a whole test package that contains one or many test classes in the test suite. To do so, you can simply add the below snippet to the testng.xml file.

      
    
  • Execute the below maven command.
    mvn install -DskipUiTests=false

    You may need to find out the correct browser version that’s compatible with the Selenium library version used in the WSO2 Test Automation Framework. Sometimes it is required to downgrade or upgrade your browser version. To run Selenium with particular browser version (firefox), refer to the documentation provided here.

Summary

This article provided a step-by-step guide on our UI testing scenario. The article can be used as a foundation and guide for users to add UI modules to products and implement different UI testing scenarios.

About Author

  • Dimuthu De Lanerolle
  • Software Engineer
  • WSO2