Friday, October 7, 2011

Selenium Tests: How to make them Better

May be you should be thinking about writing selenium tests in a better manner considering various things about the application, areas of testing that are in-scope for automation, time constraints you are running under etc etc. However below are some examples of tests that can be written in different ways to make it more maintainable, readable, scalable..

If you're using a sample application to record a simple Selenium test. Of course, you can use selenium IDE to record your first test and then export the same to your development IDE (like Eclipse, Visual Studio etc) and try to run it. When you have actually exported your recorded test, it might look something like below (Note: I have used Java code throughout this)

package com.example.tests;
import com.thoughtworks.selenium.*;
import java.util.regex.Pattern;


public class Untitled extends SeleneseTestCase {
public void setUp() throws Exception {
setUp("http://www.mortgagecalculator.org/", "*chrome");
}
public void testUntitled() throws Exception {
selenium.open("/");
selenium.type("name=param[homevalue]", "150000");
selenium.select("name=param[credit]", "label=Excellent");
selenium.type("name=param[principal]", "125000");
selenium.select("name=param[rp]", "label=New Purchase");
selenium.type("name=param[interest_rate]", "2.5");
selenium.type("name=param[term]", "25");
selenium.select("name=param[start_month]", "label=Jan");
selenium.select("name=param[start_year]", "label=2010");
selenium.type("name=param[property_tax]", "1.1");
selenium.type("name=param[pmi]", "0.35");
selenium.click("css=input[type=\"submit\"]");
selenium.waitForPageToLoad("30000");
String Monthly_Pay = selenium.getText("css=td > h3");
}
}

Manual test for the above class is simple;
1. Open MortgageCalculator.org web page
2. Enter homevalue as 150,000
3. Enter Credit Profile as Excellent
4. Enter Loan Amount as 125,000
5. Enter Loan Purpose
6. Enter Loan Term
7. Enter Start Date
8. Enter Property Tax %
9. Enter PMI %
10. Click Calculate button
11. Finally, verify the Monthly Payment

Now the first thing that one would want to do is to data drive it. May be from an excel file or from a database or whatever.. Here we will try to do the same using Excel which is most widely used for data driven frameworks. If you're using TestNG (as I do) you can actually look at one good article about using TestNG capabilities to data drive a selenium test at Mahesh's blog. I am also using the same data driver here, anyway as a programmer or a tester with common understanding of programming as you could imagine the basic code will change something like below...

public void testMortgages(String homeValue, String creditRate, String principalAmt,String typePurchase, String intRate, String termYrs, String startMonth, String startYr, String propTax, String pmiPercent, String expMthlyPay, String actMthlyPay) throws Exception {
selenium.type("name=param[homevalue]", homeValue);
selenium.select("name=param[credit]", "label=" +creditRate);
selenium.type("name=param[principal]", principalAmt);
selenium.select("name=param[rp]", "label="+typePurchase);
selenium.type("name=param[interest_rate]", intRate);
selenium.type("name=param[term]", termYrs);
selenium.select("name=param[start_month]", "label=" +startMonth);
selenium.select("name=param[start_year]", "label=" +startYr);
selenium.type("name=param[property_tax]", propTax);
selenium.type("name=param[pmi]", pmiPercent);
selenium.click("css=input[type=\"submit\"]");
selenium.waitForPageToLoad("30000");
String monthlyPay = selenium.getText("css=td > h3");
Assert.assertEquals(monthlyPay, expMthlyPay);
System.out.println("Actual Monthly Pay is: " + monthlyPay);
}

Basically we have replaced all the static data with variables. How these variables can be picked up from an excel file? well, we use jxl to do the job of retrieving and/or reading excel file etc and TestNG to do the rest of the job, to use the data returned by a method that uses jxl capabilities to read an excel file and then run a test...

Here as an example, I used Mahesh's code for reading an excel file using jxl

public String[][] getTableArray(String xlFilePath, String sheetName, String tableName) throws Exception{
        String[][] tabArray=null;
       
            Workbook workbook = Workbook.getWorkbook(new File(xlFilePath));
            Sheet sheet = workbook.getSheet(sheetName);
            int startRow,startCol, endRow, endCol,ci,cj;
            Cell tableStart=sheet.findCell(tableName);
            startRow=tableStart.getRow();
            startCol=tableStart.getColumn();


            Cell tableEnd= sheet.findCell(tableName, startCol+1,startRow+1, 100, 64000,  false);              


            endRow=tableEnd.getRow();
            endCol=tableEnd.getColumn();
            System.out.println("startRow="+startRow+", endRow="+endRow+", " +
                    "startCol="+startCol+", endCol="+endCol);
            tabArray=new String[endRow-startRow-1][endCol-startCol-1];
            ci=0;


            for (int i=startRow+1;i<endRow;i++,ci++){
                cj=0;
                for (int j=startCol+1;j<endCol;j++,cj++){
                    tabArray[ci][cj]=sheet.getCell(j,i).getContents();
                }
            }
        return(tabArray);
    }

and created a @dataprovider annotation to read from a given file using the above method..


    @DataProvider(name = "DP2")
    public Object[][] createData2() throws Exception{
        Object[][] retObjArr=getTableArray("lib\\data1.xls","Sheet1","mrtgCalcData1");
        return(retObjArr);
    }

But for my Test method to use this data provider, I have to clearly specify the same who's the data provider for that test, so I will add  @Test (dataProvider = <<name>>) annotation for my test, that would make it look like below...

@Test (dataProvider = "DP2")
<<<<your selenium test method>>>

And that's it. You just need to add a Setup(), Teardown() methods, if you're using Junit framework or some test annotations if you're using TestNG framework to start the selenium server and stop it when the job done..!

package com.tests;


import com.thoughtworks.selenium.*;
//import com.tests.publicLibrary.*;
import org.junit.AfterClass;
import org.openqa.selenium.server.SeleniumServer;
import org.testng.Assert;
import org.testng.annotations.*;
import java.io.File;
import jxl.*;


@SuppressWarnings("deprecation")
public class mortgageCalc extends SeleneseTestCase{
   
    @BeforeClass
    public void beforeJobStarted() throws Exception {
        SeleniumServer seleniumserver=new SeleniumServer();
        seleniumserver.boot();
        seleniumserver.start();
        setUp("http://www.mortgagecalculator.org/", "*firefox");
        selenium.open("/");
        selenium.windowMaximize();
        selenium.windowFocus();
    }


    @DataProvider(name = "DP2")
    public Object[][] createData2() throws Exception{
        Object[][] retObjArr=getTableArray("lib\\data1.xls","Sheet1","mrtgCalcData1");
        return(retObjArr);
    }  
   
@Test (dataProvider = "DP2")
public void testMortgages(String homeValue, String creditRate, String principalAmt,String typePurchase, String intRate, String termYrs, String startMonth, String startYr, String propTax, String pmiPercent, String expMthlyPay, String actMthlyPay) throws Exception {
selenium.type("name=param[homevalue]", homeValue);
selenium.select("name=param[credit]", "label=" +creditRate);
selenium.type("name=param[principal]", principalAmt);
selenium.select("name=param[rp]", "label="+typePurchase);
selenium.type("name=param[interest_rate]", intRate);
selenium.type("name=param[term]", termYrs);
selenium.select("name=param[start_month]", "label=" +startMonth);
selenium.select("name=param[start_year]", "label=" +startYr);
selenium.type("name=param[property_tax]", propTax);
selenium.type("name=param[pmi]", pmiPercent);
selenium.click("css=input[type=\"submit\"]");
selenium.waitForPageToLoad("30000");
String monthlyPay = selenium.getText("css=td > h3");
Assert.assertEquals(monthlyPay, expMthlyPay);
System.out.println("Actual Monthly Pay is: " + monthlyPay);
}  
   
   
    @AfterClass
    public void afterJobDone(){
        selenium.close();
        selenium.stop();
    }
   
    public String[][] getTableArray(String xlFilePath, String sheetName, String tableName) throws Exception{
        String[][] tabArray=null;
       
            Workbook workbook = Workbook.getWorkbook(new File(xlFilePath));
            Sheet sheet = workbook.getSheet(sheetName);
            int startRow,startCol, endRow, endCol,ci,cj;
            Cell tableStart=sheet.findCell(tableName);
            startRow=tableStart.getRow();
            startCol=tableStart.getColumn();


            Cell tableEnd= sheet.findCell(tableName, startCol+1,startRow+1, 100, 64000,  false);              


            endRow=tableEnd.getRow();
            endCol=tableEnd.getColumn();
            System.out.println("startRow="+startRow+", endRow="+endRow+", " +
                    "startCol="+startCol+", endCol="+endCol);
            tabArray=new String[endRow-startRow-1][endCol-startCol-1];
            ci=0;


            for (int i=startRow+1;i<endRow;i++,ci++){
                cj=0;
                for (int j=startCol+1;j<endCol;j++,cj++){
                    tabArray[ci][cj]=sheet.getCell(j,i).getContents();
                }
            }
        return(tabArray);
    }
}

If you run this using TestNG, it should work fine. You can use any other framework by making lil updates to the above class, that is out of scope for this.. But what can we do better, here? Is this the best automated test case, I can ever have using Selenium. Obviously not, because one could use his/ her own intelligence to come up with a brilliant solution to make it more maintainable, scalable etc. However there's pretty basic things that one should consider that're already listed in selenium's website. Below are some examples of how this test can be made better readable, maintainable, scalable. I will try to explain them in my next few posts..

No comments:

Post a Comment