简体   繁体   English

Selenium和Parallelized JUnit - WebDriver实例

[英]Selenium and Parallelized JUnit - WebDriver instances

The setup 设置

So, basically i am trying to achieve Selenium tests that run in parallel using JUnit. 所以,基本上我正在尝试使用JUnit实现并行运行的Selenium测试。

For that i have found this JUnit runner . 为此我找到了这个JUnit跑步者 It works really well, i like it alot. 它工作得很好,我很喜欢它。

However, i am running into problems regarding the handling of WebDriver instances. 但是,我遇到了有关WebDriver实例处理的问题。

What i want 我想要的是

Each WebDriver element should be created once for every class before @Test methods are executed. 在执行@Test方法之前,应为每个类创建一个WebDriver元素。

Logically, i could use the classes constructor for this. 从逻辑上讲,我可以使用类构造函数。 Actually this is quite the requirement for my tests because i need to make use of the @Parameters so that i can create the WebDriver instance accordingly (Chrome,FF,IE ...). 实际上这是我的测试的要求,因为我需要使用@Parameters以便我可以相应地创建WebDriver实例(Chrome,FF,IE ......)。

The problem 问题

The problem is that i want the WebDriver instance to be cleared ( driver.quit() ) after a class is done and not after each @Test method is done. 问题是我希望在完成一个类之后清除WebDriver实例( driver.quit() ),而不是在每个@Test方法完成之后清除它。 But i cannot use @AfterClass because i cannot make WebDriver a static member, since each class instance has to use its own (otherwise tests would try to run in the same browser). 但是我不能使用@AfterClass因为我无法使WebDriver成为静态成员,因为每个类实例都必须使用它自己(否则测试会尝试在同一个浏览器中运行)。

A possible solution 可能的解决方案

I have found a possible suggestion here by Mrunal Gosar . 我在这里找到了Mrunal Gosar的可能建议。 Following his advise i have changed WebDriver to be a static ThreadLocal<WebDriver> instead and then i create instances of it in each constructor using 根据他的建议,我已经将WebDriver改为static ThreadLocal<WebDriver> ,然后我在每个构造函数中创建它的实例

 // in the classes constructor

driver = new ThreadLocal<WebDriver>() {
           @Override
           protected WebDriver initialValue() {
                 return new FirefoxDriver(); /
           }
};

Needles to say that i replaced every driver.whatever call with driver.get().whatever in my code. 有人说我替换了每个driver.whatever driver.get().whatever使用driver.get().whatever调用, driver.get().whatever我的代码是driver.get().whatever

Now, to address the ultimate purpose of this i also wrote a @AfterClass method that would call driver.get().quit(); 现在,为了解决这个问题的最终目的,我还写了一个@AfterClass方法,它将调用driver.get().quit(); which is now accepted by the compiler, as the variable is static. 现在由编译器接受,因为变量是静态的。

Testing this however leads to unexpected behavior. 然而,测试这会导致意外行为。 I have a Selenium Grid setup with 2 nodes running on a remote machine. 我有一个Selenium Grid设置,在远程计算机上运行2个节点。 I had this setup running as expected before, but now browsers are spammed all over and tests fail. 我之前已按预期运行此设置,但现在浏览器全部被垃圾邮件发送并且测试失败。 (While 2 browsers should be running instead 8+ are opened) (虽然应该运行2个浏览器而不是8个以上)

The thread i linked suggesting this solution had someone commenting that it might be a bad idea to manually handle threads if already using a framework like JUnit. 我链接的线程暗示这个解决方案有人评论说,如果已经使用像JUnit这样的框架,手动处理线程可能是个坏主意。

My Question 我的问题

What is the right design to do this? 做到这一点的正确设计是什么?

I could only think of 我只能想到

  1. Make what is suggested here work 使这里建议的工作
  2. Write a single @Test annotated method that executes all other methods and then use @After to achieve the same as @AfterClass 编写一个@Test注释方法,执行所有其他方法,然后使用@After实现与@AfterClass相同的方法
  3. Save the constructor parameter in a member variable and deal with the fact that i have to create a browser before each @Test annotated method is executed (Using @Before to create the WebDriver instance and @After to close the session) 将构造函数参数保存在成员变量中,并处理在执行每个@Test注释方法之前必须创建浏览器的事实(使用@Before创建WebDriver实例和@After关闭会话)

I don't quite know if option 3 runs into possible problems though. 我不太清楚选项3是否会遇到可能出现的问题。 If i close the session after each method, then the grid-Server might actually open a new session with an entirely new class on this node before this one has finished the previous ones. 如果我在每个方法之后关闭会话,那么在此节点完成之前的节点之前,网格服务器可能实际上在此节点上打开一个具有全新类的新会话。 While the tests are independent of each other, i still feel like this is potential danger. 虽然测试是相互独立的,但我仍然认为这是潜在的危险。

Is anyone on here actively using a multithreaded Selenium test suit and can guide me what is proper design? 有没有人在这里积极使用多线程Selenium测试服,并可以指导我什么是正确的设计?

In general I agree that: 一般来说,我同意:

it might be a bad idea to manually handle threads if already using a framework like JUnit 如果已经使用像JUnit这样的框架,手动处理线程可能是个坏主意

But, looking at Parallelized runner you mentioned and internal implementation of @Parametrized in junit 4.12 it is possible. 但是,看看你提到的Parallelized runner和junit 4.12中@Parametrized内部实现是可能的。

Each test case is scheduled for execution. 每个测试用例都计划执行。 By default junit executes test cases in single thread. 默认情况下,junit在单线程中执行测试用例。 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: Parallelized扩展Parametrized以单线程测试调度程序被多线程调度程序替换,因此,为了理解这是如何影响Parametrized测试用例运行的方式,我们必须查看JUnit Parametrized源:

https://github.com/junit-team/junit/blob/r4.12/src/main/java/org/junit/runners/Parameterized.java#L303 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 @Parametrized测试用例被拆分为每个测试参数的TestWithParameters
  2. for every TestWithParameters instance of Runner is created and scheduled for execution (in this case Runner instance is specialized BlockJUnit4ClassRunnerWithParameters ) Runner每个TestWithParameters实例创建并安排执行(在这种情况下Runner实例是专门的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. 实际上, 每个@Parametrized测试用例都会生成一组要运行的测试实例(每个参数都有一个实例),并且每个实例都是独立调度的,因此在我们的情况下(使用WebDriver实例作为参数进行Parallelized@Parametrized测试)将执行多个独立测试在每个WebDriver类型的专用线程中。 And this is important because allows us to store specific WebDriver instance in scope of the current thread. 这很重要,因为我们可以将特定的WebDriver实例存储在当前线程的范围内。

Please remember this behavior relies on internal implementation details of junit 4.12 and may change (for example see comments in RunnerScheduler ). 记住,此行为依赖于junit 4.12的内部实现细节,并且可能会更改 (例如,请参阅RunnerScheduler中的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. 它依赖于提到的JUnit行为,并使用ThreadLocal存储在相同案例组中的test之间共享的WebDriver实例。 Only trick with ThreadLocal is to initialize it only once (in @Before) and destroy every created instance (in @AfterClass). 只有ThreadLocal技巧只是初始化它一次(在@Before中)并销毁每个创建的实例(在@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 总而言之,我们在执行线程的上下文中需要@BeforeParameter@AfterParameter这样的东西,快速搜索显示这样的想法已经在Junit中注册为问题

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM