[英]Selenium and Parallelized JUnit - WebDriver instances
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实例处理的问题。
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 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成为静态成员,因为每个类实例都必须使用它自己(否则测试会尝试在同一个浏览器中运行)。
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这样的框架,手动处理线程可能是个坏主意。
What is the right design to do this? 做到这一点的正确设计是什么?
I could only think of 我只能想到
@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: 好像:
@Parametrized
test case is split to group of TestWithParameters
for every test parameter @Parametrized
测试用例被拆分为每个测试参数的TestWithParameters
组 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.