-2

I have created a common webdriver class that I can use for my selenium tests. Whenever I create a new test, I get the webdriver from this class.

Here is what I am using to create my driver

package com.atmn.config;

import java.net.MalformedURLException;
import java.net.URL;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.remote.CapabilityType;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.remote.RemoteWebDriver;

public class Driver {


    private static Log log = LogFactory.getLog(Driver.class);
    public static String USERNAME = "name";
    public static String ACCESS_KEY = "key";

    public static WebDriver driver = createDriver("firefox", "47.0", "Windows 10");

    public static WebDriver createDriver(String browser, String version, String os) {

        try{
            DesiredCapabilities capabilities = new DesiredCapabilities();
            capabilities.setCapability(CapabilityType.BROWSER_NAME, browser);
            capabilities.setCapability(CapabilityType.VERSION, version);
            capabilities.setCapability(CapabilityType.PLATFORM, os);

            // Launch remote browser and set it as the current thread
            return new RemoteWebDriver(
                    new URL("http://"+USERNAME+":"+ACCESS_KEY+"@ondemand.saucelabs.com:80/wd/hub"),
                    capabilities);
        }
        catch(MalformedURLException e)
        {
            e.printStackTrace();
            //log message
            log.info(e.getMessage());
        }
        return null;
    }

Here is my test, which will run on SauceLabs use Windows 10 and firefox 47.

package com.atmn.tests;

import com.tplus.atmn.tasks.CommonTasks;
import com.tplus.atmn.objects.TOA6;
import java.lang.reflect.Method;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openqa.selenium.By;
import org.testng.Assert;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import com.tplus.atmn.tasks.HttpRequest;


public class Testing extends CommonTasks {

    private static Log log = LogFactory.getLog(TB6_TOA.class);


    @Test (groups = {"Testing"})
    public static void endToEnd() throws Exception {


        //Load URL
        HttpRequest http = new HttpRequest();
        String URL = http.internetWebAddress();
        driver.get(URL);


        //Page 1
            // Item Text
        verifyText(Object.ItemText, "Multiple Choice - Single Answer Radio - Vertical");
        verifyText(Object.Progress_PercentComplete, "0%");
            //URL
        verifyNoRedirect();
            //Go to next page
        driver.findElement(Object.Start).click();
        System.out.println("Next Button was clicked.");

}
}

Instead of just running my test on 1 browser in SauceLabs, I want to run this test on multiple browser/OS combinations in parallel. I'm not sure how to change my driver class to make this happen.

TestRaptor
  • 1,045
  • 4
  • 20
  • 35
  • Did you google this question? Did you read their help wiki? – JeffC Jul 27 '16 at 23:47
  • Is that a serious question. Yes I googled it. Yes I looked at the wiki. I'm not able to figure it out, which is why I posted the question. – TestRaptor Jul 28 '16 at 02:46
  • Yes, it's a serious question. When I can google your question and find answers in less than 60s, I generally ask if the asker has bothered to google their own question. Did you read this article titled [Running Tests in Parallel](https://wiki.saucelabs.com/display/DOCS/Java+Test+Setup+Example#JavaTestSetupExample-RunningTestsinParallel) on the Saucelabs wiki? Seems like it would have what you are looking for. If not, what's missing? – JeffC Jul 28 '16 at 06:30

1 Answers1

1

So you are using TestNG. That test framework has support for running in parallel built in (it's a setting you can add to a suite file as well as set in other ways). It also allows you to take in arguments to a test method using a DataProvider. There are many, many ways to set up a testing framework and there are a lot of factors that should go into how it's structured.

First off, you are creating a static WebDriver instance in the Driver class which is not what you are going to want. You need multiple instances so what you are really going to want is the factory method on that class that allows you to create WebDrivers for each method as needed. Another consideration you are going to want to take into account is the fact that TestNG does not create new instances of the test class object for each method. Because of that, you either can't store any state as a field on the test class object unless it is done in a way that avoids contention (e.g. a ThreadLocal field for instance). Unfortunately, in the case of a Selenium test, you are going to want to quit the WebDriver when you are finished with it to free up the SauceLabs resources. You could normally do that with an @AfterMethod configuration method, but you aren't guaranteed that the configuration methods will run in the same thread as the test method so using a ThreadLocal field won't work to allow access to the same argument (e.g. the web driver). You could use a map of test methods to web drivers on the class object (TestNG allows you to inject the test method as an argument) but in the case of a data provider to allow for multiple OS / browser combinations it will have the same test method as well. In the end with TestNG and Selenium I find it best (or at least easiest), in my experience, to use DataProviders and just store the fields on the test class object and restrict parallelism to class level.

That being said, here is an example of how to test with parallel methods that. Starting with a simple pom.xml dependency section to show the versions used for this example. Even if your versions are a little different, it should be very similar.

<dependencies>
    <dependency>
        <groupId>org.seleniumhq.selenium</groupId>
        <artifactId>selenium-java</artifactId>
        <version>2.53.1</version>
    </dependency>
    <dependency>
        <groupId>org.testng</groupId>
        <artifactId>testng</artifactId>
        <version>6.9.10</version>
    </dependency>
</dependencies>

Update the Driver class to make sure it cleanly returns a new driver instance each time. I removed the logger portions of it just to save some space. I also just made it non-instantiable since it is really only a static factory and allowing instances of it doesn't make sense. Also you really probably shouldn't allow access to things like the name and key as they are purely implementation details.

public final class Driver {

    private static final String USERNAME = "name";
    private static final String ACCESS_KEY = "key";

    public static WebDriver createDriver(String browser, String version, String os) {
        // Should probably validate the arguments here
        try {
            DesiredCapabilities capabilities = new DesiredCapabilities();
            capabilities.setCapability(CapabilityType.BROWSER_NAME, browser);
            capabilities.setCapability(CapabilityType.VERSION, version);
            capabilities.setCapability(CapabilityType.PLATFORM, os);
            return new RemoteWebDriver(new URL("http://" + USERNAME + ":" + ACCESS_KEY + "@ondemand.saucelabs.com:80/wd/hub"),
                                       capabilities);
        } catch (MalformedURLException e) {
            throw new RuntimeException("Failure forming the URL to create a web driver", e);
        }
    }

    private Driver() {
        throw new AssertionError("This is a static class and shouldn't be instantiated.");
    }
}

Finally, in the test class itself you need to define the actual test method and a data provider. If you want the same data provider for multiple tests / test classes, that is fine. Refer to the TestNG documentation for details:

http://testng.org/doc/documentation-main.html#parameters-dataproviders

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

public class FooTest {

    @DataProvider(name = "DriverInfoProvider", parallel = true) // data providers force single threaded by default
    public Object[][] driverInfoProvider() {
        return new Object[][] {
            { "firefox", "47.0", "Windows 10" },
            { "chrome" , "51.0", "Windows 10" },
            // etc, etc
        };
    }

    @Test(dataProvider = "DriverInfoProvider")
    public void testFoo(String browser, String version, String os) {
        WebDriver driver = Driver.createDriver(browser, version, os);
        try {
            // simple output to see the thread for each test method instance
            System.out.println("starting test in thread: " + Thread.currentThread().getName());
            // Putting this in a try catch block because you want to be sure to close the driver to free
            // up the resources even if the test fails
            driver.get("https://www.google.com");
            driver.findElement(By.name("q")).sendKeys("Hello, world");
        } finally {
            driver.quit();
        }
    }
}

Please note that there are a number of things about these examples that I don't like from an architecture standpoint, but I wanted to give you an idea of a few of the issues to think about and a quick working example that is similar to what you already have. Finally, to run test classes in parallel, you would create and then run a TestNG suite. These are usually defined in XML (though you can also use YAML).

<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="MySuiteNameHere" parallel="methods" thread-count="15">

    <test name="Selenium Tests">
        <classes>
            <class name="foo.bar.FooTest"/>
        </classes>
    </test>
</suite>

Finally running that lead to two tests that loaded google and performed the action along with this sample output:

starting test in thread: PoolService-1
starting test in thread: PoolService-0
Aaron Davis
  • 1,711
  • 7
  • 13