Tuesday, January 31, 2012

Selenium

What if you wanted to write Unit Tests for testing the UI? You want your test code to open a browser, go to your URL, click buttons and check values.

Enter Selenium.

Selenium is an open source project for automating UI testing.

Let us write a JUnit test which uses Selenium to test the UI for the JSON example project we created in the earlier post.

First, we need to download the selenium.jar.

Update your ivy.xml to create a dependency on Selenium, like this:

(Note - due to the way my internal repository is setup, I had to explicitly declare dependencies on other jars on which Selenium depends - you might not have to do this if you are pointing to the Maven central repository).

 <ivy-module version="2.0">  
   <info organisation="org.confucius" module="helloworld"/>  
   <dependencies>  
           <dependency org="commons-logging" name="commons-logging" rev="1.1.1"/>  
           <dependency org="org.apache.httpcomponents" name="httpcore" rev="4.2-alpha2"/>            
           <dependency org="org.apache.httpcomponents" name="httpclient" rev="4.2-alpha1"/>             
           <dependency org="org.apache.commons" name="commons-exec" rev="1.1"/>        
           <dependency org="com.google.guava" name="guava" rev="r09"/>  
           <dependency org="org.seleniumhq.selenium" name="selenium-api" rev="2.17.0"/>  
           <dependency org="org.seleniumhq.selenium" name="selenium-remote-driver" rev="2.17.0"/>  
           <dependency org="org.seleniumhq.selenium" name="selenium-firefox-driver" rev="2.17.0"/>  
           <dependency org="org.seleniumhq.selenium" name="selenium-java" rev="2.16.1"/>            
           <dependency org="junit" name="junit" rev="4.10"/>            
           <dependency org="org.json" name="json" rev="20090211"/>  
     <dependency org="javax.servlet" name="servlet-api" rev="2.5"/>  
     <dependency org="javax.servlet" name="jsp-api" rev="2.0"/>  
     <dependency org="jstl" name="jstl" rev="1.2"/>  
         <dependency org="log4j" name="log4j" rev="1.2.16"/>   
   </dependencies>  
 </ivy-module>  


Run the Ant resolve target to download all the jars.

Next, we need to update Eclipse Classpath to include these jars.

Go to Project->Properties->Java Build Path ->Add Jars
and add all the newly downloaded jars.

We also need to update the Classpath for Ant.

Update your build.xml to include the new jars, like this:
 <project name="HelloWorld" xmlns:ivy="antlib:org.apache.ivy.ant" >  
   
   <target name="resolve" description="--> retrieve dependencies with ivy">  
     <ivy:retrieve />  
   </target>  
   
      <target name="init" depends="resolve">  
           <mkdir dir="classes"/>  
           <mkdir dir="target"/>  
      </target>  
   
      <path id="build.classpath">  
       <pathelement location="lib/commons-logging-1.1.1.jar"/>  
       <pathelement location="lib/httpcore-4.2-alpha2.jar"/>  
       <pathelement location="lib/httpclient-4.2-alpha1.jar"/>  
       <pathelement location="lib/commons-exec-1.1.jar"/>  
       <pathelement location="lib/guava-r09.jar"/>  
       <pathelement location="lib/selenium-api-2.17.0.jar"/>  
       <pathelement location="lib/selenium-firefox-driver-2.17.0.jar"/>  
       <pathelement location="lib/selenium-java-2.16.1.jar"/>  
       <pathelement location="lib/selenium-remote-driver-2.17.0.jar"/>  
       <pathelement location="lib/junit-4.10.jar"/>  
       <pathelement location="lib/json-20090211.jar"/>  
       <pathelement location="lib/servlet-api-2.5.jar"/>  
       <pathelement location="lib/jsp-api-2.0.jar"/>  
       <pathelement location="lib/log4j-1.2.16.jar"/>  
      </path>  
   
      <target name="compile" depends="init">  
           <javac srcdir="." destdir="classes">  
                <classpath refid="build.classpath"/>  
           </javac>  
      </target>  
   
      <path id="test.classpath">  
       <pathelement location="classes"/>  
       <pathelement location="lib/junit-4.10.jar"/>  
       <pathelement location="lib/selenium-api-2.17.0.jar"/>  
       <pathelement location="lib/selenium-firefox-driver-2.17.0.jar"/>  
       <pathelement location="lib/selenium-java-2.16.1.jar"/>  
       <pathelement location="lib/selenium-remote-driver-2.17.0.jar"/>  
       <pathelement location="lib/guava-r09.jar"/>  
       <pathelement location="lib/commons-exec-1.1.jar"/>  
       <pathelement location="lib/httpclient-4.2-alpha1.jar"/>  
       <pathelement location="lib/httpcore-4.2-alpha2.jar"/>  
       <pathelement location="lib/commons-logging-1.1.1.jar"/>  
      </path>  
   
      <target name="test" depends="compile" >  
           <junit failureproperty="junit.failure">  
                <test name="org.confucius.TestCalculator"/>  
                <classpath refid="test.classpath"/>  
                <formatter type="plain" usefile="false" />  
           </junit>  
           <fail if="junit.failure" message="Unit test(s) failed. See reports!"/>  
      </target>  
        
      <target name="dist" depends="test">  
           <war destfile="target/HelloWorld.war" webxml="web.xml">  
                 <classes dir="classes"/>  
                 <lib dir="lib">  
                      <exclude name="jsp-api*.jar"/>  
                      <exclude name="servlet-api*.jar"/>  
                 </lib>       
                 <fileset dir="web-content"/>  
                 <webinf dir="WEB-INF"/>  
           </war>  
           <echo>Build executed at ${TIME_NOW}</echo>  
      </target>  
   
      <tstamp>   
           <format property="TIME_NOW" pattern="hh:mm:ss aa MM/dd/yyyy"/>   
      </tstamp>   
        
 </project>  


We also need to give an "id" to the button in HelloWorld.jsp, like this:
  <html>   
    <head>   
                 <script src="../js/jquery-1.7.1.min.js"></script>   
            <script type="text/javascript">   
         function getMusicSchool()   
            {   
              // Create AJAX object   
              var xmlhttp;   
                 
              if (window.XMLHttpRequest)   
               xmlhttp=new XMLHttpRequest(); // IE7+, Firefox, Chrome, Opera, Safari   
              else   
               xmlhttp=new ActiveXObject("Microsoft.XMLHTTP"); // IE6, IE5   
     
              // Associate a method for AJAX response   
              xmlhttp.onreadystatechange=function()   
               {   
               if (xmlhttp.readyState==4 && xmlhttp.status==200) // Successful response   
                         getJSONObject(xmlhttp.responseText);  
               }   
                  
              // Send AJAX request   
              xmlhttp.open("GET","http://localhost:8080/HelloWorld/music-school",true);   
              xmlhttp.send();             
            }   
           
         function getJSONObject(jsonStr)  
              {  
              if (null != jsonStr && 0 != jsonStr.length)  
                   {  
                alert(jsonStr)  
                            try  
                                 {  
                                 jsonObj = $.parseJSON( jsonStr );  
                                 displatStr = jsonObj.name + ", located at " + jsonObj.address + " teaches " + jsonObj.instruments.length + " instruments, including " + jsonObj.instruments[1] + ".";   
                     alert(displatStr);  
                                 }  
                            catch(e)  
                                 {  
                     alert(e.toString());  
                                 }   
                     
                   }  
              }  
            </script>      
    </head>   
    <body>   
       <form>   
         <button id="musicButton" type="button" onclick="getMusicSchool()">Show Music School</button>   
       </form>   
    </body>   
  </html>   



Now let us write a Junit test and use Selenium to test the UI.

In your /test/org/confucius folder, create a class TestHelloWorldUI.java, like this:
 package org.confucius;  
   
 import org.openqa.selenium.Alert;  
 import org.openqa.selenium.By;  
 import org.openqa.selenium.WebDriver;  
 import org.openqa.selenium.WebElement;  
 import org.openqa.selenium.firefox.FirefoxDriver;  
   
 import junit.framework.TestCase;  
   
 public class TestHelloWorldUI extends TestCase {  
        
      public void testMusicSchool(){  
     WebDriver driver = new FirefoxDriver();  
     driver.get("http://localhost:8080/HelloWorld/jsp/HelloWorld.jsp");  
     WebElement element = driver.findElement(By.id("musicButton"));  
     element.click();  
   
     Alert jsonAlert = driver.switchTo().alert();  
     String jsonText = jsonAlert.getText();  
     String expectedJSONString = new String("{\"address\":\"9000 Cliff Drive, Santa Cruz, California\",\"name\":\"Beethoven Music School\",\"instruments\":[\"Piano\",\"Guitar\",\"Trumpet\",\"Violin\"]}");  
     assertTrue(jsonText.trim().equals(expectedJSONString.trim()));  
     jsonAlert.accept();  
   
     Alert javascriptAlert = driver.switchTo().alert();  
     String javascriptText = javascriptAlert.getText();  
     String expectedJavascriptString = new String("Beethoven Music School, located at 9000 Cliff Drive, Santa Cruz, California teaches 4 instruments, including Guitar.");  
     assertTrue(javascriptText.trim().equals(expectedJavascriptString.trim()));  
     javascriptAlert.accept();  
       
     driver.close();  
      }  
        
 }  
   


Note that we are testing in the Firefox browser.

In the above test:
- We open a browser
- Go to http://localhost:8080/HelloWorld/jsp/HelloWorld.jsp
- Click on the musicButton
- Check the text in the alert boxes which popup
- Close the browser

If you run this JUnit test, you will see the Browser popup and Selenium clicking around.

No comments: