使用单个驱动程序实例的 TestNG 并行执行
TestNG parallel execution with single driver instance
在我的测试套件中,我有一个 Base class,我在其中声明了一个静态 WebDriver,然后在打开一个 base url.此方法在我的所有测试用例的 BeforeMethod 中使用,因此在每个测试执行中使用驱动程序的单个静态实例,并贯穿测试用例中使用的一系列可重用方法(例如,登录方法)。
这似乎是一个问题,现在我正在尝试引入并行执行,因为看起来我需要为任何我想要同时 运行 的脚本单独的驱动程序实例,以避免线程干扰。有什么方法可以让我的脚本 运行 并行而不用重新构建我的整个套件,这样每个脚本都是完全独立编写的?
我曾尝试声明一个 WebDriver 数组,然后根据从启动套件 XML 发送的参数选择特定索引,但这没有帮助。第二个浏览器打开,但没有任何反应。
这是我的基地class:
public class Base {
public static WebDriver driver = null;
//Input client under test
public static final String Client = "Client1";
//Input default environment under test
public static final String DefaultEnvironment = "Chrome_Hub";
//CALL WEB BROWSER AND OPEN WEBSITE
public static void openURL(String environment) throws InterruptedException, IOException {
try{
if(environment.equals("Chrome_Hub")) {
System.setProperty("webdriver.chrome.driver", "/Users/rossdonohoe/Desktop/SeleniumJava/Drivers/chromedriver");
driver = new ChromeDriver();
}
if(environment.equals("Firefox_Hub")) {
System.setProperty("webdriver.gecko.driver", "/Users/rossdonohoe/Desktop/SeleniumJava/Drivers/geckodriver");
driver = new FirefoxDriver();
}
driver.get(DataFinder.ReadData("front end url"));
}
catch(Exception E) {
E.printStackTrace();
}
}
}
这是一个示例脚本:
public class LoginLogoutScript extends Base {
@Test (priority = 2)
public void login() throws InterruptedException, IOException {
LoginLogout.loginFrontEnd();
Assert.assertTrue(driver.getPageSource().contains("My Account") || driver.getPageSource().contains("My Dashboard"));
}
@Parameters({ "Environment" })
@BeforeClass
public void setUp(@Optional(DefaultEnvironment) String Environment) throws InterruptedException, IOException {
Base.openURL(Environment);
}
@AfterClass public void tearDown() {
Base.driver.quit();
}
}
这是该脚本中调用的方法:
public class LoginLogout extends Base {
public static void loginFrontEnd () throws InterruptedException, IOException {
Thread.sleep(5000);
WebDriverWait wait = new WebDriverWait(driver, 10);
WebElement login2 = wait.until(ExpectedConditions.elementToBeClickable(By.linkText(DataFinder.ReadData("sign in link"))));
((JavascriptExecutor)driver).executeScript("arguments[0].click();", login2);
WebDriverWait wait = new WebDriverWait(driver, 15);
wait.until(ExpectedConditions.visibilityOfElementLocated(By.id("email")));
driver.findElement(By.id("email")).sendKeys(DataFinder.ReadData("username entry"));
driver.findElement(By.id("pass")).sendKeys("I$$\"Cnewton1");
Thread.sleep(5000);
driver.findElement(By.id("send2")).click();
}
}
这是另一个脚本:
@Listeners(CustomListener.class)
public class CreateDeleteCustomerAccountScript extends Base {
@Test (priority = 1)
public void createAccount() throws InterruptedException, IOException {
WebDriverWait wait = new WebDriverWait(driver, 15);
wait.until(ExpectedConditions.visibilityOfElementLocated(By.partialLinkText(DataFinder.ReadData("navigate to register account 1"))));
driver.findElement(By.partialLinkText(DataFinder.ReadData("navigate to register account 1"))).click();
driver.findElement(By.cssSelector(DataFinder.ReadData("navigate to register account 2"))).click();
driver.findElement(By.name("firstname")).sendKeys("Testy");
driver.findElement(By.name("lastname")).sendKeys("McTester");
driver.findElement(By.cssSelector("#email_address")).sendKeys(Misc.generateRandomString() + "@thepixel.com");
driver.findElement(By.name("password")).sendKeys("I$$\"Cnewton1");
driver.findElement(By.name(DataFinder.ReadData("password confirmation"))).sendKeys("I$$\"Cnewton1");
//verify account created
Thread.sleep(5000);
Assert.assertTrue(driver.findElement(By.tagName("body")).getText().contains(DataFinder.ReadData("account created message")));
}
@Parameters({ "Environment" })
@BeforeClass
public void setUp(@Optional(DefaultEnvironment) String Environment) throws InterruptedException, IOException {
Base.openURL(Environment, "front end");
}
@AfterClass public void tearDown() {
Base.driver.quit();
}
}
这是我的 XML 文件:
<?xml version="1.0" encoding="UTF-8"?>
<suite name="FullRegressionSuite" parallel="tests" thread-count="2">
<listeners>
</listeners>
<test name="Test1">
<parameter name ="DriverNo" value="1"/>
<classes>
<class name="userManagement.LoginLogoutScript"/>
</classes>
</test>
<test name="Test2">
<parameter name ="DriverNo" value="2"/>
<classes>
<class name="userManagement.CreateDeleteCustomerAccountScript"/>"/>
</classes>
</test>
<!-- Test -->
</suite> <!-- Suite -->
您可以使用下面给出的 threadlocal webdriver。
public class Base {
public static ThreadLocal<WebDriver> driver = new ThreadLocal<>();
//Input client under test
public static final String Client = "Client1";
//Input default environment under test
public static final String DefaultEnvironment = "Chrome_Hub";
//CALL WEB BROWSER AND OPEN WEBSITE
public static void openURL(String environment) throws InterruptedException, IOException {
try{
if(environment.equals("Chrome_Hub")) {
System.setProperty("webdriver.chrome.driver", "/Users/rossdonohoe/Desktop/SeleniumJava/Drivers/chromedriver");
driver.set(new ChromeDriver());
}
if(environment.equals("Firefox_Hub")) {
System.setProperty("webdriver.gecko.driver", "/Users/rossdonohoe/Desktop/SeleniumJava/Drivers/geckodriver");
driver.set(new FirefoxDriver());
}
driver.get(DataFinder.ReadData("front end url"));
}
catch(Exception E) {
E.printStackTrace();
}
}
}
您可以使用 driver.get() 方法获取它的实例。
public class LoginLogoutScript extends Base {
@Test (priority = 2)
public void login() throws InterruptedException, IOException {
LoginLogout.loginFrontEnd();
Assert.assertTrue(driver.getPageSource().contains("My Account") || driver.get().getPageSource().contains("My Dashboard"));
}
@Parameters({ "Environment" })
@BeforeClass
public void setUp(@Optional(DefaultEnvironment) String Environment) throws InterruptedException, IOException {
Base.openURL(Environment);
}
@AfterClass public void tearDown() {
Base.driver.get().quit();
}
}
如果您通过 Maven 执行测试,您可以重新考虑 Fork Options so each TestNG thread will be executed in a separate JVM 实例
<forkCount>2</forkCount> <!- amend this to be equal to the number of TestNG threads -->
<reuseForks>false</reuseForks>
然而它只是掩盖了问题,更大的限制是你正在使用 static modifier for WebDriver instance, it definitely violates Parallel Testing Best Practices
在我的测试套件中,我有一个 Base class,我在其中声明了一个静态 WebDriver,然后在打开一个 base url.此方法在我的所有测试用例的 BeforeMethod 中使用,因此在每个测试执行中使用驱动程序的单个静态实例,并贯穿测试用例中使用的一系列可重用方法(例如,登录方法)。
这似乎是一个问题,现在我正在尝试引入并行执行,因为看起来我需要为任何我想要同时 运行 的脚本单独的驱动程序实例,以避免线程干扰。有什么方法可以让我的脚本 运行 并行而不用重新构建我的整个套件,这样每个脚本都是完全独立编写的?
我曾尝试声明一个 WebDriver 数组,然后根据从启动套件 XML 发送的参数选择特定索引,但这没有帮助。第二个浏览器打开,但没有任何反应。
这是我的基地class:
public class Base {
public static WebDriver driver = null;
//Input client under test
public static final String Client = "Client1";
//Input default environment under test
public static final String DefaultEnvironment = "Chrome_Hub";
//CALL WEB BROWSER AND OPEN WEBSITE
public static void openURL(String environment) throws InterruptedException, IOException {
try{
if(environment.equals("Chrome_Hub")) {
System.setProperty("webdriver.chrome.driver", "/Users/rossdonohoe/Desktop/SeleniumJava/Drivers/chromedriver");
driver = new ChromeDriver();
}
if(environment.equals("Firefox_Hub")) {
System.setProperty("webdriver.gecko.driver", "/Users/rossdonohoe/Desktop/SeleniumJava/Drivers/geckodriver");
driver = new FirefoxDriver();
}
driver.get(DataFinder.ReadData("front end url"));
}
catch(Exception E) {
E.printStackTrace();
}
}
}
这是一个示例脚本:
public class LoginLogoutScript extends Base {
@Test (priority = 2)
public void login() throws InterruptedException, IOException {
LoginLogout.loginFrontEnd();
Assert.assertTrue(driver.getPageSource().contains("My Account") || driver.getPageSource().contains("My Dashboard"));
}
@Parameters({ "Environment" })
@BeforeClass
public void setUp(@Optional(DefaultEnvironment) String Environment) throws InterruptedException, IOException {
Base.openURL(Environment);
}
@AfterClass public void tearDown() {
Base.driver.quit();
}
}
这是该脚本中调用的方法:
public class LoginLogout extends Base {
public static void loginFrontEnd () throws InterruptedException, IOException {
Thread.sleep(5000);
WebDriverWait wait = new WebDriverWait(driver, 10);
WebElement login2 = wait.until(ExpectedConditions.elementToBeClickable(By.linkText(DataFinder.ReadData("sign in link"))));
((JavascriptExecutor)driver).executeScript("arguments[0].click();", login2);
WebDriverWait wait = new WebDriverWait(driver, 15);
wait.until(ExpectedConditions.visibilityOfElementLocated(By.id("email")));
driver.findElement(By.id("email")).sendKeys(DataFinder.ReadData("username entry"));
driver.findElement(By.id("pass")).sendKeys("I$$\"Cnewton1");
Thread.sleep(5000);
driver.findElement(By.id("send2")).click();
}
}
这是另一个脚本:
@Listeners(CustomListener.class)
public class CreateDeleteCustomerAccountScript extends Base {
@Test (priority = 1)
public void createAccount() throws InterruptedException, IOException {
WebDriverWait wait = new WebDriverWait(driver, 15);
wait.until(ExpectedConditions.visibilityOfElementLocated(By.partialLinkText(DataFinder.ReadData("navigate to register account 1"))));
driver.findElement(By.partialLinkText(DataFinder.ReadData("navigate to register account 1"))).click();
driver.findElement(By.cssSelector(DataFinder.ReadData("navigate to register account 2"))).click();
driver.findElement(By.name("firstname")).sendKeys("Testy");
driver.findElement(By.name("lastname")).sendKeys("McTester");
driver.findElement(By.cssSelector("#email_address")).sendKeys(Misc.generateRandomString() + "@thepixel.com");
driver.findElement(By.name("password")).sendKeys("I$$\"Cnewton1");
driver.findElement(By.name(DataFinder.ReadData("password confirmation"))).sendKeys("I$$\"Cnewton1");
//verify account created
Thread.sleep(5000);
Assert.assertTrue(driver.findElement(By.tagName("body")).getText().contains(DataFinder.ReadData("account created message")));
}
@Parameters({ "Environment" })
@BeforeClass
public void setUp(@Optional(DefaultEnvironment) String Environment) throws InterruptedException, IOException {
Base.openURL(Environment, "front end");
}
@AfterClass public void tearDown() {
Base.driver.quit();
}
}
这是我的 XML 文件:
<?xml version="1.0" encoding="UTF-8"?>
<suite name="FullRegressionSuite" parallel="tests" thread-count="2">
<listeners>
</listeners>
<test name="Test1">
<parameter name ="DriverNo" value="1"/>
<classes>
<class name="userManagement.LoginLogoutScript"/>
</classes>
</test>
<test name="Test2">
<parameter name ="DriverNo" value="2"/>
<classes>
<class name="userManagement.CreateDeleteCustomerAccountScript"/>"/>
</classes>
</test>
<!-- Test -->
</suite> <!-- Suite -->
您可以使用下面给出的 threadlocal webdriver。
public class Base {
public static ThreadLocal<WebDriver> driver = new ThreadLocal<>();
//Input client under test
public static final String Client = "Client1";
//Input default environment under test
public static final String DefaultEnvironment = "Chrome_Hub";
//CALL WEB BROWSER AND OPEN WEBSITE
public static void openURL(String environment) throws InterruptedException, IOException {
try{
if(environment.equals("Chrome_Hub")) {
System.setProperty("webdriver.chrome.driver", "/Users/rossdonohoe/Desktop/SeleniumJava/Drivers/chromedriver");
driver.set(new ChromeDriver());
}
if(environment.equals("Firefox_Hub")) {
System.setProperty("webdriver.gecko.driver", "/Users/rossdonohoe/Desktop/SeleniumJava/Drivers/geckodriver");
driver.set(new FirefoxDriver());
}
driver.get(DataFinder.ReadData("front end url"));
}
catch(Exception E) {
E.printStackTrace();
}
}
}
您可以使用 driver.get() 方法获取它的实例。
public class LoginLogoutScript extends Base {
@Test (priority = 2)
public void login() throws InterruptedException, IOException {
LoginLogout.loginFrontEnd();
Assert.assertTrue(driver.getPageSource().contains("My Account") || driver.get().getPageSource().contains("My Dashboard"));
}
@Parameters({ "Environment" })
@BeforeClass
public void setUp(@Optional(DefaultEnvironment) String Environment) throws InterruptedException, IOException {
Base.openURL(Environment);
}
@AfterClass public void tearDown() {
Base.driver.get().quit();
}
}
如果您通过 Maven 执行测试,您可以重新考虑 Fork Options so each TestNG thread will be executed in a separate JVM 实例
<forkCount>2</forkCount> <!- amend this to be equal to the number of TestNG threads -->
<reuseForks>false</reuseForks>
然而它只是掩盖了问题,更大的限制是你正在使用 static modifier for WebDriver instance, it definitely violates Parallel Testing Best Practices