Wednesday, February 15, 2012

Another view on how to simulate multi-user interaction in your Selenium 2 scenarios

I heard from a lot of people that the best pattern for keeping your web driver in the project is singleton. That looks doubtful. 
Since I got the requirement of multi-user interacting in my scenarios I used singleton pattern for webdriver and I had to use the following workflow for interaction
1. Log in with user A
2. Send message to user B
3. Log out with user A
4. Log in with user B
5. Handle the request from user A

That is not quite obvious and organic workflow. The much more convenient is to do something like this

1. Log in with user A
2. Send message to user B
3. Log in with user B
4. Handle the request from user A--- Finish the test or:
5. Log out with user A
6. Log out with user B

Add support for simultaneous work of several browsers

This cannot be implemented having singleton pattern as the one for your webdriver. This is my way of how to support several browsers simultaneously

First of all we need the factory to easily create new browser instances (aka new drivers)

 private static class WDFactory{
  public static WebDriver createWebDriver(Properties properties){
   String browser = properties.getProperty("browser");
   WebDriver driverToCreate;
   if(browser.toLowerCase().equals("*firefox")){
    driverToCreate = new FirefoxDriver();
   }else if(browser.toLowerCase().equals("*googlechrome")){
    driverToCreate = new ChromeDriver();
   }else{
    throw new UnsupportedOperationException("Not supported browser yet");
   }
   driverToCreate.get(properties.getProperty("domain"));
   driverToCreate.manage().timeouts().implicitlyWait(500, TimeUnit.MILLISECONDS);
   return driverToCreate;
  }
 }

Also we sure need the pool where we're going to store all the browser instances we're going to work with

public class WDPool {

 private HashMap<String, WebDriver> pool;
 
 public WDPool(){
  pool = new HashMap<String, WebDriver>();
 }
 
 public void closeDrivers(){
  for (String key: pool.keySet()){
   getDriver(key).close();
  }
 }
 
 public void addDriver(String id, WebDriver driver){
  ARTestCase.logger.debug("Adding driver with id [" + id + "]");
  driver.manage().deleteAllCookies();
  pool.put(id, driver);
 }
 
 public WebDriver getDriver(String id){
  ARTestCase.logger.debug("Returning driver with id [" + id + "]");
  return pool.get(id);
 }
}

Okay. Now you should consider your design. I have the class laying between the browser control engine (like selenium) and the business logic of the scenarios. So if you have one introduce the following method there. Otherwise introduce it into the class where your actual scenario is described

 public void pushDriver(String id){
  driver = WDFactory.createWebDriver(properties);
  stack.addDriver(id, driver);
 }

Here and after stack represents the instance of WDPool.

It will be used on the stage the new user is logging in.
and the facility to obtain the driver instance for the current user

 private WebDriver getDriver(){
  return stack.getDriver(ARTestCase.getCurrentUser());
 }

So now we're ready to instantiate the new browser each time the new user gets logged in. The browser instance is stored into the hashmap with the id holding the user's nick-name. There are only two things to do:

1. Change your log-in functionality of the scenario so that it requests pushDriver. In my case it has the following look:

    public void logIn(String username) throws ARAutomationException {
     getTestCase().getAdapter().pushDriver(username);
     getTestCase().setCurrentUser(username);
 // some actions to perform
    }

Where getTestCase().getAdapter() returns the interlayer of the test case we're currently executing holding all the stuff I'm writing here about.

2. Wherever  you used the pattern like driver.someMethod() you now should use getDriver().someMethod() described in two snippets above. That will make your framework to switch the browsers each time you either log in with new user or call

getTestCase().setCurrentUser(existingUserNameHoldingOneOfTheBrowserInstances);