Selenium and Parallelized JUnit - WebDriver instances

前端 未结 1 826
南方客
南方客 2020-12-30 16:58

The setup

So, basically i am trying to achieve Selenium tests that run in parallel using JUnit.

For that i have found this JUnit runner. It works really we

相关标签:
1条回答
  • 2020-12-30 17:37

    In general I agree that:

    it might be a bad idea to manually handle threads if already using a framework like JUnit

    But, looking at Parallelized runner you mentioned and internal implementation of @Parametrized in junit 4.12 it is possible.

    Each test case is scheduled for execution. By default junit executes test cases in single thread. Parallelized extends Parametrized in that way that single threaded test scheduler is replaced with multi-thread scheduler so, to understand how this affects way Parametrized test cases are run we have to look inside JUnit Parametrized sources:

    https://github.com/junit-team/junit/blob/r4.12/src/main/java/org/junit/runners/Parameterized.java#L303

    Looks like:

    1. @Parametrized test case is split to group of TestWithParameters for every test parameter
    2. for every TestWithParameters instance of Runner is created and scheduled for execution (in this case Runner instance is specialized BlockJUnit4ClassRunnerWithParameters)

    In effect, every @Parametrized test case generates group of test instances to run (single instance for every parameter) and every instance is scheduled independently so in our case (Parallelized and @Parametrized test with WebDriver instances as parameters) multiple independent tests will be executed in dedicated threads for every WebDriver type. And this is important because allows us to store specific WebDriver instance in scope of the current thread.

    Please remember this behavior relies on internal implementation details of junit 4.12 and may change (for example see comments in RunnerScheduler).

    Take I look at example below. It relies on mentioned JUnit behavior and uses ThreadLocal to store WebDriver instances shared between test in the same cases groups. Only trick with ThreadLocal is to initialize it only once (in @Before) and destroy every created instance (in @AfterClass).

    package example.junit;
    
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.Collection;
    import java.util.Collections;
    import java.util.Iterator;
    import java.util.List;
    
    import org.apache.commons.lang3.StringUtils;
    import org.junit.AfterClass;
    import org.junit.Before;
    import org.junit.BeforeClass;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.junit.runners.Parameterized;
    import org.openqa.selenium.By;
    import org.openqa.selenium.WebDriver;
    import org.openqa.selenium.WebElement;
    import org.openqa.selenium.chrome.ChromeDriver;
    import org.openqa.selenium.firefox.FirefoxDriver;
    
    /**
     * Parallel Selenium WebDriver example for http://stackoverflow.com/questions/30353996/selenium-and-parallelized-junit-webdriver-instances
     * Parallelized class is like http://hwellmann.blogspot.de/2009/12/running-parameterized-junit-tests-in.html
     */
    @RunWith(Parallelized.class)
    public class ParallelSeleniumTest {
    
        /** Available driver types */
        enum WebDriverType {
            CHROME,
            FIREFOX
        }
    
        /** Create WebDriver instances for specified type */
        static class WebDriverFactory {
            static WebDriver create(WebDriverType type) {
                WebDriver driver;
                switch (type) {
                case FIREFOX:
                    driver = new FirefoxDriver();
                    break;
                case CHROME:
                    driver = new ChromeDriver();
                    break;
                default:
                    throw new IllegalStateException();
                }
                log(driver, "created");
                return driver;
            }
        }
    
        // for description how to user Parametrized
        // see: https://github.com/junit-team/junit/wiki/Parameterized-tests
        @Parameterized.Parameter
        public WebDriverType currentDriverType;
    
        // test case naming requires junit 4.11
        @Parameterized.Parameters(name= "{0}")
        public static Collection<Object[]> driverTypes() {
            return Arrays.asList(new Object[][] {
                    { WebDriverType.CHROME },
                    { WebDriverType.FIREFOX }
                });
        }
    
        private static ThreadLocal<WebDriver> currentDriver = new ThreadLocal<WebDriver>();
        private static List<WebDriver> driversToCleanup = Collections.synchronizedList(new ArrayList<WebDriver>());
    
        @BeforeClass
        public static void initChromeVariables() {
            System.setProperty("webdriver.chrome.driver", "/path/to/chromedriver");
        }
    
        @Before
        public void driverInit() {
            if (currentDriver.get()==null) {
                WebDriver driver = WebDriverFactory.create(currentDriverType);
                driversToCleanup.add(driver);
                currentDriver.set(driver);
            }
        }
    
        private WebDriver getDriver() {
            return currentDriver.get();
        }
    
        @Test
        public void searchForChromeDriver() throws InterruptedException {
            openAndSearch(getDriver(), "chromedriver");
        }
    
        @Test
        public void searchForJunit() throws InterruptedException {
            openAndSearch(getDriver(), "junit");
        }
    
        @Test
        public void searchForStackoverflow() throws InterruptedException {
            openAndSearch(getDriver(), "stackoverflow");
        }
    
        private void openAndSearch(WebDriver driver, String phraseToSearch) throws InterruptedException {
            log(driver, "search for: "+phraseToSearch);
            driver.get("http://www.google.com");
            WebElement searchBox = driver.findElement(By.name("q"));
            searchBox.sendKeys(phraseToSearch);
            searchBox.submit();
            Thread.sleep(3000);
        }
    
        @AfterClass
        public static void driverCleanup() {
            Iterator<WebDriver> iterator = driversToCleanup.iterator();
            while (iterator.hasNext()) {
                WebDriver driver = iterator.next();
                log(driver, "about to quit");
                driver.quit();
                iterator.remove();
            }
        }
    
        private static void log(WebDriver driver, String message) {
            String driverShortName = StringUtils.substringAfterLast(driver.getClass().getName(), ".");
            System.out.println(String.format("%15s, %15s: %s", Thread.currentThread().getName(), driverShortName, message));
        }
    
    }
    

    It will open two browsers and execute three test cases in every browser window concurrently.

    Console will print something like:

    pool-1-thread-1,    ChromeDriver: created
    pool-1-thread-1,    ChromeDriver: search for: stackoverflow
    pool-1-thread-2,   FirefoxDriver: created
    pool-1-thread-2,   FirefoxDriver: search for: stackoverflow
    pool-1-thread-1,    ChromeDriver: search for: junit
    pool-1-thread-2,   FirefoxDriver: search for: junit
    pool-1-thread-1,    ChromeDriver: search for: chromedriver
    pool-1-thread-2,   FirefoxDriver: search for: chromedriver
               main,    ChromeDriver: about to quit
               main,   FirefoxDriver: about to quit
    

    You can see that drivers are created once for every worker thread and destroyed at the end.

    To summarize, we need something like @BeforeParameter and @AfterParameter in context of execution thread and quick search shows that such idea is already registered as issue in Junit

    0 讨论(0)
提交回复
热议问题