Selenium 和并行化 JUnit - WebDriver 实例
Selenium and Parallelized JUnit - WebDriver instances
设置
所以,基本上我正在尝试使用 JUnit 并行实现 运行 的 Selenium 测试。
为此我找到了 this JUnit runner。效果很好,很喜欢
但是,我 运行 遇到了有关 WebDriver 实例处理的问题。
我想要什么
在执行 @Test
方法之前,应为每个 class 创建一次每个 WebDriver 元素。
从逻辑上讲,我可以为此使用 classes 构造函数。实际上这对我的测试来说是非常必要的,因为我需要使用 @Parameters
以便我可以相应地创建 WebDriver 实例 (Chrome,FF,IE ...)。
问题
问题是我希望在 a class 完成之后而不是在 [=68= 之后清除 WebDriver 实例 (driver.quit()
) ]每个@Test
方法都完成了。
但是我不能使用 @AfterClass
因为我不能让 WebDriver 成为静态成员,因为每个 class 实例都必须使用它自己的(否则测试会尝试在同一个浏览器中 运行)。
可能的解决方案
我找到了 M运行al Gosar 的可能建议 here。
按照他的建议,我将 WebDriver 更改为 static ThreadLocal<WebDriver>
,然后我使用
在每个构造函数中创建它的实例
// in the classes constructor
driver = new ThreadLocal<WebDriver>() {
@Override
protected WebDriver initialValue() {
return new FirefoxDriver(); /
}
};
不用说我在代码中用 driver.get().whatever
替换了每个 driver.whatever
调用。
现在,为了解决这个问题的最终目的,我还编写了一个 @AfterClass
方法来调用 driver.get().quit();
,现在编译器可以接受它,因为变量是静态的。
然而,对此进行测试会导致意外行为。我在远程机器上有一个带有 2 个节点 运行ning 的 Selenium 网格设置。我按预期进行了此设置 运行ning,但现在浏览器到处都是垃圾邮件,测试失败。 (虽然 2 个浏览器应该 运行ning 而不是 8+ 打开)
我链接的线程建议这个解决方案有人评论说如果已经使用像 JUnit 这样的框架,手动处理线程可能是个坏主意。
我的问题
这样做的正确设计是什么?
我只能想到
- 使这里的建议有效
- 写一个带注释的@Test
执行所有其他方法然后使用@After 的方法
达到与@AfterClass
相同的效果
- 将构造函数参数保存在成员变量中并处理我必须在每个
@Test
注释方法执行之前创建浏览器的事实(使用 @Before
创建 WebDriver 实例和 @After
关闭会话)
不过我不太清楚选项 3 运行 是否有可能出现问题。如果我在每种方法之后关闭会话,那么网格服务器实际上可能会在此节点上打开一个全新的 class 的新会话,在此节点完成之前的会话之前。虽然测试彼此独立,但我仍然觉得这是潜在的危险。
这里有没有人积极使用多线程 Selenium 测试套件,可以指导我什么是正确的设计?
总的来说我同意:
it might be a bad idea to manually handle threads if already using a
framework like JUnit
但是,看看你提到的 Parallelized
运行 和 junit 4.12 中 @Parametrized
的内部实现是可能的。
每个测试用例都计划执行。默认情况下,junit 在单线程中执行测试用例。 Parallelized
扩展 Parametrized
以单线程测试调度程序替换为多线程调度程序的方式,因此,要了解这如何影响 Parametrized
测试用例 运行 我们必须查看 JUnit Parametrized
来源:
看起来像:
@Parametrized
测试用例被分成 TestWithParameters
组
每个测试参数
- 为
Runner
的每个 TestWithParameters
个实例创建和调度
用于执行(在这种情况下 Runner
实例是专门的
BlockJUnit4ClassRunnerWithParameters
)
实际上,每个@Parametrized 测试用例都会生成一组测试实例到运行(每个参数的单个实例)并且每个实例都是独立调度的 所以在我们的案例(Parallelized
和 @Parametrized
以 WebDriver
实例作为参数进行测试)多个独立测试将在每个 WebDriver
类型的专用线程中执行。这很重要,因为允许我们在当前线程的范围内存储特定的 WebDriver
实例。
请记住此行为依赖于 junit 4.12 的内部实现细节并且可能会更改(例如请参阅 RunnerScheduler
中的评论)。
我看下面的例子。
它依赖于提到的 JUnit 行为,并使用 ThreadLocal
来存储 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
* 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 searchForWhosebug() throws InterruptedException {
openAndSearch(getDriver(), "Whosebug");
}
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));
}
}
会打开两个浏览器,在每个浏览器window同时执行三个测试用例.
控制台将打印如下内容:
pool-1-thread-1, ChromeDriver: created
pool-1-thread-1, ChromeDriver: search for: Whosebug
pool-1-thread-2, FirefoxDriver: created
pool-1-thread-2, FirefoxDriver: search for: Whosebug
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
的东西,快速搜索显示这样的想法已经注册为 issue in Junit
设置
所以,基本上我正在尝试使用 JUnit 并行实现 运行 的 Selenium 测试。
为此我找到了 this JUnit runner。效果很好,很喜欢
但是,我 运行 遇到了有关 WebDriver 实例处理的问题。
我想要什么
在执行 @Test
方法之前,应为每个 class 创建一次每个 WebDriver 元素。
从逻辑上讲,我可以为此使用 classes 构造函数。实际上这对我的测试来说是非常必要的,因为我需要使用 @Parameters
以便我可以相应地创建 WebDriver 实例 (Chrome,FF,IE ...)。
问题
问题是我希望在 a class 完成之后而不是在 [=68= 之后清除 WebDriver 实例 (driver.quit()
) ]每个@Test
方法都完成了。
但是我不能使用 @AfterClass
因为我不能让 WebDriver 成为静态成员,因为每个 class 实例都必须使用它自己的(否则测试会尝试在同一个浏览器中 运行)。
可能的解决方案
我找到了 M运行al Gosar 的可能建议 here。
按照他的建议,我将 WebDriver 更改为 static ThreadLocal<WebDriver>
,然后我使用
// in the classes constructor
driver = new ThreadLocal<WebDriver>() {
@Override
protected WebDriver initialValue() {
return new FirefoxDriver(); /
}
};
不用说我在代码中用 driver.get().whatever
替换了每个 driver.whatever
调用。
现在,为了解决这个问题的最终目的,我还编写了一个 @AfterClass
方法来调用 driver.get().quit();
,现在编译器可以接受它,因为变量是静态的。
然而,对此进行测试会导致意外行为。我在远程机器上有一个带有 2 个节点 运行ning 的 Selenium 网格设置。我按预期进行了此设置 运行ning,但现在浏览器到处都是垃圾邮件,测试失败。 (虽然 2 个浏览器应该 运行ning 而不是 8+ 打开)
我链接的线程建议这个解决方案有人评论说如果已经使用像 JUnit 这样的框架,手动处理线程可能是个坏主意。
我的问题
这样做的正确设计是什么?
我只能想到
- 使这里的建议有效
- 写一个带注释的@Test 执行所有其他方法然后使用@After 的方法 达到与@AfterClass 相同的效果
- 将构造函数参数保存在成员变量中并处理我必须在每个
@Test
注释方法执行之前创建浏览器的事实(使用@Before
创建 WebDriver 实例和@After
关闭会话)
不过我不太清楚选项 3 运行 是否有可能出现问题。如果我在每种方法之后关闭会话,那么网格服务器实际上可能会在此节点上打开一个全新的 class 的新会话,在此节点完成之前的会话之前。虽然测试彼此独立,但我仍然觉得这是潜在的危险。
这里有没有人积极使用多线程 Selenium 测试套件,可以指导我什么是正确的设计?
总的来说我同意:
it might be a bad idea to manually handle threads if already using a framework like JUnit
但是,看看你提到的 Parallelized
运行 和 junit 4.12 中 @Parametrized
的内部实现是可能的。
每个测试用例都计划执行。默认情况下,junit 在单线程中执行测试用例。 Parallelized
扩展 Parametrized
以单线程测试调度程序替换为多线程调度程序的方式,因此,要了解这如何影响 Parametrized
测试用例 运行 我们必须查看 JUnit Parametrized
来源:
看起来像:
@Parametrized
测试用例被分成TestWithParameters
组 每个测试参数- 为
Runner
的每个TestWithParameters
个实例创建和调度 用于执行(在这种情况下Runner
实例是专门的BlockJUnit4ClassRunnerWithParameters
)
实际上,每个@Parametrized 测试用例都会生成一组测试实例到运行(每个参数的单个实例)并且每个实例都是独立调度的 所以在我们的案例(Parallelized
和 @Parametrized
以 WebDriver
实例作为参数进行测试)多个独立测试将在每个 WebDriver
类型的专用线程中执行。这很重要,因为允许我们在当前线程的范围内存储特定的 WebDriver
实例。
请记住此行为依赖于 junit 4.12 的内部实现细节并且可能会更改(例如请参阅 RunnerScheduler
中的评论)。
我看下面的例子。
它依赖于提到的 JUnit 行为,并使用 ThreadLocal
来存储 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
* 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 searchForWhosebug() throws InterruptedException {
openAndSearch(getDriver(), "Whosebug");
}
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));
}
}
会打开两个浏览器,在每个浏览器window同时执行三个测试用例.
控制台将打印如下内容:
pool-1-thread-1, ChromeDriver: created
pool-1-thread-1, ChromeDriver: search for: Whosebug
pool-1-thread-2, FirefoxDriver: created
pool-1-thread-2, FirefoxDriver: search for: Whosebug
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
的东西,快速搜索显示这样的想法已经注册为 issue in Junit