Quantcast
Channel: Selenium – Vinsguru
Viewing all 69 articles
Browse latest View live

Selenium WebDriver – Automatic Switching between Browser Windows using Guice Method Interceptor

$
0
0

Overview:

One of the common challenges we face while automating our tests is – switching between open windows. Many of us would have seen the code as shown below.

There is nothing wrong in the above code to switch to a new window! But it looks a bit ugly. Once some action is performed in the new window and if you have to perform some other action in the old window, you should NOT forget to switch back to the old window. Otherwise, your script might fail. So how to avoid this kind of challenges!?

Lets see how we could switch among multiple windows without much boilerplate code!  I will be using the Guice library for this article! I would suggest you to read Guice + WebDriver integration article first if you have not already.

Goal:

Our goal is to have a clean, easily maintainable page objects. Switching among the open windows should be easy! Page objects themselves should know how to switch without you writing much code!

Demo:

By default, we create a separate page object for each page in the application. Since a new window of the application is a separate page, we can assume that we would create a separate page object for that as well.

For this example,  Lets consider this below ToolsQA site for our demo!

windows-switch-001

 

I create below very simple page objects for above pages.

Below page object is for the main window.

This page object to interact with the elements in the pop up window.

Now, I want to execute below test – simply to switch back and forth between the windows. Just for this example, I print only the title of the window.

Output:

Demo Windows for practicing Selenium Automation
Demo Windows for practicing Selenium Automation
Demo Windows for practicing Selenium Automation
Demo Windows for practicing Selenium Automation
Demo Windows for practicing Selenium Automation
Demo Windows for practicing Selenium Automation
Demo Windows for practicing Selenium Automation
Demo Windows for practicing Selenium Automation
Demo Windows for practicing Selenium Automation
Demo Windows for practicing Selenium Automation
Demo Windows for practicing Selenium Automation
Demo Windows for practicing Selenium Automation

It always shows the same title even though the pop up window has a different title. This is expected. Because as you might have noticed, We just launched the new window & we have not switched to the new window anywhere in our test / page object in the above example. If we do not switch, driver continues to interact with the current window. That is why, It always prints the same title.

I correct that by adding an annotation @Window(1) to the page object of the New window as shown here. 1 is the index and it starts from 0. It basically tells the driver to look for the elements in the 2nd window.

I reran the test after adding the annotation.

Output:

Demo Windows for practicing Selenium Automation
QA Automation Tools Tutorial
Demo Windows for practicing Selenium Automation
QA Automation Tools Tutorial
Demo Windows for practicing Selenium Automation
QA Automation Tools Tutorial
Demo Windows for practicing Selenium Automation
QA Automation Tools Tutorial
Demo Windows for practicing Selenium Automation
QA Automation Tools Tutorial
Demo Windows for practicing Selenium Automation
QA Automation Tools Tutorial

This is exactly what I wanted!! driver gets switched to the appropriate window automatically and the Page objects know where to look for their elements.

You do not have to use index if you do not like. You might like to use Windows title as shown here instead of index.

Are you curious how it works? Lets see how it has been implemented.

Implementation:

@Window:

This is a simple annotation definition. Default window index is 0.

Guice Method Interceptor:

Guice has a method interceptor. This feature let us write some code which gets executed every time before any method of your class / Page objects gets executed.  That is, below invoke method gets executed every time, before ‘getTitle()’ method of the above page object gets executed – behind the scenes!

In the above class, We access the index value of the window annotation. Based on the index, we switch to the appropriate window. If you use windows title, you need to check all windows matching the title here.

Guice Module:

Update Guice module to include the interceptor. We bind all the classes annotated with ‘Window.class’. So any method of those classes will be intercepted.

That is it!

Summary:

By adding a simple annotation to a page object and using guice method interceptor, we automatically switch between the windows. The above guice config, after proper implementation, can be exported as a library and can be reused in other projects. It saves us a lot of time from writing a boiler plate code. The code looks much better, clean and neat! It is less error-prone as you do not have to remember to add the code to switch back to the main window.

 

Happy Testing & Subscribe 🙂

 

Share This:


Ocular – Automated Visual Validation for Selenium WebDriver Test Automation Frameworks

$
0
0

Overview:

Visual Validation is a process of validating the visual aspects of an application’s user interface – Verifying each element in the application is appearing exactly in the same size, color, shape etc.

Lets say, your application has charts to represent some data to the end user, how can we verify if the chart is appearing as expected?

Verifying the visual aspects using automated scripts is tedious. We might have to write tons of assertions! Mostly, automation engineers find this difficult to automate & take care of the application’s functionality testing and leave the UI related testing to the manual testers.

Even if the application layout changes, if the functionality works fine, automated test scripts never catch those issues!  Enhancing the automated tests to include the automated visual validation feature for an application is also very challenging!

 

Challenges in Visual Validation:

  • Commercial visual validation tools are very expensive – (We were given a quote of $100K per year by one of the famous vendors – for one of the applications)
  • Organizing / maintaining the baseline images for comparison for each pages / components of the application
  • Responsive Testing – Same page appears in different layouts in different devices
  • Reporting the differences
  • Applications tend to change over time – Visual validation tests are always failing!
  • Modifying existing framework is very difficult to include the visual validation feature.

 

TestAutomationGuru has come up with its own library, Ocular, for your selenium WebDriver test automation frameworks which could solve most of these challenges. It is FREE & Open Source!

Github:

Check here for the source.


Ocular:

Ocular is a simple java utility which helps us to add the visual validation feature into the existing WebDriver test automation frameworks.

(Ocular is developed on top of Arquillian RushEye which is a command-line image-comparison tool. Special thanks to Mr. Lukas who created the rusheye project & Arquillian’s team for maintaining it.

If you are an arquillian-graphene user, I am working on a Graphene Extension to include this ocular. I will update here when it is ready to use.)

Maven Dependency:

Place below artifact configuration into Maven dependencies just below your selenium-java dependency:


Terms:

Ocular uses below terms.

  • Snapshot – the baseline image.
  • Sample – the actual image.
  • Similarity – how similar the sample should be while comparing against snapshot.

Global Configuration:

Ocular expects the user to configure the following properties before doing the visual validation.

Configuration Property Description Type Default Value
snapshotPath location of the baseline images Path null
resultPath location where the results with differences highlighted should be stored Path null
globalSimilarity % of pixels should match for the visual validation to pass int 100
saveSnapshot flag to save the snapshots automatically if they are not present. (useful for the very first time test run) boolean true

Code example:

Update Ocular global configuration as shown here once – for ex: in your @BeforeSuite method.

Ocular.config()
	.resultPath(Paths.get("c:/ocular/result"))
	.snapshotPath(Paths.get("c:/ocular/snpshot"))
	.globalSimilarity(95)
	.saveSnapshot(false);

Note: Ocular does NOT create these directories. Please ensure that they are present.


Snapshot in Page Objects:

Page Object has become the standard design pattern in test automation frameworks as it helps us to encapsulate/hide the complex UI details of the page and makes it easy for the client/tests to interact with the Page. Ocular makes use of the Page Object structure to keep the baseline images organized.

  • Using @Snap annotation, Page objects / Page abstractions are mapped to the baseline(snapshot) images for the visual validation.
  • The baseline images are expected to be available relative to the snapshotPath.

Example:

Lets assume, Ocular configuration is done as shown below.

Ocular.config()
	.resultPath(Paths.get("c:/ocular/result"))
	.snapshotPath(Paths.get("c:/ocular/snpshot"))
	.globalSimilarity(99)
	.saveSnapshot(true);

A Page Object is created for the below page as shown here.

RichFacesTheme-classic

 

@Snap("RichFace.png")
public class RichFace {

	private final WebDriver driver;

	public RichFace(WebDriver driver){
		this.driver = driver;
	}

}

Now Ocular looks for the snapshot image at this location – c:/ocular/snpshot/RichFace.png – (snapshotPath + Path given in @Snap)- in order to do the visual validation.


 Note: If the image is not present in the location, Ocular creates image and place it under snapshot path. So that Ocular can use this image from the next run onward for comparison.


Comparing Snapshot against Sample:

Sample is the actual image. Sample is created by taking the screenshot of the current page using the WebDriver.

@Snap("RichFace.png")
public class RichFace {

	private final WebDriver driver;

	public RichFace(WebDriver driver) {
		this.driver = driver;
	}

	public OcularResult compare() {
		return Ocular.snapshot().from(this) 	(1)
                     .sample().using(driver)    (2)
                     .compare(); 	         	(3)
    }
}
  1. snpshot.from(this) – lets Ocular read the @Snap value
  2. sample.using(driver) – lets Ocular to take the current page screenshot
  3. compare() – compares the snapshot against sample and returns the result

Different ways to access Snapshot:

Ocular.snapshot()
	.from(this) 	// or
	.from(RichFace.class) // or
	.from(Paths.get("/path/of/the/image"))

Choosing Right Snapshots at Run Time:

Class is a template. Objects are instances. How can we use a hard coded value for @Snap of a Page Class for different instances? What if we need to use different baseline images for different instances? May be different baselines different browsers or different devices. How can we do the mapping here?

Valid Question!!

Lets consider the below RichFace page for example.

RichFacesTheme-classic

If you notice, this page has different themes – like ruby, wine, classic, blue sky etc. Same content with different themes. So, We can not create different page classes for each theme. Ocular provides a workaround for this as shown here!

@Snap("RichFace-#{THEME}.png")
public class RichFace {

	private final WebDriver driver;

	public RichFace(WebDriver driver){
		this.driver = driver;
	}

	public OcularResult compare() {
		return Ocular
			.snapshot()
				.from(this)
				.replaceAttribute("THEME","ruby") // lets the ocular look for RichFace-ruby.png
			.sample()
				.using(driver)
			.compare();
	}
}

You could use some variables in the @Snap , get them replaced with correct values.


Excluding Elements:

Sometimes your application might have an element which could contain some non-deterministic values. For example, some random number like order conformation number, date/time or 3rd party ads etc. If we do visual validation, We know for sure that snapshot and sample will not match. So, We need to exclude those elements. It can be achieved as shown here.

Ocular.snapshot()
		.from(this)
	.sample()
		.using(driver)
		.exclude(element)
	.compare();

If we need to exclude a list of elements,

List<WebElement> elements = getElementsToBeExcluded();

Ocular.snapshot()
		.from(this)
	.sample()
		.using(driver)
		.exclude(elements)
	.compare();

or

Ocular.snapshot()
		.from(this)
	.sample()
		.using(driver)
		.exclude(element1)
		.exclude(element2)
		.exclude(element3)
	.compare();

Comparing One Specific Element:

Ocular can also compare a specific element instead of a whole web page.

Ocular.snapshot()
		.from(this)
	.sample()
		.using(driver)
		.element(element)
	.compare();

Similarity:

Sometimes we might not be interested in 100% match. Applications might change a little over time. So we might not want very sensitive compare. In this case, We could use similarity to define the % of pixels match. For the below example, If 85% of the pixels match, then Ocular will pass the visual validation. This will override the Ocular.config().globalSimilarity() config settings for this page object / fragment.

@Snap(value="RichFace-#{THEME}.png",similarity=85)
public class RichFace {

}

You could also override global similarity config value as shown here.

@Snap("RichFace-#{THEME}.png")
public class RichFace {

	private final WebDriver driver;

	public RichFace(WebDriver driver){
		this.driver = driver;
	}

	public OcularResult compare() {
		return Ocular.snapshot()
			.from(this)
			.replaceAttribute("THEME","ruby") // lets the ocular look for RichFace-ruby.png
		.sample()
			.using(driver)
			.similarity(85)
		.compare();
	}
}

Ocular Result:

OcularResult extends ComparisonResult class of arquillian-rusheye which has a set of methods & provides result of the visual validation. The image with differences highlighted is getting saved at Ocular.config().resultPath() every time!

OcularResult result = Ocular.snapshot().from(this)
			 .sample().using(driver)
			 .compare();

result.isEqualImages() // true or false
	  .getSimilairty() // % of pixels match
	  .getDiffImage()  // BufferedImage differences highlighted
	  .getEqualPixels() // number of pixels matched
	  .getTotalPixels() // total number of pixels

Sample Diff Image:

Ocular creates below diff image in case of snapshot & sample comparison mismatch highlighting the differences between them.

RichFaces-Poll


Responsive Web Design Testing:

Ocular can also be used to test the responsive web design of your application.

responsive

To the responsive web design, Lets organize our snapshots in our test framework as shown here.

src
	test
		resources
			ocular
				iphone
					PageA.png
					PageB.png
					PageC.png
				ipad
					PageA.png
					PageB.png
					PageC.png
				galaxy-tab
					PageA.png
					PageB.png
					PageC.png					
				1280x1024
					PageA.png
					PageB.png
					PageC.png					

Lets update the Ocular global config with the path where Ocular should expect the snapshots for the visual validation.

String device = "ipad";
Path path = Paths.get(".", "src/test/resources/ocular", device);
Ocular.config().snapshotPath(path);


// now Ocular will use the PageB.png under ipad folder.					
@Snap(value="PageB.png")
public class PageB {
 
}

Now as shown above we could easily incorporate the responsive web design validation in our test framework.


Summary:

Ocular, with its static and fluent API, makes its easy for us to use the library & include the automated UI / Responsive web design testing feature in our test frameworks.

Check here for the source. I would love to see your feedbacks / suggestions / contributions to the library.

 

To the open-source community!!   18217-cheers1

 

Happy Testing & Subscribe 🙂

 

 

Share This:

Selenium WebDriver – Creating TestNG Suite XML From Spreadsheet

$
0
0

Overview:

If we use testNG framework, it could be difficult to maintain the testNG suite xml for a huge project where we have thousands of test cases. For such projects, it is convenient to maintain the tests in a spreadsheet & create the testNG suite xml from the spreadsheet.

In this article, let’s see how we could create the testNG suite XML file at run time!

Sample Spreadsheet:

Lets assume, I keep all my test cases in a spreadsheet as shown here.

  • Description column is used to provide high level description of the test. It is used as the value of the name tag in testNG suite xml.
  • There is an Active indicator to run only Active (marked as ‘Y’) test cases. We might not want to run all.
  • There is a Module column to group the tests. For ex: All tests related to Order module should be executed.
  • ClassName column shows the list of classes (comma separated) to be executed as part of the test.
  • Data column (you could add more coulmns for data) is used to pass the data as the parameter to the test in the testNG suite XML.

testng-xls-1

I would want to query the above spreadsheet to get the specific tests. For example, If I need to execute all the active and order module tests, I would be using SQL as shown here.

Select * from TestCase where Active = 'Y' and Module = 'Order';

 Fillo:

To query the spreadsheet, I have been using Fillo for the past few years. It is just a Java API to query xls, xlsx files. The advantage of using Fillo here is, it treats your spreadsheet as a database and each sheet as a table of  the database.

Check here for the maven dependency and some sample code to use the library.

Suite Object Implementation:

First lets create a class diagram for the testNG suite xml. We could expect the testNG suite xml in the below format

So, our class hierarchy will be as shown here.

suite (name)
	test (name)
		parameter (name, value)
		classes
			class (name)

For the above model, test, class, parameter could be Lists.

I add the below mvn dependencies in my java project.

I create a Suite.java to represent the above class hierarchy – I would create an instance of this class by reading the spreadsheet, so that I could export this as a testNG xml.

XLS Reader:

I create a sample XLS reader as shown here using Fillo. I query the spreadsheet and then create the xml.

TestNG Suite XML:

Simply executing specific query will fetch corresponding tests and create a testng xml at run time as shown here.

testng xml:

Summary:

Above approach for creating testNG suite xml at run time saves a lot of time in maintaining the suite file for big projects where we could have thousands of test cases.  We maintain all the tests in a spreadsheet and simply tag the module names to logically group the tests. So that I could use SQL to filter specific tests I need to execute.

Happy Testing & Subscribe 🙂

 

 

Share This:

Selenium WebDriver – Creating A Simple Keyword-Driven Framework Using Java Lambdas

$
0
0

Overview:

In this article, I would like to show you a simple keyword-driven framework using Java8. Please do note that this article will not cover all the possible keywords. My aim here is  just to give you an idea how we could use Java – lambdas.

Java 8:

Java-8 has introduced a lot of cool features. Lambdas and streams etc which makes the code look readable and easily maintainable.

For ex: Assume, we have a requirement to calculate the square for the list of numbers which are greater than 5.

Function<Integer, Integer> square = (i) -> i*i;

Arrays.asList(1,2,3,4,5,6,7,8,9)
       .stream()        // fetches the element one by one
       .filter(i -> i > 5)  // filters the number
       .map(square)     // transforms the current element to i*i
       .forEach(System.out::println);  // prints the transformed element

The above code looks much better than the below traditional code.

int[] arr = new int[]{1,2,3,4,5,6,7,8,9};
for(int i = 0; i < arr.length; i++){
    if(i > 5){
        System.out.println(i*i);
    } 
}

square is a lambda function in the above example. The advantage of using lambda is – it can be passed as an argument to a method. It will be very useful in replacing traditional if-else / switch conditions. I would be using lambda to define the operation for each keywords in the framework.

Demo Application:

I am going to use below Mercury tours application for the demo. I will consider only the registration flow. As I had mentioned, aim of the article is not to create a framework for you, instead, give you an idea.

mercury-site

I need 3 keywords to complete the registration process.

  • SET_VALUE – to fill the form with data
  • SELECT – to select the drop down
  • CLICK – to click on the submit button.

Keyword Library:

I create a separate class which contains the list of keywords and the corresponding operations in a map. We get specific operation by using the Key.

public class Actions {
       
    // to enter given value for an element
    private static BiConsumer<WebElement, String> SET_VALUE = (ele, param) -> {
        ele.sendKeys(param);
    };

    // to perform click operation
    private static BiConsumer<WebElement, String> CLICK = (ele, param) -> {
        ele.click();
    };

    // to select drop down values
    private static BiConsumer<WebElement, String> SELECT = (ele, param) -> {
        Select select = new Select(ele);
        select.selectByVisibleText(param);
    };

    // this map will hold all the lamdas / keyword operations
    private static Map<String, BiConsumer<WebElement, String>> map = null;
    
    static {
        map = new HashMap<String, BiConsumer<WebElement, String>>();
        map.put("SetValue", SET_VALUE);
        map.put("Click", CLICK);
        map.put("Select", SELECT);
    }
   
   // return the specific operation based on the key
    public static BiConsumer<WebElement, String> get(String action){
        return map.get(action);
    }
    
}

Demo Application – Registration Process:

The registration page has the following steps.The steps are written in a spreadsheet as shown below. I use ‘name’ selector. But I would suggest you to go for CSS Selectors when you design the framework.

keyword-testcases-1

We would be reading the steps & convert to a list of TestStep objects which will look like this.

public class TestStep {

    private final By selector;
    private final WebDriver driver;
    private final BiConsumer < WebElement, String > consumer;
    private final String param;

    // pass the driver, selector, action keyword, any paramater to be used 
    public TestStep(WebDriver driver, String selector, String action, String param) {
        this.selector = By.name(selector);
        this.driver = driver;
        this.consumer = Actions.get(action);
        this.param = param;
    }

    // find the element & perform the action as per the keyword
    public void perform() {
        WebElement ele = this.driver.findElement(selector);
        this.consumer.accept(ele, param);
    }
}

Reading Spreadsheet:

Reading spreadsheet using Java is very easy. I had recommended Fillo library as used in the article.

// To store the list of TestSteps from the spreadsheet
List < TestStep > steps = new ArrayList < > ();

while (recordset.next()) {

    String nameSelector = recordset.getField("Selector");
    String action = recordset.getField("Action");
    String param = recordset.getField("Param");

    //create new TestStep
    TestStep step = new TestStep(driver, nameSelector, action, param);

    steps.add(step);
}

Create an instance of the WebDriver as shown here,

WebDriver driver = new ChromeDriver();
driver.get("http://newtours.demoaut.com/mercuryregister.php");

Then last step would be to connect everything together.  The getSteps will return the above list of TestSteps. Then, we basically iterate all the TestStep objects, call the perform method to invoke the corresponding operation.

XLSReader suite = new XLSReader("tests.xls");

suite.getSteps(driver, "select * from Step where Active = 'Y'")
    .stream()
    .forEach(TestStep::perform);

Demo:

Summary:

Using Java 8 lambdas, we could create a simple keyword driven framework very easily. By storing Lambdas in a map, we avoid the traditional if-else / switch statements. We will explore more Java 8 features in the upcoming articles.

 

Happy Testing & Subscribe 🙂

 

Share This:

Selenium WebDriver – How To Find Broken Links On a Page

$
0
0

Overview:

In this article, we will see how we could use Selenium WebDriver to find broken links on a webpage.

Utility Class:

To demonstrate how it works, I would be using below simple utility. This below method simply returns the HTTP response code the given URL.

public class LinkUtil {

    // hits the given url and returns the HTTP response code
    public static int getResponseCode(String link) {
        URL url;
        HttpURLConnection con = null;
        Integer responsecode = 0;
        try {
            url = new URL(link);
            con = (HttpURLConnection) url.openConnection();
            responsecode = con.getResponseCode();
        } catch (Exception e) {
            // skip
        } finally {
            if (null != con)
                con.disconnect();
        }
        return responsecode;
    }

}

Usage:

Rest is simple.  Find all the elements which has the href src attribute and by using the above utility class, we could collect the response codes for all the links and groups them based on the response codes.

driver.get("https://www.yahoo.com");

Map<Integer, List<String>> map = driver.findElements(By.xpath("//*[@href]")) 
                .stream()                             // find all elements which has href attribute & process one by one
                .map(ele -> ele.getAttribute("href")) // get the value of href
                .map(String::trim)                    // trim the text
                .distinct()                           // there could be duplicate links , so find unique
                .collect(Collectors.groupingBy(LinkUtil::getResponseCode)); // group the links based on the response code

Now we could access the urls based on the response code we are interested in.

map.get(200) // will contain all the good urls
map.get(403) // will contain all the 'Forbidden' urls   
map.get(404) // will contain all the 'Not Found' urls   
map.get(0) // will contain all the unknown host urls

We can even simplify this – just partition the urls if the response code is 200 or not.

Map<Boolean, List<String>> map= driver.findElements(By.xpath("//*[@href]"))  // find all elements which has href attribute
                  .stream()
                  .map(ele -> ele.getAttribute("href"))   // get the value of href
                  .map(String::trim)                      // trim the text
                  .distinct()                             // there could be duplicate links , so find unique
                  .collect(Collectors.partitioningBy(link -> LinkUtil.getResponseCode(link) == 200)); // partition based on response code

Simply we could access the map to list all the bad urls as shown here.

map.get(true) // will contain all the good urls
map.get(false) // will contain all the bad urls

Print all the bad URLs.

map.get(false)
   .stream()
   .forEach(System.out::println);

 

Happy Testing & Subscribe 🙂

 

Share This:

Selenium WebDriver – How To Execute Tests In Multiple Environments

$
0
0

Overview:

I have been running thousands of automated regression test cases in multiple test environments for years. As part of CI / CD pipeline, I run the tests in QA, UAT, Staging and PROD. In this article, I would like to show you the approach I follow to make the tests run on any given test environment.

Many of us tend to write the scripts as shown below with a lot of variables and if-else blocks which is very difficult to maintain.

    if(environment.equals("prod")){       
        url="http://testautomationguru.com";
        username="tag";
        password="password";   
    }else if(environment.equals("qa")){
        url="http://qa.testautomationguru.com";
        username="qatag";
        password="qapassword";        
    }else if(environment.equals("dev")){
        url="http://dev.testautomationguru.com";
        username="devtag";
        password="devpassword";        
    }

    driver.get(url);
    driver.findElement(By.id("username")).sendKeys(username);
    driver.findElement(By.id("password")).sendKeys(password);

Now adding few environment details, say DB connections details to this script is not an easy task! It requires a lot of code change and testing to ensure that the script is not broken.

Keeping Environment Specific Data:

In order to make the script work for any given environment, We should avoid using any hard coded environment specific details in the script. As part of this article, Lets assume, We would need below details to run the script.

  • application URL
  • admin username
  • admin password
  • database host / IP address
  • database port
  • database username
  • database password

I would suggest you to keep the environment specific details completely away from the test data as It is not going to change for each test. So, Lets create a property file as shown here.

# application properties
url=http://testautomationguru.com
username=tag
password=password123

# databsse properties
db.hostname=db.testautomationguru.com
db.port=3306
db.username=dba_admin
db.password=secured!

Lets maintain a separate property file for each environment as shown here.

env-specific-prop

Accessing Environment Specific Data:

To access environment specific data, I will be using Java – Owner library.

  • Add the below dependency in your Maven project.
<dependency>
    <groupId>org.aeonbits.owner</groupId>
    <artifactId>owner</artifactId>
    <version>1.0.8</version>
</dependency>
  • Create an Interface as shown here.
@Sources({
    "classpath:qa.properties" // mention the property file name
})
public interface Environment extends Config {

    String url();

    String username();

    String password();

    @Key("db.hostname")
    String getDBHostname();

    @Key("db.port")
    int getDBPort();

    @Key("db.username")
    String getDBUsername();

    @Key("db.password")
    String getDBPassword();

}
  • If the name of the method matches with the key of the property file, we can just call the method to access the value. (For ex: the method url() will refer to the key url in the property file)
  • If the name of the method does not match with key in the property file, then we need to use @Key explicitly. (For ex: getDBPassword() will fetch the value of db.password by using @Key in the above Interface.)
  • By using ConfigFactory, we create an instance of the Environment interface & access the property file.
Environment testEnvironment = ConfigFactory.create(Environment.class);

 // prints http://qa.testautomationguru.com
 System.out.println(testEnvironment.url());
 
 // prints qa.db.testautomationguru.com
 System.out.println(testEnvironment.getDBHostname());
  • Now throughout your test and page objects, you would be using the testEnvironment object.
public class EnvironmentTest {

    Environment testEnvironment;

    @Test
    public void functionalTest() {
        System.out.println(testEnvironment.url());
        System.out.println(testEnvironment.getDBHostname());
        System.out.println(testEnvironment.getDBPassword());
    }

    @BeforeTest
    public void beforeTest() {
        testEnvironment = ConfigFactory.create(Environment.class);
    }

}
  • There is a small issue with the above approach that the environment is hard coded to read the qa.properties file. We could easily fix this by using a variable as shown here.
@Sources({
    "classpath:${env}.properties"
})
public interface Environment extends Config {

    String url();

    String username();

    String password();

    @Key("db.hostname")
    String getDBHostname();

    @Key("db.port")
    int getDBPort();

    @Key("db.username")
    String getDBUsername();

    @Key("db.password")
    String getDBPassword();

}
  • Now we could pass the environment name as a variable to the test as shown here.
<suite name="TAG Suite">
    <parameter name="environment"  value="prod"/>
    <test name="Simple example">
    <-- ... -->
  • TestNG test should access the environment parameter from the suite.xml and use it to read the specific property file.
public class EnvironmentTest {

    Environment testEnvironment;

    @Test
    public void functionalTest() {
        System.out.println(testEnvironment.url());
        System.out.println(testEnvironment.getDBHostname());
        System.out.println(testEnvironment.getDBPassword());
    }

    @BeforeTest
    @Parameters({"environment"})
    public void beforeTest(String environemnt) {
        ConfigFactory.setProperty("env", environemnt);
        testEnvironment = ConfigFactory.create(Environment.class);
    }

}

Summary:

Adding any new environment specific data is very easy with this approach. Your tests and page objects remain unchanged. We need to update only Environment interface.

You can run the test against any given environment Dev, QA, UAT, Pre-Production, PROD without any code change. Name of the environment can be passed to the test as a parameter as shown above.

 

Happy Testing & Subscribe 🙂

 

 

Share This:

Selenium WebDriver – How To Automate Charts And Graphs Validation

$
0
0

Overview:

Automated validation of Charts & Graphs is one of the biggest challenges in test automation. In our application, We have tons of charts. I would show you how I have automated that to give you some idea.

Ocular:

I would be using Ocular – the image validation library! In fact, I created this Ocular lib just for this purpose!

Sample Application:

In order to explain things better, I am going to create 2 simple HTML files as shown below (I took the HTML from this site) & each HTML file contains 3 charts.

chart-base-2-html

Basically here we are going to assume that we would expect the right side charts to be exactly like the ones on the left side. Right side charts are almost same – except the January data of the Income chart.

Our expectation here is – as part of our automated testing, this difference should be reported and the test should fail!

The HTML source looks like this.

chart-source

 

Now It is time for us to design the Page Object for the above HTML file.

Page Object:

public class SampleChartPage {

    private WebDriver driver;
    private Map<String, String> map;

    @FindBy(id = "buyers")
    private WebElement buyersChart;

    @FindBy(id = "countries")
    private WebElement countriesChart;

    @FindBy(id = "income")
    private WebElement incomeChart;

    public SampleChartPage(WebDriver driver) {
        this.driver = driver;
        PageFactory.initElements(driver, this);

        // Page object should contain the location of the baseline images
        // these images no need to be present
        // Ocular will create these images if they are not found
        map = new HashMap<String, String>();
        map.put("buyers", "buyers.png");
        map.put("countries", "countries.png");
        map.put("income", "income.png");
    }

    public boolean verifyBuyersChart() {
        return this.verifyChart(map.get("buyers"), buyersChart);
    }

    public boolean verifyCountriesChart() {
        return this.verifyChart(map.get("countries"), countriesChart);
    }

    public boolean verifyIncomeChart() {
        return this.verifyChart(map.get("income"), incomeChart);
    }

    // common method to compare the baseline image against actual whole page / specific element
    private boolean verifyChart(String fileName, WebElement element) {

        Path path = Paths.get(fileName);

        OcularResult result = Ocular.snapshot()
                                    .from(path)
                                    .sample()
                                    .using(driver)
                                    .element(element)
                                    .compare();

        return result.isEqualsImages();
    }

}

I assume above page object is self-explanatory. I create folders like this in the project.

  • snap folder should contain all the baseline images
  • result will contain the comparison result

ocular-base-location

Initially these folders can be empty.  Because we would not have the images of those 3 charts web elements.  [Ocular will create these images under the snap folder during the first run].

Now it is time for us to create the Test.

 

TestNG Test:

In my testNG test for this sample application, I have 3 tests.

  • baseline_test – aim of this test to produce the baseline images first – when you run the test for the first time, Ocular creates the baseline images. So that, you can use it for any future validation. This test will always PASS.
  • visual_test_without_any_change – here, basically I am going to call the same HTML file. So Ocular will compare the charts against the baselines created as part of the previous test (baseline_test). This test will PASS because since the same HTML is launched with same data, charts would be as expected.
  • visual_test_after_change – in this test, I would launch another HTML where the income chart data is slightly changed.  So Ocular will validate and report the differences.

Once I run the baseline_test, snap folder will contain all the images we need!

ocular-snap-location

 

public class OcularDemo {

    private WebDriver driver;
    private SampleChartPage page;

    @BeforeSuite
    public void ocularConfig() {

        // update ocular config
        // set the base location of the baseline images - this folder should be present
        // set the base location of the result after comparison - this folder should be present
        Ocular.config()
               .snapshotPath(Paths.get(".", "src/test/resources/snap"))
               .resultPath(Paths.get(".", "src/test/resources/result"));
        
    }

    @BeforeMethod
    public void beforeMethod() {
        driver = new ChromeDriver();
    }

    @AfterMethod
    public void afterMethod() {
        driver.quit();
    }

    // during this test, snap folder is empty. ocular does not find any baseline images. 
    // so, ocular creates the baseline images and comparison result will be always TRUE.
    @Test
    public void baselineTest() throws InterruptedException {
        driver.get("file:///" + Paths.get("chart-js.html").toAbsolutePath());

        page = new SampleChartPage(driver);

        Assert.assertTrue(page.verifyBuyersChart());
        Assert.assertTrue(page.verifyCountriesChart());
        Assert.assertTrue(page.verifyIncomeChart());
    }

    // during this test, snap folder contains the baseline images.
    // so, ocular compares the actual webelement against the given image & returns the result
    @Test(dependsOnMethods = "baselineTest")
    public void visaul_test_without_any_change() throws InterruptedException {
        driver.get("file:///" + Paths.get("chart-js.html").toAbsolutePath());

        page = new SampleChartPage(driver);

        Assert.assertTrue(page.verifyBuyersChart());
        Assert.assertTrue(page.verifyCountriesChart());
        Assert.assertTrue(page.verifyIncomeChart());
    }

    // snap folder already contains the baseline images.
    // so, ocular compares the actual webelement against the given image & returns the result
    // income chart data is changed - so it will fail
    @Test(dependsOnMethods = "visaul_test_without_any_change")
    public void visaul_test_after_change() throws InterruptedException {
        driver.get("file:///" + Paths.get("chart-js-changed.html").toAbsolutePath());

        page = new SampleChartPage(driver);

        Assert.assertTrue(page.verifyBuyersChart());
        Assert.assertTrue(page.verifyCountriesChart());
        Assert.assertTrue(page.verifyIncomeChart());  // this will fail - because data is changed
    }
}

Executing the above test produced below result.

chart-result-2

For the failed test, difference was highlighted as shown here!!
chart-result-1

Summary:

Most of the automation suites compares the charts by reading the chart data and compare if the data is as expected assuming actual validation of the chart is difficult. But if we see the above example, Verifying the charts is no longer a big challenge with Ocular! If we pass the baseline image location and element, Ocular compares and highlights the differences easily!

Hope the above example was easy for you to understand this better. Ocular can do a lot more than this!! So, Please check that out and leave a comment / suggestion.

 

Happy Testing & Subscribe 🙂

 

Share This:

Selenium WebDriver – How To Set Up Dockerized Selenium Grid in AWS / Cloud

$
0
0

Overview:

We already have seen setting up JMeter Distributed Infrastructure using RancherOS.  We will see how to manage dockerized selenium grid using RancherOS. Rancher helps us to set up the grid in the local machine / AWS / any cloud.

I would suggest you to read the above article on setting up the Rancher UI and adding AWS hosts etc.

Rancher – Stack:

Stack is a set of services to be run for the application. For the ‘Selenium Grid’ Stack, Lets assume we need below services.

  • 1 selenium-hub container
  • at least 1 chrome container – connected to the above selenium-hub container
  • at least 1 firefox container – connected to the above selenium-hub container

Setting Up Selenium Grid:

  • Go to Rancher UI – Stack screen to add a new Stack.

grid-stack-1

  • I am going to call the stack as selenium-grid as shown here. [You could also upload your stack YAML config instead of setting up everything manually.]

grid-stack-2

  • Once it is created, lets define the set of services we need. Click on the Add Service.

grid-stack-3

  • I need the selenium-hub container first. I need to map the port 4444 of the container to the host port 4444.

grid-hub-1

  • I am going to create a chrome container as shown here. It needs to be linked with selenium-hub container we had created. Repeat this step for firefox container as well.

grid-hub-chrome

  • Now our grid-stack will look like this.

grid-hub-all

  • Now click on the chrome/firefox to monitor, scale them up/down etc

grid-scale

  • Rancher gives us an option to export the config in the YAML file for backup of our infrastructure.

stack-export-yml

  • I could access the dockerized selenium grid in the cloud using http://[ip of hub]:4444/grid/console

grid-console

 

 

Happy Testing & Subscribe 🙂

 

Share This:


Selenium WebDriver – How To Run Automated Tests Inside A Docker Container

$
0
0

Overview:

Testautomationguru already has few docker and selenium related articles which talks about setting up the dockerized selenium grid.

  1. Setting up Dockerized Selenium grid.
  2. Managing Dockerized Selenium Grid using Arquillian cube.
  3. Setting up Dockerized Selenium grid in AWS / Cloud using RancherOS

Even if we have a selenium grid, we still need a host to run our tests – ie.. a host is required to run your maven command/ant/some batch file to execute the tests – so that test could talk to remote grid to send the WebDriver related instruction. In this article, we will see how we could use a container (as a host) for running our tests.

Challenges In Test Execution:

We already have simplified our selenium grid set up using docker using the above articles. But we still face few challenges. Your tests still have few dependencies to run them!

  • Test depends on specific version of java (like java 8)
  • Host should have maven or Ant or Gradle or other build tools installed
  • Tests depend on number of 3rd party jar files and other files like csv etc.
  • Tests depend on few jar files which are not part of maven repo!!
  • Remote slaves need to be set up with the build tools, java and any dependent jar files to execute the tests.

Managing these dependencies & Setting up remote slaves are time-consuming for big projects.

We will see how docker could help us here!!

Sample Test:

To show a demo, I am creating a simple maven project. I add the selenium-java & testNG dependencies.

<dependencies>
        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-java</artifactId>
            <version>3.5.2</version>
        </dependency>
        <dependency>
            <groupId>org.testng</groupId>
            <artifactId>testng</artifactId>
            <version>6.11</version>
        </dependency>
</dependencies>

I add the below page object.

  • A Google Page Object which searches for a string and return search results
public class GooglePage {

    private final WebDriver driver;
    private final WebDriverWait wait;

    @FindBy(name = "q")
    private WebElement searchBox;

    @FindBy(css = "input.lsb")
    private WebElement searchButton;

    @FindBy(className = "rc")
    private List<WebElement> searchResults;

    @FindBy(id = "foot")
    private WebElement footer;

    public GooglePage(final WebDriver driver) {
        this.driver = driver;
        PageFactory.initElements(driver, this);
        this.wait = new WebDriverWait(driver, 30);
    }

    public void goTo() {
        this.driver.get("https://www.google.com");
    }

    public void searchFor(String text) throws InterruptedException {
        this.searchBox.sendKeys(text);
        wait.until(ExpectedConditions.elementToBeClickable(this.searchButton));
        this.searchButton.click();
        wait.until(ExpectedConditions.presenceOfElementLocated(By.className("rc")));
    }

    public List<WebElement> getResults() {
        return this.searchResults;
    }

}

A corresponding TestNG Test would be as shown here.

  • The test searches for ‘automation’ and checks if the search results count is at least 10.
public class GoogleTest {

    private WebDriver driver;
    private GooglePage google;

    @BeforeTest
    public void setUp() throws MalformedURLException {
        DesiredCapabilities dc = DesiredCapabilities.chrome();
        driver = new RemoteWebDriver(new URL("http://localhost:4444/wd/hub"), dc);
        google = new GooglePage(driver);
    }

    @Test
    public void googleTest() throws InterruptedException {
        google.goTo();
        google.searchFor("automation");
        Assert.assertTrue(google.getResults().size() >= 10);
    }
    
    @AfterTest
    public void tearDown() throws InterruptedException {
        driver.quit();
    }    

}

Now You could simply execute the test,

  • Using maven, mvn clean test
  • Directly in the command line   java -cp mytest.jar org.testng.TestNG -testclass org.testautomationguru.test.GoogleTest

If I need to execute my tests using Jenkins in a remote slave, I need to ensure that my remote machine also has  java, maven etc. If the test depends on few jar files which are not part of maven repo, We need to ensure that it is copied to the appropriate remote local maven repo.  Basically – you need to set up entire dependencies in each machine for your tests to execute!  A lot of manual work!!

Dockerfile Maven Plugin:

Maven has a dockerfile plugin which nicely integrates docker and maven. It automates docker image build process in maven. By issuing a maven command, it creates a docker image based on the given Dockerfile in your project.

My project looks like this.

container-proj

To use the plugin, I add the below dependency in the maven pom file.

<plugin>
    <groupId>com.spotify</groupId>
    <artifactId>dockerfile-maven-plugin</artifactId>
    <version>1.3.5</version>
    <executions>
        <execution>
            <id>default</id>
            <goals>
                <goal>build</goal>
                <goal>push</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <repository>vinsdocker/containertest</repository>
        <tag>googletest</tag>
    </configuration>
</plugin>

My Dockerfile is as shown below. I create a single jar (container-test.jar) with all the dependencies by using maven-assemply-plugin.

Dockerfile:

FROM openjdk:8-jre-slim

# Add the service itself
ADD  target/container-test.jar /usr/share/tag/container-test.jar

# Command line to execute the test
ENTRYPOINT ["/usr/bin/java", "-cp", "/usr/share/tag/container-test.jar", "org.testng.TestNG", "-testclass", "com.testautomationguru.container.test.GoogleTest"]

When I issue the below command, it creates a jar with all the test dependencies and also creates a docker image.

mven clean package

dockerfile-maven-build

It builds a new docker image which could be accessible using vinsdocker/containertest.

Now the docker image has been built with all the dependencies. An entrypoint has also been added – it means whenever you create a container (an instance of the docker image), it starts executing the test.

In my remote slave, I need to issue only below command – docker run vinsdocker/containertest:googletest

sudo docker run vinsdocker/containertest:googletest
Aug 27, 2017 4:06:05 AM org.openqa.selenium.remote.ProtocolHandshake createSession
INFO: Detected dialect: OSS
Browser launched and navigated to Google
automation - entered for search
Search button clicked
Results appeared
Results Count : 11

===============================================
Command line suite
Total tests run: 1, Failures: 0, Skips: 0
===============================================

Summary:

Docker not only helps us in setting up the selenium grid – but also simplifies managing test dependencies. Maven creates a new docker image as part of maven build process by using the given Dockerfile. Once the image is built, any machine with docker can execute the test!  It saves a lot of manual work in setting up the remote slave for execution.

 

Happy Testing & Subscribe 🙂

 

Share This:

Selenium WebDriver – How To Run Multiple Test Suites Using Docker Compose

$
0
0

Overview:

Testautomationguru already has few docker and selenium related articles which talks about setting up the dockerized selenium grid & running a docker container with a test.

  1. Setting up Dockerized Selenium grid.
  2. Running Automated Tests Inside A Docker Container

This article is going to explain how to combine above 2 articles – ie running all regression testcases with multiple test suites & selenium grid with a single docker-compose file.

Test Suites:

In our automated regression testing framework, we might have multiple test suites to test the different functionalities of the application.

Lets assume – in our application we have 3 modules.

  1. Registration Module
  2. Search Module
  3. Order Module

In our test framework, each module might have hundreds of test cases. So, You need multiple hosts + Selenium Grid set up as shown here to run the entire regression test suites!!

 

reg-suite-set-up

Lets see how we could set up the above entire infrastructure in a single compose file and execute the tests!!

Sample Project:

For the demo, I have created a simple project as shown here. This sample project is available in Github.

reg-suite-proj-set-up-1

Page Objects:

  • Above project has 2 page objects
    • SearchPage – acts as a page object for the search module (this is actually a page object for google search).
    • OrderPage  – acts as a page object for order module (this is actually a page object for  demoqa site).

Test Classes:

  • BaseTest – a base test which contains the logic for driver instantiation.
  • SearchTest – extends BaseTest – contains the test for the Search Module.
  • OrderTest – extends BaseTest – contains the test for the Order Module.

Test Suites:

In real life, we will have multiple tests for a single module. In our sample project, we have only one test. We collect all the tests for a module and create a test suite file.

  • order-module.xml – a testNG suite file which invokes order module tests.
<suite name="order-module" parallel="none" allow-return-values="true" >
  <test name="order-test">
    <classes>
      <class name="com.testautomationguru.container.test.OrderTest"/>
    </classes>
  </test>
</suite>
  • search-module.xml – a testNG suite file which invokes search module tests.
<suite name="search-module" parallel="none" allow-return-values="true" >
  <test name="search-test">
    <classes>
      <class name="com.testautomationguru.container.test.SearchTest"/>
    </classes>
  </test>
</suite>

Test Requirement:

In order to do the complete regression testing for the application, Lets assume I need to invoke the tests for both modules as given below.

  • order module testing on chrome
  • search module testing on firefox

Dockerfile:

I create the below Dockerfile which creates an image which contains all the dependencies to the run the test including the testNG suite files. [I have already explained this here – Please read the article if you have not already]

FROM openjdk:8-jre-slim

# Add the jar with all the dependencies
ADD  target/container-test.jar /usr/share/tag/container-test.jar

# Add the suite xmls
ADD order-module.xml /usr/share/tag/order-module.xml
ADD search-module.xml /usr/share/tag/search-module.xml

# Command line to execute the test
# Expects below ennvironment variables
# BROWSER = chrome / firefox
# MODULE  = order-module / search-module
# SELENIUM_HUB = selenium hub hostname / ipaddress

ENTRYPOINT /usr/bin/java -cp /usr/share/tag/container-test.jar -DseleniumHubHost=$SELENIUM_HUB -Dbrowser=$BROWSER org.testng.TestNG /usr/share/tag/$MODULE

Creating An Image:

Executing below command compiles, packages the jar with all the dependencies and builds the docker image based on the above Dockerfile.

mvn clean package

 

reg-suite-build-1

Docker Compose File:

Testautomationguru has already explained setting up the whole selenium grid infrastructure using docker-compose in this article – Setting up Dockerized Selenium grid.

version: "3"
services:
  selenium-hub:
    image: selenium/hub
    container_name: selenium-hub
    ports:
      - "4444:4444"
  chrome:
    image: selenium/node-chrome
    depends_on:
      - selenium-hub
    environment:
      - HUB_PORT_4444_TCP_ADDR=selenium-hub
      - HUB_PORT_4444_TCP_PORT=4444
  firefox:
    image: selenium/node-firefox
    depends_on:
      - selenium-hub
    environment:
      - HUB_PORT_4444_TCP_ADDR=selenium-hub
      - HUB_PORT_4444_TCP_PORT=4444

We were able to successfully create a Selenium Grid using above docker compose file.  Now we are going to add the containers we created with tests.

search-module:
    image: vinsdocker/containertest:demo
    container_name: search-module
    depends_on:
      - chrome
      - firefox
    environment:
      - MODULE=search-module.xml
      - BROWSER=firefox
      - SELENIUM_HUB=selenium-hub
order-module:
    image: vinsdocker/containertest:demo
    container_name: order-module
    depends_on:
      - chrome
      - firefox
    environment:
      - MODULE=order-module.xml
      - BROWSER=chrome
      - SELENIUM_HUB=selenium-hub
  • depends_on – lets the docker know that the service should be created once the all the dependent containers have been created.

Test Suites Execution:

Once the docker compose file is ready, the rest is very simple!!

  • A single command creates the entire infrastructure and executes the test.
sudo docker-compose up -d
Creating selenium-hub
Creating sel_firefox_1
Creating sel_chrome_1
Creating order-module
Creating search-module
  • Execute the below command to see the status
sudo docker-compose ps
    Name                   Command               State           Ports
-------------------------------------------------------------------------------
order-module    /bin/sh -c /usr/bin/java - ...   Up
search-module   /bin/sh -c /usr/bin/java - ...   Up
sel_chrome_1    /opt/bin/entry_point.sh          Up
sel_firefox_1   /opt/bin/entry_point.sh          Up
selenium-hub    /opt/bin/entry_point.sh          Up      0.0.0.0:4444->4444/tcp
  • Once the test execution is done, you would see Exit 0  (in case of any test failures, you would see Exit 1)
sudo docker-compose ps
    Name                   Command               State           Ports
-------------------------------------------------------------------------------
order-module    /bin/sh -c /usr/bin/java - ...   Exit 0
search-module   /bin/sh -c /usr/bin/java - ...   Exit 0
sel_chrome_1    /opt/bin/entry_point.sh          Up
sel_firefox_1   /opt/bin/entry_point.sh          Up
selenium-hub    /opt/bin/entry_point.sh          Up      0.0.0.0:4444->4444/tcp
  • If you are curious to see the test log of order-module and search-module only, then go for below command.
sudo docker-compose up | grep -e 'order-module' -e 'search-module'

reg-suite-log

  • To bring everything down, of course with a single command!
sudo docker-compose down
Removing search-module ... done
Removing order-module ... done
Removing sel_firefox_1 ... done
Removing sel_chrome_1 ... done
Removing selenium-hub ... done

GitHub:

This sample project is available here.

Summary:

Docker compose made our life very easier! With a single command,  we create Virtual Machines (containers are light weight VMs) with required browsers, tests and executes the test. Again with another command, we bring the entire infrastructure down!!

No more environment specific issues like ‘the test is not working in the machine’   🙂

By implementing docker in your project, you become 100% awesome as Barney!!

barney-awesomeness-thums-up

 

Happy Testing & Subscribe 🙂

 

Share This:

Selenium WebDriver – Running Dockerizied Automated Tests using Portainer

$
0
0

Overview:

TestAutomationGuru has already released few articles on creating a docker image with your tests + all the dependencies and running them on a docker container as and when we want!

  1. Running Automated Tests Inside A Docker Container
  2. Running Multiple Test Suites using Docker Compose

In the above articles, even if we had automated the process of creating a docker image etc, we were still running/managing docker containers manually.  We could automate that using a shell script etc & manage the execution through Jenkins. It is a fair point!   But if you need to do a lot of docker stuff and you / your team members, are not comfortable with command-line, then you could use a GUI tool which abstracts these docker commands.

This article is going to explain one such GUI tool – Portainer – for docker. I am going to re-use some of the artifacts I had created as part of above articles. So, Please read above articles if you have not already!

Portainer:

Portainer is a simple tool with a nice GUI to manage docker containers, images, networks, volumes etc on a host! Portainer itself is running as a docker container (portainer image is only 10MB).

Run the below command once & you are set!

sudo docker run -d -p 9000:9000 -v /var/run/docker.sock:/var/run/docker.sock -v /opt/portainer:/data portainer/portainer

Now access the portainer dashboard using http://[ip address]/:9000

portainer-dashboard

Now you could use those tabs on the left navigation to manage docker containers, images, networks, volumes etc.

Lets see how we could use it to run our tests!

Docker Image:

I had created a docker image which contains our selenium tests as part of this article – Running Multiple Test Suites using Docker Compose

  • Image name: vinsdocker/containertest:demo
  • Input Parameters / Environment variables:
    • SELENIUM_HUB : hostname / ipaddress of selenium grid hub
    • BROWSER : chrome / firefox
    • MODULE : order-module.xml /  search-module.xml

 

It is basically a single image – based on the module name we pass as environment variable, it executes the different modules.

Container Setup:

  • Go to container tab in Portainer. [It will show all the running / stopped containers]
  • Click on ‘Add Container’
  • Based on the above docker image info, create 2 containers as shown here.
    • order-module
    • search-module

create-container-oder

  • Our test results will be stored inside the docker container ‘/test-output‘ directory – I use volume to map the container directory to our local ‘/home/qa/result’ directory on the host. Basically once the container is done with the test execution, all the results would be available in my ‘/home/qa/result‘ directory.

create-container-oder-volume

  • Click on ‘Create’ to start the container test execution.

create-container-oder-running

  • Portainer has nice GUI to check the container log, CPU/Memory utilization etc.  We could even attach ourselves with the running container & execute commands directly inside the container.

create-container-oder-stats

create-container-log

  • With Portainer, you could start these containers as when we want.

create-container-start

 

Summary:

Now all the team members do not need special access to the server to run the commands to manage containers – Instead with Portainer’s GUI, the team could manage docker containers and executes the test as and when it is required! No need to memorize all the docker commands!

 

Happy Testing & Subscribe 🙂

 

Share This:

Selenium WebDriver – How To Query HTML Web Table

$
0
0

Overview:

As an automation engineer , often, we might have to find / interact with some web elements of a HTML Web Table based on certain conditions. There are various approaches to do that.  We will see how to use Java Streams to play with HTML tables.

Sample HTML Table:

I create a Simple HTML page with below table.

sample-table-1

 

Our requirement is to select check boxes based on some conditions like – DOB should match ’01/01/1970′ or Country should be ‘USA’ etc – basically any given condition at run time.

Filter Rows Using Java Streams:

  • First, I launch site as shown here.
WebDriver driver = new ChromeDriver();
driver.get("sample.html");
WebElement table = driver.findElement(By.tagName("table"));
  • Get the column names
List<String> columnNames = table.findElements(By.tagName("th"))   // get table headers
                                        .stream()
                                        .map(WebElement::getText)        // get the text
                                        .map(String::trim)               // trim - no space
                                        .collect(Collectors.toList());   // collect to a list
// columnNames prints [Row Index, Name, DOB, City, Country, Checkbox]
  • Do a mapping of column name and the corresponding index
Map<String, Integer> columnMap = IntStream.range(0, columnNames.size())
                                               .boxed()
                                               .collect(Collectors.toMap(columnNames::get, 
                                                                         Function.identity()));

//columnMap {Row Index=0, Name=1, DOB=2, City=3, Country=4, Checkbox=5}
  • Now we want to select all check boxes when the DOB is 01/01/1960
table.findElements(By.tagName("tr"))  //get all rows
             .stream()
             .skip(1)                 // skip first row as we do not need header
             .map(tr -> tr.findElements(By.tagName("td")))   // get all cells for each rows
             .filter(tds -> tds.get(columnMap.get("DOB")).getText().equals("01/01/1960"))   // find the row which has DOB as 01/01/1960
             .map(tds -> tds.get(columnMap.get("Checkbox")))   // get cell which contains checkbox
             .map(td -> td.findElement(By.tagName("input")))   // get checkbox
             .forEach(WebElement::click);                      // click checkbox
  • Our aim is to choose different filters at run time – so that appropriate check boxes can be selected. So, I create a function which accepts a Predicate – It is basically a filter.
private void filterRows(Predicate<List<WebElement>> compositeCheck){
        
        table.findElements(By.tagName("tr")) 
            .stream()
            .skip(1)        
            .map(tr -> tr.findElements(By.tagName("td"))) 
            .filter(compositeCheck)  // passed as argument
            .map(tds -> tds.get(columnMap.get("Checkbox"))) 
            .map(td -> td.findElement(By.tagName("input")))
            .forEach(WebElement::click);
        
}
  • We might want to include AND, OR conditions as well in our filters.  Lets create below conditions.
Predicate<List<WebElement>> dobCheck = (tds) -> tds.get(columnMap.get("DOB")).getText().equals("01/01/1960");   // check if DOB is '01/01/1960'
Predicate<List<WebElement>> countryCheck = (tds) -> tds.get(columnMap.get("Country")).getText().equals("USA");  // check Country is 'USA'
Predicate<List<WebElement>> cityCheck = (tds) -> tds.get(columnMap.get("City")).getText().equals("Oran");       // check City is 'Oran'
  • To select all the rows for which DOB is 01/01/1960 OR city is Oran – use the below Predicate for the filter.
dobCheck.or(cityCheck)

dobCheck-or-cityCheck

  • To select the rows DOB is 01/01/1960 AND country is USA
dobCheck.and(countryCheck)

dobCheck-and-countryCheck

  • Below check will select all the rows.
dobCheck.or(countryCheck).or(cityCheck)
  • If you have multiple columns and would like to create Predicate at run time, You can create a function which returns a Predicate.
private Predicate<List<WebElement>> getPredicate(String col, String text){
        return (tds) -> tds.get(columnMap.get(col)).getText().equals(text);
}

getPredicate("DOB", "01/01/1960").and(getPredicate("City", "Oran")) 

 

Happy Testing & Subscribe 🙂

 

 

Share This:

Selenium WebDriver – How To Wait For Expected Conditions Using Awaitility

$
0
0

Overview:

One of the most common challenges with test automation is dealing with test flakiness! People who have done some automation or developers who have worked on multi-threaded application development can only understand the pain! It is painful because there are 2 different processes (test scripts in your favorite language & the browser driver) trying to talk to each other! So one might have to wait for other to be in desired state!

Usual Wait Techniques:

Page Synchronization is not very difficult as it might sound! There are many ways to handle the situations.

  • Thread.sleep()   – This is something like Alcohol! We know that Consuming Alcohol is injurious to health. But we still have that! Similarly we all use ‘Thread.sleep’ in our script once in a while even if we know that it is brittle and but we never accept to others we use it!  It might get the job done sometimes, but affects the performance of the scripts very badly & does not produce consistent test results! [That is what flakiness is, right?]
  • Implicit Wait – WebDriver is configured with specific duration for timeout to find an element. The timeout is used throughout the test script! Again, it is not a good idea & it affects the performance of the test scripts very badly!
  • Explicit Wait  – We use ExpectedConditions api along with WebDriverWait to make the driver wait for specific conditions like element becomes visible, expected text to appear etc! Compared to the above approaches, this is definitely better! But sometimes we might end up writing many lines of code using this lib.
  • Fluent Wait

I personally use the Arquillian Graphene library which has much better synchronization methods in fluent API style – which makes the code looks neat and clean. You can check here.

If you do not want to use Arquillian for some reason, then you can take a look at this Awaitility library. Lets see how we could handle the page synchronization using this library in this article.

Using Awaitility:

  • Include below dependencies in your pom file.
<dependency>
    <groupId>org.awaitility</groupId>
    <artifactId>awaitility</artifactId>
    <version>3.0.0</version>
</dependency>
<dependency>
    <groupId>org.awaitility</groupId>
    <artifactId>awaitility-proxy</artifactId>
    <version>3.0.0</version>
</dependency>
  • Add the below static imports to use the Awaitility effectively.
import static org.awaitility.Awaitility.*;
import static org.awaitility.Duration.*;
import static java.util.concurrent.TimeUnit.*;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;

Awaitility with WebDriver:

  • Waiting for specific text to appear. In the below example – we are waiting for the File download progress bar to show the Complete! message.

download-example                         download-example-complete

driver.get("http://www.seleniumeasy.com/test/jquery-download-progress-bar-demo.html");
driver.findElement(By.id("downloadButton")).click();
WebElement progress= driver.findElement(By.cssSelector("div.progress-label"));
        
// Wait for the download to complete - max 20 seconds
await("Download did not complete within 20 seconds").atMost(20, TimeUnit.SECONDS)
                                                    .until(progress::getText, is("Complete!"));
        
//Did you notice? The robust wait statement is just an one-liner! It looks neat and clean!
System.out.println("DONE!!");
  • Waiting for AJAX request to complete / ‘loading’ message to disappear. In the below example, whenever you click on the ‘Get New User’ an AJAX rquest is made to get a new user information. A loading message is shown as shown here till the request is complete.

get-userget-user-loading

 

driver.get("http://www.seleniumeasy.com/test/dynamic-data-loading-demo.html");
WebElement newUserBtn = driver.findElement(By.id("save"));
WebElement loadingElement = driver.findElement(By.id("loading"));

// Get a new User
newUserBtn.click();

//Wait for the loading to disappear
await("Wait for new user to load").atMost(5, SECONDS)
                                  .until(loadingElement::getText, not("loading..."));
  • I clicked on the ‘Get New User’ 20 times in a loop – It worked like charm.
for(int i=0; i<20; i++){
    newUserBtn.click();
    await("Wait for new user to load").atMost(5, SECONDS)
                                      .until(loadingElement::getText, not("loading..."));
}

 

 

  • Handling Timing Alert and Exceptions:  In the below example, we wait for the alert to appear – when we try to switch to alert when it is not present, it throws an exception – we handle that using ‘ignoreExceptions()’
driver.get("http://www.seleniumframework.com/Practiceform/");
WebElement alertButton = driver.findElement(By.id("timingAlert"));
      
//To check if alert is present - By the way, predicates are cool!     
Predicate<WebDriver> isAlertPresent = (d) -> {
    d.switchTo().alert();
    return true;
};

//click - alert appears after 3 seconds  
alertButton.click();

//wait for 5 seconds - ignore alert not present exception
await("Wait for alert").atMost(5, SECONDS)
                       .ignoreExceptions()
                       .until(() -> isAlertPresent.test(driver));

driver.switchTo().alert().accept();
  • At Least – Lets say we have a requirement to show the alert only after 2 seconds – then the test should fail if it appears before that. Awaitility can verify that too!
//alert should not appear within 2 seconds - max 5 seconds - ignore alert not present exception
await("Wait for alert").atLeast(2, SECONDS)
                        .and()
                        .atMost(5, SECONDS)
                        .ignoreExceptions()
                        .until(() -> isAlertPresent.test(driver));

Summary:

Awaitility is a cool library with a lot of features. This was a quick tutorial. I might not be able to cover all the possible combinations in this. But hopefully you got the idea! I would suggest you to read more on this wiki. You could also combine this lib with ExpectedConditions API to write a better readable code.

All the best for you have to robust test suite with Awaitlity!

Happy Testing & Subscribe 🙂

 

Share This:

Selenium WebDriver – How To Automate Responsive Design Testing

$
0
0

Overview:

In IoT era, where everything is integrated, It is almost impossible to find someone without a mobile device! Any user with an internet connection could potentially be a customer of your application. So, It is your job to ensure that they have a best user experience on your website.

Responsive web design makes your application look good on all the devices!

In the good old days we used to identify the functional test cases which are being executed in every cycle (smoke/regression tests) or data-driven test cases for automation. We used to avoid any UI related test cases as much as possible considering the difficulties in automating that. Now, in the fast paced application development and continuous production deployment, We can not avoid this any longer!

There are many commercial tools for this type of testing if your organization is ready to pay for it! If you are like me, looking for some open source solution, then I would like to show you, how I do the responsive web design testing using Ocular library.

Sample Application:

I am going to consider the few pages of the famous Netflix application for the responsive web design testing.

  • Home Page
  • ‘Choose Your Plan’ page
  • Plan Page

netflix-samsung      netflix-amzn-fire

 

 

netflix-plans-1        netflix-plans

Baseline Images:

In order to do the responsive web design testing, we need to have the baseline images. If you do not have, It is fine. Ocular library, during the very first run, takes the screenshots and stores them in appropriate folders. So that, when you run next time, It uses those baselines to compare and gives the results.

We need to maintain the baselines for various dimensions/devices we are interested in as shown here.

responsive-test-baselines

Page Objects:

I create few page objects as shown here.

  • BasePage which contains the reusable compare method – this compare is responsible for verifying the current page against the baseline image.
abstract class BasePage {

    protected final WebDriver driver;

    public BasePage(WebDriver driver) {
        this.driver = driver;
        PageFactory.initElements(driver, this);
    }

    public OcularResult compare() {
        return Ocular.snapshot()
                        .from(this)
                      .sample()
                           .using(driver)
                      .compare();
    }

}
  • Netflix Home Page – It assumes there is a baseline image ‘HomePage.png’ . If there are multiple HomePage.png for different resolutions, we will configure it at run time for which one to choose.
@Snap("HomePage.png")
public class HomePage extends BasePage {

    @FindBy(css = "button.btn-large")
    private WebElement joinFreeForAMonth;

    public HomePage(WebDriver driver) {
        super(driver);
    }

    public SeeThePlans clickOnJoinButton() {
        this.joinFreeForAMonth.click();
        return new SeeThePlans(driver);
    }
}
  • Choose Your Plans Message Page.
@Snap("SeeThePlans.png")
public class SeeThePlans extends BasePage {

    @FindBy(css = "button.nf-btn-primary")
    private WebElement seeThePlansBtn;

    public SeeThePlans(WebDriver driver) {
        super(driver);
    }

    public PlanList clickToSeeThePlans() {
        this.seeThePlansBtn.click();
        return new PlanList(driver);
    }

}
  • Plans List
@Snap("PlanList.png")
public class PlanList extends BasePage {

    public PlanList(WebDriver driver) {
        super(driver);
    }

}
  • TestN Test
public class ResponsiveDesignTest {
    
    WebDriver driver;

    @Test
    public void test() throws InterruptedException {
        
        HomePage home = new HomePage(driver);
        
        //verify home page
        Assert.assertTrue(home.compare().isEqualsImages());
        
        SeeThePlans seeThePlans = home.clickOnJoinButton();

        //verify 'Choose your Plan' message page
        Assert.assertTrue(seeThePlans.compare().isEqualsImages());
        
        PlanList planList = seeThePlans.clickToSeeThePlans();

        //verify Plans List page
        Assert.assertTrue(planList.compare().isEqualsImages());
    }
 

    @BeforeTest
    @Parameters({ "dimension"})
    public void beforeTest(String dimension) {
    
         //Ocular config - Ocular should look for the baseline image depends on the dimension or device
        Path snapShotPath = Paths.get("./src/test/resources/", dimension);
        Ocular.config()
                .snapshotPath(snapShotPath)
                .resultPath(Paths.get("./src/test/resources/result"));
    
        //Launch Browser with the dimension or use appium driver to test on mobile devices
        driver = new ChromeDriver();
        driver.get("https://www.netflix.com");
        
        int width = Integer.parseInt(dimension.split("x")[0]);
        int height = Integer.parseInt(dimension.split("x")[1]);
        
        driver.manage().window().setSize(new Dimension(width, height));
    }

    @AfterTest
    public void afterTest() {
        driver.quit();
    }

}
  • Based on the dimension we passed to the test, Ocular will get the baseline image and will compare against the actual image and gives us the results automatically (difference would be highlighted in the images under the result folder).

responsive-test-result1HomePage

Summary:

Automating Responsive Web Application is important as the most of the users access the application through mobile devices only & it will keep growing in the future. I hope the above page objects design helped you to understand how we could we design this type of testing in our current automation framework.

 

Happy Testing & Subscribe 🙂

 

 

Share This:

Selenium WebDriver – How To Automatically Skip Tests Based On Open Defects

$
0
0

Overview:

I have been running thousands of automated test cases as part of our nightly regression testing. One of the challenges in running this is analyzing the test results next day morning – Particularly when you have large set of regression test cases. Even though I have tried to keep the test flakiness as low as possible, there could be many genuine failures in the test results due to open defects. It is good that your automated regression suite works great by reporting them as issues. But you need to spend some time to find out if they are new issues or some existing issues in the issue tracking system.

To further simplify our work, We could add a feature in our test framework to automatically decide if the particular test should be executed or not.  That is what we are going to see in this article.

Assumptions:

  1. I assume you use TestNG or similar framework.
  2. Your Issue tracking system has APIs enabled. For this article, I will be using GitHub as our issue tracker.

Sample testNG Test:

For better understanding, Lets create a sample testNG test first.

public class UserRegistrationTest {
    
      @Test
      public void launchRegistrationPage() {
          RegistrationPage.launch();
          Assert.assertTrue(RegistrationPage.isAt());
      }

      @Test(dependsOnMethods= "launchRegistrationPage")
      public void enterDemographicInformation() {
          RegistrationPage.enterDemographicInformation();
          RegistrationPage.submit();
          Assert.assertTrue(ProductsListPage.isAt());
      }
      
      @Test(dependsOnMethods= "enterDemographicInformation")
      public void chooseProduct() {
          ProductsListPage.chooseProduct(Products.DUMMY);
          ProductsListPage.submit();
          Assert.assertTrue(PaymentInformationPage.isAt());
      }

      @Test(dependsOnMethods= "chooseProduct")
      public void enterPayment() {
          PaymentInformationPage.enterPayment(PaymentMethods.CC);
          PaymentInformationPage.submit();
          Assert.assertTrue(OrderConfirmationPage.isAt());
      }
      
       
      @Test(dependsOnMethods= "enterPayment")
      public void orderConfirmation() {
          Assert.assertTrue(OrderConfirmationPage.isOrderSuccessful());
          OrderConfirmationPage.close();
      } 
}

In the above test, lets assume that we have been facing some issues with payment system – so all the tests are failing. As It is known issue, we do not want them to appear in the failed tests list as it increases the failed tests count and wastes our time. So , we would like to skip the test as long as the issue is still open.

testng-issue-status-1

 

IssueTrackerUtil:

In order to enhance our testNG framework, I create the following.

public enum IssueStatus {
    OPEN,
    CLOSED;
}

 

public class IssueTrackerUtil {

    private static final String ISSUE_TRACKER_API_BASE_URL = "https://api.github.com/repos/username/dummy-project/issues/";
    
    public static IssueStatus getStatus(String issueID) {
        String githubIssueStatus = "CLOSED";
        try {
            githubIssueStatus = Unirest.get(ISSUE_TRACKER_API_BASE_URL.concat(issueID))
                                       .asJson()
                                       .getBody()
                                       .getObject()
                                       .getString("state")
                                       .toUpperCase();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return IssueStatus.valueOf(githubIssueStatus);
    }
    
}

I used Unirest library to make a call to the REST API. To include in your maven project,

<dependency>
    <groupId>com.mashape.unirest</groupId>
    <artifactId>unirest-java</artifactId>
    <version>1.4.9</version>
</dependency>

Issue Status Listener:

First, I would like to mention the open defects in the in testNG tests as shown here.

@Issue("APP-1234")
@Test(dependsOnMethods= "chooseProduct")
public void enterPayment() {
  PaymentInformationPage.enterPayment(PaymentMethods.CC);
  PaymentInformationPage.submit();
  Assert.assertTrue(OrderConfirmationPage.isAt());
}

So, I create an Issue annotation.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Issue {
    String value() default "";
}

Now we need to get the issue ID for each tests before it gets executed, call the above IssueTrackerUtil to get the issue status and skip the tests based on the status. Lets use testNG’s IInvokedMethodListener  as shown here.

 

public class MethodIssueStatusListener implements IInvokedMethodListener {

    @Override
    public void afterInvocation(IInvokedMethod invokedMethod, ITestResult result) {
        // nothing to do
    }

    @Override
    public void beforeInvocation(IInvokedMethod invokedMethod, ITestResult result) {

        Issue issue = invokedMethod.getTestMethod()
                                   .getConstructorOrMethod()
                                   .getMethod()
                                   .getAnnotation(Issue.class);

        if (null != issue) {
            if (IssueStatus.OPEN.equals(IssueTrackerUtil.getStatus(issue.value()))) {
                throw new SkipException("Skipping this due to Open Defect - " + issue.value());
            }
        }
    }

}

The rest is simple. Add the above listener in the testNG test class (usually in the base class or suite xml file) & mention the issue ID if any of the tests is associated with particular issue.

@Listener(MethodIssueStatusListener.class)
public class UserRegistrationTest {
    
      @Test
      public void launchRegistrationPage() {
          RegistrationPage.launch();
          Assert.assertTrue(RegistrationPage.isAt());
      }

      @Test(dependsOnMethods= "launchRegistrationPage")
      public void enterDemographicInformation() {
          RegistrationPage.enterDemographicInformation();
          RegistrationPage.submit();
          Assert.assertTrue(ProductsListPage.isAt());
      }
      
      @Test(dependsOnMethods= "enterDemographicInformation")
      public void chooseProduct() {
          ProductsListPage.chooseProduct(Products.DUMMY);
          ProductsListPage.submit();
          Assert.assertTrue(PaymentInformationPage.isAt());
      }
    
      @Issue("APP-1234")
      @Test(dependsOnMethods= "chooseProduct")
      public void enterPayment() {
          PaymentInformationPage.enterPayment(PaymentMethods.CC);
          PaymentInformationPage.submit();
          Assert.assertTrue(OrderConfirmationPage.isAt());
      }
       
      @Test(dependsOnMethods= "enterPayment")
      public void orderConfirmation() {
          Assert.assertTrue(OrderConfirmationPage.isOrderSuccessful());
          OrderConfirmationPage.close();
      } 
}

When we execute the above test, I get the results as shown here.

testng-issue-status

Updating Issue Status Automatically:

Lets further enhance this!

Instead of skipping the tests, lets execute it every time.

  • If it it fails, change the result to SKIPPED (Because we know that it might fail. We just ran to see if there is any luck!)
  • If it passes, close the defect automatically.
public class IssueTrackerUtil {

    private static final String ISSUE_TRACKER_API_BASE_URL = "https://api.github.com/repos/username/dummy-project/issues/";
    private static final String ISSUE_TRACKER_USERNAME = "username";
    private static final String ISSUE_TRACKER_PASSWORD = "password";
    
    public static IssueStatus getStatus(String issueID) {
        String githubIssueStatus = "CLOSED";
        try {
            githubIssueStatus = Unirest.get(ISSUE_TRACKER_API_BASE_URL.concat(issueID))
                                       .asJson()
                                       .getBody()
                                       .getObject()
                                       .getString("state")
                                       .toUpperCase();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return IssueStatus.valueOf(githubIssueStatus);
    }

    public static void updateStatus(String issueID, IssueStatus status) {
        try {
            
            Unirest.post(ISSUE_TRACKER_API_BASE_URL.concat(issueID).concat("/comments"))
                    .header("accept", ContentType.APPLICATION_JSON.toString())
                    .basicAuth(ISSUE_TRACKER_USERNAME, ISSUE_TRACKER_PASSWORD)
                    .body("{ \"body\" : \" testng method passed now. closing automatically.\" }")
                    .asJson();
            
            Unirest.patch(ISSUE_TRACKER_API_BASE_URL.concat(issueID))
                    .header("accept", ContentType.APPLICATION_JSON.toString())
                    .basicAuth(ISSUE_TRACKER_USERNAME, ISSUE_TRACKER_PASSWORD)
                    .body("{ \"state\" : \""+ status.toString().toLowerCase() + "\" }")
                    .asJson();
            
        } catch (UnirestException e) {
            e.printStackTrace();
        }
    }
    
}

MethodIssueStatusListener is updated to take action after method invocation as shown here.

public class MethodIssueStatusListener implements IInvokedMethodListener {

    @Override
    public void afterInvocation(IInvokedMethod invokedMethod, ITestResult result) {
        
        Issue issue = invokedMethod.getTestMethod()
                                    .getConstructorOrMethod()
                                    .getMethod()
                                    .getAnnotation(Issue.class);
            
        if (null != issue) {
            if(IssueStatus.OPEN.equals(IssueTrackerUtil.getStatus(issue.value()))){
                switch(result.getStatus()){
                    case ITestResult.FAILURE:
                            // no need to fail as we might have expected this already.
                            result.setStatus(ITestResult.SKIP);  
                            break;
                            
                    case ITestResult.SUCCESS:
                            // It is a good news. We should close this issue.
                            IssueTrackerUtil.updateStatus(issue.value(), IssueStatus.CLOSED);
                            break;
                }
            }
        }
    }

    @Override
    public void beforeInvocation(IInvokedMethod invokedMethod, ITestResult result) {
        // nothing to do
    }

}

That’s it!   When I execute my testNG tests, based on the tests status, it updates the issue status accordingly.

new-issue      new-issue-auto-close

 

Summary:

By using GitHub API, testNG method listener, we are able to improve our existing framework to skip tests based on the status of the issue or update issue status based on the test results etc.

It saves our time by skipping the known issues . You could modify IssueTrackerUtil based on issue tracking system you use.

 

Happy Testing & Subscribe 🙂

 

 

 

Share This:


Selenium WebDriver – How To Automatically Skip Tests Based On Test Environment

$
0
0

Overview:

This article is almost same as the previous article – How To Automatically Skip Tests Based On Defects Status – But a slightly different use case!! If you have not read the previous article, I would suggest you to read that first.

I run my automated tests in all the environments including Production. There are some test steps which I can not run on Production due to some restrictions. Basically we need to skip those steps which are not applicable for Production. I do not want to throw any testNG’s Skipped exception. I would follow a slightly different approach in this article. Lets see how we can get that implemented.

Sample testNG Test:

I create a sample test as shown here just for the demo.

public class UserRegistrationTest {

      @BeforeTest
      public void doDataBaseSetup(){
          if(!testEnvironment.equals("PRODUCTION")){
              DBUtil.doCleanUp();
          }   
      }
    
      @Test(dependsOnMethods="doDataBaseSetup")
      public void launchRegistrationPage() {
          RegistrationPage.launch();
          Assert.assertTrue(RegistrationPage.isAt());
      }

    @Test(dependsOnMethods="launchRegistrationPage")
      public void enterDemographicInformation() {
          RegistrationPage.enterDemographicInformation();
          RegistrationPage.submit();
          Assert.assertTrue(ProductsListPage.isAt());
      }
         
      @Test(dependsOnMethods="enterDemographicInformation")
      public void showCustomerData() {
          if(!testEnvironment.equals("PRODUCTION")){
              AdminPage.login();
              AdminPage.searchForCustomerData();
              Assert.assertTrue(AdminPage.isCustomerFound());
          }
      } 
}

Since I do have only limited access on Production, I can not run all the steps – which will make the test fail on the report. On top of that – I hate the if-else conditions in the test everywhere – which makes the code less readable.

We need a clean solution to ignore those methods which are not applicable for Production.

I have an enum which contains all the possible environments where I would be running my tests.

public enum TestEnvironment {
    
    DEV,
    QA,
    STAGING,
    PROD

}

I also have another class TestParameters which contains all the input parameters for the test suite. One of the methods can return the current environment on which the test is being executed as shown here.

public class TestParameters {
    
    // Not showing all the methods.
    
    public static TestEnvironment getEnvironment(){
        return testEnvironment;
    }
}

First, I am going to create a separate annotation as shown here.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface NotOnProduction {

}

Then, I need to create some kind of interceptor to completely ignore the execution of the steps which are not applicable on Production.

Below interceptor basically gets executed before testNG starts executing any test class. The intercept method filters all the method which are not applicable for Production only If the current test execution is happening on Production. If the test execution is on other environment, all the test steps should be executed.

public class TestEnvironmentListener implements IMethodInterceptor {
    
    Predicate<IMethodInstance> isTestExecutingOnProduction = (tip) -> {
        return TestParameters.getEnvironment()
                                  .equals(TestEnvironment.PROD);
    };

    Predicate<IMethodInstance> isNotApplicableForProduction = (method) -> {
        return method.getMethod()
                     .getConstructorOrMethod()
                     .getMethod()
                     .isAnnotationPresent(NotOnProduction.class);
    };
    
    @Override
    public List<IMethodInstance> intercept(List<IMethodInstance> testMethods, ITestContext context) {    
        
        //removeIf method returns a boolean if any of the item is removed
        boolean isTestRemoved = testMethods.removeIf(isTestExecutingOnProduction.and(isNotApplicableForProduction));
        
        //if any item is removed, then there is a chance that we might get execption due to missing dependencies.
        testMethods.stream()
                   .map(IMethodInstance::getMethod)
                   .forEach(method -> method.setIgnoreMissingDependencies(isTestRemoved));
        
        return testMethods;
    }

}

The rest is simple. We need to connect everything together. Add the listener in the testNG suite xml or in the base test.

@Listener(TestEnvironmentListener.class) // should be included in the base class
public class UserRegistrationTest {

    Test
    @NotOnProduction
    public void doDataBaseSetup(){
        DBUtil.doCleanUp();
    }

    @Test(dependsOnMethods="doDataBaseSetup")
    public void launchRegistrationPage() {
        RegistrationPage.launch();
        Assert.assertTrue(RegistrationPage.isAt());
    }

    @Test(dependsOnMethods="launchRegistrationPage")
    public void enterDemographicInformation() {
        RegistrationPage.enterDemographicInformation();
        RegistrationPage.submit();
        Assert.assertTrue(ProductsListPage.isAt());
    }
     
    @Test(dependsOnMethods="enterDemographicInformation")
    @NotOnProduction
    public void showCustomerData() {
        AdminPage.login();
        AdminPage.searchForCustomerData();
        Assert.assertTrue(AdminPage.isCustomerFound());
    } 
}

When we execute the test, if the test is executing on Production and if the annotation is found on the method, testNG will silently ignore those and executes only the remaining tests.

Test Execution on QA:

qa-test-1

 

Test Execution on PROD:

prod-test-1

NotOnProduction methods are simply disappeared when it is PROD.

Summary:

By creating a custom annotation and using testNG’s listener, We are able to skip methods without any issues and maintains the order the test execution.

Advantages:

  • No ugly if-else conditions everywhere.
  • No Skipped Exception.
  • No failed tests.
  • Readable tests.

 

Happy Testing & Subscribe 🙂

 

Share This:

Selenium WebDriver – How To Design Page Objects In Fluent Style

$
0
0

Overview:

As an automation engineer, you would have understood that creating an automated test for an application is not very difficult. Instead the difficult part is maintaining the existing tests!! That too, When you have thousands of automated tests. Oh Wait..on top of that, You have got Junior team members to maintain those thousands of tests!!

We do not work alone! We work with our fellow QA engineers with different skill sets! Some team members might find it very difficult to understand the code. The more time they spend to understand the code the less productive they would be as you could imagine! As a lead/architect of the automation framework, You need to come up with a proper design for the page objects/tests for easy maintenance and readability of the tests. So everyone in the team is very comfortable with the framework.

Lets see how we could design page objects for improving its readability.

Fluent Style:

Lets say,  You are given a task to calculate the sum of the first 2 numbers which are greater than 5, lesser than 20 and divisible by 3 from a list of integers!

The traditional for-loop approach:

List<Integer> list = Arrays.asList(1, 3, 6, 9, 11, 14, 16, 15, 21, 23);

int sum = 0;
int limit = 0;
for (Integer i : list) {
    if (i > 5 && i < 20 && (i % 3 == 0)) {
        limit++;
        sum = sum + (i * 2);
        if (limit == 2){
            break;
        }
    }
}
System.out.println(sum);

The Java8-Stream approach:

List<Integer> list = Arrays.asList(1, 3, 6, 9, 11, 14, 16, 15, 21, 23);

int sum = list.stream()
                .filter(i -> i > 5)
                .filter(i -> i < 20)
                .filter(i -> i % 3 == 0)
                .mapToInt(i -> i * 2)
                .limit(2)
                .sum();

System.out.println(sum);

As you could see, the number of lines could be more or less same in both approaches. But Java8 is definitely the winner when it comes to readability. When you use fluent style API and an IDE for development, the IDE itself will show all the possible methods you could invoke to proceed further on completing the task!

Sample Application:

I am going to consider the below mercury tours ticket booking workflow – check here.

mercury-fluent-style-001

Registration – Page Object:

public class RegistrationPage {

    private final WebDriver driver;

    @FindBy(name = "firstName")
    private WebElement firstName;

    @FindBy(name = "lastName")
    private WebElement lastName;

    @FindBy(name = "email")
    private WebElement userName;

    @FindBy(name = "password")
    private WebElement password;

    @FindBy(name = "confirmPassword")
    private WebElement confirmPassword;

    @FindBy(name = "register")
    private WebElement submit;

    private RegistrationPage(WebDriver driver) {
        this.driver = driver;
        PageFactory.initElements(driver, this);
    }

    public static RegistrationPage using(WebDriver driver) {
        return new RegistrationPage(driver);
    }

    public RegistrationPage launch() {
        driver.get("http://newtours.demoaut.com/mercuryregister.php");
        return this;
    }

    public RegistrationPage setFirstName(String firstName) {
        this.firstName.sendKeys(firstName);
        return this;
    }

    public RegistrationPage setLastName(String lastName) {
        this.lastName.sendKeys(lastName);
        return this;
    }

    public RegistrationPage setUserName(String userName) {
        this.userName.sendKeys(userName);
        return this;
    }

    public RegistrationPage setPassword(String password) {
        this.password.sendKeys(password);
        return this;
    }

    public RegistrationPage setConfirmPassword(String confirmPassword) {
        this.confirmPassword.sendKeys(confirmPassword);
        return this;
    }

    public void submit() {
        this.submit.click();
    }
}

Login – Page Object:

public class LoginPage {

    @FindBy(name = "email")
    private WebElement userName;

    @FindBy(name = "password")
    private WebElement password;

    @FindBy(name = "login")
    private WebElement loginBtn;

    private LoginPage(WebDriver driver) {
        PageFactory.initElements(driver, this);
    }

    public static LoginPage using(WebDriver driver) {
        return new LoginPage(driver);
    }

    public LoginPage setUsername(String username) {
        this.userName.sendKeys(username);
        return this;
    }

    public LoginPage setPassword(String password) {
        this.password.sendKeys(password);
        return this;
    }

    public void login() {
        this.loginBtn.click();
    }

}

Tests:

I could call the entry points ‘using’ and chain the methods to create a fluent style code in my tests to test my pages.

RegistrationPage.using(driver)
                .launch()
                .setFirstName("fn")
                .setLastName("ln")
                .setUserName("abcd")
                .setPassword("abcd")
                .setConfirmPassword("abcd")
                .submit();

LoginPage.using(driver)
                 .setUsername("abcd")
                 .setPassword("abcd")
                 .login();

The above code is much better and cleaner than the below traditional approach.

RegistrationPage registrationPage = new RegistrationPage(driver);
registrationPage.launch();
registrationPage.setFirstName("fn");
registrationPage.setLastName("ln");
registrationPage.setUserName("abcd");
registrationPage.setPassword("abcd");
registrationPage.setConfirmPassword("abcd");
LoginPage loginPage = registrationPage.submit();                 

loginPage.setUsername("abcd")
loginPage.setPassword("abcd")
loginPage.login();

 

Summary:

Fluent Style API improves the code readability and gives a very clear view of what the page does.

Each and every design/approach has its own pros and cons.  Even if this approach improves the code readability, it sometimes make it difficult to debug the code!  For ex: You want to print some debug statements between ‘setUsername‘ and ‘setPassword‘.   It is not possible to do it without breaking the chain!

LoginPage.using(driver)
            .setUsername("abcd")   //Can I print something here for debugging??
            .setPassword("abcd")
            .login();

However I would not encourage you to write debug statements everywhere in your tests. Instead the methods themselves should write enough info for you to debug later  in a separate log file.

Happy Testing & Subscribe 🙂

 

Share This:

Selenium WebDriver – How To Design Business Workflows In Fluent Style

$
0
0

Overview:

We already have seen the page objects design in the fluent style here. Properly designed page objects are good. But our design is not just yet complete with that. We might need a proper technique to connect these pages to create a business work flow.

Lets see how we could create a business workflow – by using Java8 consumer objects.

Sample Application:

Lets consider an application which has below pages to register a new user. The user has to navigate all these screens to complete the business process.

fluent-workflow-001

As an automation engineer, you would have to create page objects for the above pages and then call them in the sequence to create the workflow.

All these page objects will have some dependencies like WebDriver instances which you need to pass. You also need to ensure that Page is loaded fully before interacting with the page. Adding these repetitive conditions makes the workflow process is less readable. Lets see how we could handle that!

Page Objects:

Lets assume that we have below page objects.

Abstract Page:

public abstract class Page {
    public abstract boolean isAt();
}

User Details Page:

public class UserDetailsPage extends Page{

    public void firstname(){
        System.out.println("entering firstname");
    }

    public void lastname(){
        System.out.println("entering lastname");
    }
    
    public void address(){
        System.out.println("entering address");
    }
    
    @Override
    public boolean isAt() {
        return true;
    }

}

Product Search Page:

public class ProductSearchPage extends Page {

    public void search(){
       System.out.println("searching for product");
    }
    
    public void choose(){
        System.out.println("found a product");
    }
    
    @Override
    public boolean isAt() {
        return true;
    }

}

Order Summary Page:

public class OrderSummaryPage extends Page{
    
    public void enterCC(){
        System.out.println("entering credit card details");
    }
    
    public void enterBillingAddress(){
        System.out.println("entering billing address");
    }

    @Override
    public boolean isAt() {
        return true;
    }

}

Order Confirmation Page:

public class OrderConfirmationPage extends Page{
    
    public void verify(){
        System.out.println("verifying the order confirmation page");
    }

    public void print(){
        System.out.println("printing the order confirmation page");
    }
    
    @Override
    public boolean isAt() {
        return true;
    }

}

Creating A Workflow:

We have created all the page objects required for the workflow. As you have noticed, all the methods are void. That is, they do not return anything. So basically we are not going design the page objects in the fluent style. Instead we would create the workflow in the fluent style as shown here.

public class RegistrationWorkflow {
    
  //This is static to give an entry point
    public static RegistrationWorkflow userDetailsPage(Consumer<UserDetailsPage> c){
        UserDetailsPage p = new UserDetailsPage();
        c.accept(p);
        return new RegistrationWorkflow();
    }

    public RegistrationWorkflow productSearchPage(Consumer<ProductSearchPage> c){
        ProductSearchPage p = new ProductSearchPage();
        this.waitForPageLoad(p);
        c.accept(p);
        return this;
    }

    public RegistrationWorkflow orderSummaryPage(Consumer<OrderSummaryPage> c){
        OrderSummaryPage p = new OrderSummaryPage();
        this.waitForPageLoad(p);
        c.accept(p);
        return this;
    }

    public void orderConfirmationPage(Consumer<OrderConfirmationPage> c){
        OrderConfirmationPage p = new OrderConfirmationPage();
        this.waitForPageLoad(p);
        c.accept(p);
    }

    //handle page sync techniques here
    private void waitForPageLoad(Page p){
        System.out.println("------------------------------");
        System.out.println("Waiting for " + p.toString() + " to load" );
        // wait till p.isAt() returns true with a timeout
    }
}

Test:

Once the workflow is created, rest is simple.  You need to call them in the sequence. Above workflow class should be responsible for creating and managing the reporting and logging functionalities.

RegistrationWorkflow.userDetailsPage(u -> {
    u.firstname();
    u.lastname();
    u.address();
}).productSearchPage(p -> {
    p.search();
    p.choose();
}).orderSummaryPage(o -> {
    o.enterCC();
    o.enterBillingAddress();
}).orderConfirmationPage(o -> {
    o.verify();
    o.print();
});

Test Output:

entering firstname
entering lastname
entering address
------------------------------
Waiting for ProductSearchPage to load
searching for product
found a product
------------------------------
Waiting for OrderSummaryPage to load
entering credit card details
entering billing address
------------------------------
Waiting for OrderConfirmationPage to load
verifying the order confirmation page
printing the order confirmation page

Summary:

Workflow classes would be helpful when you need to manage multiple page objects, WebDriver instance, Reporting, Logging etc and pass them to the subsequent page objects.  Workflow accepts a consumer object which gives more flexibility to the user. For ex:  You need to add some debug statement. You can add that without breaking the chain as shown here which was not easy for the Fluent style page objects!

RegistrationWorkflow.userDetailsPage(u -> {
    u.firstname();
    u.lastname();
    u.address();
}).productSearchPage(p -> {
    p.search();
    System.out.println("debugging"); //debug
    p.choose();
}).orderSummaryPage(o -> {
    o.enterCC();
    o.enterBillingAddress();
}).orderConfirmationPage(o -> {
    o.verify();
    o.print();
});

 

Happy Testing & Subscribe 🙂

 

 

Share This:

Selenium WebDriver – How To Design Tests & Test Data

$
0
0

Overview:

If you are an experienced automation engineer, you would know better that maintaining an automated test framework/project is NOT an easy task! You could easily create a page object and a test which works just fine / get things done. But what happens once you have thousands of tests? How long does it take to look into the failures as part of your continuous regression? If you touch something in the code, does something else break? Do you have to have a dedicated person to look into the test suite failures? Then the problem could be with your test framework!

Proper design techniques/principles /best practices should be followed right from beginning – not only for the Page Objects design, but also for your tests and test data.

We already have seen many page objects related designs in our TestAutomationGuru articles.  You can check here for the list of articles. Lets see how we should model / design our testNG test in this article.

Sample Workflow:

In order to explain things better, I am considering below workflow which I have used in the previous article.

fluent-workflow-001

I am assuming that we have created page objects for the above pages as I had mentioned here.  I also assume that all the page objects extend the below abstract page.

public abstract class Page {
    public abstract boolean isAt();
}

The idea here is all the page objects should have a ‘isAt‘ method which returns a boolean to confirm if they are in the appropriate page as shown below. [Inspired from Jon Sonmez‘s idea]

@Override
public boolean isAt(){
    return this.username.isDisplayed();
}

They all also have a static entry point ‘init‘ to create an instance and chain other methods of the page object to improve the readability.

private UserDetailsPage(WebDriver driver) {
    this.driver = driver;
    PageFactory.initElements(driver, this);
}

public static UserDetailsPage init(WebDriver driver) {
    return new UserDetailsPage(driver);
}

Test Steps:

I need to have below simple steps to ensure that user registration functionality of the application works fine.

  • launch
  • enter user details
  • search for a product with specific criteria & select
  • enter payment info & submit
  • verify the order confirmation to complete the registration process

As part of the registration flow, I need to cover if the application accepts different input parameters.

For ex:

  • Ordering product with just CC.
  • Ordering product  with CC + Promocode  etc

In order to design our tests better, We need to ensure that we follow some of the best practices.

Each Test Step/Checkpoint should be a @Test method:

Do not place all the driver invocation, page object creation, test data in a single test method. Instead try to create separate test methods for each step you would like to perform / validate.

For ex: For the above test steps, Lets create a test template as shown here. We need to call the methods in a sequence. So I use dependsOnMethods.

public class UserRegistrationTest {
    
    @BeforeTest
    public void setUpDriver(){
        // setup driver
    }
    
    @Test
    public void launch() {
        // launch site
    }

    @Test(dependsOnMethods = "launch")
    public void enterUserInfoAndSubmit() {
        // enter user details
    }

    @Test(dependsOnMethods = "enterUserInfoAndSubmit")
    public void searchProductAndSubmit() {  
        // search for a product with specific criteria & select
    }

    @Test(dependsOnMethods = "searchProductAndSubmit")
    public void enterPaymentInfoAndSubmit() {       
        // enter payment info & submit
    }

    @Test(dependsOnMethods = "enterPaymentInfoAndSubmit")
    public void validateOrderConfirmation() {
        // verify the order confirmation
    }
    
    @AfterTest
    public void tearDownDriver(){
        // quit driver
    }
}

Instance Variables:

We might need to reuse some of the objects like page objects, test data, driver instances in other methods in the Test class. So lets create them as instance variable.

private WebDriver driver;
private UserDetailsPage userDetailsPage;
private ProductSearchPage productSearchPage;
private OrderSummaryPage orderSummaryPage;
private OrderConfirmationPage orderConfirmationPage;

Driver Manager:

Do not handle all the driver management in the test class / base test class. Always follow Single Responsibility Principle. Create a separate DriverManager factory class to get you the specific WebDriver instance you want as shown here.

@BeforeTest
public void setUpDriver(){
    driver = DriverManager.getChromeDriver();
}

If we would like to get the browser through testNG suite file as shown here,

<test name="user registration test - USA user">
   <parameter name="browser" value="chrome" />
   <classes>
      <class name="com.testautomationguru.UserRegistrationTest" />
   </classes>
</test>

then, we could modify the setup method as shown here.

@BeforeTest
@Parameters({ "browser" })
public void setUpDriver(String browser){
    driver = DriverManager.getDriver(browser);
}

Assertion:

Our workflow starts with UserDetailsPage. Using ‘init‘ method, I create the new instance method. Always validate if the expected page has loaded before interacting with the page. If the page itself is not loaded, then there is no point in proceeding with the test. So when the assertion fails, all the dependent methods are skipped.

@Test
public void launch() {
    userDetailsPage = UserDetailsPage.init(driver)
                                     .launch();
    //validate if the page is loaded
    Assert.assertTrue(userDetailsPage.isAt());  
}

We could overload the isAt method with some timeout if the test should have some control over it.

Assert.assertTrue(userDetailsPage.isAt(10, TimeUnit.SECONDS));

We do not need to add this for each and every page. Instead modifying abstract Page with the below method will take care!  Try to use ‘Awaitility‘ to handle page synchronization. It is much better and easier than WebDriverWait.

public abstract class Page {
    public abstract boolean isAt();
    
    public boolean isAt(long timeout, TimeUnit timeunt){
        try{
            await().atMost(timeout, timeunit)
                   .ignoreExceptions()
                   .until(() -> isAt());
            return true;        
        }catch(Exception e){
            return false;
        }
    }
}

Never hard-code Data:

Do not hard-code the test data in your test.  You might want to reuse this test for multiple input parameters.

@Test(dependsOnMethods = "launch")
public void enterUserInfoAndSubmit() {
    userDetailsPage.setFirstname(".....")   // test-data to be passed
                   .setLastname(".....")    // test-data to be passed
                   .setDOB(".....")         // test-data to be passed
                   .setEMail(".....")       // test-data to be passed
                   .setAddress(".....")     // test-data to be passed
                   .submit();   
    //validate if the page is loaded
    Assert.assertTrue(ProductSearchPage.init(driver).isAt());
}

Never Pass ‘too many parameters’:

Hard-coding is not good. Passing all the values as String from the suite file might sound OK.

<test name="user registration test - USA user">
   <parameter name="browser" value="chrome" />
   <parameter name="firstname" value="fn" />
   <parameter name="lasttname" value="ln" />
   <parameter name="dob" value="01/01/2000" />
   <parameter name="email" value="noreply@gmail.com" />
   <parameter name="address" value="123 non main st" />
   <parameter name="CC" value="1234-1234-1234-1234" />
   <parameter name="billingaAddress" value="123 non main st" />
   <classes>
      <class name="com.testautomationguru.UserRegistrationTest" />
   </classes>
</test>

But it makes the test & suite file very difficult to read / manage – with too many parameters!

@Test(dependsOnMethods = "launch")
@Parameters("firstname", "lastname", "dob"........)
public void enterUserInfoAndSubmit(String firstnam, String lastname, String dob......) {
    userDetailsPage.setFirstname(firstname)
                   .setLastname(lastname)  
                   .setDOB(dob) 
                   .setEMail(".....")
                   .setAddress(".....")
                   .submit();   
    //validate if the page is loaded
    Assert.assertTrue(ProductSearchPage.init(driver).isAt());
}

Test Data Management:

Instead of keeping everything in a suite file, Lets keep our test data in a separate json file as shown here or any other format you prefer.

{
   "firstname":"testautomation",
   "lastname":"guru",
   "dob":"01/01/2000",
   "email":"noreply@gmail.com",
   "address":"123 non main st",
   "cc":{
      "number":"4444-4444-4444-4444",
      "billing":"123 non main st"
   },
   "product":{
      "searchCriteria":"criteria"
   }
}

To de-serialize above json file into a Java object, Lets model a class by using jaskson library. [I have intentionally ignored getters in the below class to reduce the length of the article]

public class Data {

    @JsonProperty("firstname")
    String firstname;

    @JsonProperty("lastname")
    String lastname;

    @JsonProperty("dob")
    String DOB;

    @JsonProperty("email")
    String email;

    @JsonProperty("address")
    String address;

    @JsonProperty("cc")
    CC cc;

    @JsonProperty("product")
    Product product;

    static class CC {
        @JsonProperty("number")
        String number;

        @JsonProperty("billing")
        String billingAddress;
    }

    static class Product {
        @JsonProperty("searchCriteria")
        String criteria;
    }
    
    public static Data get(String filename) throws JsonParseException, JsonMappingException, IOException{
        ObjectMapper mapper = new ObjectMapper();
        return mapper.readValue(new File(filename), Data.class);
    }
  
}

Then, lets go back to our setup method to modify slightly to create the data instance.

private WebDriver driver;
private Data data;

private UserDetailsPage userDetailsPage;
private ProductSearchPage productSearchPage;
private OrderSummaryPage orderSummaryPage;
private OrderConfirmationPage orderConfirmationPage;

@BeforeTest
@Parameters({"browser", "user-registration-data-file"})
public void setUpDriver(String browser, String userDataFile){
    driver = DriverManager.getDriver(browser);
    data = Data.get(userDataFile);
}

Now we could pass the test multiple data file through suite as shown here. This looks cleaner than the previous approach with tons of String parameters.

<suite name="TestAutomationGuruSuite">
   <test name="user registration test - USA user">
      <parameter name="browser" value="chrome" />
      <parameter name="user-registration-data-file" value="/src/test/resources/usa-user.json" />
      <classes>
         <class name="com.testautomationguru.UserRegistrationTest" />
      </classes>
   </test>
   <test name="user registration test - India user">
      <parameter name="browser" value="chrome" />
      <parameter name="user-registration-data-file" value="/src/test/resources/india-user.json" />
      <classes>
         <class name="com.testautomationguru.UserRegistrationTest" />
      </classes>
   </test>
</suite>

Now, our ‘data‘ object has all the data we need to proceed with our test.  Just use the getters wherever you need the test data.

@Test(dependsOnMethods = "launch")
public void enterUserInfoAndSubmit() {
    userDetailsPage.setFirstname(data.getFirstname())
                   .setLastname(data.getLastname())  
                   .setDOB(data.getDOB())   
                   .setEMail(data.getEMailAddress())
                   .setAddress(data.getAddress())
                   .submit();   
    //validate if the page is loaded
    Assert.assertTrue(ProductSearchPage.init(driver).isAt());
}

 

Summary:

Following design principles / best practices initially could be little bit annoying or time consuming. This is because, We all want to quickly get the job done. We do not spend a lot of time in reviewing the tests/test data design. We do not worry about 1000 more tests we might be automating in future. To have successful automated test framework, invest sometime in following all the design principles, best practices etc even for the test scripts design/development as well.

 

Happy testing & Subscribe 🙂

 

Share This:

Selenium WebDriver – How To Design Hybrid Framework in TestNG

$
0
0

Overview:

As an automation test engineer, you might have noticed that some of our test steps get repeated very often in multiple tests.  In such cases, designing the tests in a such a way that it could be reused in multiple workflow is very important. Re-usability allows us to be more efficient & to write better and clean code.

Lets see how we could design reusable tests.

Sample Workflow:

Lets consider a sample application which has below workflows.

  • An user can register himself in the application by ordering a product.
  • An existing registered user can also order a new product.
  • Once a product is ordered, it can be viewed.
  • User can logout.

testng-hybrid-001a

Based on the above requirements, we can come up with different workflows as shown in above picture.

  1. A new user enters details -> orders a product -> logout
  2. A new user enters details -> orders a product -> views product -> logout
  3. An existing user  -> orders a product -> views product -> logout
  4. An existing user  -> orders a product -> logout
  5. An existing user  -> views product -> logout

We might be interested in testing all these above workflows. The same workflow should also be tested for different input parameters. For ex: Different types of products, different payment methods like Credit card, promocodes etc. So writing reusuable tests is very important to maintain these tests in future.

Sample Tests:

This article just explains on writing testNG tests. So I assume that we already have Page objects for each page in the above workflow.

  • We would like to launch a browser once for the workflow – all other test should reuse the browser instance. So, Lets create a base class which does this.  We use TestNG ITestContext object to store the driver instance. so that any other tests can use it later.
public class BaseTest {

    private WebDriver driver;

    @BeforeTest
    @Parameters({ "browser" })
    public void setUpDriver(String browser){
        driver = DriverManager.getDriver(browser);
    }
    
    @BeforeClass
    public void storeDriver(ITestContext ctx){
        ctx.setAttribute("driver", driver);
    }

}
  • User Details Page Test should extend the BaseTest – as the workflow can start from the user Registration Page. It retrieves the driver instance from the ITestContext as shown below.
public class UserDetailsTest extends BaseTest{

    private WebDriver driver;
    private UserDetailsPage userDetailsPage;

    @BeforeClass
    public void getCtx(ITestContext ctx){
        driver = ctx.getAttribute("driver");
    }
    
    @Test
    public void launch() {
        userDetailsPage = UserDetailsPage.init(driver)
                                         .launch();
        //validate if the page is loaded
        Assert.assertTrue(userDetailsPage.isAt());
    }

    @Test
    public void enterDetails() {
        userDetailsPage.enterDetails();
        Assert.assertTrue(userDetailsPage.isSubmitButtonEnabled());
    }

}
  • User Login Test – should extend the BaseTest – as the workflow can start from the user Login Page.
public class UserLoginTest extends BaseTest{
    
    private WebDriver driver;
    private UserLoginPage userLoginPage;

    @BeforeClass
    public void getCtx(ITestContext ctx){
        driver = ctx.getAttribute("driver");
    }
    
    @Test
    public void launch() {
        userLoginPage = UserLoginPage.init(driver)
                                         .launch();
        //validate if the page is loaded
        Assert.assertTrue(userLoginPage.isAt());
    }

    @Test
    public void login() {
        userLoginPage.enterCredentials();
        Assert.assertTrue(userLoginPage.isAuthenticated());
    }

}
  • OrderProductTest should NOT extend BaseTest  as the user can not start the session from this page. The user will land on this page either after login or user registration page. So, only those tests should extend BaseTest. Once the user lands on ‘Product Search Page’, the user has to navigate till the ‘Order Confirmation’ page as per above our workflow diagram.  So, it can be grouped together.
public class OrderProductTest{
    
    private WebDriver driver;
    private ProductSearchPage productSearchPage;
    private OrderSummaryPage orderSummaryPage;
    private OrderConfirmationPage orderConfirmationPage;

    @BeforeClass
    public void getCtx(ITestContext ctx){
        driver = ctx.getAttribute("driver");
    }
    
    @Test
    public void productSearchTest() {
        productSearchPage = ProductSearchPage.init(driver);
        Assert.assertTrue(productSearchPage.isAt());
        productSearchPage.selectProduct(Products.A);
        productSearchPage.goToOrderSummaryPage();
    }
    
    @Test
    public void orderSummaryTest() {
        orderSummaryPage = OrderSummaryPage.init(driver);
        Assert.assertTrue(orderSummaryPage.isAt());
        orderSummaryPage.enterPaymentDetails();
    }

    @Test
    public void orderConfirmationTest() {
        orderConfirmationPage = OrderConfirmationPage.init(driver);
        Assert.assertTrue(orderConfirmationPage.isAt());
    }

}
  • Test for product view.
public class ViewProductTest{
    
    private ProductViewPage productViewPage;

    @BeforeClass
    public void getCtx(ITestContext ctx){
        WebDriver driver = ctx.getAttribute("driver");
        productViewPage = ProductViewPage.init(driver);
    }
    
    @Test
    public void validate() {
        Assert.assertTrue(productViewPage.isAt());
    }

}

At this point, Your test package might look like this.

testng-hybrid-002

TestNG Suite:

Rest is simple. We need to call the above testNG tests in a proper sequence to create the business workflow as shown below. Now we could create N number of workflows by changing the order. In future, if we need to include any validation point, just updating in one reusable test will do.

<suite name="TestNG HyBrid Tests" parallel="none" allow-return-values="true">
    
    <parameter name="browser" value="chrome"></parameter>
    <!-- 
        by calling the TestNG reusable tests in a sequence we create different work flows.
     -->
    <test name="Check for successful user registration by order product A">
        <classes>
            <class name="com.test.UserDetailsTest" />
            <class name="com.test.OrderProductTest" />
        </classes>
    </test>
    <test name="Check for successful user registration by order product B and view Product B">
        <classes>
            <class name="com.test.UserDetailsTest" />
            <class name="com.test.OrderProductTest" />
            <class name="com.test.ViewProductTest" />
        </classes>
    </test>   
    <test name="Check if existing user can view his product">
        <classes>
            <class name="com.test.UserLoginTest" />
            <class name="com.test.ViewProductTest" />
        </classes>
    </test>       
    
</suite>

Summary:

In Continous Regression testing, the effort spent on maintenance should be as low as possible. So that team can focus on enhancing the test suite with more automated tests. But most of the time, we see a lot of red flags /  failed tests. Mostly it happens due to poorly designed flaky tests. Proper design techniques should be used for Page objects, Tests & Test Data design. Like the Page components, your tests should be also reusable if possible to reduce the maintenance effort and control flaky tests.

Happy Testing & Subscribe 🙂

 

Share This:

Viewing all 69 articles
Browse latest View live