[Article] Introducing UI tests for WSO2 Products
- Dimuthu De Lanerolle
- Software Engineer - WSO2
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.automation org.wso2.carbon.automation.engine ${test.framework.version} test org.wso2.carbon.automation org.wso2.carbon.automation.extensions ${test.framework.version} test org.wso2.carbon.automation org.wso2.carbon.automation.test.utils ${test.framework.version} test org.wso2.bam org.wso2.bam.integration.ui.pages ${bam.version} test org.wso2.carbon org.wso2.carbon.integration.common.admin.client ${carbon.version} test org.wso2.carbon org.wso2.carbon.integration.common.extensions ${carbon.version} test …………... 4.3.0-SNAPSHOT 4.3.1-SNAPSHOT 2.5.0-SNAPSHOT
tests-common pom.xml
4.0.0 org.wso2.bam bam-integration-tests-common pom 2.5.0-SNAPSHOT WSO2 BAM Server Integration Test Common ui-pages
ui-pages pom.xml
org.wso2.bam bam-integration-parent 2.5.0-SNAPSHOT ../../pom.xml 4.0.0 WSO2 BAM - Integration Test UI Module org.wso2.bam org.wso2.bam.integration.ui.pages org.wso2.carbon.automation org.wso2.carbon.automation.extensions compile org.testng testng compile org.wso2.carbon.automation org.wso2.carbon.automation.engine compile org.wso2.carbon.automation org.wso2.carbon.automation.test.utils compile org.wso2.carbon org.wso2.carbon.integration.common.admin.client compile
tests-ui-integration pom.xml
………org.wso2.bam bam-integration-parent 2.5.0-SNAPSHOT ../pom.xml 4.0.0 BAM Server Integration test UI module org.wso2.bam.ui.integration.test jar maven-surefire-plugin false 2.12.3 -Xmx1024m -XX:PermSize=256m -XX:MaxPermSize=512m src/test/resources/testng.xml ${skipUiTests} maven.test.haltafterfailure false carbon.zip ${basedir}/../../distribution/target/wso2bam-${project.version}.zip samples.dir ${basedir}/../../../samples/product framework.resource.location ${basedir}/src/test/resources/ server.list BAM usedefaultlisteners false ${basedir}/target/security-verifier/ ${basedir}/target/emma ${basedir}/src/test/resources/instrumentation.txt ${basedir}/src/test/resources/filters.txt ${basedir}/target/emma ${basedir}/target org.apache.maven.plugins maven-clean-plugin 2.5 ${basedir}/src/test/resources/client/modules **/*.mar false maven-dependency-plugin copy-emma-dependencies compile copy-dependencies ${project.build.directory}/emma jar emma copy-jar-dependencies compile copy-dependencies ${basedir}/src/test/resources/artifacts/BAM/jar jar mysql-connector-java true copy-secVerifier compile copy-dependencies ${basedir}/target/security-verifier aar SecVerifier true unpack-mar-jks compile unpack org.wso2.bam wso2bam ${project.version} zip true ${basedir}/target/tobeCopied/ **/*.jks,**/*.mar org.apache.maven.plugins maven-jar-plugin 2.4 test-jar maven-resources-plugin 2.6 copy-resources-jks compile copy-resources ${basedir}/src/test/resources/keystores/products ${basedir}/target/tobeCopied/wso2bam-${project.version}/repository/resources/security/ **/*.jks copy-resources-mar compile copy-resources ${basedir}/src/test/resources/client/modules ${basedir}/target/tobeCopied/wso2bam-${project.version}/repository/deployment/client/modules **/*.mar org.wso2.carbon.automation org.wso2.carbon.automation.engine org.wso2.carbon.automation org.wso2.carbon.automation.test.utils org.wso2.carbon org.wso2.carbon.integration.common.extensions org.wso2.bam org.wso2.bam.integration.ui.pages compile true
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
- 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
- We use this property tag to skip all Ui tests from regular builds and enable UI tests only in build servers
- 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.
- 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.
- 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.
- jar:jar
- maven-resources-plugin
Copies project resources to the output director.
WSO2 TAF recommends the following two approaches for writing automated UI tests.
- Selenium record and playback
- 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.
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.
https://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.
admin admin testuser11 testuser11 testuser21 testuser21
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.
localhost 9763 9443
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.
-
-
- 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.
- Provide the test class you need to execute. This will run the mentioned class only in the test suite.
Description:
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=falseYou 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.