Sunday, January 25, 2015

Tips on using Selenium-IDE to generate UI tests

My previous posts provides advanced use of exporting test cases generated from Selenium IDE to different languages and technologies. I thought of giving a guide to the basic use of test case exporting in Selenium IDE.

You can download and install Selenium-IDE plugin on FireFox from here.

Exporting test case

Once you have recorded a test using the Selenium-IDE, you can generate tests based on different languages and technologies.

  • Java / JUnit 4 / WebDriver
  • Java / JUnit 4 / Remote Control
  • C# / NUnit / WebDriver
  • C# / NUnit / Remote Control
  • Python 2 / unittest / WebDriver
  • etc

Exporting steps

  1. Goto File > Export Test Case As
  2. This will give a drop down menu with a list of possible languages and technologies as mentioned above.
  3. When you select one option, you will be prompted to save the test case. Save it with an appropriate name and correct file extension.

Use of element locators

When you generate test cases, you often need to refer to an element on the page. Each element often has several locators (css locators, xpath locators etc.) By default you might be provided with CSS locators by the Selenium IDE. But you might prefer to use an xpath locator of the element. 

This plugin helps you to get locators using xpath for different languages. (Java, Ruby, Python, C# etc.)


Selenium-IDE template for TestNG

As mentioned in the previous post we can get a customized test class template for Selenium-IDE using existing templates. As we use TestNG for our testing we faced an issue when generating UI tests through Selenium-IDE. Selenium-IDE currently has support for JUnit but not for TestNG. As a solution we customized the JUnit 4 template for TestNG.

We use the  'Java / JUnit 4 / WebDriver' as the base template in this post as well.
The changes needed to be done
  • Change the imports to TestNG
  • Modify assertion methods according to TestNG specification
    • TestNG and JUnit has different order of parameters in 'assertEquals' method [1] [2]
JUNIT  ---> assertEquals(expected, actual);
TESTNG ---> assertEquals(actual, expected);
Create a new template as described in the previous post using Java / JUnit 4 / WebDriver' template and  give a new name. Then do the following changes to the source file in a text editor.

Replace following imports with the imports given next.
import org.junit.*;
import static org.junit.Assert.*;
New Imports
import org.testng.annotations.*;
import static org.testng.Assert.*;
Next we need to change the order of the arguments. Replace the Equals.prototype.assert section with the following.
Equals.prototype.assert = function() {
  return "assertEquals(" + this.e2.toString() + ", " + this.e1.toString() + ");";
};
  • Click Add button in the formats section mentioned in previous post. 
  • Copy and paste the customized source on it. 
  • Save and Close.
  • Restart the Selenium-IDE.
Now you are ready to generate UI tests on TestNG!!!

Saturday, January 24, 2015

Customizing Selenium IDE test templates

Using Selenium-IDE is one of the easiest ways to write your UI test cases. It is just record and play back. What is even more important is through the ability to export the tests into different languages, test technologies etc. Selenium-IDE contributes greatly to UI test automation.

When it comes to test automation you probably use a framework of your own and has a model of the test cases. For example you will have your Util classes. When you write the UI test cases it might bee necessary to set some environmental values etc. using these Util classes. In such scenarios you will need to generate a customized test class which is different from the default class structure generated by Selenium IDE.

I will provide a few scenarios as examples and how to customize the templates accordingly.
I will consider the default template provided by for 'Java / JUnit 4 / WebDriver' as the base.

Calling your own classes within the test case

We will consider a scenario where you want to include the test case into a set of existing test case set. You have written all other test classes by extending a common class and you need call methods in the parent class within the test class.

Following is the start of a test class generated by exporting using  'Java / JUnit 4 / WebDriver'
package com.example.tests;

import java.util.regex.Pattern;
import java.util.concurrent.TimeUnit;
import org.junit.*;
import static org.junit.Assert.*;
import static org.hamcrest.CoreMatchers.*;
import org.openqa.selenium.*;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.support.ui.Select;

public class Test {
  private WebDriver driver;
  private String baseUrl;
  private boolean acceptNextAlert = true;
  private StringBuffer verificationErrors = new StringBuffer();

  @Before
  public void setUp() throws Exception {
    driver = new FirefoxDriver();
    baseUrl = "http://techspace-ps.blogspot.com/";
    driver.manage().timeouts().implicitlyWait(30, TimeUnit.SECONDS);
  }
We need to change the following sections in order for this test class to fit into the rest of the test classes.
  • The package name
  • Add imports
  • Extend another class
  • Take WebDriver through another class
Now let's see how we can do these.
  1. Open Selenium IDE
  2. Go to Options > Options
  3. Open Formats tab
  4. Select  'Java / JUnit 4 / WebDriver'
  5. Click on Source button on the bottom
  6. Copy and paste the content into a Text Editor
First give a new name to the customized template. (Let's say OurTemp)

Edit 'this.name' property to include the new name.
this.name = "OurTemp";
To change the package name,

Edit 'this.options' section as follows
this.options = {
  receiver: "driver",
  packageName: "our.test.sample",
  indent:    '2',
  initialIndents:    '2',
  showSelenese: 'false',
  defaultExtension: "java"
};
By editing options.header section you can add new imports and extend a another class.
Also you can use the methods of parent class as well.
options.header =
    "package ${packageName};\n" +
        "\n" +
        "import java.util.regex.Pattern;\n" +
        "import java.util.concurrent.TimeUnit;\n" +
        "import org.junit.*;\n" +
        "import static org.junit.Assert.*;\n" +
        "import static org.hamcrest.CoreMatchers.*;\n" +
        "import org.openqa.selenium.*;\n" +
        "import org.openqa.selenium.firefox.FirefoxDriver;\n" +
        "import our.sample.import.BaseTest;\n" +
        "import our.sample.import.BrowserManager;\n" +
        "import org.openqa.selenium.support.ui.Select;\n" +
        "\n" +
        "public class ${className} extends BaseTest{\n" +
        indents(1) + "private WebDriver driver;\n" +
        indents(1) + "private String baseUrl;\n" +
        indents(1) + "private boolean acceptNextAlert = true;\n" +
        indents(0) + "\n" +
        indents(1) + "@Before\n" +
        indents(1) + "public void setUp() throws Exception {\n" +
        indents(2) + "super.init(userMode);\n" +
        indents(2) + "driver = new FirefoxDriver();\n" +
        indents(2) + "baseUrl = \"${baseURL}\";\n" +
        indents(1) + "}\n" +
        indents(0) + "\n" +
        indents(1) + "@Test\n" +
        indents(1) + "public void ${methodName}() throws Exception {\n";

Moving Selenium generated util methods to a separate class

If you have generated test classes using Selenium IDE, you must have noticed following method at the end of each test class.
  private boolean isElementPresent(By by) {
    try {
      driver.findElement(by);
      return true;
    } catch (NoSuchElementException e) {
      return false;
    }
  }
So when we generate 10 test classes, we will have this method in each of them. When all those test classes are put together, it seems to be a bad practice to have the same method everywhere.

You can easily move the method to the base test class, so that it is available to all the test classes by inheritance. In that case you just need to stop the method generating for each class through modifying the template.

You need to remove the lines relevant to such private methods from the 'options.footer' section.
options.footer =
    indents(1) + "}\n" +
        indents(0) + "\n" +
        indents(1) + "@After\n" +
        indents(1) + "public void tearDown() throws Exception {\n" +
        indents(2) + "driver.quit();\n" +
        indents(2) + "String verificationErrorString = verificationErrors.toString();\n" +
        indents(2) + "if (!\"\".equals(verificationErrorString)) {\n" +
        indents(3) + "fail(verificationErrorString);\n" +
        indents(2) + "}\n" +
        indents(1) + "}\n" +
        indents(0) + "\n" +
        indents(1) + "private boolean isElementPresent(By by, WebDriver wd) {\n" +
        indents(2) + "try {\n" +
        indents(3) + "driver.findElement(by);\n" +
        indents(3) + "return true;\n" +
        indents(2) + "} catch (NoSuchElementException e) {\n" +
        indents(3) + "return false;\n" +
        indents(2) + "}\n" +
        indents(1) + "}\n" +
        indents(0) + "\n" +
        indents(1) + "private boolean isAlertPresent() {\n" +
        indents(2) + "try {\n" +
        indents(3) + "driver.switchTo().alert();\n" +
        indents(3) + "return true;\n" +
        indents(2) + "} catch (NoAlertPresentException e) {\n" +
        indents(3) + "return false;\n" +
        indents(2) + "}\n" +
        indents(1) + "}\n" +
        indents(0) + "\n" +
        indents(1) + "private String closeAlertAndGetItsText() {\n" +
        indents(2) + "try {\n" +
        indents(3) + "Alert alert = driver.switchTo().alert();\n" +
        indents(3) + "String alertText = alert.getText();\n" +
        indents(3) + "if (acceptNextAlert) {\n" +
        indents(4) + "alert.accept();\n" +
        indents(3) + "} else {\n" +
        indents(4) + "alert.dismiss();\n" +
        indents(3) + "}\n" +
        indents(3) + "return alertText;\n" +
        indents(2) + "} finally {\n" +
        indents(3) + "acceptNextAlert = true;\n" +
        indents(2) + "}\n" +
        indents(1) + "}\n" +
        indents(0) + "}\n";
Alternatively, we can write a util class in our test package and move this method in to that. When doing that we will also have to pass the 'driver' instance as a parameter. So the new method will look like following.
  public boolean isElementPresent(By by, WebDriver driver) {
    try {
      driver.findElement(by);
      return true;
    } catch (NoSuchElementException e) {
      return false;
    }
  }
With this change you will have to stop the method generation using the same approach as above. Additionally, you need to modify the template to change how this method gets called. (Include the argument in method call)
WDAPI.Utils.isElementPresent = function(how, what) {
  return "isElementPresent(" + WDAPI.Driver.searchContext(how, what) + ", driver)";
};
So the customized template is still on the text editor. Let's add it to Selenium-IDE.
  1. Goto Selenium IDE
  2. Go to Options > Options
  3. Open Formats tab
  4. Click Add button on bottom-left corner
  5. Copy and paste the template into the new window replacing its source content and add the name you added in the source, to the top field
  6. Save the new template
  7. Restart IDE
Now we are all set to generate a test class of our own structure!!!

  • Record a test
  • Go to 'File > Export Test Case As' menu
  • Select the new template ('OurTemp')
  • Save the test with a proper name and correct file extension

 I have generated the same test using the default template and our customized template. You can compare the differences...

Test generated with default template 

package com.example.tests;

import java.util.regex.Pattern;
import java.util.concurrent.TimeUnit;
import org.junit.*;
import static org.junit.Assert.*;
import static org.hamcrest.CoreMatchers.*;
import org.openqa.selenium.*;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.support.ui.Select;

public class Blog {
  private WebDriver driver;
  private String baseUrl;
  private boolean acceptNextAlert = true;
  private StringBuffer verificationErrors = new StringBuffer();

  @Before
  public void setUp() throws Exception {
    driver = new FirefoxDriver();
    baseUrl = "http://www.seleniumhq.org/";
    driver.manage().timeouts().implicitlyWait(30, TimeUnit.SECONDS);
  }

  @Test
  public void testBlog() throws Exception {
    driver.get(baseUrl + "/projects/ide/");
    try {
      assertTrue(isElementPresent(By.linkText("Browser Automation")));
    } catch (Error e) {
      verificationErrors.append(e.toString());
    }
  }

  @After
  public void tearDown() throws Exception {
    driver.quit();
    String verificationErrorString = verificationErrors.toString();
    if (!"".equals(verificationErrorString)) {
      fail(verificationErrorString);
    }
  }

  private boolean isElementPresent(By by) {
    try {
      driver.findElement(by);
      return true;
    } catch (NoSuchElementException e) {
      return false;
    }
  }

  private boolean isAlertPresent() {
    try {
      driver.switchTo().alert();
      return true;
    } catch (NoAlertPresentException e) {
      return false;
    }
  }

  private String closeAlertAndGetItsText() {
    try {
      Alert alert = driver.switchTo().alert();
      String alertText = alert.getText();
      if (acceptNextAlert) {
        alert.accept();
      } else {
        alert.dismiss();
      }
      return alertText;
    } finally {
      acceptNextAlert = true;
    }
  }
}

Test generated with customized template

package our.test.sample;

import java.util.regex.Pattern;
import java.util.concurrent.TimeUnit;
import org.junit.*;
import static org.junit.Assert.*;
import static org.hamcrest.CoreMatchers.*;
import org.openqa.selenium.*;
import org.openqa.selenium.firefox.FirefoxDriver;
import our.sample.import.BaseTest;
import our.sample.import.BrowserManager;
import org.openqa.selenium.support.ui.Select;

public class Blogtest extends BaseTest{
  private WebDriver driver;
  private String baseUrl;
  private boolean acceptNextAlert = true;
  private StringBuffer verificationErrors = new StringBuffer();

  @Before
  public void setUp() throws Exception {
    driver = new FirefoxDriver();
    baseUrl = "http://www.seleniumhq.org/";
    driver.manage().timeouts().implicitlyWait(30, TimeUnit.SECONDS);
  }

  @Test
  public void testBlog() throws Exception {
    driver.get(baseUrl + "/projects/ide/");
    try {
      assertTrue(isElementPresent(By.linkText("Browser Automation")));
    } catch (Error e) {
      verificationErrors.append(e.toString());
    }
  }

  @After
  public void tearDown() throws Exception {
    driver.quit();
    String verificationErrorString = verificationErrors.toString();
    if (!"".equals(verificationErrorString)) {
      fail(verificationErrorString);
    }
  }

}
Hope this helps ...

In the next post I will explain how to use this JUnit 4 based template to generate testNG based test classes with simple modifications as above.

Sunday, August 18, 2013

Using OpenLayers to write OGC clients

One of my recent projects involved implementing several OGC clients: WPS client, WFS client, KML reader and GML reader. WPS client and WFS client required accessing URLs of servers where particular services (WFS, WPS) were hosted.

OpenLayers is a good library which supports implementing these OGC clients. But as I was trying out these clients using Apache tomcat in localhost, they did not work properly. The requests to the servers were not sent as expected. I did a bit of Googling and found out the issue: same-origin policy. You can read more about it here:  http://docs.openlayers.org/library/request.html .

It took me some effort to find a complete solution to my issue. Parts of the solution were scattered around the web and few contained the complete solution. Many had the solution for Apache HTTP server and not for tomcat. I thought it would be useful to put a post with the solution for anyone who needs it and also for my future reference.

Solution for default Apache configuration can be found here:  http://trac.osgeo.org/openlayers/wiki/FrequentlyAskedQuestions#ProxyHost

The solution for Apache tomcat has two parts.

1. From OpenLayers side: a proxy script should be used due to the restrictions in JavaScript on the use of XMLHTTPRequest making requests to remote servers. 

  • Download the proxy.cgi from  http://trac.osgeo.org/openlayers/browser/trunk/openlayers/examples/proxy.cgi
  • Create a directory named 'cgi' in the WEB-INF directory of your web application in which you are using OpenLayers. Place the downloaded file named 'proxy.cgi' in it.
  • To tell OpenLayers what script to use as its proxy host, make a declaration like: 
    OpenLayers.ProxyHost = "/cgi-bin/proxy.cgi?url=";
    
2. From tomcat: CGI has to be enabled.
  • Open web.xml in your tomcat installation. (/tomcat/conf/)
  • Locate the following commented code in web.xml. Then uncomment that section. 
    <servlet>
        <servlet-name>cgi</servlet-name>
        <servlet-class>org.apache.catalina.servlets.CGIServlet</servlet-class>
        <init-param>
          <param-name>debug</param-name>
          <param-value>0</param-value>
        </init-param>
        <init-param>
          <param-name>cgiPathPrefix</param-name>
          <param-value>WEB-INF/cgi</param-value>
        </init-param>
         <load-on-startup>5</load-on-startup>
    </servlet>
  • Add the following following section within the <servlet> tag.
    <init-param>
         <param-name>executable</param-name>
         <param-value>/usr/bin/python</param-value> 
    </init-param>
    <init-param>
         <param-name>passShellEnvironment</param-name>
         <param-value>true</param-value>
    </init-param>

Finally it should look like follows.

    <servlet>
        <servlet-name>cgi</servlet-name>
        <servlet-class>org.apache.catalina.servlets.CGIServlet</servlet-class>
        <init-param>
          <param-name>debug</param-name>
          <param-value>0</param-value>
        </init-param>
        <init-param>
          <param-name>cgiPathPrefix</param-name>
          <param-value>WEB-INF/cgi</param-value>
        </init-param>
         <load-on-startup>5</load-on-startup>
    <init-param>
         <param-name>executable</param-name>
         <param-value>/usr/bin/python</param-value> 
    </init-param>
    <init-param>
         <param-name>passShellEnvironment</param-name>
         <param-value>true</param-value>
    </init-param>
    </servlet>
  • Scroll down from that point and you will locate <servlet-mapping> section relevant to CGI similar to following. Uncomment that section as well. Then save the file.
   <servlet-mapping>
        <servlet-name>cgi</servlet-name>
        <url-pattern>/cgi-bin/*</url-pattern>
    </servlet-mapping>
  • Finally open the context.xml file (tomcat/conf/). Edit it as follows.
<Context privileged="true" antiResourceLocking="false" antiJARLocking="false">

Now you should be able to use OpenLayers without an issue.