Friday, March 2, 2012

SeleniumWebdriver - Page objects Implementation - Part 1

SeleniumWebdriver - Page objects Implementation - Part 1

SeleniumWebdriver - Page objects Implementation - Part 2

I have implemented complex QTP hybrid frameworks. Now I am working on Selenium2 aka. Selenium Webdriver and implemented the similar framework. Why? Open source, No licence fee, multi browser os language support.
I would like to share my experience through this post.

I take into consideration following points while designing the framework.

1. Re-usability - Any change in the application (Change in the object property) that change need to be done at one place only, if we are doing at more than one place it is not re-usable code, there are slight exceptions.
2. Independent modules -
3. Easy to use - Selecting the keywords from the auto suggest, not required any programming knowledge to create the test case.

4. Multi-level abstraction -
5. Run single, multiple and selected test cases.


Following are the challenges that I came across during implementation.

1. Email Notifications of test results - Initially I have used Hudson CI to schedule the test and receive email notifications. IE Driver is not stable in few scenarios like Modal-Dialog, it keep on waiting for ever without moving ahead, user need to close the IEs and restart the test again. There is no option of sending attachments through Hudson. So I started invoking the ANT using .VBS where I have better control of the execution and I can kill the process if it is not responding. Email notification component is written in VBS, where I can send email with attachments with intermediate test status (I need not require to wait for hours to get the results, there is an option that than be set so that results are generated after executing every 20 test cases) . Any .vbs file can be invoked easily through Java. Attaching the email notification screen shot below.



2. Building JAR file - You have option to execute the test directly on eclipse. When it is scheduled we need to compile all the files into a single JAR file so that it can be invoked using ANT and easily distributed to other systems. Build.xml is created to handle all these activities.

3. TestNG - Pass/Fail/Skipped test cases count is recorded using TestNG, but this report is generated after completion of all the methods. Lets assume we ran test for 10 hours, browser or Selenium don't respond; all your test results are lost. Implemented intermediate test status update using VBS, so that even the application crashed at-least I can get the results till the test executed.

4. Screenshot on Failure - Take screen shot and send it in the email along with the test results.

5. Backup the results - Before each run all the test results need to be copied in separate folder, so that results are not lot and can be verified in future. If do it manually, you may miss to copy the files.

6. Page Object Implementations - In QTP Page Objects Implementation is accomplished used dual function design. In Webdriver Page Objects are independent class where all the methods and locators of a particular page is kept in one class, this is the design feature which I like in Webdriver.

7. TestData - My project has lot of test data. Initially I have used TestNG xml and pass it as parameters. I felt it was difficult to maintain 100+ fields, so I have decided to create a new class for test data.

8. Test case log - Lets assume a single test case run for 30 min, how will you trace if some thing goes wrong in between? using logs. Statements are generated at important points like saved, deleted, ID=1234dd.... in an notepad, so that it act as a reference to compare against the actual data created.

9. Configuration Management - As many people are working on the same project, files need to be stored safely in the configuration management tool so that any conflicts can be resolved by version comparison. As .java is a text file, so it can be easily uploaded to any tool. While working with QTP I use to maintain the "Keywords" in excel files, the two major disadvantages I have noticed was (1) If the Keyword is incorrect, I will not know until the test is executed. (2) Maintaining excel sheets in an configuration management tool and performing the version comparison is not possible.

I started my carrier with Winrunner and love the Mercury Tours Site. This is the website on which I have learnt my first automation skills. All the page objects are created based on this site, any one can access this site.

Project folder structure screen shot below.

1. Package Mercury Tours - This is the base folder where Initilize.java, TC(Test Cases), SendEmail.Java files are stored.
Initilize.Java - File call Initilize.vbs that copies all the previous test results if there are any from ScreenShots and Test_reports folder to a backup folder.


package MercuryTours;

import java.io.File;
import org.testng.annotations.Test;

import MercuryTours.PageObjects.UtilityScript;

public class initilize extends UtilityScript {
@Test
public void Test_initilize_main() throws Exception {
File directory = new File (".");
try{Runtime.getRuntime().exec("wscript.exe "+directory.getCanonicalPath()+"\\initilize.vbs" );
}
 catch(Exception e){e.printStackTrace();
}
xKillIEs();
Wait(3000);
 }

}


2. TC(Test Cases) - Test case logic is written here by using page objects. All the test cases have separate files TC1, TC2....


package MercuryTours;

import org.openqa.selenium.ie.InternetExplorerDriver;
import org.openqa.selenium.support.PageFactory;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;

import MercuryTours.PageObjects.UtilityScript;
import MercuryTours.PageObjects._01_Initilize;
import MercuryTours.PageObjects._02_Login;
import MercuryTours.PageObjects._03_FindAFlight;
import MercuryTours.PageObjects._04_SelectAFlight;
import MercuryTours.PageObjects._05_BookAFlight;
import MercuryTours.PageObjects._06_FlightConformation;

//@Listeners({ p2pZions.TestNG.TestNGCustom.class, p2pZions.TestNG.TestNGCustomeListener.class })
public class MercuryTours_TC_01 extends UtilityScript {

InternetExplorerDriver driver;

@BeforeClass(alwaysRun = true)
protected void setUp() throws Exception {
driver = new InternetExplorerDriver();
}

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

@Test(groups = { "MercuryToursTestCases" }, enabled = true)
public void Test_E2E_01() throws Exception {
MethodName = MethodName + "TC_01-";  //Used while sending email to report the test cases executed
Method = "TC_01"; //Used while sending email to report the test cases executed
Print("Start:" + xGetDateTimeIP());
_01_Initilize Initilize = PageFactory.initElements(driver,_01_Initilize.class);
Initilize.zOpen(Url);
_02_Login Login = PageFactory.initElements(driver,_02_Login.class);
Login.zEnterCrediantials("qtp123", "qtp123");
_03_FindAFlight FindAFlight = PageFactory.initElements(driver,_03_FindAFlight.class);
FindAFlight.zTripTypeOneWay();
FindAFlight.zNumberOfPassengers("4");
FindAFlight.zDepartingFrom("London");
FindAFlight.zDepartingOnDay("2");
FindAFlight.zDepartingOnMonth("2");
FindAFlight.zArrivingIn("Seattle");
FindAFlight.zFirstClass();
FindAFlight.zContinue();
_04_SelectAFlight SelectAFlight = PageFactory.initElements(driver,_04_SelectAFlight.class);
SelectAFlight.zDepartFlightSelection();
SelectAFlight.zContinue();
_05_BookAFlight BookAFlight = PageFactory.initElements(driver,_05_BookAFlight.class);
BookAFlight.zPassengerDetails("FirstName1", "LastName1", "HNML", "0");
BookAFlight.zPassengerDetails("FirstName2", "LastName2", "HNML", "1");
BookAFlight.zPassengerDetails("FirstName3", "LastName3", "HNML", "2");
BookAFlight.zPassengerDetails("FirstName4", "LastName4", "HNML", "3");
BookAFlight.zCardDetails("123456789111");
BookAFlight.zContinue();
_06_FlightConformation FlightConformation = PageFactory.initElements(driver,_06_FlightConformation.class);
FlightConformation.zGetConformationNumber();
FlightConformation.zLogOut();

}
}



3. SendEmail.Java - This file is used to call the SendEmail.vbs file with all the executed test cases (Variable MethodName)


package MercuryTours;

import java.io.File;

import org.testng.annotations.Test;

import MercuryTours.PageObjects.UtilityScript;

public class SendEmail extends UtilityScript {
@Test
public void Test_SendEmail_main() throws InterruptedException {
Wait(3000);
File directory = new File(".");
// Print(MethodName);
try {
Runtime.getRuntime().exec(
"wscript.exe " + directory.getCanonicalPath()
+ "\\sendemail.vbs " + MethodName);
} catch (Exception e) {
e.printStackTrace();
}
}

}



4. _01_initilize.java - This file is used to open the browser, open the URL and set the implicit timeouts.


package MercuryTours.PageObjects;

import java.util.concurrent.TimeUnit;

import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;

public class _01_Initilize extends UtilityScript {

private WebDriver driver;

public _01_Initilize(WebDriver driver) throws InterruptedException {
this.driver = driver;
}

public _01_Initilize zOpen(String url) throws Exception {
driver.manage().timeouts()
.implicitlyWait(ImplicitWait, TimeUnit.SECONDS);
// Code to mazimize the window. Reason some times Auto suggest,some
// objects will fail if not maximized
String script = "if (window.screen){window.moveTo(0,0);window.resizeTo(window.screen.availWidth,window.screen.availHeight);};";
((JavascriptExecutor) driver).executeScript(script);
driver.get(url);
return this;
}

}


5. _02_Login.Java - All the locators pertaining to the login page. I group similar page objects into one group so I have used _01,_02.....


package MercuryTours.PageObjects;

import org.openqa.selenium.By;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;

public class _02_Login extends UtilityScript {
private WebDriver driver;

public _02_Login(WebDriver driver) throws InterruptedException {
this.driver = driver;
}

public _02_Login zEnterCrediantials(String UserNameTxt, String PasswordTxt)
throws InterruptedException {
Wait(3000);
driver.findElement(By.name("userName")).sendKeys(UserNameTxt);
driver.findElement(By.name("password")).sendKeys(UserNameTxt);
driver.findElement(By.name("login")).click();
Print("UserName:" + UserNameTxt);
Print("---Login");
Wait(3000);
return this;
}

}


6. _03_FindAFlight.java


package MercuryTours.PageObjects;

import org.openqa.selenium.By;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;

public class _03_FindAFlight extends UtilityScript {

private WebDriver driver;

public _03_FindAFlight(WebDriver driver) throws InterruptedException {
this.driver = driver;
}

public void zTripTypeOneWay() throws InterruptedException {
driver.findElement(By.xpath("//input[@value='oneway']")).click();
}

public void zTripTypeRoundTrip() throws InterruptedException {
driver.findElement(By.xpath("//input[@value='roundtrip']")).click();
}

public void zNumberOfPassengers(String Values_1to4)
throws InterruptedException {
driver.findElement(
By.xpath("//select[@name='passCount']/option[@value='"
+ Values_1to4 + "']")).click();
}

public void zDepartingFrom(String DepartingPlace)
throws InterruptedException {
driver.findElement(
By.xpath("//select[@name='fromPort']/option[@value='"
+ DepartingPlace + "']")).click();
}

public void zDepartingOnMonth(String Month_1to12)
throws InterruptedException {
driver.findElement(
By.xpath("//select[@name='fromMonth']/option[@value='"
+ Month_1to12 + "']")).click();
}

public void zDepartingOnDay(String Day_1to31) throws InterruptedException {
driver.findElement(
By.xpath("//select[@name='fromDay']/option[@value='"
+ Day_1to31 + "']")).click();
}

public void zArrivingIn(String ArrivingPlace) throws InterruptedException {
driver.findElement(
By.xpath("//select[@name='toPort']/option[@value='"
+ ArrivingPlace + "']")).click();
}

public void zReturningOnMonth(String Month_1to12)
throws InterruptedException {
driver.findElement(
By.xpath("//select[@name='toMonth']/option[@value='"
+ Month_1to12 + "']")).click();
}

public void zReturingOnDay(String Day_1to31) throws InterruptedException {
driver.findElement(
By.xpath("//select[@name='toDay']/option[@value='" + Day_1to31
+ "']")).click();
}

public void zEconomyClass() throws InterruptedException {
driver.findElement(By.xpath("//input[@value='Coach']")).click();
}

public void zBusinessClass() throws InterruptedException {
driver.findElement(By.xpath("//input[@value='Business']")).click();
}

public void zFirstClass() throws InterruptedException {
driver.findElement(By.xpath("//input[@value='First']")).click();
}

public _03_FindAFlight zContinue() throws InterruptedException {
driver.findElement(By.name("findFlights")).click();
Wait(3000);
return this;
}

}




7. _04_SelectAFlight.Java


package MercuryTours.PageObjects;

import org.openqa.selenium.By;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;


public class _04_SelectAFlight extends UtilityScript  {

    private WebDriver driver;
 
    public _04_SelectAFlight(WebDriver driver) throws InterruptedException {
    this.driver = driver;
    }

    public void zDepartFlightSelection () throws InterruptedException {
    //Implement logic to choose the flight based on Airline, Price
    //Selected Second option (ODD numbers 3 5 7 8 there are some blank divs between)
    driver.findElement(By.xpath("//tr[5]/td/input[@name='outFlight']")).click();
    }
 
    public void zReturnFlightSelection () throws InterruptedException {
    //Implement logic to choose the flight based on Airline, Price
    driver.findElement(By.xpath("//input[@name='inFlight'][1]")).click();
    }  
 
    public _04_SelectAFlight zContinue () throws InterruptedException  {
   driver.findElement(By.name("reserveFlights")).click();
   Wait(3000);
   return this;
    }  

}



8. _05_BookAFlight


package MercuryTours.PageObjects;

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;

public class _05_BookAFlight extends UtilityScript  {

    private WebDriver driver;
 
    public _05_BookAFlight(WebDriver driver) throws InterruptedException {
    this.driver = driver;
    }

    public void zPassengerDetails (String FirstName, String LastName, String Meal, String IndexStartFrom0) throws InterruptedException {
    driver.findElement(By.name("passFirst"+IndexStartFrom0)).sendKeys(FirstName);
    driver.findElement(By.name("passLast"+IndexStartFrom0)).sendKeys(LastName);
    driver.findElement(By.xpath("//select[@name='pass."+IndexStartFrom0+".meal']/option[@value='"+ Meal +"']")).click();
    }
 
    public void zCardDetails (String CardNumber) throws InterruptedException {
    driver.findElement(By.name("creditnumber")).sendKeys(CardNumber);
    }  
 
    public _05_BookAFlight zContinue () throws InterruptedException  {
   driver.findElement(By.name("buyFlights")).click();
   Wait(3000);
   return this;
    }  

}



9. _06_FlightConformation

package MercuryTours.PageObjects;

import org.openqa.selenium.By;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;


public class _06_FlightConformation extends UtilityScript  {
    private WebDriver driver;
    
    public _06_FlightConformation(WebDriver driver) throws InterruptedException {
    this.driver = driver;
    }

    public void zGetConformationNumber () throws InterruptedException {
    Print(driver.findElement(By.xpath("//tr/td/b/font/font/b/font[1]")).getText());
    }
    
    public _06_FlightConformation zLogOut () throws InterruptedException  {
   driver.findElement(By.xpath("//tbody/tr/td[3]/a")).click();
   Print("---Logout---");
   Wait(3000);
   return this;
    }       
    

}



9. TestData.java

package MercuryTours.PageObjects;


public class TestData extends MercuryTours.TestNG.TestNGAssertionsCustom {
public static final String Url = "http://newtours.demoaut.com/";
public static final String UserName = "qtp123";
public static final String UserNameP = "qtp123";

//##################################################################################
// Don't change any thing below ####################################################
//##################################################################################
//Assigned variables****************************************************************
public static final int ImplicitWait =  5;
public static final int TimeOut10 =  10000;
public static final int TimeOut20 =  20000;

//Date Data*************************************************************************
//public static final String DateFormat = "dd.MM.yyyy_hh:mm a";
public static final String DateTimeFormat = "MM/dd/yyyy_hh:mm a";

//public static final String DateFormat = "dd.MM.yyyy";
public static final String DateFormat = "MM/dd/yyyy";
public static final String DateFormat1 = "MMM dd, yyyy";

//Non Assigned variables ***********************************************************

public static String NewDate, NewTime, Script, MethodName = "",Method="";
public static String Temp, Temp1,sMessages ="";

}

10. UtilityScript.java
There are lot of custom function written based on project requirement


package MercuryTours.PageObjects;

import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
import java.awt.AWTException;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;

import javax.imageio.ImageIO;

import org.apache.commons.io.FileUtils;

import org.testng.Reporter;

public class UtilityScript extends TestData {

// Get date time
public java.lang.String xGetDateTime() throws Exception {
// get current date time with Date() to create unique file name
DateFormat dateFormat = new SimpleDateFormat("hh_mm_ssaadd_MMM_yyyy");
// get current date time with Date()
Date date = new Date();
return (dateFormat.format(date));
}

// DateFormat = "MMM dd, yyyy";
public java.lang.String xGetDate(String DateFormat) throws Exception {
// get current date time with Date() to create unique file name
DateFormat dateFormat = new SimpleDateFormat(DateFormat);
// get current date time with Date()
Date date = new Date();
return (dateFormat.format(date));
}

// Get date time with SelText
public java.lang.String xGetDateTimeSel() throws Exception {
// get current date time with Date() to create unique file name
DateFormat dateFormat = new SimpleDateFormat("hh_mm_ssaadd_MMM_yyyy");
// get current date time with Date()
Date date = new Date();
return ("S_" + dateFormat.format(date));
}

// Get date time with System IP
public java.lang.String xGetDateTimeIP() throws Exception {
// get current date time with Date() to create unique file name
DateFormat dateFormat = new SimpleDateFormat("hh_mm_ssaa_dd_MMM_yyyy");
// get current date time with Date()
Date date = new Date();
// To identify the system
InetAddress ownIP = InetAddress.getLocalHost();
return (dateFormat.format(date) + "_IP" + ownIP.getHostAddress());
}

public static void Wait(int MilliSec) throws InterruptedException {
Thread.sleep(MilliSec);
}

public void Print(String Text) {
System.out.println(Text);
Reporter.log(Text);
String Temp = Text;
sMessages = sMessages + Temp.replaceAll(" ", "_") + "#";
//System.out.println(Temp);
//System.out.println(sMessages);
}

public java.lang.String xAddMinutesToTheDateTime(String Date_TimeFormat,
int NumberOfMinutes) throws InterruptedException, ParseException {
SimpleDateFormat sdf = new SimpleDateFormat(DateTimeFormat);
Calendar c = Calendar.getInstance();
c.setTime(sdf.parse(Date_TimeFormat));
c.add(Calendar.MINUTE, NumberOfMinutes); // number of minutes
String str = sdf.format(c.getTime());
String delimiter = "_";
String[] temp;
temp = str.split(delimiter);
for (int i = 0; i < temp.length - 1; i++) {
NewDate = temp[i];
NewTime = temp[i + 1];
}
// Print(NewDate);
// Print(NewTime);

return (str); // dt is now the new date
}

public java.lang.String xAddDaysToTheDateTime(String CurrentDate,
int NumberOfDays, String DateFormat)
throws InterruptedException, ParseException {
SimpleDateFormat sdf = new SimpleDateFormat(DateFormat);
Calendar c = Calendar.getInstance();
c.setTime(sdf.parse(CurrentDate));
c.add(Calendar.DATE, NumberOfDays); // number of days
String str = sdf.format(c.getTime());
return (str); // dt is now the new date
}

public java.lang.String xGetCurrentDateEST(String DateFormat) throws Exception {
SimpleDateFormat dateFormat = new SimpleDateFormat(DateFormat);
dateFormat.setTimeZone(TimeZone.getTimeZone("EST5EDT"));
NewDate = dateFormat.format(new Date());
return (NewDate);
}

public java.lang.String xGetCurrentTimeEST() throws Exception {
SimpleDateFormat dateFormat = new SimpleDateFormat("hh:mm a");
dateFormat.setTimeZone(TimeZone.getTimeZone("EST5EDT"));
NewTime = dateFormat.format(new Date());
return (NewTime);
}

public void xKillIEs() throws Exception {
// I felt this is not closing the IEs effectively, so started relaying
// on VB script
Wait(3000);
/*
* final String KILL = "taskkill /IM "; String processName =
* "iexplore.exe"; // IE process Runtime.getRuntime().exec(KILL +
* processName);
*/

File directory = new File(".");
try {
Runtime.getRuntime().exec(
"wscript.exe " + directory.getCanonicalPath()
+ "\\KillIEs.vbs");
} catch (Exception e) {
e.printStackTrace();
}
Wait(5000); // Allow OS to kill the process
}

public boolean xFileExist(String FileNameWithPath) throws Exception {
java.io.File myDir = new java.io.File(FileNameWithPath);
if (myDir.exists()) {
Print("file exist");
return true;
} else {
Print("file does not exist");
assertTrue(false);
return false;
}
}

public void xMakeFileCopy(String NewFileNameWithPath,
String FileNameWithPath) throws Exception {
java.io.File base = new java.io.File(FileNameWithPath);
java.io.File newfile = new java.io.File(NewFileNameWithPath);
if (xFileExist(FileNameWithPath)) {
FileUtils.copyFile(base, newfile);
} else {
Print("file does not existcould not copy");
assertTrue(false);
}
if (xFileExist(NewFileNameWithPath)) {
Print("file copied sucessfully");
}

}

public void xDeleteFile(String FileNameWithPath) throws Exception {
java.io.File file = new java.io.File(FileNameWithPath);
if (xFileExist(FileNameWithPath)) {
FileUtils.deleteQuietly(file);
Print("File Deleted Successfully");
} else {
Print("file does not exist.Could not Delete");
// assertTrue(false);
}
}

public static void xScreenShot() {
try {

String NewFileNamePath;

java.awt.Dimension resolution = Toolkit.getDefaultToolkit()
.getScreenSize();
Rectangle rectangle = new Rectangle(resolution);

// Get the dir path
File directory = new File(".");
// System.out.println(directory.getCanonicalPath());

// get current date time with Date() to create unique file name
DateFormat dateFormat = new SimpleDateFormat(
"MMM_dd_yyyy__hh_mm_ssaa");
// get current date time with Date()
Date date = new Date();
// System.out.println(dateFormat.format(date));

// To identify the system
InetAddress ownIP = InetAddress.getLocalHost();
// System.out.println("IP of my system is := "+ownIP.getHostAddress());

NewFileNamePath = directory.getCanonicalPath() + "\\ScreenShots\\"
+ Method + "___" + dateFormat.format(date) + "_"
+ ownIP.getHostAddress() + ".png";
System.out.println(NewFileNamePath);

// Capture the screen shot of the area of the screen defined by the
// rectangle
Robot robot = new Robot();
BufferedImage bi = robot.createScreenCapture(new Rectangle(
rectangle));
ImageIO.write(bi, "png", new File(NewFileNamePath));
NewFileNamePath = "<a href=" + NewFileNamePath + ">ScreenShot"
+ "</a>";
// Place the reference in TestNG web report
Reporter.log(NewFileNamePath);

} catch (AWTException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}

public static void xUpdateTestDetails(String Status) throws Exception   {
File directory = new File(".");
String Temp = Method + "_" + Status;
if (Method != ""){
try {
Runtime.getRuntime().exec(
"wscript.exe " + directory.getCanonicalPath()
+ "\\UpdateTestDetails.vbs "+ Temp + " " + sMessages);
Method = "";
sMessages = "";
} catch (Exception e) {
e.printStackTrace();
}
}
Wait(5000); // Allow OS to kill the process
}

}

MercuryTours.TestNg Package

package MercuryTours.TestNG;

import org.testng.IInvokedMethod;
import org.testng.IInvokedMethodListener;
import org.testng.ITestResult;

public class TestListenerAdapter implements IInvokedMethodListener {

public void afterInvocation(IInvokedMethod arg0, ITestResult arg1) {}

public void beforeInvocation(IInvokedMethod arg0, ITestResult arg1) {}


}



package MercuryTours.TestNG;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.testng.Assert;
import org.testng.ITestResult;
import org.testng.Reporter;



public class TestNGAssertionsCustom {

private static Map<ITestResult, List<Throwable>> verificationFailuresMap = new HashMap<ITestResult, List<Throwable>>();

    public static void assertTrue(boolean condition) {
    Assert.assertTrue(condition);
    }
 
    public static void assertTrue(boolean condition, String message) {
    Assert.assertTrue(condition, message);
    }
 
    public static void assertFalse(boolean condition) {
    Assert.assertFalse(condition);
    }
 
    public static void assertFalse(boolean condition, String message) {
    Assert.assertFalse(condition, message);
    }
 
    public static void assertEquals(boolean actual, boolean expected) {
    Assert.assertEquals(actual, expected);
    }
 
    public static void assertEquals(Object actual, Object expected) {
    Assert.assertEquals(actual, expected);
    }
 
    public static void assertEquals(Object[] actual, Object[] expected) {
    Assert.assertEquals(actual, expected);
    }
 
    public static void assertEquals(Object actual, Object expected, String message) {
    Assert.assertEquals(actual, expected, message);
    }
 
    public static void verifyTrue(boolean condition) {
    try {
    assertTrue(condition);
    } catch(Throwable e) {
    addVerificationFailure(e);
    }
    }
 
    public static void verifyTrue(boolean condition, String message) {
    try {
    assertTrue(condition, message);
    } catch(Throwable e) {
    addVerificationFailure(e);
    }
    }
 
    public static void verifyFalse(boolean condition) {
    try {
    assertFalse(condition);
} catch(Throwable e) {
    addVerificationFailure(e);
}
    }
 
    public static void verifyFalse(boolean condition, String message) {
    try {
    assertFalse(condition, message);
    } catch(Throwable e) {
    addVerificationFailure(e);
    }
    }
 
    public static void verifyEquals(boolean actual, boolean expected) {
    try {
    assertEquals(actual, expected);
} catch(Throwable e) {
    addVerificationFailure(e);
}
    }

    public static void verifyEquals(Object actual, Object expected) {
    try {
    assertEquals(actual, expected);
} catch(Throwable e) {
    addVerificationFailure(e);
}
    }
 
    public static void verifyEquals(Object[] actual, Object[] expected) {
    try {
    assertEquals(actual, expected);
} catch(Throwable e) {
    addVerificationFailure(e);
}
    }

    public static void fail(String message) {
    Assert.fail(message);
    }
 
public static List<Throwable> getVerificationFailures() {
List<Throwable> verificationFailures = verificationFailuresMap.get(Reporter.getCurrentTestResult());
return verificationFailures == null ? new ArrayList<Throwable>() : verificationFailures;
}

private static void addVerificationFailure(Throwable e) {
List<Throwable> verificationFailures = getVerificationFailures();
verificationFailuresMap.put(Reporter.getCurrentTestResult(), verificationFailures);
verificationFailures.add(e);
}

}


package MercuryTours.TestNG;

import org.testng.*;
import org.testng.TestListenerAdapter;

public class TestNGCustom extends TestListenerAdapter {

// Take screen shot only for failed test case
@Override
public void onTestFailure(ITestResult tr) {
try {
MercuryTours.PageObjects.UtilityScript.xScreenShot();
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
try {
MercuryTours.PageObjects.UtilityScript.xUpdateTestDetails("FAIL");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

@Override
public void onTestSkipped(ITestResult tr) {
// p2pZions.PageObjects.UtilityScript.xScreenShot();
try {
MercuryTours.PageObjects.UtilityScript.xUpdateTestDetails("SKIPPED");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

@Override
public void onTestSuccess(ITestResult tr) {
//p2pZions.PageObjects.UtilityScript.xScreenShot();
try {
MercuryTours.PageObjects.UtilityScript.xUpdateTestDetails("PASS");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

}


package MercuryTours.TestNG;

import java.util.List;

import org.testng.IInvokedMethod;
import org.testng.ITestResult;
import org.testng.Reporter;
import org.testng.internal.Utils;



public class TestNGCustomeListener extends TestListenerAdapter {
@Override
public void afterInvocation(IInvokedMethod method, ITestResult result) {
Reporter.setCurrentTestResult(result);
if (method.isTestMethod()) {

List<Throwable> verificationFailures = TestNGAssertionsCustom.getVerificationFailures();

//if there are verification failures...
if (verificationFailures.size() > 0) {
//set the test to failed
result.setStatus(ITestResult.FAILURE);

//if there is an assertion failure add it to verificationFailures
if (result.getThrowable() != null) {
verificationFailures.add(result.getThrowable());
}
int size = verificationFailures.size();
//if there's only one failure just set that
if (size == 1) {
result.setThrowable(verificationFailures.get(0));
} else {
//create a failure message with all failures and stack traces (except last failure)
StringBuffer failureMessage = new StringBuffer("Multiple failures (").append(size).append("):\n\n");
for (int i = 0; i < size-1; i++) {
failureMessage.append("Failure ").append(i+1).append(" of ").append(size).append(":\n");
Throwable t = verificationFailures.get(i);
String fullStackTrace = Utils.stackTrace(t, false)[1];
failureMessage.append(fullStackTrace).append("\n\n");
}
//final failure
Throwable last = verificationFailures.get(size-1);
failureMessage.append("Failure ").append(size).append(" of ").append(size).append(":\n");
failureMessage.append(last.toString());
//set merged throwable
Throwable merged = new Throwable(failureMessage.toString());
merged.setStackTrace(last.getStackTrace());
result.setThrowable(merged);
}
}
}
}
}



SeleniumWebdriver - Page objects Implementation - Part 2


27 comments:

  1. Great post Bharath.
    I am also using a Selenium2 + TestNG + Ant framework. Can u please tell me the mechanisam that you use to take the screen shot of the failed tests....?
    Are you able to add these screen shots to TestNg Report?

    ReplyDelete
    Replies
    1. Please read this post
      http://bharath-marrivada.blogspot.in/2011/04/selenium-screenshot-testng-capture.html

      Delete
  2. Hi Bharath,

    i am a very big fan of you blog .Thanks for the detailed explanation of Pageobject Framework.

    when will you provide Utility functions?

    ReplyDelete
    Replies
    1. Hello Sree,

      Utility functions are created based on your project requirements. It consist of screen shot, date time, reading data from disk functions.

      Delete
  3. great stuff..

    this is exactly wot i am looking for . can i plz have the eclipse project so i can download and change the details based on my project requirment.

    This will guide me a lot

    ReplyDelete
  4. Could you share the project so that I can learn and implement web driver

    ReplyDelete
    Replies
    1. Currently can't share the project, most of the code and folder structure already shown above, it is just integrating them based on your project requirement.
      My project contain VB script, utilities for sending email notifications, WEB service calls, different IP address...(Confidential, tightly integrated) used for my project requirements, may not be required for you.

      I will publish the project after 2 weeks, removing the unnecessary content.

      Delete
  5. Hi Bharath,
    awesome stuffs.
    Please share the MercuryTours.TestNg Package !!!
    Also please post some of the topics on defect management for selenium webdriver !!

    Thanks
    Utpal

    ReplyDelete
  6. Hope you can publish the project soon :-D
    Really well done. Nice job.

    ReplyDelete
  7. Hi Bharat,

    Thanks for such a nice post.
    I need your favor.
    Can you plz share the code of sendmail.vbs file actually I am trying to integrate my framework with Hudson but dont knw how to do it.
    Thanks in advance

    ReplyDelete
  8. Hi Bharat,
    Can you clarify one thing in Keyword driven, we have a file that has table containing object, action and testdata columns. So, do we pick the object and actions as well from the table using some code(like we pick testdata using @dataprovider)? or we just hardcode the function names based on the actions we see in table.
    If not then is the same table used to pick data or we have different file for picking the test data?

    ReplyDelete
    Replies
    1. I am not clear with your question.

      Based on your framework each stem contain object, auction and test data. Lets assume object properties got changed and the same object is repeated 100 times, are you going to replace at 100 places? Bad idea.

      As per page the page object design, all the objects of one page is created in one class and each object is assigned a method name, usernameTxt(String Username){ object locator}.
      Here we call only the method name, not the object. Assume object locators(Properties) got changed, we are changing at one place, this is automatically reflected in all the places.
      In general not required to changed the method named, unless they change the functionality.

      When you design any code think of Re-usability.
      What is Re-usability?
      If there is a change in the object properties, code change need to happen at one place only, if it is happening at more than one place it is not reusable code.



      Delete
  9. Hi Bharath,

    I have only had a brief look at your code so if I have missed something , please excuse my mistake.
    I am currently trying to build a framework such as this due to cheaper runnning costs than QTP.

    However with a QTP keyword driven framework, testers who are not knowledgeable in vbsript can be taught to create tests using keywords and data in an excel file. Have you got to the point that they can use keywords to run a function in java from an excel file (or substitute)? From the above, it would appear you haven't yet, or have chose not to? If you chose not to, is it due to speed of execution at runtime?

    Happy Testing:)

    ReplyDelete
    Replies
    1. Hello Mark,
      Based on my experience with the QTP keyword driven framework following are the issues (1) As the excel keywords count increases QTP engineers are making mistakes connecting it with the Driver script (Issues with the spelling and parameters) (2) Every one is working on different excel files comparing files is difficult task. (3) As the files are huge not so useful in storing in configuration management tool (4) To run a test on a new machine, lot of files need to be copied (5) If there is issue with the Keyword, we will not know until it get executed. (6) If we rename or remove any keyword, need to manually take care of the changes, but in selenium it will through syntax errors .

      In the Selenium once we implement methods in the page object, in your test cases all the methods are displayed when you type "PageObject. " you don't require any Java knowledge and in the excel all the methods are displayed, but here only object related methods are displayed with parameter names.
      Look at this screen shot for better understanding. People with out any programming knowledge can select the methods and create the test case.

      http://bharath-marrivada.blogspot.in/2011/06/selenium-keyword-framework-driven.html

      Let me know if you are not clear with my explanation.

      Delete
  10. Hi Bharath,
    Thanks for the speedy response, I will hopefully take a look at it in more detail this week.
    Best Regards.

    ReplyDelete
  11. Hi Bharat,
    Fentastic Post using the "page object pattern". I know it sounds silly,but can we also abstract out the "locators" that are directly specified in the webdriver api's to the TestData.java or may be "objectmap.xxx" so that instead of referring the locator directly with xpath or name,we can simply use its reference from the objectmap.xxx ?

    ReplyDelete
    Replies
    1. I have not understood your question. We are not using the locators directly, it is defined in the page objects. We use the methods of the page objects.

      Delete
  12. Hi Bharath,

    Thank you very much for detailed explanation with code. i have a query on this.

    In _02_Login class, in below method, why we are returning class itself. can you please clear me on this? what happens if use void.


    public _02_Login zEnterCrediantials(String UserNameTxt, String PasswordTxt)
    throws InterruptedException {
    Wait(3000);
    driver.findElement(By.name("userName")).sendKeys(UserNameTxt);
    driver.findElement(By.name("password")).sendKeys(UserNameTxt);
    driver.findElement(By.name("login")).click();
    Print("UserName:" + UserNameTxt);
    Print("---Login");
    Wait(3000);
    return this;
    }

    ReplyDelete
    Replies
    1. For send keys you don't require return, but for page navigation events you need to send back the browser session to the main test, so that it can be sent to other page object classes.

      Delete
  13. Hi Bharath,

    I got round to trying out your framework (but on a Windows 7 machine). I know it only took me a few months!

    With the initilize.vbs script, how do you use wshSystemEnv to set the environment variables, now that windows now has user access control?
    Or are you only working on xp?

    ReplyDelete
    Replies
    1. Hello Mark,

      I am currently working on removing the wshSystemEnv, so that it would work in any Windows OS, I am also planning to convert the VB script to PERL so that we can make it run in other OS too.

      I will update the post once it is done.

      Delete
  14. why we need pagefactory class?
    can we create object of a class using new operator by passing driver?
    what is the advantage of using pagefactory class?
    pagefactory.initElements(driver,homepage.class);
    homepage hm=new homepage(driver);

    ReplyDelete
  15. Hi Bharath,

    I was trying to learn from your post.
    Here I did not understand one thing, in your test case you have not verified anything. so how do u use assert and u have so many assert methods defined but how and where u have used it?? If you can clear my doubt the it ll be very helpful.

    Thanks

    ReplyDelete
  16. Hi Bharath,

    I was trying to learn from your blog. I got the idea to create the webdriver framework, but I did not understand how you are verifying data or results of your test case. You have not used assert in your test case, but there are too many assert methods defined. So I want to know how and where u have used or u can use assertion. Please clarify this. Thanks

    ReplyDelete