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

 

Advertisements

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.

Why do static variables and methods suck?

I believe they are very full at times.

I got this comment on a LinkedIn post for the differences between Selenium testers and Selenium developers.

It is worth providing an answer.

Now, I am not a Java expert, far from it.

But others are.

For example, Simon Stewart.

Yes, that Simon Stewart who created Seleniumn WebDriver.

This is from his blog:

“Singletons? Static Methods? Also No.
Singletons (in the traditional “implemented as a static field in a class” sense, not in the “ideally we’d only have one of these” sense) destroy our ability to have fun and write tests that can run in parallel, slashing our potential productivity. Also, it leads people to start using the Service Locator pattern instead of Dependency Injection, and we take DI as an article of faith (see above), mainly because it facilitates TDD by making collaborators clear, like we (also) said above.”

So, static methods? Also no.

Do you need more reasons against static variables and methods?

They promote code that is not object oriented.

 

Object oriented code is about objects.

 

Every method is used on an object.

 

Since static variables or methods are for the class but not the class’s objects, you are writing code that does not use objects.

 

Have a look at the following articles for more details:

Still not convinced?

Read this topic from Stack Overflow.

 

Still in doubt?

Continue to use them.

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

 

 

How much Java should you know for a Selenium testing job?

odd-one-out[1]

Selenium tester hiding in the middle of Selenium developers

This is a bad question.

The following are testing jobs:

  • manual testing job
  • functional testing job
  • explorary testing job

 

Since when Selenium has anything in common with manual or functional testing?

Web automation with Selenium WebDriver is not a testing job.

 

Selenium tester does not explain anything about what the job is about.

It is misleading as well since no testing is involved.

When doing web automation with Selenium, you do not

  • create test cases
  • execute test cases
  • explore applications
  • report bugs for your testing
  • map test cases to requirements

 

Instead, you take a user story and implement automated tests for it using Java code and the Selenium WebDriver library.

I said library and not tool or application.

We will come back to the distinction between the library and application.

 

So, there is no such thing as a Selenium testing job.

Test automation with Selenium is a development job.

How much Java should you know for a Selenium development job?

First, you need to be a developer.

 

How much Java should a developer know to do a development job?

In-depth language knowledge (Java, C#, etc).

 

What other skills should the developer have?

Design patterns.

How to create a framework.

How to use an existing framework.

How to document code.

How to use a source control system (GIT, SVN).

How to review code.

How to integrate his code with other developers’ code.

How to use Maven to manage dependencies.

How to use Jenkins for continuous integration.

How to use JUNIT or TESTNG for unit testing.

 

How much Java should a developer know for a Selenium job?

All of the above.

 

But, but, I want to be a Selenium tester.

This cannot be right.

I want to be a tester that uses Selenium.

Tell me what to do.

 

Selenium WebDriver automation is for developers and testers who want to become developers.

Anyone else is just wasting his time with Selenium.

 

One more thing should be said about Selenium WebDriver.

Selenium WebDriver is not an automation tool.

It is not like QTP.
It does not have an IDE.
It does not allow record-and-playback.
It does not create an object repository.

It is instead an automation library.

You cannot use it as is.

You need a lot of other knowledge before you can use.
Programming knowledge.

 

This may be why people talk about Selenium testers.

If Selenium would be a tool, testers should be able to learn it.

They can learn QTP, they can learn SOAP UI.

They should be able to learn Selenium.

But Selenium is not a tool.

It is an automation library.

 


 

Want to become a Selenium developer instead of staying a tester?

I have the right book for you:

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

 

amazon book