In Junit 5 I'm trying to get a test class method to run from an extension. I'm using the Junit 5 extension interface, TestWatcher, and overriding the testFailed() method.
The purpose of this extension is to take a screen shot on failure in the test class's Selenium WebDriver browser and attach it to that test's Allure report. The test class method has the instantiated browser and annotation for attaching to Allure. And my takeScreenshot method relies on the browser and a testName string from the test class to run correctly.
package utils;
public class ScreenshotOnFailureExtension implements TestWatcher{
@Override
public void testFailed(ExtensionContext context, Throwable cause) {
try {
Object clazz = context.getRequiredTestInstance();
Method takeScreenshot = clazz.getClass().getMethod("takeScreenshot");
takeScreenshot.setAccessible(true);
Object test = clazz.getClass().getConstructor().newInstance();
takeScreenshot.invoke(test);
} catch (Exception e) {
e.printStackTrace();
}
}
And the code in my test class is something like this:
package tests;
@ExtendWith(ScreenshotOnFailureExtension.class)
public class MyTest implements Config {
public WebDriver driver;
public String testName;
//bunch of Junit5 annotations with functions to initialize above variables omitted...
//take a screen shot
public void takeScreenshot() {
System.out.println("Taking screenshot.");
byte[] srcFile=((TakesScreenshot)driver).getScreenshotAs(OutputType.BYTES);
saveScreenshot(srcFile, testName+ ".png");
}
//this attaches screenshot to an allure test result
@Attachment(value = "{testName}", type = "image/png")
public byte[] saveScreenshot(byte[] screenShot, String testName) {
System.out.println("Attaching screenshot to Allure report");
return screenShot;
}
}
The above test class is able to take a screen shot correctly when calling from @AfterEach in the test method. But I only want to take it on a failure.
When I run the test it calls takeScreenshot, but then gives an exception while executing it:
Taking screenshot.java.lang.reflect.InvocationTargetException
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:566) at utils.ScreenshotOnFailureExtension.testFailed(ScreenshotOnFailureExtension.java:49) at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$nodeFinished$14(TestMethodTestDescriptor.java:299) at org.junit.jupiter.engine.descriptor.MethodBasedTestDescriptor.lambda$invokeTestWatchers$3(MethodBasedTestDescriptor.java:134) at java.base/java.util.ArrayList.forEach(ArrayList.java:1540) at org.junit.jupiter.engine.descriptor.MethodBasedTestDescriptor.invokeTestWatchers(MethodBasedTestDescriptor.java:132) at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.nodeFinished(TestMethodTestDescriptor.java:290) at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.nodeFinished(TestMethodTestDescriptor.java:65) at org.junit.platform.engine.support.hierarchical.NodeTestTask.reportCompletion(NodeTestTask.java:176) at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:89) at java.base/java.util.ArrayList.forEach(ArrayList.java:1540) at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:143) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129) at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126) at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84) at java.base/java.util.ArrayList.forEach(ArrayList.java:1540) at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:143) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129) at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126) at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84) at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32) at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57) at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51) at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:108) at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88) at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54) at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67) at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52) at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:96) at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:75) at org.eclipse.jdt.internal.junit5.runner.JUnit5TestReference.run(JUnit5TestReference.java:89) at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:41) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:541) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:763) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:463) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:209) Caused by: java.lang.NullPointerException at tests.Base.takeScreenshot(Base.java:240) ... 49 more
You can see my logging statement being output before the NullPointerException caused by the next line of code in that method (referencing the driver
from the test instance). Is there a correct way to trigger the existing test instance's takeScreenshot()
method in context?
OR
If there is a simpler way to take a screen shot on failure directly in the test's @AfterEach
method, PLEASE let me know. Seems like a pretty basic use case. :)
WebDriver
from the extension and then migrate the screenshot logic to the extension? – GlainstakeScreenshot.invoke(clazz);
I'm now getting a Selenium exception org.openqa.selenium.NoSuchSessionException. I added some logging and it seems the Junit5 TestWatcher testFailed() method is being invoked after the @AfterEach method in the test where I'm quitting the browser. Seems to me like the TestWatcher methods should be done before any of the cleanup/teardown part of the test lifecycle. – Joel Kruse