Don’t Do It Yourself

DDIY

DIY or Do it yourself.

I started paying attention to the DIY concept after seeing the following method that waits until a page is loaded in the browser:

public void waitForPageToLoad(String string){
  String url = browserDriver.getCurrentUrl();
  Boolean stringExists = url.contains(string);
  int Count = 0;
 
  while (Boolean.FALSE.equals(stringExists) && Count < 100){
     url = browserDriver.getCurrentUrl();
     stringExists = url.contains(string);
     Count = Count + 1;
     try {
        Thread.sleep(500);
     } catch (InterruptedException e) {
         // TODO Auto-generated catch block
         e.printStackTrace();
     }
  }
 
   //if(stringExists==false)
   if(Boolean.FALSE.equals(stringExists))
      throw new IllegalStateException(
        “Page not loaded. Current page” + “is: “
         + browserDriver.getCurrentUrl()); 
   }
}

 

The  method seems useful at first sight. But reading the code with more attention, one realizes that it is just a bad implementation of concepts that already exist in the Selenium WebDriver library: WebDriverWait and ExpectedConditions.

 

The same result is provided by 2 lines of code:

WebDriverWait wait = new WebDriverWait(driver, 30);
wait.until(ExpectedConditions.urlContains(keyword));

 

 

 

When writing code, this is one of the things that are always so tempting.

  1. You need to convert an array into a list so you put together a method that does it.
  2. You need a  way to wait until a page is loaded so you write quickly some code for it.
  3. You need random strings and numbers in your project so you create your class that generates them.

 

After doing it yourself, the result is always the same. You are satisfied about being capable of completing the task. You are capable of writing code that is useful in your project. You can show it to a colleague with pride.

 

But then, you discuss your new classes with someone a bit more senior.

 

Who listens to you carefully and then replies with 3 words:

Don’t do it yourself!

(There are actually 4 words here, hmmm, my counting is not what it used to be.)

 

What did you say? What do you mean by “don’t do it yourself”? What is wrong with my code? It works very well. It has no bugs. It is well designed.

 

That’s what you think now. It may be working for now but will it continue to do so in the future for all your other needs?

Are you sure that it will be easy to extend and maintain?

And actually, why are you doing it? Don’t you have other more important code to write?

 

Yes, I have more code to write. But what about all these things? Generating a random number and string, for example?

 

 

Before starting to write any common-purpose code, check if anyone else did it before you. Chances are very high that there is already a library out there that does what you need and much more. This library was created to work in a variety of situations. It is well designed and probably extensible. It is well documented and well tested.

 

 

Let’s take generating a random string and number.

Your code may look like this:

String text = "";
char[] symbols = new char[] {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'};

int size = 20;

for (int i = 0; i < size; i++) {
Random random = new Random();
int randomIndex = random.nextInt(symbols.length);
text = text + symbols[randomIndex];
}

System.out.println(text);

 

Easy enough, isn’t it?

 

Yes, but very limited too.

 

How about using instead the RandomStringGenerator class from the commons apache library?

Look how easy it is to

  • generate random string values from letters only
  • generate random string values from digits only
  • generate random string values from letters and digits and with variable length
char[] letters = new char[] {'a', 'z'};
char[] numbers = new char[] {'0', '9'};

RandomStringGenerator generator;

generator = new RandomStringGenerator
.Builder().withinRange(letters).build();

System.out.println(generator.generate(20));

generator = new RandomStringGenerator
.Builder().withinRange(numbers).build();

System.out.println(generator.generate(20));

generator = new RandomStringGenerator
.Builder().withinRange(letters, numbers).build();

for (int i = 1; i <=10; i++)
System.out.println(generator.generate(10, 20));

 

In conclusion …

 

There is no need to re-invent the wheel.

Unless you have so many ideas and intuitions that your new wheel is compared to the old one what the first iphone was compared to a blackberry.

 

Otherwise, spend your time to write code that implements a good problem for the company you work for.

And, every time you can, don’t do it yourself.

Use third party libraries instead such as apache commons.

Advertisements

You dont need static driver or static methods

You dont need a static driver or static methods in Selenium test automation.

Because they bring more problems than benefits.

Such as breaking encapsulation, making objects mutable and promoting procedural code.

not-sure-why-you-dont-like-static

Enough technical terms, lets talk common sense.

We have a test for a successful user log in:

public class LoginTests {

 private WebDriver driver;
 private final String EMAIL = "test@test.com";
 private final String PASSWORD = "password123";
  
 @Before
 public void setUp() {
   driver = new FirefoxDriver();
 }
  
 @After
 public void tearDown() {
   driver.quit();
 }
  
 @Test
 public void successfulLoginWorks() {
   LoginPage loginPage = new LoginPage(driver);
   loginPage.open();
    
   MainPage mainPage = loginPage.loginAs(EMAIL, PASSWORD);
   assertTrue(mainPage.isUserNameDisplayed() == true);
 }
  
}

 

It uses 2 page objects:

public class LoginPage {

 private final String URL = "http://www.abc.com";
 private final String TITLE = "Login Page";
 private final By EMAIL_ID = By.id("emailId");
 private final By PASSWORD_ID = By.id("passwordId");
 private final By LOGIN_ID = By.id("loginBtnId");

 private WebDriver driver;
 private WebDriverWait wait;

 public LoginPage(WebDriver d) {
   this.driver = d;
   this.wait = new WebDriverWait(this.driver, 30);
 }

 public void open() {
   driver.get(URL);
   if (title().equals(TITLE) == false)
     throw new RuntimeException("Home Page is not displayed!");
 }
  
 public String url() {
   return this.driver.getCurrentUrl();
 }
  
 public String title() {
   return this.driver.getTitle();
 }

 public MainPage loginAs(String username, String password) {
   WebElement emailTextBox = 
   wait.until(visibilityOfElementLocatedBy(EMAIL_ID));
   emailTextBox.sendKeys(username);
    
   WebElement passwordTextBox = 
   wait.until(visibilityOfElementLocatedBy(PASSWORD_ID);
   passwordTextBox.sendKeys(password);
    
   WebElement loginButton = 
   wait.until(visibilityOfElementLocatedBy(LOGIN_ID);
   loginButton.click();
    
   return new MainPage(this.driver);
 }
}

 

And MainPage …..

public class MainPage {
 private final String URL = "http://www.abc.com/main";
 private final By USERNAME_ID = By.id("userNameId");
  
 private WebDriver driver;
 private WebDriverWait wait;

 public MainPage(WebDriver d) {
   this.driver = d;
   this.wait = new WebDriverWait(this.driver, 30);
   if (url().equals(URL) == false)
     throw new RuntimeException("Main Page is not displayed!");
 }

 public String url() {
   return this.driver.getCurrentUrl();
 }
  
 public boolean isUserNameDisplayed() {
   WebElement userNameLabel = 
   wait.until(visibilityOfElementLocatedBy(USERNAME_ID));
   
   return userNameLabel.isDisplayed();
 }

}

 

The test can be improved in 2 places.

First, the driver is passed to the LoginPage object.

Second, we create a LoginPage object in the test.

 

How is the driver used by the page classes?

The test creates a LoginPage object and passes the driver object to it:

LoginPage loginPage = new LoginPage(driver);

 

The LoginPage constructor gets the driver as parameter and saves it in a class field:

public LoginPage(WebDriver d) {
 this.driver = d;

 

LoginPage class passes the driver to a MainPage object in the loginAs() method:

public MainPage loginAs(String username, String password) {
 .......    
 return new MainPage(this.driver);
}

 

The MainPage constructor gets the driver as parameter and saves it in a class field.

The driver object is passed around by the page classes.

 

How can we fix these 2 problems?

One way would be with a static driver and static page methods.

 

exactly-my-point-static-is-the-only-way

 

First, we create a BaseTest class as a parent for test classes.

We move to it the driver object and make it static.

We also move to it the setUp() and tearDown() methods, make them static and change their annotations from @Before and @After to @BeforeClass and @AfterClass:

public class BaseTest {

 public static WebDriver driver;

 @BeforeClass
 public static void setUp() {
   driver = new FirefoxDriver();
 }
  
 @AfterClass
 public static void tearDown() {
   driver.quit();
 }

}

 

Next, we make the LoginTest class inherit from BaseTest and remove the driver object:

public class LoginTests extends BaseTest {
 private final String EMAIL = "test@test.com";
 private final String PASSWORD = "password123";

 @Test
 public void successfulLoginWorks() {
   LoginPage loginPage = new LoginPage();
   loginPage.open();
    
   MainPage mainPage = loginPage.loginAs(EMAIL, PASSWORD);
   assertTrue(mainPage.isUserNameDisplayed() == true);
 }
  
}

 

Notice that there is no driver, setUp() and tearDown() methods in LoginTests.java.

How do the page classes access the static driver?

 

A static import for the BaseTest class is added both to LoginPage and MainPage classes:

import static BaseTest.*;

 

Because of it, since the driver field of BaseTest is public, the page classes can use it as if it is local to the page class.

import static BaseTest.*;

public class LoginPage {

 private final String URL = "http://www.abc.com";
 private final String TITLE = "Login Page";
 private final By EMAIL_ID = By.id("emailId");
 private final By PASSWORD_ID = By.id("passwordId");
 private final By LOGIN_ID = By.id("loginBtnId");

 private WebDriverWait wait = new WebDriverWait(driver, 30);

 public void open() {
   driver.get(URL);
   if (title().equals(TITLE) == false)
     throw new RuntimeException("Home Page is not displayed!");
 }
  
 public String url() {
   return driver.getCurrentUrl();
 }
  
 public String title() {
   return driver.getTitle();
 }

 public MainPage loginAs(String username, String password) {
   WebElement emailTextBox = 
   wait.until(visibilityOfElementLocatedBy(EMAIL_ID));
   emailTextBox.sendKeys(username);
    
   WebElement passwordTextBox = 
   wait.until(visibilityOfElementLocatedBy(PASSWORD_ID);
   passwordTextBox.sendKeys(password);
    
   WebElement loginButton = 
   wait.until(visibilityOfElementLocatedBy(LOGIN_ID);
   loginButton.click();
    
  return new MainPage();
 }
}

 

And MainPage …..

import static BaseTest.*;

public class MainPage {
 private final String URL = "http://www.abc.com/main";
 private final By USERNAME_ID = By.id("userNameId");
  
 public MainPage() {  
   if (url().equals(URL) == false)
     throw new RuntimeException("Main Page is not displayed!");
 }

 public String url() {
   return driver.getCurrentUrl();
 }
  
 public boolean isUserNameDisplayed() {
   WebElement userNameLabel = 
   wait.until(visibilityOfElementLocatedBy(USERNAME_ID));
   return userNameLabel.isDisplayed();
 }

}

 

Notice that there are no driver parameters for the constructors of the page classes.

The driver fields are removed as well.

Every time a page method needs the driver object, it does not prefix it with this any longer since the driver is not a class field.

 

Great, there is no driver in the test!

 

How do we remove the LoginPage object?

We can make all page methods static for both classes.

The public page methods are all declared as static and do not return any objects.

All page fields become static as well.

import static BaseTest.*;

public class LoginPage {
 private static final String URL = "http://www.abc.com";
 private static final String TITLE = "Login Page";
 private static final By EMAIL_ID = By.id("emailId");
 private static final By PASSWORD_ID = By.id("passwordId");
 private static final By LOGIN_ID = By.id("loginBtnId");

 private static WebDriverWait wait = new WebDriverWait(driver, 30);

 public static void open() {
   driver.get(URL);
   if (title().equals(TITLE) == false)
     throw new RuntimeException("Home Page is not displayed!");
 }
  
 public static String url() {
   return driver.getCurrentUrl();
 }
  
 public static String title() {
   return driver.getTitle();
 }

 public static void loginAs(String username, String password) {
   WebElement emailTextBox = 
   wait.until(visibilityOfElementLocatedBy(EMAIL_ID));
   emailTextBox.sendKeys(username);
    
   WebElement passwordTextBox = 
   wait.until(visibilityOfElementLocatedBy(PASSWORD_ID);
   passwordTextBox.sendKeys(password);
    
   WebElement loginButton = 
   wait.until(visibilityOfElementLocatedBy(LOGIN_ID);
   loginButton.click();	
 }
}

 

And MainPage class …

import static BaseTest.*;

public class MainPage {
 private static final String URL = "http://www.abc.com/main";
 private static final By USERNAME_ID = By.id("userNameId");
  
 private static WebDriverWait wait = new WebDriverWait(driver, 30);
  
 public static void open() {
   if (url().equals(URL) == false)
     throw new RuntimeException("Main Page is not displayed!");
 }
  
 public static String url() {
   return driver.getCurrentUrl();
 }
  
 public static boolean isUserNameDisplayed() {
   WebElement userNameLabel = 
   wait.until(visibilityOfElementLocatedBy(USERNAME_ID));
  
   return userNameLabel.isDisplayed();
 }

}

 

Since all methods are static and we dont want any objects in the test, the page methods do not return any objects.

We do not have constructors either.

For the MainPage class, this creates a problem since we were validating in the constructor that the page is displayed.

The loginAs() method does not return a MainPage object too.

To go around all these restrictions, we create an open() method in the MainPage class that does not open the page but validates that it is displayed.

 

How does the test look with static methods?

public class LoginTests extends BaseTest {

 @Test
 public void successfulLoginWorks() {
   LoginPage.open();
   LoginPage.loginAs(EMAIL, PASSWORD);
   MainPage.open();
   assertTrue(MainPage.isUserNameDisplayed() == true);
 }
  
}

 

Very convenient, is it not?

No driver, no LoginPage object, no setUp() and tearDown()!!!

All these are achieved with static methods, static variables and a static driver.

 

yep-you-are-wasting-my-time

 

Is this correct?

I dont think it is.

Lets look first at concerns about the public static driver.

Our driver is a public field of the BaseTest class.

Class fields should always be private so that they cannot be accessed and modified from outside of the class.

It should not be possible to change the fields of a class since this means changing the state of the class’s objects.

This is called immutability in programming meaning that the state of an object should not be changed after the object is created.

Our driver is also static.

Java code should be object oriented so it should use objects.

Each object encapsulates fields and methods and its methods should work on the class fields and the methods parameters only.

Using external static variables in a class means that we break the encapsulation concept.

The state of the object is known when the object’s methods only use the object’s fields.

When external static variables are used as well, the state of the object is not known any longer.

Finally, the static page methods.

Because of using static page methods, all class fields must be static as well.

So the page class uses everything static.

We dont need objects any longer when using static methods.

This is wrong for the same reasons already discussed (immutability, encapsulation).

 

But there is one more reason for not using static variables and methods.
Static methods and variables encourage procedural code to be written instead of object oriented.

 

Now, it seems like we are failing at automation.

It is not too bad to just have a static driver variable.

But using static in other ways is not recommended.

 

static-is-great-you-dont-know-how-to-use-it

So, if static is bad, how do we solve our problems?

First, how important are these problems?

Having the driver passed around by the page classes is not a big deal.

It is a minor case of duplication that is ok to live with.

For the driver in the test and the page object, we can solve this in a different way.

First, we go back to the previous code version where nothing static was used.

Then, we change the BaseTest class by adding a method to open the site:

public class BaseTest {

 private final String URL = "http://www.abc.com";
  
 public WebDriver driver;

 @Before
 public void setUp() {
   driver = new FirefoxDriver();
 }
  
 @After
 public void tearDown() {
   driver.quit();
 }

 public LoginPage openSite() {
   driver.get(URL);
	 
   return new LoginPage(this.driver);
 }
  
}

 

Notice that BaseTest uses non-static methods again and @Before/@After annotations.

The test becomes:

public class LoginTests {

 @Test
 public void successfulLoginWorks() {
   LoginPage loginPage = openSite();
   loginPage.open();
    
   MainPage mainPage = loginPage.loginAs(EMAIL, PASSWORD);
   assertTrue(mainPage.isUserNameDisplayed() == true);
 }
  
}

 

Now, openSite() launches the site and returns the LoginPage object.

The LoginPage object encapsulates the driver and passes it to the MainPage object.

 

i-am-not-convinced-try-again

 


 

If you liked this article,

you may be interested in my new KINDLE book about how to simplify Selenium code with automation patterns:

Improve Selenium Code with Automation Patterns:

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