Selenium和Parallelized JUnit – WebDriver实例

设置

所以,基本上我正在尝试使用JUnit实现并行运行的Selenium测试。

为此我找到了这个JUnit跑步者 。 它工作得很好,我很喜欢它。

但是,我遇到了有关WebDriver实例处理的问题。

我想要的是

在执行@Test方法之前,应为每个类创建一个WebDriver元素。

从逻辑上讲,我可以使用类构造函数。 实际上这是我的测试的要求,因为我需要使用@Parameters以便我可以相应地创建WebDriver实例(Chrome,FF,IE ……)。

问题

问题是我希望在完成一个类之后清除WebDriver实例( driver.quit() ),而不是在每个@Test方法完成之后清除它。 但是我不能使用@AfterClass因为我无法使WebDriver成为静态成员,因为每个类实例都必须使用它自己(否则测试会尝试在同一个浏览器中运行)。

可能的解决方案

我在这里找到了Mrunal Gosar的可能建议。 根据他的建议,我已经将WebDriver改为static ThreadLocal ,然后我在每个构造函数中创建它的实例

  // in the classes constructor driver = new ThreadLocal() { @Override protected WebDriver initialValue() { return new FirefoxDriver(); / } }; 

有人说我替换了每个driver.whatever driver.get().whatever使用driver.get().whatever调用driver.get().whatever ,我的代码都是如此。

现在,为了解决这个问题的最终目的,我还写了一个@AfterClass方法,它将调用driver.get().quit(); 现在由编译器接受,因为变量是静态的。

然而,测试这会导致意外行为。 我有一个Selenium Grid设置,在远程计算机上运行2个节点。 我之前已按预期运行此设置,但现在浏览器全部被垃圾邮件发送并且测试失败。 (虽然应该运行2个浏览器而不是8个以上)

我链接的线程暗示这个解决方案有人评论说,如果已经使用像JUnit这样的框架,手动处理线程可能是个坏主意。

我的问题

做到这一点的正确设计是什么?

我只能想到

  1. 使这里建议的工作
  2. 编写一个@Test注释方法,执行所有其他方法,然后使用@After实现与@AfterClass相同的方法
  3. 将构造函数参数保存在成员变量中,并处理在执行每个@Test注释方法之前必须创建浏览器的事实(使用@Before创建WebDriver实例和@After关闭会话)

我不太清楚选项3是否会遇到可能出现的问题。 如果我在每个方法之后关闭会话,那么在此节点完成之前的节点之前,网格服务器实际上可能会在此节点上打开一个具有全新类的新会话。 虽然测试是相互独立的,但我仍然认为这是潜在的危险。

有没有人在这里积极使用multithreadingSelenium测试服,并可以指导我什么是正确的设计?

一般来说,我同意:

如果已经使用像JUnit这样的框架,那么手动处理线程可能是个坏主意

但是,看看你提到的Parallelized runner和junit 4.12中@Parametrized内部实现是可能的。

每个测试用例都计划执行。 默认情况下,junit在单线程中执行测试用例。 Parallelized扩展Parametrized以单线程测试调度程序被multithreading调度程序替换,因此,为了理解这是如何影响Parametrized测试用例运行的方式,我们必须查看JUnit Parametrized源:

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

好像:

  1. @Parametrized测试用例被拆分为每个测试参数的TestWithParameters
  2. Runner每个TestWithParameters实例创建并安排执行(在这种情况下Runner实例是专门的BlockJUnit4ClassRunnerWithParameters

实际上, 每个@Parametrized测试用例都会生成一组要运行的测试实例(每个参数都有一个实例),并且每个实例都是独立调度的,因此在我们的情况下(使用WebDriver实例作为参数进行Parallelized@Parametrized测试)将执行多个独立测试在每个WebDriver类型的专用线程中。 这很重要,因为我们可以将特定的WebDriver实例存储在当前线程的范围内。

记住,此行为依赖于junit 4.12的内部实现细节,并且可能会更改 (例如,请参阅RunnerScheduler中的RunnerScheduler )。

我看下面的例子。 它依赖于提到的JUnit行为,并使用ThreadLocal存储在相同案例组中的test之间共享的WebDriver实例。 只有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 driverTypes() { return Arrays.asList(new Object[][] { { WebDriverType.CHROME }, { WebDriverType.FIREFOX } }); } private static ThreadLocal currentDriver = new ThreadLocal(); private static List driversToCleanup = Collections.synchronizedList(new ArrayList()); @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 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)); } } 

它将打开两个浏览器并同时在每个浏览器窗口中执行三个测试用例

控制台将打印如下内容:

 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 

您可以看到为每个工作线程创建了一次驱动程序,并在最后销毁。

总而言之,我们在执行线程的上下文中需要@BeforeParameter@AfterParameter这样的东西,快速搜索显示这样的想法已经在Junit中注册为问题