[英]Selenium and Parallelized JUnit - WebDriver instances
所以,基本上我正在尝试使用JUnit实现并行运行的Selenium测试。
为此我找到了这个JUnit跑步者 。 它工作得很好,我很喜欢它。
但是,我遇到了有关WebDriver实例处理的问题。
在执行@Test
方法之前,应为每个类创建一个WebDriver元素。
从逻辑上讲,我可以使用类构造函数。 实际上这是我的测试的要求,因为我需要使用@Parameters
以便我可以相应地创建WebDriver实例(Chrome,FF,IE ......)。
问题是我希望在完成一个类之后清除WebDriver实例( driver.quit()
),而不是在每个@Test
方法完成之后清除它。 但是我不能使用@AfterClass
因为我无法使WebDriver成为静态成员,因为每个类实例都必须使用它自己(否则测试会尝试在同一个浏览器中运行)。
我在这里找到了Mrunal Gosar的可能建议。 根据他的建议,我已经将WebDriver改为static ThreadLocal<WebDriver>
,然后我在每个构造函数中创建它的实例
// in the classes constructor
driver = new ThreadLocal<WebDriver>() {
@Override
protected WebDriver initialValue() {
return new FirefoxDriver(); /
}
};
有人说我替换了每个driver.whatever
driver.get().whatever
使用driver.get().whatever
调用, driver.get().whatever
我的代码是driver.get().whatever
。
现在,为了解决这个问题的最终目的,我还写了一个@AfterClass
方法,它将调用driver.get().quit();
现在由编译器接受,因为变量是静态的。
然而,测试这会导致意外行为。 我有一个Selenium Grid设置,在远程计算机上运行2个节点。 我之前已按预期运行此设置,但现在浏览器全部被垃圾邮件发送并且测试失败。 (虽然应该运行2个浏览器而不是8个以上)
我链接的线程暗示这个解决方案有人评论说,如果已经使用像JUnit这样的框架,手动处理线程可能是个坏主意。
做到这一点的正确设计是什么?
我只能想到
@Test
注释方法之前必须创建浏览器的事实(使用@Before
创建WebDriver实例和@After
关闭会话) 我不太清楚选项3是否会遇到可能出现的问题。 如果我在每个方法之后关闭会话,那么在此节点完成之前的节点之前,网格服务器可能实际上在此节点上打开一个具有全新类的新会话。 虽然测试是相互独立的,但我仍然认为这是潜在的危险。
有没有人在这里积极使用多线程Selenium测试服,并可以指导我什么是正确的设计?
一般来说,我同意:
如果已经使用像JUnit这样的框架,手动处理线程可能是个坏主意
但是,看看你提到的Parallelized
runner和junit 4.12中@Parametrized
内部实现是可能的。
每个测试用例都计划执行。 默认情况下,junit在单线程中执行测试用例。 Parallelized
扩展Parametrized
以单线程测试调度程序被多线程调度程序替换,因此,为了理解这是如何影响Parametrized
测试用例运行的方式,我们必须查看JUnit Parametrized
源:
好像:
@Parametrized
测试用例被拆分为每个测试参数的TestWithParameters
组 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<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));
}
}
它将打开两个浏览器并同时在每个浏览器窗口中执行三个测试用例 。
控制台将打印如下内容:
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中注册为问题
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.