我有一个
TestListener
类,它实现了ITestListener
,它负责在selenium测试失败时拍照并保存html。
我的硒测试有一个父类
BaseTest
,它有助于执行常见任务,例如启动每个驱动程序。
各重要区域的内容:
TestListener
public class TestListener implements ITestListener {
WebDriver driver=null;
ITestContext context = null;
String filePath = "artifacts/";
@Override
public void onTestFailure(ITestResult result) {
ITestContext context = result.getTestContext();
driver = (WebDriver) context.getAttribute("WebDriver");
System.out.println("***** Error "+result.getName()+" test has failed *****");
String methodName=result.getName().toString().trim();
String currentTime = getCurrentTime();
saveScreenShot(methodName, currentTime);
savePageSource(methodName, currentTime);
saveConsoleLog(methodName, currentTime);
}
...
BaseTest
(每个测试单独调用此)
private void testngSetup(ITestContext context){
context.setAttribute("WebDriver", driver);
TestRunner runner = (TestRunner) context;
runner.setOutputDirectory(artifactLocation);
}
pom.xml
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
<configuration>
<suiteXmlFiles>
<suiteXmlFile>testng.xml</suiteXmlFile>
</suiteXmlFiles>
<useSystemClassLoader>false</useSystemClassLoader>
</configuration>
</plugin>
testng.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="esm4hpc">
<listeners>
<listener class-name="selenium.listeners.TestListener"/>
</listeners>
<test name="sample-Test" verbose="2" parallel = "classes">
<packages>
<package name=".*"/>
</packages>
</test>
</suite>
我找到了这个线程(How to get the current class driver in ItestListener),它建议按照以下方式更改在
ITestListener
中获取驱动程序:(WebDriver)result.getTestClass().getRealClass().getDeclaredField("driver").get(result.getInstance())
,但它会抛出java.lang.NoSuchFieldException : driver
public class TestListener implements ITestListener {
WebDriver driver=null;
ITestContext context = null;
String filePath = "artifacts/";
@Override
public void onTestFailure(ITestResult result) {
ITestContext context = result.getTestContext();
driver = (WebDriver)result.getTestClass().getRealClass().getDeclaredField("driver").get(result.getInstance());
System.out.println("***** Error "+result.getName()+" test has failed *****");
String methodName=result.getName().toString().trim();
String currentTime = getCurrentTime();
saveScreenShot(methodName, currentTime);
savePageSource(methodName, currentTime);
saveConsoleLog(methodName, currentTime);
}
...
我还尝试将
parallel
中的 testng.xml
标签设置为 false,但似乎没有帮助。
我也尝试过单独列出每个测试类,但它也返回了错误的图片。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="esm4hpc">
<listeners>
<listener class-name="selenium.listeners.TestListener"/>
</listeners>
<test name="sample-Test" verbose="2" parallel = "false">
<classes>
<class name="selenium.esm4hpc.AuthenticationTest"/>
<class name="selenium.esm4hpc.FileEditorTest"/>
<class name="selenium.esm4hpc.FileManagerTest"/>
<class name="selenium.esm4hpc.JobTest"/>
<class name="selenium.esm4hpc.ProjectTest"/>
</classes>
</test>
</suite>
唯一有帮助的似乎是将每个测试类移动到不同的测试用例中:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="esm4hpc">
<listeners>
<listener class-name="selenium.listeners.TestListener"/>
</listeners>
<test name="AuthenticationTest" verbose="2" parallel = "false">
<classes>
<class name="selenium.esm4hpc.AuthenticationTest"/>
</classes>
</test>
<test name="FileEditorTest" verbose="2" parallel = "false">
<classes>
<class name="selenium.esm4hpc.FileEditorTest"/>
</classes>
</test>
<test name="FileManagerTest" verbose="2" parallel = "false">
<classes>
<class name="selenium.esm4hpc.FileManagerTest"/>
</classes>
</test>
<test name="JobTest" verbose="2" parallel = "false">
<classes>
<class name="selenium.esm4hpc.JobTest"/>
</classes>
</test>
<test name="ProjectTest" verbose="2" parallel = "false">
<classes>
<class name="selenium.esm4hpc.ProjectTest"/>
</classes>
</test>
</suite>
但这会导致结果文件需要单独生成,导致调试失败的情况变得很麻烦。
让我们从清理一些细节开始。
ITestContext
是 TestNG 指向 <test>
标签的 API 方式(来自套件术语)ITestResult
是 TestNG 的 API 方式,用于指向特定 @Test
或配置方法的执行结果。ITestContext
。因此,一个 ITestContext
中可以包含一个或多个 ITestResult
对象。@Test
方法中,如果您调用 org.testng.Reporter.getCurrentTestResult()
,您将始终可以访问当前运行的测试方法的结果。ITestResult.setAttribute()
方法可让您将属性附加到特定测试方法,类似于 ITestContext.setAttribute()
允许您在 <test>
标签级别附加属性,而 ISuite.setAttribute()
在 <suite>
标签级别附加属性。要解决您遇到的问题,您需要执行以下操作:
driver = (WebDriver) context.getAttribute("WebDriver");
至 driver = (WebDriver) testResult.getAttribute("WebDriver");
ITestResult
对象,作为使用其 setAttribute()
方法的属性。您需要确保通过 @Test
方法显式调用设置方法,或者通过其 run()
方法隐式调用设置方法(如果您的基类正在实现 TestNG 提供的 IHookable
接口)确保共享 WebDriver 实例以及在测试类和侦听器中访问它时绝对线程安全的最简单方法是执行以下示例中所示的操作。
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.testng.ITestListener;
import org.testng.ITestResult;
import org.testng.Reporter;
import java.util.Optional;
public class WebDriverLifeCycleManager implements ITestListener {
public static final String DRIVER = "driver";
public static RemoteWebDriver driver() {
return Optional.ofNullable(Reporter.getCurrentTestResult().getAttribute(DRIVER))
.map(it -> (RemoteWebDriver) it)
.orElseThrow(() -> new IllegalArgumentException("Could not find driver"));
}
@Override
public void onTestStart(ITestResult result) {
ChromeDriver driver = new ChromeDriver();
result.setAttribute(DRIVER, driver);
}
@Override
public void onTestSuccess(ITestResult result) {
cleanupDriverReference(result);
}
@Override
public void onTestFailure(ITestResult result) {
//Add logic to capture screenshot etc.,
cleanupDriverReference(result);
}
@Override
public void onTestFailedButWithinSuccessPercentage(ITestResult result) {
cleanupDriverReference(result);
}
@Override
public void onTestSkipped(ITestResult result) {
cleanupDriverReference(result);
}
private void cleanupDriverReference(ITestResult result) {
Optional.ofNullable(result.getAttribute(DRIVER))
.map(it -> (RemoteWebDriver) it)
.ifPresent(RemoteWebDriver::quit);
result.removeAttribute(DRIVER);
}
}
这是一个显式使用上述侦听器的测试类(您可以将其更改为使用自动连接侦听器的服务加载方法,如文档中所述)
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;
@Listeners(WebDriverLifeCycleManager.class)
public class ThreadSafeWebDriverSample {
@Test
public void test() {
WebDriverLifeCycleManager.driver().get("https://www.google.com");
}
}