How difficult is to rewrite a Java Selenium test in C#?

not so bad

I recently wrote an automated test that

  • opens a site
  • searches for a keyword
  • selects a result on results page
  • enables an alert on details page
  • gets api calls executed by details page when enabling the alert
  • gets data from the response of the api calls
BrowserMob is ideal for getting the api calls but i could not get it to work in C# with https requests.

So i wrote the code in Java which worked very well.

The development team insisted though that the automation code is in C# since this is the development language.

So, after figuring out a way of not using BrowserMob (I did not need to get those API calls because the information I was after was also in a cookie), I started to convert the Java code to C#.

How difficult can it be to convert Java code to C#?

It turned out that it was very easy.

 

These were the major differences for my test so it runs in Visual Studio, C# instead of Eclipse and Java:

 

1. Selenium interfaces are named differently (WebDriver is IWebDriver in C#, WebElement is IWebElement)

 

2. There is no CamelCase notation

 

3. String formatting is slightly different and uses {} instead of %

 

4. Writing text to files is a bit different

 

5. const is used instead of final static for constants

 

6. the collection returned by driver.FindElements() is not a list but a readonly collection

 

7. the equivalent of IllegalArgumentException is ArgumentException

 

8. all Selenium methods are minimally different

 

9. there are no expected conditions but the alternative is better and more flexible

 

10. Collection methods have different names

 

11. String in Java is string in C#

 

12. packages in Java are namespaces in C#

 

13. nUnit is similar to jUnit with minor differences

C# and Java are so similar

That it makes me believe that coding in C# after coding in Java is like driving a Honda after driving a Ford.

There are differences, some important but most minimal.

Knowing one well should make learning the other one very easy.

And having 2 languages in your toolbox just makes you more employable.

How to simulate network conditions

It is useful to be able to run Selenium tests on different types of networks, having different download and upload throughput.

This was not possible until recently when the needed support was added to the ChromeDriver class.

See below the code that opens the Google home page on multiple download and upload speeds:

import org.testng.annotations.Test;
import com.google.common.collect.ImmutableMap;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.remote.Command;
import org.openqa.selenium.remote.CommandExecutor;
import org.openqa.selenium.remote.Response;
import org.testng.annotations.DataProvider;
public class TestClass {
   //data provider for the connection's download and upload throughput    
   @DataProvider(name = "test1")
    public Object[][] networkConditions() {
        return new Object[][] {
            { 5000 , 5000 },
            { 10000, 7000 },
            { 15000, 9000 },
            { 20000, 10000 },
            { 23000, 11000 },
            { 30000, 15000 },
            { 40000, 20000 },
            { 50000, 20000 },
            { 75000, 20000 },
            { 100000, 20000 },
            { 0, 0 },
        };
    }
    
    @Test(dataProvider = "networkConditions")
    public void test(int downloadThroughput, int uploadThroughput)
throws IOException {
        System.setProperty("webdriver.chrome.driver",
"c:/selenium/chromedriver.exe");
        ChromeDriver driver = new ChromeDriver();
        
        if (downloadThroughput > 0 && uploadThroughput > 0) {
            CommandExecutor executor = driver.getCommandExecutor();
                   
            Map map = new HashMap();
            map.put("offline", false);
            map.put("latency", 5);
                
            map.put("download_throughput", downloadThroughput);
            map.put("upload_throughput", uploadThroughput);
            Response response = executor.execute(
new Command(driver.getSessionId(), 
"setNetworkConditions", 
 ImmutableMap.of("network_conditions", ImmutableMap.copyOf(map))));
        }
            
        driver.get("http://google.com");
        
        driver.quit();
    }
    
}

 

The code should be easy to follow.

 

A hashmap is created with the network conditions to be used for the browser.

 

The driver gets a command executor and uses it to execute a command on the current session to set the network conditions based on the hash map.

 

These are the results of executing the code:

 

download: 5000   , upload: 5000  , finished in 87221 ms
download: 10000  , upload: 7000  , finished in 46126 ms
download: 15000  , upload: 9000  , finished in 32165 ms
download: 20000  , upload: 10000 , finished in 25703 ms
download: 23000  , upload: 11000 , finished in 22966 ms
download: 30000  , upload: 15000 , finished in 19065 ms
download: 40000  , upload: 20000 , finished in 15008 ms
download: 50000  , upload: 20000 , finished in 13116 ms
download: 75000  , upload: 20000 , finished in 11031 ms
download: 100000 , upload: 20000 , finished in 9463 ms
download: 0      , upload: 0     , finished in 5874 ms

 

If the download and upload are 0, the code is executed with no network conditions specified.

 

In all other cases, a download and upload throughput is being used.

How to make better decisions when buying Selenium courses

where should i go

A few days ago, a manual tester reached out on LinkedIn asking for help on making a change from manual testing to test automation.

He wanted to know what good materials to buy so that he can get started on this transition.

I recommended 2 paid resources of Alan Richardson, none of them cheap, but worth the money:

Java For Tests (book)

Selenium WebDriver Basics With Java (online course)

None of them are free. The book is about $15 and the online course $225.

How do I know that they are good?

I used both of them a while ago and was satisfied with what I learned.

I try not to make recommendations to others for books, courses, blogs, unless I reviewed these materials by myself.

The materials are not free and especially the online course expensive. $225 is a lot of money compared to most (or all) Udemy courses, for example.

How can anyone be sure that they are worth buying?

Both the book and the online course allow you to preview them for free.

About 70 pages of Java For Testers can be read for free. A lot of lessons of the online course can also be previewed.

So anyone interested in either the book or the course should preview the free stuff to get a feeling of the author’s learning style, how clear his English is, how useful is the content, how good is the organization of the material, how fast or slow is the pace.

All these contribute to a good experience of reading the book or going through the course.

Let’s say that you like what you see.

The next question is …

Who is Alan Richardson?

He has a personal blog where you can read everything he blogged on in the last few years.

He also has a business blog where more information about him is available:

4 published books

4 published online courses

Frequent speaker at conferences

From all these, he seems to be very engaged in the test automation field and actually pretty good at it.

It is difficult not to start trusting him after going through all these materials.

So, his materials are good quality, he knows what he writes about, he seems to be an important name in test automation (and testing).

But why are these materials so expensive?

The price can be an issue. Or maybe not.

$225 is expensive compared to $10 on Udemy, $5 on Amazon, free on Youtube.

But for $225, you get content that you verified as valuable, from an author who is credible, knowledgeable and skilled, content that will give you everything you need, content that will keep you engaged and satisfied with what you learn.

You will not need to check other courses or books.

You will not get demoralized because the content is poor or you don’t understand what the author says.

You will not waste any time or money.

You will start on the learning track and stay on it until you get where you wanted and that is to writing automation code.

$225 is the price for not wasting time, not checking online courses randomly, being motivated, knowing that you go the proper direction.

It is difficult to get wrong with these 2 materials for all the reasons already mentioned.

So, before you buy more Selenium materials, try doing a bit of homework about

  • reading the author’s blog
  • going through his free online materials, webinars
  • reading about the author’s activity
  • going through any previews of the paid material that you plan on buying

If after this investigation, you are still interested, do not focus so much on the price but on what you get for it.

As soon as you have your new skills, you will make $225 back in a week.

By the way,

I chose Alan Richardson as an example.

I could have written the same things about other great authors from the Selenium space such as Dave Haeffner, John Sonmez and others.

In summary, learning more about what you want to buy and who made it helps a lot with getting a product worth of your time and money.

 

 

 

 

 

Use Test Factories for Dynamic Tests

Most of the time, we need normal unit tests for test automation:

@Test
public void testSearch() {
}

A normal unit test is a public method that does not return a value, it does not have parameters and uses the @Test annotation.

The unit test can have a parameter though if it is used with a data provider:

@DataProvider(name = "searchKeywords")
public Object[][] searchKeywords() {
return new Object[][] {
{ "iphone"}, { "ipad"}, { "iwatch"}  };
}

@Test(dataProvider = "searchKeywords")
public void testSearch(String keyword) {
}

What is a dynamic test then?

A dynamic test is a unit test of a test class that has a constructor.

The constructor may have a parameter or more and saves them as class members:

import org.testng.annotations.Test;

public class SampleTest {

private int n;

public SampleTest(int n) {
  this.n = n;
}

@Test
public void testScript() {
  System.out.println("test script: " + n);
}

}

The test class gets an integer parameter in the constructor and saves it in a member of the class.

The testScript() method displays the value of the n class member.

testScript() is a dynamic unit test because it belongs to a test class that uses a constructor with a parameter (or more).

How do you use such a unit test?

A test factory creates objects of the test class.

These objects are then executed in the background:

import org.testng.annotations.Factory;

public class FactoryTest {

  @Factory
  public Object[] createTests() {
    System.out.println("create the tests dynamically!");
    Object[] tests = new Object[10];

    for (int i = 0; i < 10; i++)
      tests[i] = new SampleTest(i);

    return tests;

}

}

The createTests method creates first an array to store of test objects of the SampleTest class.

It then creates multiple SampleTest objects and saves them in the array.

Finally, it returns the array of objects.

This is the result of executing the FactoryTest class:

[RemoteTestNG] detected TestNG version 6.14.2
create the tests dynamically!
test script: 0
test script: 1
test script: 5
test script: 3
test script: 9
test script: 2
test script: 7
test script: 6
test script: 8
test script: 4
PASSED: testScript
PASSED: testScript
PASSED: testScript
PASSED: testScript
PASSED: testScript
PASSED: testScript
PASSED: testScript
PASSED: testScript
PASSED: testScript
PASSED: testScript

===============================================
Default test
Tests run: 10, Failures: 0, Skips: 0
===============================================

We see in these results 10 tests being executed and passing.

The interesting part is that they were not executed in the order they were created.

When would dynamic tests be needed in Selenium test automation?

Let’s assume that we want to automate a test case for the https://www.thebay.com/ site.

If you mouse over the accessories option of the menu, multiple categories are displayed.

bay

We want to verify that when clicking on any of these categories, results are displayed.

We could get started with a test as follows:

import static org.testng.Assert.assertTrue;

import java.util.List;

import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

public class CategoriesTest {

@DataProvider(name = "accessoriesCategories")
public Object[][] createData1() {
  return new Object[][] {
    { "New Arrivals"},
    { "Best Sellers"},
    { "Online Only"},
    { "Watches"}
  };
}

@Test(dataProvider = "accessoriesCategories")
public void testCategoriesUsingDataProvider(String category) {

  HomePage homePage = new HomePage();
  homePage.open();

  Menu menu = homePage.openAccessoriesMenu();

  ResultsPage resultsPage = menu.selectCategory(category);
  assertTrue(resultsPage.hasResults(), "no results for " + category);

}

}

The categories are defined in a data provider and the test has a parameter for a category.

The test will be executed once for each category.

The test uses 3 classes.

HomePage.java

public class HomePage {

public void open() {
  System.out.println("open home page");
}

public Menu openAccessoriesMenu() {
  System.out.println("open accessories menu");
  return new Menu();
}

}

Menu.java

import java.util.Arrays;
import java.util.List;

public class Menu {

public ResultsPage selectCategory(String category) {
  System.out.println("category selected: " + category);
  return new ResultsPage();
}

public List getCategories() {
  System.out.println("get categories");

  return Arrays.asList( "New Arrivals",
                        "Best Sellers",
                        "Online Only",
                        "Watches");
  }

}

ResultsPage.java

public class ResultsPage {

public boolean hasResults() {
  return true;
}

public HomePage goBack() {
  System.out.println("go back to home page");
  return new HomePage();
}
}

The result of executing the test is below:

[RemoteTestNG] detected TestNG version 6.14.2
open home page
open accessories menu
category selected: New Arrivals
open home page
open accessories menu
category selected: Best Sellers
open home page
open accessories menu
category selected: Online Only
open home page
open accessories menu
category selected: Watches
PASSED: testCategoriesUsingDataProvider("New Arrivals")
PASSED: testCategoriesUsingDataProvider("Best Sellers")
PASSED: testCategoriesUsingDataProvider("Online Only")
PASSED: testCategoriesUsingDataProvider("Watches")

===============================================
Default test
Tests run: 4, Failures: 0, Skips: 0
===============================================

The solution works if the categories for the Accessories option do not change.

But what if they change all the time?

Obviously, we will not be able to use the data provider any longer so the test will change:

@Test
public void testCategoriesWithoutDataProvider() {
  HomePage homePage = new HomePage();
  homePage.open();

  Menu menu = homePage.openAccessoriesMenu();

  List < String > categories = menu.getCategories();

  for (String category: categories) {
    ResultsPage resultsPage = menu.selectCategory(category);
    assertTrue(resultsPage.hasResults(), "no results for " + category);

    homePage = resultsPage.goBack();
    menu = homePage.openAccessoriesMenu();
  }

}

The test is more complicated than before.

It gets all categories of the menu and then iterates through them. For each category, the test selects the category, verifies that there are results and then goes back to HomePage.

The result of executing the test is below:

[RemoteTestNG] detected TestNG version 6.14.2
open home page
open accessories menu
get categories
category selected: New Arrivals
go back to home page
open accessories menu
category selected: Best Sellers
go back to home page
open accessories menu
category selected: Online Only
go back to home page
open accessories menu
category selected: Watches
go back to home page
open accessories menu
PASSED: testCategoriesWithoutDataProvider

===============================================
Default test
Tests run: 1, Failures: 0, Skips: 0
===============================================

The test does pretty much the same thing as before.

But instead of having one test for each category, we have 1 single test for all categories.

If the test fails, it will not be clear which category has a problem.

So this is what we want to fix.

We will simplify the test class so it does only the selection of the category:

import static org.testng.Assert.assertTrue;

import java.util.List;

import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

public class SelectCategory {

private String category;

public SelectCategory(String category) {
this.category = category;
}

@Test
public void selectCategory() {
  HomePage homePage = new HomePage();
  homePage.open();

  Menu menu = homePage.openAccessoriesMenu();

  ResultsPage resultsPage = menu.selectCategory(category);
  assertTrue(resultsPage.hasResults(), "no results for " + category);

}
}

And we will use a test factory class that gets all categories and creates multiple objects for the SelectCategory class:

import java.util.List;

import org.testng.annotations.Factory;

public class SelectCategoryFactory {

@Factory
public Object[] testCategories() {
  System.out.println("start test factory");

  HomePage homePage = new HomePage();
  homePage.open();

  Menu menu = homePage.openAccessoriesMenu();

  List < String > categories = menu.getCategories();

  closeBrowser();

  Object[] tests = new Object[categories.size()];

  for (int i = 0; i < categories.size(); i++)
    tests[i] = new SelectCategory(categories.get(i));

  System.out.println("end of test factory");

  return tests;
}

private void closeBrowser() {
  System.out.println("close site");
}

}

The result of executing the test factory shows again 4 tests passing:

[RemoteTestNG] detected TestNG version 6.14.2
start test factory
open home page
open accessories menu
get categories
close site
end of test factory
open home page
open accessories menu
category selected: Watches
open home page
open accessories menu
category selected: Best Sellers
open home page
open accessories menu
category selected: New Arrivals
open home page
open accessories menu
category selected: Online Only
PASSED: selectCategory
PASSED: selectCategory
PASSED: selectCategory
PASSED: selectCategory

===============================================
Default test
Tests run: 4, Failures: 0, Skips: 0
===============================================

The test factory allows the initial test to be changed so that it works for dynamic categories. It also allows executing a test for each category.

Program to an interface with Selenium WebDriver (eBook)

pexels-photo-755834.jpeg

Be Flexible With Java Interfaces – Pexels.com

 

Anyone can write code that a computer can understand.
Good programmers write code that humans can understand.

Martin Fowler

 

Testers and developers use the Selenium WebDriver library for implementing test automation for various websites.

They use Selenium WebDriver for its major purpose: to interact with a site in the browser.

 

But I believe that there is something else that we can use this library for.

We can use it to learn how to write good code.

Code that is easy to maintain.

Easy to extend, easy to use and understand.

 

What can you learn from the Selenium WebDriver code?

 

You can learn how to apply programming concepts such as factories.

Or how to extend code instead of modifying it with decorators.

Or how program to an interface.

 

Programming to an interface is the focus here.

 

What does it mean?

 

It means two things:

1. each class that you create should implement an interface

2. every time an object of the class is created, its type should be the interface that the class implements

 

This sounds pretty abstract so lets see if an example makes things simpler.

 

This is the example provided for interfaces in the java documentation.

In its most common form, an interface is a group of related methods with empty bodies.

A bicycle’s behavior, if specified as an interface, might appear as follows:

 

interface Bicycle {

  void changeCadence(int newValue);

  void changeGear(int newValue);

  void speedUp(int increment);

  void applyBrakes(int decrement);
}

 

To implement this interface, the name of the class would change to a particular brand of bicycle and you’d use the implements keyword in the class declaration.

The class would also have to provide an implementation for each method defined by the interface.
class TrekBicycle implements Bicycle {

  int cadence = 0;
  int speed = 0;
  int gear = 1;

  void changeCadence(int newValue) {
    cadence = newValue;
  }

  void changeGear(int newValue) {
    gear = newValue;
  }

  void speedUp(int increment) {
    speed = speed + increment;
  }

  void applyBrakes(int decrement) {
    speed = speed - decrement;
  }

  void printStates() {
    System.out.println("cadence:" +
      cadence + " speed:" +speed + " gear:" + gear);
 }

}

 

Implementing an interface allows a class to become more formal about its behavior.

If your class claims to implement an interface, all methods defined by that interface must appear in its source code before the class will successfully compile.

 

What is so special about a class implementing an interface?

 

When you create an object of the class, you can do it in 2 different ways:

TrekBicycle trekBike = new TrekBicycle();
Bicycle trekBike = new TrekBicycle();

 

The second way of creating the object provides a lot of flexibility in dealing with all bicycle objects .

If you have an additional Bicycle class, for example

class GTBicycle implements Bicycle {

  int cadence = 0;
  int speed = 0;
  int gear = 1.25;

  void changeCadence(int newValue) {
    cadence = newValue;
  }

  void changeGear(int newValue) {
    gear = newValue * 1.1;
  }

  void speedUp(int increment) {
    speed = speed + increment + 1.2;
  }

  void applyBrakes(int decrement) {
    speed = speed - decrement;
  }

  void printStates() {
    System.out.println("cadence:" +cadence + " speed:" +
   speed + " gear:" + gear);
  }

}


an object of the GTBicycle class can also be saved in a Bicycle variable:

Bicycle gtBike = new TrekBicycle();

 

Any object of a class than implements the Bicycle interface can be saved in a Bicycle variable.

 

Where have we seen this with Selenium WebDriver?

 

You can see interfaces everywhere:

 

1. When creating a driver object

A driver object for the Chrome browser is created as follows

WebDriver driver = new ChromeDriver();

 

WebDriver is an interface while ChromeDriver is a class that extends RemoteDriver which implement WebDriver.

 

To create a driver for Firefox, we just change the class name:

WebDriver driver = new FirefoxDriver();

Same for the driver for Edge:

WebDriver driver = new EdgeDriver();

 

2. When finding a web element

WebElement button = driver.findElement(locator);

 

WebElement is an interface.

For this reason, you can save in a WebElement variable any type of html element: button, link, label, image, list, textbox, etc.

 

3. When typing a keyword in a textbox

All of the following ways of typing a keyword in a textbox work:

a. type a String value

WebElement searchBox = driver.findElement(textBoxId);
String keyword = "symphony";
searchBox.sendKeys(keyword);

 

b. type a StringBuilder value

WebElement searchBox = driver.findElement(textBoxId);
StringBuilder author = new StringBuilder()
                          .append("wolfgang")
                          .append(" ")
                          .append("amadeus")
                          .append(" ")
                          .append("mozart");

searchBox.sendKeys(author);

 

c. type a StringBuffer value

WebElement searchBox = driver.findElement(textBoxId);
StringBuffer topic = new StringBuffer().append("classical")
                                       .append(" ")
                                       .append("music");

searchBox.sendKeys(author);

 

d. type a String, a StringBuilder, a StringBuffer and a key

WebElement searchBox = driver.findElement(searchBoxId);
StringBuilder author = new StringBuilder()
                         .append("wolfgang")
                         .append(" ")
                         .append("amadeus")
                         .append(" ")
                         .append("mozart");

String space = " ";
StringBuffer topic = new StringBuffer()
                         .append("classical")
                         .append(" ")
                         .append("music");

String subTopic = "symphony";

searchBox.sendKeys(author, space, topic, space, subTopic, Keys.TAB);

 

Why do all these different ways of typing a keyword work?

 

It looks as if sendKeys may have one or more parameters and they can have different types: String, StringBuilder, StringBuffer, Keys.

The sendKeys signature explains it all:
void sendKeys(CharSequence... keysToSend)

 

The … explains why you can have one or more parameters.
… means that sendKeys can have a variable number of parameters of the same type.
All parameters must have the CharSequence type.

 

CharSequence explains why we can have so many classes as parameter types for sendKeys.

 

CharSequence is an interface in Java that is implemented by classes such as String, StringBuilder and StringBuffer.

This means that you can use any object of the String, StringBuffer and StringBuilder classes as a CharSequence variable.

 

What about Keys?

Keys is a Selenium enumeration which implements as well the CharSequence interface.

What should we learn from all this?

A few things …

1. Each class that we create should implement an interface.

2. When creating an object of the class, use the interface as its type.

 

How can you use interfaces in a Selenium project?

 

If you are interested in seeing how “programming to an interface” is done in a Selenium WebDriver project step by step, I have an ebook for you.

 

It will show you how to change code that does not use interfaces so that it follows the “programming to an interface” principle.

 

The result of applying this principle is that we will be able to replace 10 automated scripts that search for different categories (car, minivan, boat, rv, etc) with one script.

So, instead of having a search test for each category

public class TestClass {

   CarInfo carInfo = new CarInfo("audi", "a4");

   MinivanInfo minivanInfo = new MinivanInfo("nissan","navara", "pickup");

   BikesInfo bikesInfo = new BikesInfo(
                             "kawasaki", "gpz1000", "sports tourer");

   @Test
   public void canSearchForCars() {

     CarHome homePage = new CarHome();
     homePage.open();

     CarResults resultsPage = homePage.searchFor(carInfo);
     assertTrue(resultsPage.count() > 0);

   }

   @Test
   public void canSearchForMinivans() {

     MinivanHome homePage = new MinivanHome();
     homePage.open();

     MinivansResults resultsPage = homePage.searchFor(minivanInfo);
     assertTrue(resultsPage.count() > 0);

  }

  @Test
  public void canSearchForBikes() {

     BikesHome homePage = new BikesHome();
     homePage.open();

     BikesResults resultsPage = homePage.searchFor(bikesInfo);
     assertTrue(resultsPage.count() > 0);

  }


}

 

you can have

public class TestClass {

  String postalCode = "w1f7tu";

  @DataProvider(name = "vehicleInfo")
  public Object[][] vehicleInfo() {
    return new Object[][] {
      { "cars" , new CarInfo("BMW", "3 SERIES") },
      { "minivans", new MinivanInfo("NISSAN", "NAVARA", "Pickup")},
      { "bikes" , new MotorHomesInfo("AUTO-SLEEPERS", "2")},
    };
  }

  @Test(dataProvider = "vehicleInfo")
  public void canSearchForCategory(String category,
                                   VehicleInfo vehicleInfo) {

     HomePage homePage = openHomePage(category);
     homePage.open();

     ResultsPage resultsPage = homePage.searchFor(
                                     vehicleInfo, postalCode);
     assertTrue(resultsPage.count() > 0);
  }

}

 

Interested?

 

Send USD 15 to alex@alexsiminiuc.com through Paypal.
I will email you the book and answer all your questions about it, also by email.

 

6 things to avoid when learning selenium automation

dontdoit-1

1. Aiming at becoming a Selenium automation expert

This goal is so high that you will probably fail.

You should replace it with something more achievable like “i want to get a test automation job in 1 year”.

 

2. Selenium WebDriver certifications

Selenium WebDriver certifications are scams. Even if they would be good, they wont help you getting hired. When studying for a certification, you study for the exam that you will take and not for the subject.

Passing an exam is a very poor way of measuring your programming skills.

 

I met people who passed with 90% grades TOEFL (test of english as a foreign language) and TWE (test of written english) but speak and write very bad english.

 

3. Jumping into writing Selenium WebDriver tests before learning a programming language

Test automation with Selenium WebDriver is basically programming.

Without knowing a programming language well, you will always have big obstacles in writing automation code. Selenium WebDriver is just a library that can be used through multiple languages.

Jumping to Selenium tests with no programming is similar with trying to cook lasagna with no cooking skills.

Shortcuts never work. There are no shortcuts to math, to learning french, to getting fit, to anything. You have to put the effort in and follow a process that takes time.

 

4. Looking for the best selenium book or online course

There is no such thing. You will find though many great books and online courses.

The best book for Selenium should include everything about Selenium, everything about the programming language (lets say Java), everything about an IDE (example: Eclipse), everything about unit testing, maven, code design, code refactoring, maven, etc.

Do you really think that it exists?

It does not.

To learn test automation with Selenium, you need to use a multitude of resources, books, blogs, youtube videos, online courses, webinars, etc.

Because the amount of information needed for an automation job is very high.

 

5. Learning by yourself

If you attempt learning by yourself, chances are high that you wont make it.

Find other people with the same goal, take instructor-led courses, find developers willing to help you, go to meetups, anything but being by yourself.

 

6. Selenium IDE

Record and play is not test automation

 

Use streams for lists of web elements

2179048[1]

Every time you need to find multiple elements in your Selenium code, you get a list of web elements, so a collection.

Streams and predicates, introduced in Java 8, are the best way of working with collections.

When you use them, you don’t only get code that is shorter and easier to understand but also better performance.

Why is this useful for you, a Selenium WebDriver developer?

It is useful because instead of getting texts and attributes of all elements (matched by the same locator) with

private List< String > textValues(By locator) {
  List elements = driver.findElements(locator);

  List< String > values = new ArrayList<>();

  for (WebElement e : elements)
    values.add(e.getText());

  return values;
}

private List< String > attrValues(By locator, String name) {
  List elements = driver.findElements(locator);

  List< String > values = new ArrayList<>();

  for (WebElement e : elements)
    values.add(e.getAttribute(name));

  return values;
}

you can use

private List< String > textValues(By loc) {
  return getValues(loc, e -> e.getText());
}

private List< String > attrValues(By loc, String name) {
  return getValues(loc, e -> e.getAttribute(name));

private List< String > getValues(
By loc, Function<WebElement,String > pred) {

  List< WebElement > elements = driver.findElements(loc);       

  List< String > values = elements.stream().map(pred)
                                  .collect(Collectors.toList());

  return values;


}

Read along to understand why the second code version is much better.

How to use collections the old way

Assume that you have a Result class for results of a page:

public class Result {

  private String name;
  private int price;
  private String owner;
  private boolean onlineOnly;

  public Result(String name, int price, String owner, boolean onlineOnly) {
    this.name = name;
    this.price = price;
    this.owner = owner;
    this.onlineOnly = onlineOnly;
  }

  public String name() {
    return this.name;
  }

  public int price() {
    return this.price;
  }

  public String owner () {
    return this.owner;
  }

  public boolean onlineOnly() {
    return this.onlineOnly;
  }

  @Override
  public String toString() {
    return name + " - " + price + " - " + owner + " - " + onlineOnly;
  }

}

You need also a Results class (for lists of results):

public class Results {

  List< Result > results;

  public Results(List results) {
    this.results = results;
  }

  public List< Result > get() {
    return this.results;
  }

  public Result getResult(int i) {
    return this.results.get(i);
  }

  public int size() {
    return this.results.size();
  }

  @Override
  public String toString() {
    return this.results.toString();
  }

}

So far, Results class has methods for returning

  • the list of results
  • an element of the list
  • the size of the list
  • a String value for the list

Pretty basic, so far.

We would like to add filtering methods to the Results class such as:

public Results filterByOnlineOnly() {
  List< Result > list = new ArrayList()<>;

  for (Result r : this.results)
    if (r.onlineOnly() == true)
      list.add(r);

  return new Results(list);
}

public Results filterByOwner(String name) {
  List< Result > list = new ArrayList()<>;

  for (Result r : this.results)
    if (r.owner().equals(name))
      list.add(r);

  return new Results(list);
}

public Results filterByName(String name) {
  List< Result > list = new ArrayList()<>;

  for (Result r : this.results)
    if (r.name().equals(name))
      list.add(r);

  return new Results(list);
}

public Results filterByPrice(int price) {
  List< Result > list = new ArrayList()<>;

  for (Result r : this.results)
    if (r.price() == price)
      list.add(r);

  return new Results(list);
}

So far, we have filtering methods by name, owner, price and online only.

But, what should we do if we need more filtering methods such as

  • by price in a range
  • with a keyword in the name
  • with a keyword in the name and a specific owner

Adding more methods to the Results class is not the solution.

The more filters we need, the more methods.

One thing that is easy to notice is that all filtering methods have very similar code.

The only exception is the condition that the list elements should match.

How to use collections with streams and predicates

We can replace all methods with one method that uses a predicate parameter (for the filtering condition).

public Results filterBy(Predicate predicate) {

  List< Result > list = this.results.stream()
                  .filter(predicate)
                  .collect(Collectors.toList());

  return new Results(list);
}

How does this work?

  1. First, the results list is converted to a stream by the stream() method.
  2. Then, the filter() method filters the stream with the predicate . This is done by going through each element of the stream, applying the predicate to each element and selecting only the elements that match the predicate.
  3. Finally, collect() collects all selected elements and returns them as another list.

Amazing, isn’t it?

How can this method be used?

Results products = results.filterBy(r -> r.onlineOnly() == true);

Results products = results.filterBy(r -> r.name().equals("IPHONE 8"));

Results products = results.filterBy(r -> r.price() == 500);

We can get really creative now:

Results list = results.filterBy(r -> r.name().contains("IPHONE"));

Results products = results.filterBy(r -> r.name().contains("IPHONE") &&
                                         r.price() > 500 &&
                                         r.price() < 1000 &&
                                         r.onlineOnly() == false);

What else can you do with streams?

How about creating the list of all names, or all prices or all owners?

public List < String > values(Predicate predicate) {
  List list = this.results.stream()
                          .map(predicate)
                          .collect(Collectors.toList());

  return list;
}

Notice that the filter() method is replaced by the map().
map() extracts the value as specified by the predicate.
The method returns a list of Strings since names and owners are Strings:

List< String > owners = results.values(r -> r.owner());

List< String > names = results.values(r -> r.names());

There are many other things that can be done with streams and predicates:

Results onlineProducts = results.stream()
                                .filter(Result::onlineOnly)
                                .skip(1)
                                .collect(Collectors.toList());

Results cheapProducts = results.stream()
                           .filter(r -> r.price() <= 100)                                               .filter(r -> r.owner().equals("Rogers"))
                           .collect(Collectors.toList());

List< String > distinctOwners = results.stream()
                             .map(Result::owner)
                             .distinct()
                             .collect(Collectors.toList());

int priceSum = results.stream()
                      .map(Result::price)
                      .reduce(0, Integer::sum);

assertTrue(results.stream()
                  .anyMatch(r -> r.owner().equals("Bell")));

Java 8 In Action is a great book for learning more on streams and predicates.

Find child elements with locator chaining

KIT518860_broad_chain_closeup_c_wikimedia.org_[1]

Have you ever had to find an element in another element?

Of course you have.

How did you do it?

 

Maybe you used a locator that searches for the parent element first and then for the child element in the parent.

 

Like in the following test:

public class TestClass {

  private String HOME_URL = "http://www.vpl.ca/";
  private String RESULTS_URL = "vpl.bibliocommons.com/search";

  private By SEARCH_BOX_ID = By.id("edit-search");
  private By SEARCH_BUTTON_ID = By.id("edit-submit");

  private By TITLE_LOCATOR = By.xpath(
    "(//div[contains(@data-js, 'bib-list-item')])[1]" +
    "//span[@class='title']");

  //driver variable, setUp() and tearDown() methods are skipped

  @Test
  public void test1() {
    driver.get(HOME_URL);
    assertEquals(driver.getCurrentUrl(), HOME_URL);

    WebElement searchBox = driver.findElement(SEARCH_BOX_ID);
    searchBox.sendKeys("java");

    WebElement searchButton = driver.findElement(SEARCH_BUTTON_ID);
    searchButton.click();

    assertTrue(driver.getCurrentUrl().contains(RESULTS_URL));

    WebElement title = driver.findElement(TITLE_LOCATOR);
    String titleText = title.getText().toLowerCase();

    assertTrue(titleText.contains("java"));
  }

}

 

In this case, the test opens the site, searches for a keyword and then finds the title of the first result.

The locator of the title includes the locator of the first result:

private final By TITLE_LOCATOR = By.xpath(
   "(//div[contains(@data-js, 'bib-list-item')])[1]" + 
   "//span[@class='title']");

(//div[contains(@data-js, ‘bib-list-item’)])[1]  is the locator of the first result.

//span[@class=’title’] is the locator of the first result’s title.

 

This seems like a decent approach but is it not.

If the code generates an exception when trying to find the title element, is the error about the title not being available or about the first result?

Since the locator combines both, it would be difficult to know.

 

We can change the code so that the parent element is found first and stored in a WebElement variable.

 

The child element is then searched inside of the parent element:

public class TestClass {

  private String HOME_URL = "http://www.vpl.ca/";
  private String RESULTS_URL = "vpl.bibliocommons.com/search";

  private By SEARCH_BOX_ID = By.id("edit-search");
  private By SEARCH_BUTTON_ID = By.id("edit-submit");

  private By RESULT_BOX_XPATH  = By.xpath(
    "(//div[contains(@data-js, 'bib-list-item')])[1]");

  private By TITLE_XPATH = By.xpath(
    ".//span[@class='title']"); 

  //driver variable, setUp() and tearDown() methods are skipped

  @Test
  public void test2() {
    driver.get(HOME_URL);   
    assertEquals(driver.getCurrentUrl(), HOME_URL);

    WebElement searchBox = driver.findElement(SEARCH_BOX_ID);
    searchBox.sendKeys("java"); 

    WebElement searchButton = driver.findElement(SEARCH_BUTTON_ID);
    searchButton.click();

    assertTrue(driver.getCurrentUrl().contains(RESULTS_URL));

    WebElement result = driver.findElement(RESULT_BOX_XPATH);
    WebElement title = result.findElement(TITLE_XPATH);
    String titleText = title.getText().toLowerCase();

    assertTrue(titleText.contains("java"));
  }
}

 

This time, the code finds the result element and stores it in a WebElement variable:

WebElement result = driver.findElement(RESULT_BOX_XPATH);

Then, it searches for the title element inside of the result element:

WebElement title = result.findElement(TITLE_INNER_XPATH);

Notice that the title locator starts with a dot.

TITLE_XPATH = By.xpath(".//span[@class='title']");

This is important when searching an element in another element.

When the code works this way, in case of an error, it is very clear which element caused it.

 

This is better than before but it extends poorly.

What if you need to find an element included in another element included in another element included in another element?

For each parent element, you need to search for the element and store it in a variable.

Which leads to lots of duplication.

This duplication can be removed using the ByChained Selenium class.

See how the code looks now:

public class TestClass {

  private String HOME_URL = "http://www.vpl.ca/";
  private String RESULTS_URL = "vpl.bibliocommons.com/search";

  private By SEARCH_BOX_ID = By.id("edit-search");
  private By SEARCH_BUTTON_ID = By.id("edit-submit");

  private By RESULT_BOX_XPATH  = By.xpath(
    "(//div[contains(@data-js, 'bib-list-item')])[1]");

  //driver variable, setUp() and tearDown() methods are skipped

  private By TITLE_XPATH = By.xpath(".//span[@class='title']");

  @Test
  public void test3() {
    driver.get(HOME_URL);    
    assertEquals(driver.getCurrentUrl(), HOME_URL);

    WebElement searchBox = driver.findElement(SEARCH_BOX_ID);
    searchBox.sendKeys("java");  

    WebElement searchButton = driver.findElement(SEARCH_BUTTON_ID);
    searchButton.click();

    assertTrue(driver.getCurrentUrl().contains(RESULTS_URL));

    WebElement title = driver.findElement(
         new ByChained(RESULT_BOX_XPATH,
                TITLE_XPATH));

    String titleText = title.getText().toLowerCase();

    assertTrue(titleText.contains("java"));
  }  
}

The code to find the title looks rather weird:

WebElement title = driver.findElement(
         new ByChained(RESULT_BOX_XPATH,TITLE_XPATH));

It creates a new object for the ByChained class.

The object gets 2 parameters in the constructor for the locators of the parent and child elements.

Finding the element happens as follows:

  • the element for the 1st locator is searched
  • the element for the 2nd locator is searched in the first element

Using this approach scales well even if we have elements included in many other elements.

There are no additional WebElements that should be created and the code stays approximately the same size.

 


Tips and tricks are great.

Keep coming to this blog for more.

However, if you are interested in better and more detailed learning, this Kindle ebook will help you improve your Selenium code:

Improve Selenium Code with Automation Patterns: Page Object Model, Page Factory , Page Elements, Base Page, Loadable Component

I am using all these patterns daily and so can you.

What are the differences between Selenium testers and Selenium developers?

wir[1]

What follows is inspired from reality.

It is not a product of my imagination.

 

Tester: Selenium is a tool like QTP.

Developer: Selenium is a Java library for web test automation.

 

Tester: I will automate test cases by recording them with Selenium IDE.
Developer: Selenium IDE is not an automation tool.

 

Tester: I dont need to write code like developers. I used to write Visual Basic code and that should be enough for automation.
Developer: The automation code should be at the level of production application code.

 

Tester: Every time my test needs a verification, I do it in IF/ELSE statements.
Developer: Verifications are done with JUNIT or TESTNG assertions.

 

Tester: Who cares if all code is in the test method? It works and it looks good to me.
Developer: I will first write an automation framework.
Then I will create the page object classes that are used in the test methods.

 

Tester: Explicit waits and expected conditions are not reliable and dont work well for me. I have created my own.
Developer: Explicit waits and expected conditions work well. I extend them in custom classes when needed.

 

Tester: Look, my test works! That is all I care about! I can start a new test!
Developer: My test should work but also be easy to understand, short and maintainable.

 

Tester: All my libraries are imported to the project. If I need new versions, I remove the old libraries and import the new ones. I used Maven long time ago and dont remember much about it.
Developer: I use Maven for managing dependencies.

 

Tester: All tests run from my computer every night. I run them anytime someone needs them.
Developer: Anyone can run my tests from Jenkins. They are scheduled to run every night.

 

Tester: My tests run in parallel on my computer.
Developer: My tests run in parallel on all available Jenkins slaves.

 

Tester: I love using static methods and static variables.
Developer: Static sucks. I avoided it as much as possible.

 

Tester: My project is very cool. I have a utility class for all common purpose methods. All page classes inherit from the utility class. This makes my page classes shorter.
Developer: I use composition to break the page classes in page element classes.

 

Tester: All my page element locators are in property text files.
Developer: If the page element locators are outside of the page classes, the class encapsulation is broken.

 

Do you have anything else that should be added to the list?

Please tell in the comments so I can add more.

 


 

Want to be more than a Selenium tester?

Start with this Kindle book on automation patterns that can make your Selenium code better:

Improve Selenium Code with Automation Patterns:

Page Object Model, Page Factory, Page Elements, Base Page, Loadable Component

 

amazon book

 

 

Use a custom driver class instead of utility classes

How can I create my own driver class in Selenium?
When learning test automation, you will want your test methods to be executed in multiple browsers such as Firefox and Chrome.

A Firefox driver is created with the FirefoxDriver class:

WebDriver driver = new FirefoxDriver();

A Chrome driver is created with the ChromeDriver class:

WebDriver driver = new ChromeDriver();

Both driver objects use the WebDriver type but are instantiated by different classes.

Now, you dont want to change the class from FirefoxDriver to ChromeDriver and back every time you want to run the tests in another browser.

Instead, you would like to create the driver based on the browser name:

WebDriver driver = new BrowserDriver(browserName);

How do we do this?

We create a custom driver class that implements the WebDriver interface and overrides its methods.

public final class BrowserDriver implements WebDriver {  

   private WebDriver driver;
   private final String browserName;
   private final int timeout = 30; 
   private final String chromeDriverPath = "./src/chromedriver.exe";  

   public BrowserDriver(String browserName) { 
      this.browserName = browserName; 
      this.driver = createDriver(browserName); 
   }  

   private WebDriver createDriver(String browserName) { 
      if (browserName.toUpperCase().equals("FIREFOX") 
         return firefoxDriver(); 

      if (browserName.toUpperCase().equals("CHROME") 
         return chromeDriver();  

      throw new RuntimeException ("invalid browser name"); 
   } 

   private WebDriver chromeDriver() { 
      if (!new File(chromeDriverPath).exists()) 
        throw new RuntimeException
                    ("chromedriver.exe does not exist!"); 

      try { 
        System.setProperty("webdriver.chrome.driver", 
                           chromeDriverPath); 
        return new ChromeDriver(); 
      } 

      catch (Exception ex) { 
        throw new RuntimeException
              ("couldnt create chrome driver"); 
      } 
   } 

   private WebDriver firefoxDriver() { 
      try { 
         return new FirefoxDriver(); 
      } 
      catch (Exception ex) {         
         throw new RuntimeException
              ("could not create the firefox driver"); 
     } 
   } 

   @Override 
   public String toString() { 
      return this.browserName; 
   } 

   public WebDriver driver() { 
      return this.driver; 
   }  

   @Override 
   public void close() { 
      driver().close();   
   }  

   @Override 
   public WebElement findElement(By locator) { 
      return driver().findElement(locator); 
   }  

   @Override 
   public List findElements(By arg0) { 
      return driver().findElements(arg0); 
   }  

   @Override 
   public void get(String arg0) { 
      driver().get(arg0); 
   }  

   @Override 
   public String getCurrentUrl() { 
      return driver().getCurrentUrl(); 
   }  

   @Override 

   public String getPageSource() { 
      return driver().getPageSource(); 
   }  

   @Override 
   public String getTitle() { 
      return driver().getTitle(); 
   }  

   @Override 
   public String getWindowHandle() { 
      return driver().getWindowHandle(); 
   }  

   @Override 
   public Set getWindowHandles() { 
     return driver().getWindowHandles(); 
   }  

   @Override 
   public Options manage() { 
      return driver().manage(); 
   }  

   @Override 
   public Navigation navigate() { 
      return driver().navigate(); 
   }  

   @Override 
   public void quit() { 
     driver().quit(); 
   }  

   @Override 
   public TargetLocator switchTo() { 
      return driver().switchTo(); 
   }  

   @Override 
   public  X getScreenshotAs(OutputType target
   throws WebDriverException { 
      return ((TakesScreenshot) driver())
                    .getScreenshotAs(target);
   } 

}

 

The only method that is not defined in the WebDriver interface is createDriver().
createDriver() creates a driver object based on the browser name and stores it in the driver variable.

All other methods just override the WebDriver interface methods.

In the test method, you can now use the new BrowserDriver class instead of FirefoxDriver and ChromeDriver:

WebDriver driver = new BrowserDriver(“CHROME”);

driver.get(“http://www.bestbuy.com”);

WebElement searchBox = driver.findElement(searchBoxLocator);

 

The custom driver class can add as well new methods.

 

Lets say that you want to have find element methods that use the WebDriverWait and ExpectedConditions classes:

public WebElement findVisibleElement(By locator) {
   WebElement element = new WebDriverWait(driver(), timeout)
                .until( visibilityOfElementLocated (locator));
   return element;
}

public WebElement findClickableElement(By locator) {
   WebElement element = new WebDriverWait(driver(), timeout)
               .until( elementToBeClickable (locator));
   return element;
}

public WebElement findHiddenElement(By locator) {
   WebElement element = new WebDriverWait(driver(), timeout)
              .until( presenceOfElementLocated (locator));
   return element;
}

You may be tempted to create a utility class, move these methods there and then use the utility class as a parent for the page classes.

Resist this temptation as it leads to bad things 🙂

Avoid utility classes in your automation projects.

More about utility classes very soon on this blog.

Instead of the utility class, you can move these methods to your custom driver class.

public final class BrowserDriver implements WebDriver {  

   private WebDriver driver; 
   private final String browserName; 
   private final int timeout = 30; 
   private final String chromeDriverPath = "./src/chromedriver.exe"; 

   public BrowserDriver(String browserName) { 
      this.browserName = browserName; 
      this.driver = createDriver(browserName); 
   }  

......................................................................

   @Override 
   public WebElement findElement(By locator) { 
     WebElement element = new WebDriverWait(driver(), timeout) 
                        .until( visibilityOfElementLocated(locator)); 
     return element; 
   }  

   @Override 
   public List findElements(By arg0) { 
     return driver().findElements(arg0); 
   }

   public WebElement findClickableElement(By locator) { 
      WebElement element = new WebDriverWait(driver(), timeout) 
                       .until( elementToBeClickable(locator)); 
      return element; 
   } 

   public WebElement findHiddenElement(By locator) { 
      WebElement element = new WebDriverWait(driver(), timeout) 
                      .until( presenceOfElementLocated(locator)); 
      return element; 
 }

}