diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties
index 754bd26d..09bd0996 100644
--- a/.mvn/wrapper/maven-wrapper.properties
+++ b/.mvn/wrapper/maven-wrapper.properties
@@ -16,5 +16,5 @@
# under the License.
wrapperVersion=3.3.2
distributionType=bin
-distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.6/apache-maven-3.9.6-bin.zip
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.11/apache-maven-3.9.11-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar
diff --git a/pom.xml b/pom.xml
index 31a37104..afa4a41f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -47,12 +47,12 @@
true
5.13.4
- 5.18.0
+ 5.19.0
2.19.2
8.12.6
4.38.0
2.0.17
- 4.34.0
+ 4.35.0
@@ -183,7 +183,7 @@
io.appium
java-client
- 9.5.0
+ 10.0.0
@@ -227,7 +227,7 @@
com.slack.api
slack-api-client
- 1.45.3
+ 1.45.4
@@ -323,7 +323,7 @@
com.puppycrawl.tools
checkstyle
- 10.26.1
+ 11.0.0
com.github.sevntu-checkstyle
@@ -485,7 +485,7 @@
org.codehaus.mojo
flatten-maven-plugin
- 1.7.1
+ 1.7.2
true
ossrh
diff --git a/spectrum/pom.xml b/spectrum/pom.xml
index 0a9e9be5..7fed8853 100644
--- a/spectrum/pom.xml
+++ b/spectrum/pom.xml
@@ -328,7 +328,7 @@
org.apache.maven.plugins
maven-javadoc-plugin
- 3.11.2
+ 3.11.3
-Xdoclint:none
diff --git a/spectrum/src/main/java/io/github/giulong/spectrum/SpectrumEntity.java b/spectrum/src/main/java/io/github/giulong/spectrum/SpectrumEntity.java
index acc41d2b..35213ba4 100644
--- a/spectrum/src/main/java/io/github/giulong/spectrum/SpectrumEntity.java
+++ b/spectrum/src/main/java/io/github/giulong/spectrum/SpectrumEntity.java
@@ -13,6 +13,7 @@
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import net.datafaker.Faker;
+import org.junit.jupiter.api.extension.ExtensionContext;
import org.openqa.selenium.By;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebDriver;
@@ -28,10 +29,15 @@
import java.nio.file.Path;
import java.security.MessageDigest;
import java.util.Arrays;
+import java.util.Map;
import static com.aventstack.extentreports.MediaEntityBuilder.createScreenCaptureFromPath;
import static com.aventstack.extentreports.Status.*;
import static io.github.giulong.spectrum.enums.Frame.MANUAL;
+import static io.github.giulong.spectrum.extensions.resolvers.DriverResolver.DRIVER;
+import static io.github.giulong.spectrum.extensions.resolvers.TestContextResolver.EXTENSION_CONTEXT;
+import static io.github.giulong.spectrum.utils.web_driver_events.ScreenshotConsumer.SCREENSHOT;
+import static org.junit.jupiter.api.extension.ExtensionContext.Namespace.GLOBAL;
import static org.openqa.selenium.OutputType.BYTES;
@Slf4j
@@ -39,6 +45,7 @@ public abstract class SpectrumEntity, Data> {
public static final String HASH_ALGORITHM = "SHA-256";
+ private final ContextManager contextManager = ContextManager.getInstance();
private final FileUtils fileUtils = FileUtils.getInstance();
private final HtmlUtils htmlUtils = HtmlUtils.getInstance();
@@ -174,27 +181,27 @@ public T screenshotFail(final String msg) {
*
* @param msg the message to log
* @param status the log's status
- * @return the generated screenshot
*/
- @SneakyThrows
- public Media addScreenshotToReport(final String msg, final Status status) {
- final String fileName = fileUtils.getScreenshotNameFrom(MANUAL, statefulExtentTest);
- final Path screenshotPath = testData.getScreenshotFolderPath().resolve(fileName);
+ public void addScreenshotToReport(final String msg, final Status status) {
+ final ExtensionContext context = testContext.get(EXTENSION_CONTEXT, ExtensionContext.class);
+ final byte[] screenshot = ((TakesScreenshot) context.getStore(GLOBAL).get(DRIVER, WebDriver.class)).getScreenshotAs(BYTES);
- Files.write(screenshotPath, ((TakesScreenshot) driver).getScreenshotAs(BYTES));
+ eventsDispatcher.fire(SCREENSHOT, SCREENSHOT, Map.of(EXTENSION_CONTEXT, context, SCREENSHOT, screenshot));
- final Media screenshot = createScreenCaptureFromPath(screenshotPath.toString()).build();
+ final Path path = fileUtils.writeTempFile("screenshot", ".png", screenshot);
+ final Media media = createScreenCaptureFromPath(path.toString()).build();
- if (msg == null) {
- statefulExtentTest.getCurrentNode().log(status, (String) null, screenshot);
- } else {
- final int frameNumber = configuration.getVideo().getAndIncrementFrameNumberFor(testData, MANUAL);
- final String tag = htmlUtils.buildFrameTagFor(frameNumber, msg, testData, "screenshot-message");
+ contextManager.getScreenshots().put(path, screenshot);
- statefulExtentTest.getCurrentNode().log(status, tag, screenshot);
+ if (msg == null) {
+ statefulExtentTest.getCurrentNode().log(status, (String) null, media);
+ return;
}
- return screenshot;
+ final int frameNumber = configuration.getVideo().getAndIncrementFrameNumberFor(testData, MANUAL);
+ final String tag = htmlUtils.buildFrameTagFor(frameNumber, msg, testData, "screenshot-message");
+
+ statefulExtentTest.getCurrentNode().log(status, tag, media);
}
/**
diff --git a/spectrum/src/main/java/io/github/giulong/spectrum/extensions/interceptors/SpectrumInterceptor.java b/spectrum/src/main/java/io/github/giulong/spectrum/extensions/interceptors/SpectrumInterceptor.java
index 2ff8ca71..e7ef1b8e 100644
--- a/spectrum/src/main/java/io/github/giulong/spectrum/extensions/interceptors/SpectrumInterceptor.java
+++ b/spectrum/src/main/java/io/github/giulong/spectrum/extensions/interceptors/SpectrumInterceptor.java
@@ -43,7 +43,9 @@ public void interceptDynamicTest(final Invocation invocation, final Dynami
final String testName = context.getDisplayName();
final Path dynamicVideoPath = Path.of(String.format("%s-%s.mp4", fileUtils.removeExtensionFrom(testData.getVideoPath().toString()), testName));
final ExtentTest currentNode = statefulExtentTest.createNode(testName);
+ final Set tags = Set.of(DYNAMIC_TEST);
+ testData.setDynamic(true);
testData.setFrameNumber(0);
testData.setDisplayName(testName);
testData.setDynamicVideoPath(dynamicVideoPath);
@@ -56,12 +58,13 @@ public void interceptDynamicTest(final Invocation invocation, final Dynami
}
try {
- eventsDispatcher.fire(className, testName, BEFORE, null, Set.of(DYNAMIC_TEST), context);
+ eventsDispatcher.fire(className, testName, BEFORE_EXECUTION, null, tags, context);
+ eventsDispatcher.fire(className, testName, BEFORE, null, tags, context);
invocation.proceed();
- eventsDispatcher.fire(className, testName, AFTER, SUCCESSFUL, Set.of(DYNAMIC_TEST), context);
+ eventsDispatcher.fire(className, testName, AFTER, SUCCESSFUL, tags, context);
} catch (Throwable t) {
currentNode.fail(t);
- eventsDispatcher.fire(className, testName, AFTER, FAILED, Set.of(DYNAMIC_TEST), context);
+ eventsDispatcher.fire(className, testName, AFTER, FAILED, tags, context);
throw t;
} finally {
diff --git a/spectrum/src/main/java/io/github/giulong/spectrum/extensions/resolvers/DriverResolver.java b/spectrum/src/main/java/io/github/giulong/spectrum/extensions/resolvers/DriverResolver.java
index 1e7c1fb8..6b5af3cf 100644
--- a/spectrum/src/main/java/io/github/giulong/spectrum/extensions/resolvers/DriverResolver.java
+++ b/spectrum/src/main/java/io/github/giulong/spectrum/extensions/resolvers/DriverResolver.java
@@ -14,7 +14,6 @@
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.support.TypeBasedParameterResolver;
import org.openqa.selenium.JavascriptExecutor;
-import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.interactions.Actions;
import org.openqa.selenium.support.decorators.Decorated;
@@ -73,10 +72,8 @@ public WebDriver resolveParameter(final ParameterContext arg0, final ExtensionCo
final ScreenshotConsumer screenshotConsumer = ScreenshotConsumer
.builder()
.enabled(true)
- .driver((TakesScreenshot) driver)
- .statefulExtentTest(statefulExtentTest)
- .testData(testData)
.video(video)
+ .context(context)
.build();
final TestStepBuilderConsumer testStepBuilderConsumer = TestStepBuilderConsumer
diff --git a/spectrum/src/main/java/io/github/giulong/spectrum/extensions/resolvers/TestContextResolver.java b/spectrum/src/main/java/io/github/giulong/spectrum/extensions/resolvers/TestContextResolver.java
index ea852b96..3efaad5d 100644
--- a/spectrum/src/main/java/io/github/giulong/spectrum/extensions/resolvers/TestContextResolver.java
+++ b/spectrum/src/main/java/io/github/giulong/spectrum/extensions/resolvers/TestContextResolver.java
@@ -1,18 +1,17 @@
package io.github.giulong.spectrum.extensions.resolvers;
-import io.github.giulong.spectrum.utils.TestContext;
import io.github.giulong.spectrum.utils.ContextManager;
+import io.github.giulong.spectrum.utils.TestContext;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.support.TypeBasedParameterResolver;
-import static org.junit.jupiter.api.extension.ExtensionContext.Namespace.GLOBAL;
-
@Slf4j
public class TestContextResolver extends TypeBasedParameterResolver {
public static final String TEST_CONTEXT = "testContext";
+ public static final String EXTENSION_CONTEXT = "extensionContext";
private final ContextManager contextManager = ContextManager.getInstance();
@@ -21,7 +20,7 @@ public TestContext resolveParameter(final ParameterContext arg0, final Extension
log.debug("Resolving {}", TEST_CONTEXT);
final TestContext testContext = contextManager.initFor(context);
- context.getStore(GLOBAL).put(TEST_CONTEXT, testContext);
+ testContext.put(EXTENSION_CONTEXT, context);
return testContext;
}
diff --git a/spectrum/src/main/java/io/github/giulong/spectrum/extensions/resolvers/TestDataResolver.java b/spectrum/src/main/java/io/github/giulong/spectrum/extensions/resolvers/TestDataResolver.java
index ef3a6262..7c27df9d 100644
--- a/spectrum/src/main/java/io/github/giulong/spectrum/extensions/resolvers/TestDataResolver.java
+++ b/spectrum/src/main/java/io/github/giulong/spectrum/extensions/resolvers/TestDataResolver.java
@@ -1,7 +1,6 @@
package io.github.giulong.spectrum.extensions.resolvers;
import io.github.giulong.spectrum.types.TestData;
-import io.github.giulong.spectrum.utils.Configuration;
import io.github.giulong.spectrum.utils.ContextManager;
import io.github.giulong.spectrum.utils.FileUtils;
import lombok.extern.slf4j.Slf4j;
@@ -14,8 +13,6 @@
import java.util.ArrayList;
import java.util.List;
-import static io.github.giulong.spectrum.extensions.resolvers.ConfigurationResolver.CONFIGURATION;
-import static java.util.UUID.randomUUID;
import static org.junit.jupiter.api.extension.ExtensionContext.Namespace.GLOBAL;
@Slf4j
@@ -30,19 +27,13 @@ public class TestDataResolver extends TypeBasedParameterResolver {
public TestData resolveParameter(final ParameterContext arg0, final ExtensionContext context) {
log.debug("Resolving {}", TEST_DATA);
- final ExtensionContext.Store store = context.getStore(GLOBAL);
- final Configuration configuration = context.getRoot().getStore(GLOBAL).get(CONFIGURATION, Configuration.class);
- final Configuration.Extent extent = configuration.getExtent();
- final String reportFolder = extent.getReportFolder();
final Class> clazz = context.getRequiredTestClass();
final String className = clazz.getSimpleName();
final String methodName = context.getRequiredTestMethod().getName();
final String classDisplayName = fileUtils.sanitize(getDisplayNameOf(clazz));
final String displayName = fileUtils.sanitize(joinTestDisplayNamesIn(context));
final String testId = buildTestIdFrom(className, displayName);
- final String fileName = fileUtils.removeExtensionFrom(extent.getFileName());
- final Path screenshotFolderPath = getScreenshotFolderPathForCurrentTest(reportFolder, fileName, classDisplayName, displayName);
- final Path videoPath = getVideoPathForCurrentTest(configuration.getVideo().isDisabled(), reportFolder, fileName, classDisplayName, displayName);
+ final Path videoPath = fileUtils.createTempFile("video", ".mp4");
final TestData testData = TestData
.builder()
.className(className)
@@ -50,11 +41,10 @@ public TestData resolveParameter(final ParameterContext arg0, final ExtensionCon
.classDisplayName(classDisplayName)
.displayName(displayName)
.testId(testId)
- .screenshotFolderPath(screenshotFolderPath)
.videoPath(videoPath)
.build();
- store.put(TEST_DATA, testData);
+ context.getStore(GLOBAL).put(TEST_DATA, testData);
contextManager.put(context, TEST_DATA, testData);
return testData;
}
@@ -79,21 +69,6 @@ public static String joinTestDisplayNamesIn(final ExtensionContext context) {
return String.join(" ", displayNames.reversed());
}
- Path getScreenshotFolderPathForCurrentTest(final String reportsFolder, final String extentFileName, final String className, final String methodName) {
- return fileUtils.deleteContentOf(Path.of(reportsFolder, extentFileName, "screenshots", className, methodName).toAbsolutePath());
- }
-
- Path getVideoPathForCurrentTest(final boolean disabled, final String reportsFolder, final String extentFileName, final String className, final String methodName) {
- if (disabled) {
- log.trace("Video disabled: avoiding video folder creation");
- return null;
- }
-
- return fileUtils
- .deleteContentOf(Path.of(reportsFolder, extentFileName, "videos", className, methodName).toAbsolutePath())
- .resolve(String.format("%s.mp4", randomUUID()));
- }
-
static String transformInKebabCase(final String string) {
return string.replaceAll("\\s", "-").toLowerCase();
}
diff --git a/spectrum/src/main/java/io/github/giulong/spectrum/extensions/watchers/EventsWatcher.java b/spectrum/src/main/java/io/github/giulong/spectrum/extensions/watchers/EventsWatcher.java
index dd2ed036..228b8ec4 100644
--- a/spectrum/src/main/java/io/github/giulong/spectrum/extensions/watchers/EventsWatcher.java
+++ b/spectrum/src/main/java/io/github/giulong/spectrum/extensions/watchers/EventsWatcher.java
@@ -11,7 +11,7 @@
import static io.github.giulong.spectrum.enums.Result.*;
import static io.github.giulong.spectrum.utils.events.EventsDispatcher.*;
-public class EventsWatcher implements TestWatcher, BeforeAllCallback, BeforeEachCallback, AfterEachCallback, AfterAllCallback {
+public class EventsWatcher implements TestWatcher, BeforeTestExecutionCallback, BeforeAllCallback, BeforeEachCallback, AfterEachCallback, AfterAllCallback {
private final EventsDispatcher eventsDispatcher = EventsDispatcher.getInstance();
@@ -25,9 +25,18 @@ public void beforeEach(final ExtensionContext context) {
notifyTest(context, BEFORE, null, Set.of(TEST));
}
+ @Override
+ public void beforeTestExecution(final ExtensionContext context) {
+ notifyTest(context, BEFORE_EXECUTION, null, Set.of(TEST));
+
+ if (isTestFactory(context)) {
+ notifyTest(context, BEFORE_EXECUTION, null, Set.of(TEST_FACTORY));
+ }
+ }
+
@Override
public void afterEach(final ExtensionContext context) {
- if (context.getRequiredTestMethod().isAnnotationPresent(TestFactory.class)) {
+ if (isTestFactory(context)) {
notifyTest(context, AFTER, SUCCESSFUL, Set.of(TEST_FACTORY));
}
}
@@ -57,6 +66,10 @@ public void testFailed(final ExtensionContext context, final Throwable exception
notifyTest(context, AFTER, FAILED, Set.of(TEST));
}
+ boolean isTestFactory(final ExtensionContext context) {
+ return context.getRequiredTestMethod().isAnnotationPresent(TestFactory.class);
+ }
+
void notifyClass(final ExtensionContext context, final String reason, final Result result, final Set tags) {
final String className = context.getDisplayName();
diff --git a/spectrum/src/main/java/io/github/giulong/spectrum/internals/web_driver_listeners/EventsWebDriverListener.java b/spectrum/src/main/java/io/github/giulong/spectrum/internals/web_driver_listeners/EventsWebDriverListener.java
index bbb392d9..eb0f8eec 100644
--- a/spectrum/src/main/java/io/github/giulong/spectrum/internals/web_driver_listeners/EventsWebDriverListener.java
+++ b/spectrum/src/main/java/io/github/giulong/spectrum/internals/web_driver_listeners/EventsWebDriverListener.java
@@ -712,14 +712,14 @@ public void afterImplicitlyWait(final WebDriver.Timeouts timeouts, final Duratio
@Override
@Generated
- public void beforeSetScriptTimeout(final WebDriver.Timeouts timeouts, final Duration duration) {
- listenTo(AUTO_BEFORE, events.getBeforeSetScriptTimeout(), timeouts, duration);
+ public void beforeScriptTimeout(final WebDriver.Timeouts timeouts, final Duration duration) {
+ listenTo(AUTO_BEFORE, events.getBeforeScriptTimeout(), timeouts, duration);
}
@Override
@Generated
- public void afterSetScriptTimeout(final WebDriver.Timeouts timeouts, final Duration duration) {
- listenTo(AUTO_AFTER, events.getAfterSetScriptTimeout(), timeouts, duration);
+ public void afterScriptTimeout(final WebDriver.Timeouts timeouts, final Duration duration) {
+ listenTo(AUTO_AFTER, events.getAfterScriptTimeout(), timeouts, duration);
}
@Override
@@ -818,6 +818,126 @@ public void afterFullscreen(final WebDriver.Window window) {
listenTo(AUTO_AFTER, events.getAfterFullscreen(), window);
}
+ @Override
+ @Generated
+ public void beforeAnyTargetLocatorCall(final WebDriver.TargetLocator targetLocator, final Method method, final Object[] args) {
+ listenTo(AUTO_BEFORE, events.getBeforeAnyTargetLocatorCall(), targetLocator, method, Arrays.toString(args));
+ }
+
+ @Override
+ @Generated
+ public void afterAnyTargetLocatorCall(final WebDriver.TargetLocator targetLocator, final Method method, final Object[] args, final Object result) {
+ listenTo(AUTO_AFTER, events.getAfterAnyTargetLocatorCall(), targetLocator, method, Arrays.toString(args), result);
+ }
+
+ @Override
+ @Generated
+ public void beforeFrame(final WebDriver.TargetLocator targetLocator, final int index) {
+ listenTo(AUTO_BEFORE, events.getBeforeFrame(), targetLocator, index);
+ }
+
+ @Override
+ @Generated
+ public void afterFrame(final WebDriver.TargetLocator targetLocator, final int index, final WebDriver driver) {
+ listenTo(AUTO_AFTER, events.getAfterFrame(), targetLocator, index, driver);
+ }
+
+ @Override
+ @Generated
+ public void beforeFrame(final WebDriver.TargetLocator targetLocator, final String nameOrId) {
+ listenTo(AUTO_BEFORE, events.getBeforeFrame(), targetLocator, nameOrId);
+ }
+
+ @Override
+ @Generated
+ public void afterFrame(final WebDriver.TargetLocator targetLocator, final String nameOrId, final WebDriver driver) {
+ listenTo(AUTO_AFTER, events.getAfterFrame(), targetLocator, nameOrId, driver);
+ }
+
+ @Override
+ @Generated
+ public void beforeFrame(final WebDriver.TargetLocator targetLocator, final WebElement frameElement) {
+ listenTo(AUTO_BEFORE, events.getBeforeFrame(), targetLocator, frameElement);
+ }
+
+ @Override
+ @Generated
+ public void afterFrame(final WebDriver.TargetLocator targetLocator, final WebElement frameElement, final WebDriver driver) {
+ listenTo(AUTO_AFTER, events.getAfterFrame(), targetLocator, frameElement, driver);
+ }
+
+ @Override
+ @Generated
+ public void beforeParentFrame(final WebDriver.TargetLocator targetLocator) {
+ listenTo(AUTO_BEFORE, events.getBeforeParentFrame(), targetLocator);
+ }
+
+ @Override
+ @Generated
+ public void afterParentFrame(final WebDriver.TargetLocator targetLocator, final WebDriver driver) {
+ listenTo(AUTO_AFTER, events.getAfterParentFrame(), targetLocator, driver);
+ }
+
+ @Override
+ @Generated
+ public void beforeWindow(final WebDriver.TargetLocator targetLocator, final String nameOrHandle) {
+ listenTo(AUTO_BEFORE, events.getBeforeWindow(), targetLocator, nameOrHandle);
+ }
+
+ @Override
+ @Generated
+ public void afterWindow(final WebDriver.TargetLocator targetLocator, final String nameOrHandle, final WebDriver driver) {
+ listenTo(AUTO_AFTER, events.getAfterWindow(), targetLocator, nameOrHandle, driver);
+ }
+
+ @Override
+ @Generated
+ public void beforeNewWindow(final WebDriver.TargetLocator targetLocator, final WindowType typeHint) {
+ listenTo(AUTO_BEFORE, events.getBeforeNewWindow(), targetLocator, typeHint);
+ }
+
+ @Override
+ @Generated
+ public void afterNewWindow(final WebDriver.TargetLocator targetLocator, final WindowType typeHint, final WebDriver driver) {
+ listenTo(AUTO_AFTER, events.getAfterNewWindow(), targetLocator, typeHint, driver);
+ }
+
+ @Override
+ @Generated
+ public void beforeDefaultContent(final WebDriver.TargetLocator targetLocator) {
+ listenTo(AUTO_BEFORE, events.getBeforeDefaultContent(), targetLocator);
+ }
+
+ @Override
+ @Generated
+ public void afterDefaultContent(final WebDriver.TargetLocator targetLocator, final WebDriver driver) {
+ listenTo(AUTO_AFTER, events.getAfterDefaultContent(), targetLocator, driver);
+ }
+
+ @Override
+ @Generated
+ public void beforeActiveElement(final WebDriver.TargetLocator targetLocator) {
+ listenTo(AUTO_BEFORE, events.getBeforeActiveElement(), targetLocator);
+ }
+
+ @Override
+ @Generated
+ public void afterActiveElement(final WebDriver.TargetLocator targetLocator, final WebDriver driver) {
+ listenTo(AUTO_AFTER, events.getAfterActiveElement(), targetLocator, driver);
+ }
+
+ @Override
+ @Generated
+ public void beforeAlert(final WebDriver.TargetLocator targetLocator) {
+ listenTo(AUTO_BEFORE, events.getBeforeAlert(), targetLocator);
+ }
+
+ @Override
+ @Generated
+ public void afterAlert(final WebDriver.TargetLocator targetLocator, final Alert alert) {
+ listenTo(AUTO_AFTER, events.getAfterAlert(), targetLocator, alert);
+ }
+
boolean isSecured(final CharSequence... keysToSend) {
final Matcher matcher = SECURED_PATTERN.matcher(keysToSend[0]);
diff --git a/spectrum/src/main/java/io/github/giulong/spectrum/pojos/events/Event.java b/spectrum/src/main/java/io/github/giulong/spectrum/pojos/events/Event.java
index b4bf89b2..0873e55b 100644
--- a/spectrum/src/main/java/io/github/giulong/spectrum/pojos/events/Event.java
+++ b/spectrum/src/main/java/io/github/giulong/spectrum/pojos/events/Event.java
@@ -2,10 +2,13 @@
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.github.giulong.spectrum.enums.Result;
-import lombok.*;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.ToString;
import lombok.extern.jackson.Jacksonized;
import org.junit.jupiter.api.extension.ExtensionContext;
+import java.util.Map;
import java.util.Set;
@Getter
@@ -23,4 +26,8 @@ public class Event {
@JsonIgnore
@ToString.Exclude
private ExtensionContext context;
+
+ @JsonIgnore
+ @ToString.Exclude
+ private Map payload;
}
diff --git a/spectrum/src/main/java/io/github/giulong/spectrum/types/TestData.java b/spectrum/src/main/java/io/github/giulong/spectrum/types/TestData.java
index 9481e4e2..622e3e92 100644
--- a/spectrum/src/main/java/io/github/giulong/spectrum/types/TestData.java
+++ b/spectrum/src/main/java/io/github/giulong/spectrum/types/TestData.java
@@ -3,8 +3,11 @@
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
+import org.jcodec.api.awt.AWTSequenceEncoder;
import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.Map;
@Getter
@Builder
@@ -14,12 +17,23 @@ public class TestData {
private String methodName;
private String classDisplayName;
private String testId;
- private Path screenshotFolderPath;
private Path videoPath;
+ @Builder.Default
+ private Map encoders = new HashMap<>();
+
@Setter
private int frameNumber;
+ @Setter
+ private boolean dynamic;
+
+ @Setter
+ private byte[] lastFrameDigest;
+
+ @Setter
+ private String lastFrameDisplayName;
+
@Setter
private Path dynamicVideoPath;
diff --git a/spectrum/src/main/java/io/github/giulong/spectrum/utils/Configuration.java b/spectrum/src/main/java/io/github/giulong/spectrum/utils/Configuration.java
index ba328221..e4e3519e 100644
--- a/spectrum/src/main/java/io/github/giulong/spectrum/utils/Configuration.java
+++ b/spectrum/src/main/java/io/github/giulong/spectrum/utils/Configuration.java
@@ -525,8 +525,8 @@ public static class Events {
private Event afterAnyTimeoutsCall;
private Event beforeImplicitlyWait;
private Event afterImplicitlyWait;
- private Event beforeSetScriptTimeout;
- private Event afterSetScriptTimeout;
+ private Event beforeScriptTimeout;
+ private Event afterScriptTimeout;
private Event beforePageLoadTimeout;
private Event afterPageLoadTimeout;
private Event beforeAnyWindowCall;
@@ -543,6 +543,22 @@ public static class Events {
private Event afterMaximize;
private Event beforeFullscreen;
private Event afterFullscreen;
+ private Event beforeAnyTargetLocatorCall;
+ private Event afterAnyTargetLocatorCall;
+ private Event beforeFrame;
+ private Event afterFrame;
+ private Event beforeParentFrame;
+ private Event afterParentFrame;
+ private Event beforeWindow;
+ private Event afterWindow;
+ private Event beforeNewWindow;
+ private Event afterNewWindow;
+ private Event beforeDefaultContent;
+ private Event afterDefaultContent;
+ private Event beforeActiveElement;
+ private Event afterActiveElement;
+ private Event beforeAlert;
+ private Event afterAlert;
}
@Getter
diff --git a/spectrum/src/main/java/io/github/giulong/spectrum/utils/ContextManager.java b/spectrum/src/main/java/io/github/giulong/spectrum/utils/ContextManager.java
index c5654be4..e4d465b9 100644
--- a/spectrum/src/main/java/io/github/giulong/spectrum/utils/ContextManager.java
+++ b/spectrum/src/main/java/io/github/giulong/spectrum/utils/ContextManager.java
@@ -1,8 +1,10 @@
package io.github.giulong.spectrum.utils;
+import lombok.Getter;
import lombok.NoArgsConstructor;
import org.junit.jupiter.api.extension.ExtensionContext;
+import java.nio.file.Path;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -19,6 +21,9 @@ public static ContextManager getInstance() {
private final Map testContexts = new ConcurrentHashMap<>();
+ @Getter
+ private final Map screenshots = new ConcurrentHashMap<>();
+
public TestContext initFor(final ExtensionContext context) {
return initFor(context, new TestContext());
}
diff --git a/spectrum/src/main/java/io/github/giulong/spectrum/utils/ExtentReporter.java b/spectrum/src/main/java/io/github/giulong/spectrum/utils/ExtentReporter.java
index 9deac28c..b663075b 100644
--- a/spectrum/src/main/java/io/github/giulong/spectrum/utils/ExtentReporter.java
+++ b/spectrum/src/main/java/io/github/giulong/spectrum/utils/ExtentReporter.java
@@ -34,11 +34,11 @@
import static com.aventstack.extentreports.markuputils.MarkupHelper.createLabel;
import static io.github.giulong.spectrum.extensions.resolvers.StatefulExtentTestResolver.STATEFUL_EXTENT_TEST;
import static io.github.giulong.spectrum.extensions.resolvers.TestDataResolver.*;
-import static lombok.AccessLevel.PROTECTED;
+import static lombok.AccessLevel.PRIVATE;
@Slf4j
-@NoArgsConstructor(access = PROTECTED)
@Getter
+@NoArgsConstructor(access = PRIVATE)
public class ExtentReporter implements SessionHook, CanProduceMetadata {
private static final ExtentReporter INSTANCE = new ExtentReporter();
diff --git a/spectrum/src/main/java/io/github/giulong/spectrum/utils/FileUtils.java b/spectrum/src/main/java/io/github/giulong/spectrum/utils/FileUtils.java
index 558414ab..bae2df37 100644
--- a/spectrum/src/main/java/io/github/giulong/spectrum/utils/FileUtils.java
+++ b/spectrum/src/main/java/io/github/giulong/spectrum/utils/FileUtils.java
@@ -1,6 +1,5 @@
package io.github.giulong.spectrum.utils;
-import io.github.giulong.spectrum.enums.Frame;
import lombok.NoArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
@@ -20,7 +19,6 @@
import java.util.stream.Stream;
import static java.util.Comparator.reverseOrder;
-import static java.util.UUID.randomUUID;
import static lombok.AccessLevel.PRIVATE;
@Slf4j
@@ -72,7 +70,7 @@ public String removeExtensionFrom(final String fileName) {
@SneakyThrows
public Path delete(final Path path) {
- if (!Files.exists(path)) {
+ if (Files.notExists(path)) {
log.debug("Avoid deleting non-existing path '{}'", path);
return path;
}
@@ -130,7 +128,17 @@ public FileTime getCreationTimeOf(final File file) {
return Files.readAttributes(file.toPath(), BasicFileAttributes.class).creationTime();
}
- public String getScreenshotNameFrom(final Frame frame, final StatefulExtentTest statefulExtentTest) {
- return String.format("%s-%s-%s.png", frame.getValue(), statefulExtentTest.getDisplayName(), randomUUID());
+ @SneakyThrows
+ public Path createTempFile(final String prefix, final String suffix) {
+ final Path path = Files.createTempFile(prefix, suffix);
+ path.toFile().deleteOnExit();
+
+ log.debug("Created file {}", path);
+ return path;
+ }
+
+ @SneakyThrows
+ public Path writeTempFile(final String prefix, final String suffix, final byte[] data) {
+ return Files.write(createTempFile(prefix, suffix), data);
}
}
diff --git a/spectrum/src/main/java/io/github/giulong/spectrum/utils/HtmlUtils.java b/spectrum/src/main/java/io/github/giulong/spectrum/utils/HtmlUtils.java
index 7da5db6e..6640963f 100644
--- a/spectrum/src/main/java/io/github/giulong/spectrum/utils/HtmlUtils.java
+++ b/spectrum/src/main/java/io/github/giulong/spectrum/utils/HtmlUtils.java
@@ -25,8 +25,11 @@ public class HtmlUtils implements SessionHook {
private static final Pattern IMAGE_TAG = Pattern.compile("", DOTALL);
private static final Base64.Encoder ENCODER = Base64.getEncoder();
private static final String SRC = "src";
+
private final FreeMarkerWrapper freeMarkerWrapper = FreeMarkerWrapper.getInstance();
+ private final ContextManager contextManager = ContextManager.getInstance();
private final FileUtils fileUtils = FileUtils.getInstance();
+
private String videoTemplate;
private String divTemplate;
private String frameTemplate;
@@ -59,11 +62,12 @@ public String buildFrameTagFor(final int number, final String content, final Tes
@SneakyThrows
public String inlineImagesOf(final String html) {
final Matcher matcher = IMAGE_TAG.matcher(html);
+ final Map screenshots = contextManager.getScreenshots();
String inlineHtml = html;
while (matcher.find()) {
final String src = matcher.group(SRC);
- final byte[] bytes = Files.readAllBytes(Path.of(src));
+ final byte[] bytes = screenshots.get(Path.of(src));
final String encoded = ENCODER.encodeToString(bytes);
final String replacement = freeMarkerWrapper.interpolate(this.inlineImageTemplate, Map.of("encoded", encoded));
diff --git a/spectrum/src/main/java/io/github/giulong/spectrum/utils/events/EventsConsumer.java b/spectrum/src/main/java/io/github/giulong/spectrum/utils/events/EventsConsumer.java
index d8ca9d71..05cf8467 100644
--- a/spectrum/src/main/java/io/github/giulong/spectrum/utils/events/EventsConsumer.java
+++ b/spectrum/src/main/java/io/github/giulong/spectrum/utils/events/EventsConsumer.java
@@ -4,6 +4,7 @@
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import io.github.giulong.spectrum.pojos.events.Event;
+import io.github.giulong.spectrum.utils.events.video.*;
import io.github.giulong.spectrum.utils.web_driver_events.TestStepsConsumer;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
@@ -22,8 +23,12 @@
@JsonSubTypes.Type(value = ExtentTestConsumer.class, name = "extentTest"),
@JsonSubTypes.Type(value = DriverConsumer.class, name = "driver"),
@JsonSubTypes.Type(value = MailConsumer.class, name = "mail"),
+ @JsonSubTypes.Type(value = VideoInitConsumer.class, name = "videoInit"),
+ @JsonSubTypes.Type(value = VideoDynamicInitConsumer.class, name = "videoDynamicInit"),
@JsonSubTypes.Type(value = VideoConsumer.class, name = "video"),
@JsonSubTypes.Type(value = VideoDynamicConsumer.class, name = "videoDynamic"),
+ @JsonSubTypes.Type(value = VideoFinalizer.class, name = "videoFinalizer"),
+ @JsonSubTypes.Type(value = VideoDynamicFinalizer.class, name = "videoDynamicFinalizer"),
@JsonSubTypes.Type(value = TestStepsConsumer.class, name = "testSteps"),
@JsonSubTypes.Type(value = LogConsumer.class, name = "log"),
})
diff --git a/spectrum/src/main/java/io/github/giulong/spectrum/utils/events/EventsDispatcher.java b/spectrum/src/main/java/io/github/giulong/spectrum/utils/events/EventsDispatcher.java
index 3e325354..8b683cbd 100644
--- a/spectrum/src/main/java/io/github/giulong/spectrum/utils/events/EventsDispatcher.java
+++ b/spectrum/src/main/java/io/github/giulong/spectrum/utils/events/EventsDispatcher.java
@@ -2,12 +2,14 @@
import io.github.giulong.spectrum.enums.Result;
import io.github.giulong.spectrum.interfaces.SessionHook;
-import io.github.giulong.spectrum.utils.Configuration;
import io.github.giulong.spectrum.pojos.events.Event;
+import io.github.giulong.spectrum.utils.Configuration;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.extension.ExtensionContext;
+import java.util.Map;
+import java.util.Optional;
import java.util.Set;
import static lombok.AccessLevel.PRIVATE;
@@ -19,6 +21,7 @@ public class EventsDispatcher implements SessionHook {
private static final EventsDispatcher INSTANCE = new EventsDispatcher();
public static final String BEFORE = "before";
+ public static final String BEFORE_EXECUTION = "beforeExecution";
public static final String AFTER = "after";
public static final String TEST = "test";
public static final String TEST_FACTORY = "testFactory";
@@ -52,6 +55,10 @@ public void fire(final String reason, final Set tags, final Result resul
fire(null, null, reason, result, tags, null);
}
+ public void fire(final String primaryId, final String reason, final Map payload) {
+ fire(primaryId, null, reason, null, null, null, payload);
+ }
+
public void fire(final String primaryId, final String reason) {
fire(primaryId, null, reason, null, null, null);
}
@@ -60,7 +67,13 @@ public void fire(final String primaryId, final String secondaryId, final String
fire(primaryId, secondaryId, reason, null, null, null);
}
- public void fire(final String primaryId, final String secondaryId, final String reason, final Result result, final Set tags, final ExtensionContext context) {
+ public void fire(final String primaryId, final String secondaryId, final String reason, final Result result, final Set tags,
+ final ExtensionContext context) {
+ fire(primaryId, secondaryId, reason, result, tags, context, null);
+ }
+
+ public void fire(final String primaryId, final String secondaryId, final String reason, final Result result, final Set tags,
+ final ExtensionContext context, final Map payload) {
final Event event = Event.builder()
.primaryId(primaryId)
.secondaryId(secondaryId)
@@ -68,6 +81,7 @@ public void fire(final String primaryId, final String secondaryId, final String
.result(result)
.tags(tags)
.context(context)
+ .payload(Optional.ofNullable(payload).orElseGet(Map::of))
.build();
log.debug("Dispatching event {}", event);
diff --git a/spectrum/src/main/java/io/github/giulong/spectrum/utils/events/VideoConsumer.java b/spectrum/src/main/java/io/github/giulong/spectrum/utils/events/VideoConsumer.java
deleted file mode 100644
index d048cf3d..00000000
--- a/spectrum/src/main/java/io/github/giulong/spectrum/utils/events/VideoConsumer.java
+++ /dev/null
@@ -1,152 +0,0 @@
-package io.github.giulong.spectrum.utils.events;
-
-import com.fasterxml.jackson.annotation.JsonView;
-import io.github.giulong.spectrum.internals.jackson.views.Views.Internal;
-import io.github.giulong.spectrum.pojos.events.Event;
-import io.github.giulong.spectrum.types.TestData;
-import io.github.giulong.spectrum.utils.Configuration;
-import io.github.giulong.spectrum.utils.ContextManager;
-import io.github.giulong.spectrum.utils.video.Video;
-import lombok.SneakyThrows;
-import lombok.extern.slf4j.Slf4j;
-import org.jcodec.api.awt.AWTSequenceEncoder;
-import org.junit.jupiter.api.extension.ExtensionContext;
-import org.openqa.selenium.Dimension;
-import org.openqa.selenium.WebDriver;
-
-import javax.imageio.ImageIO;
-import java.awt.*;
-import java.awt.image.BufferedImage;
-import java.io.File;
-import java.net.URL;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.security.MessageDigest;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Objects;
-import java.util.stream.Stream;
-
-import static io.github.giulong.spectrum.SpectrumEntity.HASH_ALGORITHM;
-import static io.github.giulong.spectrum.enums.Result.DISABLED;
-import static io.github.giulong.spectrum.extensions.resolvers.DriverResolver.ORIGINAL_DRIVER;
-import static io.github.giulong.spectrum.extensions.resolvers.TestDataResolver.TEST_DATA;
-import static java.awt.image.BufferedImage.TYPE_INT_RGB;
-import static java.util.Comparator.comparingLong;
-
-@Slf4j
-@JsonView(Internal.class)
-public class VideoConsumer extends EventsConsumer {
-
- private final ClassLoader classLoader = VideoConsumer.class.getClassLoader();
- private final Configuration configuration = Configuration.getInstance();
- private final ContextManager contextManager = ContextManager.getInstance();
-
- private byte[] lastFrameDigest;
- private MessageDigest messageDigest;
-
- @Override
- protected boolean shouldAccept(final Event event) {
- return !DISABLED.equals(event.getResult()) && !configuration.getVideo().isDisabled();
- }
-
- @SneakyThrows
- @Override
- public void accept(final Event event) {
- final Video video = configuration.getVideo();
- final ExtensionContext context = event.getContext();
- final TestData testData = contextManager.get(context, TEST_DATA, TestData.class);
-
- init();
-
- log.info("Generating video for test {}.{}", testData.getClassName(), testData.getMethodName());
-
- try (Stream screenshots = Files.walk(testData.getScreenshotFolderPath())) {
- final AWTSequenceEncoder encoder = AWTSequenceEncoder.createSequenceEncoder(getVideoPathFrom(testData).toFile(), 1);
- final List frames = screenshots
- .map(Path::toFile)
- .filter(File::isFile)
- .filter(file -> filter(file, testData))
- .filter(file -> !video.isSkipDuplicateFrames() || isNewFrame(file, testData))
- .sorted(comparingLong(File::lastModified))
- .toList();
-
- if (frames.isEmpty()) {
- log.debug("No frames were added to the video. Adding 'no-video.png'");
- final URL noVideoPng = Objects.requireNonNull(classLoader.getResource("no-video.png"));
- encoder.encodeImage(ImageIO.read(noVideoPng));
- } else {
- final Dimension dimension = chooseDimensionFor(contextManager.get(context, ORIGINAL_DRIVER, WebDriver.class), video);
-
- for (File frame : frames) {
- encoder.encodeImage(resize(ImageIO.read(frame), dimension));
- }
- }
-
- encoder.finish();
- }
- }
-
- @SneakyThrows
- protected void init() {
- this.lastFrameDigest = null;
- this.messageDigest = MessageDigest.getInstance(HASH_ALGORITHM);
- }
-
- protected Path getVideoPathFrom(final TestData testData) {
- return testData.getVideoPath();
- }
-
- protected boolean filter(final File file, final TestData testData) {
- return true;
- }
-
- @SneakyThrows
- protected boolean isNewFrame(final File screenshot, final TestData testData) {
- final byte[] digest = messageDigest.digest(Files.readAllBytes(screenshot.toPath()));
-
- if (!Arrays.equals(lastFrameDigest, digest)) {
- lastFrameDigest = digest;
- return true;
- }
-
- log.trace("Discarding duplicate frame {}", screenshot.getName());
- return false;
- }
-
- Dimension chooseDimensionFor(final WebDriver driver, final Video video) {
- int width = video.getWidth();
- int height = video.getHeight();
-
- if (video.getWidth() < 1 || video.getHeight() < 1) {
- final Dimension size = driver.manage().window().getSize();
- width = size.getWidth();
- height = size.getHeight() - video.getMenuBarsHeight();
- }
-
- final int evenWidth = makeItEven(width);
- final int evenHeight = makeItEven(height);
-
- log.debug("Video dimensions: {}x{}", evenWidth, evenHeight);
- return new Dimension(evenWidth, evenHeight);
- }
-
- int makeItEven(final int i) {
- return i % 2 == 0 ? i : i + 1;
- }
-
- BufferedImage resize(final BufferedImage bufferedImage, final Dimension dimension) {
- final int width = dimension.getWidth();
- final int height = dimension.getHeight();
- final int minWidth = Math.min(width, bufferedImage.getWidth());
- final int minHeight = Math.min(height, bufferedImage.getHeight());
- final BufferedImage resizedImage = new BufferedImage(width, height, TYPE_INT_RGB);
- final Graphics2D graphics2D = resizedImage.createGraphics();
-
- log.trace("Resizing screenshot to {}x{}", minWidth, minHeight);
- graphics2D.drawImage(bufferedImage, 0, 0, minWidth, minHeight, null);
- graphics2D.dispose();
-
- return resizedImage;
- }
-}
diff --git a/spectrum/src/main/java/io/github/giulong/spectrum/utils/events/VideoDynamicConsumer.java b/spectrum/src/main/java/io/github/giulong/spectrum/utils/events/VideoDynamicConsumer.java
deleted file mode 100644
index 553e41fe..00000000
--- a/spectrum/src/main/java/io/github/giulong/spectrum/utils/events/VideoDynamicConsumer.java
+++ /dev/null
@@ -1,48 +0,0 @@
-package io.github.giulong.spectrum.utils.events;
-
-import com.fasterxml.jackson.annotation.JsonView;
-import io.github.giulong.spectrum.internals.jackson.views.Views.Internal;
-import io.github.giulong.spectrum.types.TestData;
-
-import java.io.File;
-import java.nio.file.Path;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-@JsonView(Internal.class)
-public class VideoDynamicConsumer extends VideoConsumer {
-
- private static final Pattern PATTERN = Pattern.compile("[A-Za-z]+-(?.*)-([a-f0-9]{8}(-[a-f0-9]{4}){4}[a-f0-9]{8})\\.png");
-
- private String lastFrameDisplayName;
-
- @Override
- protected void init() {
- super.init();
- this.lastFrameDisplayName = null;
- }
-
- @Override
- protected Path getVideoPathFrom(final TestData testData) {
- return testData.getDynamicVideoPath();
- }
-
- @Override
- protected boolean filter(final File file, final TestData testData) {
- final Matcher matcher = PATTERN.matcher(file.getName());
-
- return matcher.find() && testData.getDisplayName().equals(matcher.group("displayName"));
- }
-
- @Override
- protected boolean isNewFrame(final File screenshot, final TestData testData) {
- final String displayName = testData.getDisplayName();
-
- if (!displayName.equals(lastFrameDisplayName)) {
- lastFrameDisplayName = displayName;
- return true;
- }
-
- return super.isNewFrame(screenshot, testData);
- }
-}
diff --git a/spectrum/src/main/java/io/github/giulong/spectrum/utils/events/video/VideoBaseConsumer.java b/spectrum/src/main/java/io/github/giulong/spectrum/utils/events/video/VideoBaseConsumer.java
new file mode 100644
index 00000000..8379100e
--- /dev/null
+++ b/spectrum/src/main/java/io/github/giulong/spectrum/utils/events/video/VideoBaseConsumer.java
@@ -0,0 +1,24 @@
+package io.github.giulong.spectrum.utils.events.video;
+
+import io.github.giulong.spectrum.pojos.events.Event;
+import io.github.giulong.spectrum.types.TestData;
+import io.github.giulong.spectrum.utils.Configuration;
+import io.github.giulong.spectrum.utils.events.EventsConsumer;
+
+import java.nio.file.Path;
+
+import static io.github.giulong.spectrum.enums.Result.DISABLED;
+
+public abstract class VideoBaseConsumer extends EventsConsumer {
+
+ protected final Configuration configuration = Configuration.getInstance();
+
+ @Override
+ protected boolean shouldAccept(final Event event) {
+ return !DISABLED.equals(event.getResult()) && !configuration.getVideo().isDisabled();
+ }
+
+ protected Path getVideoPathFrom(final TestData testData) {
+ return testData.getVideoPath();
+ }
+}
diff --git a/spectrum/src/main/java/io/github/giulong/spectrum/utils/events/video/VideoConsumer.java b/spectrum/src/main/java/io/github/giulong/spectrum/utils/events/video/VideoConsumer.java
new file mode 100644
index 00000000..20ebf22c
--- /dev/null
+++ b/spectrum/src/main/java/io/github/giulong/spectrum/utils/events/video/VideoConsumer.java
@@ -0,0 +1,112 @@
+package io.github.giulong.spectrum.utils.events.video;
+
+import com.fasterxml.jackson.annotation.JsonView;
+import io.github.giulong.spectrum.internals.jackson.views.Views.Internal;
+import io.github.giulong.spectrum.pojos.events.Event;
+import io.github.giulong.spectrum.types.TestData;
+import io.github.giulong.spectrum.utils.video.Video;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.jcodec.api.awt.AWTSequenceEncoder;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.openqa.selenium.Dimension;
+import org.openqa.selenium.WebDriver;
+
+import javax.imageio.ImageIO;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayInputStream;
+import java.nio.file.Path;
+import java.security.MessageDigest;
+import java.util.Arrays;
+import java.util.Map;
+
+import static io.github.giulong.spectrum.SpectrumEntity.HASH_ALGORITHM;
+import static io.github.giulong.spectrum.extensions.resolvers.DriverResolver.ORIGINAL_DRIVER;
+import static io.github.giulong.spectrum.extensions.resolvers.TestContextResolver.EXTENSION_CONTEXT;
+import static io.github.giulong.spectrum.extensions.resolvers.TestDataResolver.TEST_DATA;
+import static io.github.giulong.spectrum.utils.web_driver_events.ScreenshotConsumer.SCREENSHOT;
+import static java.awt.image.BufferedImage.TYPE_INT_RGB;
+import static org.junit.jupiter.api.extension.ExtensionContext.Namespace.GLOBAL;
+
+@Slf4j
+@JsonView(Internal.class)
+public class VideoConsumer extends VideoBaseConsumer {
+
+ private final MessageDigest messageDigest;
+
+ @SneakyThrows
+ public VideoConsumer() {
+ this.messageDigest = MessageDigest.getInstance(HASH_ALGORITHM);
+ }
+
+ @Override
+ @SneakyThrows
+ public void accept(final Event event) {
+ final Video video = configuration.getVideo();
+ final Map payload = event.getPayload();
+ final ExtensionContext.Store store = ((ExtensionContext) payload.get(EXTENSION_CONTEXT)).getStore(GLOBAL);
+ final TestData testData = store.get(TEST_DATA, TestData.class);
+ final byte[] screenshot = (byte[]) payload.get(SCREENSHOT);
+
+ if (video.isSkipDuplicateFrames() && !isNewFrame(screenshot, testData)) {
+ return;
+ }
+
+ final Path videoPath = getVideoPathFrom(testData);
+ final AWTSequenceEncoder encoder = testData.getEncoders().get(videoPath);
+ final Dimension dimension = chooseDimensionFor(store.get(ORIGINAL_DRIVER, WebDriver.class), video);
+
+ log.debug("Adding screenshot to video {}", videoPath.getFileName());
+ encoder.encodeImage(resize(ImageIO.read(new ByteArrayInputStream(screenshot)), dimension));
+ }
+
+ @SneakyThrows
+ protected boolean isNewFrame(final byte[] screenshot, final TestData testData) {
+ final byte[] digest = messageDigest.digest(screenshot);
+
+ if (!Arrays.equals(testData.getLastFrameDigest(), digest)) {
+ testData.setLastFrameDigest(digest);
+ return true;
+ }
+
+ log.trace("Discarding duplicate frame");
+ return false;
+ }
+
+ Dimension chooseDimensionFor(final WebDriver driver, final Video video) {
+ int width = video.getWidth();
+ int height = video.getHeight();
+
+ if (video.getWidth() < 1 || video.getHeight() < 1) {
+ final Dimension size = driver.manage().window().getSize();
+ width = size.getWidth();
+ height = size.getHeight() - video.getMenuBarsHeight();
+ }
+
+ final int evenWidth = makeItEven(width);
+ final int evenHeight = makeItEven(height);
+
+ log.debug("Video dimensions: {}x{}", evenWidth, evenHeight);
+ return new Dimension(evenWidth, evenHeight);
+ }
+
+ int makeItEven(final int i) {
+ return i % 2 == 0 ? i : i + 1;
+ }
+
+ BufferedImage resize(final BufferedImage bufferedImage, final Dimension dimension) {
+ final int width = dimension.getWidth();
+ final int height = dimension.getHeight();
+ final int minWidth = Math.min(width, bufferedImage.getWidth());
+ final int minHeight = Math.min(height, bufferedImage.getHeight());
+ final BufferedImage resizedImage = new BufferedImage(width, height, TYPE_INT_RGB);
+ final Graphics2D graphics2D = resizedImage.createGraphics();
+
+ log.trace("Resizing screenshot to {}x{}", minWidth, minHeight);
+ graphics2D.drawImage(bufferedImage, 0, 0, minWidth, minHeight, null);
+ graphics2D.dispose();
+
+ return resizedImage;
+ }
+}
diff --git a/spectrum/src/main/java/io/github/giulong/spectrum/utils/events/video/VideoDynamicConsumer.java b/spectrum/src/main/java/io/github/giulong/spectrum/utils/events/video/VideoDynamicConsumer.java
new file mode 100644
index 00000000..b601ac89
--- /dev/null
+++ b/spectrum/src/main/java/io/github/giulong/spectrum/utils/events/video/VideoDynamicConsumer.java
@@ -0,0 +1,51 @@
+package io.github.giulong.spectrum.utils.events.video;
+
+import com.fasterxml.jackson.annotation.JsonView;
+import io.github.giulong.spectrum.internals.jackson.views.Views.Internal;
+import io.github.giulong.spectrum.pojos.events.Event;
+import io.github.giulong.spectrum.types.TestData;
+import org.junit.jupiter.api.extension.ExtensionContext;
+
+import java.nio.file.Path;
+
+import static io.github.giulong.spectrum.extensions.resolvers.TestContextResolver.EXTENSION_CONTEXT;
+import static io.github.giulong.spectrum.extensions.resolvers.TestDataResolver.TEST_DATA;
+import static org.junit.jupiter.api.extension.ExtensionContext.Namespace.GLOBAL;
+
+@JsonView(Internal.class)
+public class VideoDynamicConsumer extends VideoConsumer {
+
+ @Override
+ protected boolean shouldAccept(final Event event) {
+ final boolean superCondition = super.shouldAccept(event);
+ if (!superCondition) {
+ return false;
+ }
+
+ final ExtensionContext context = (ExtensionContext) event.getPayload().get(EXTENSION_CONTEXT);
+ if (context == null) {
+ return false;
+ }
+
+ final TestData testData = context.getStore(GLOBAL).get(TEST_DATA, TestData.class);
+
+ return testData != null && testData.isDynamic();
+ }
+
+ @Override
+ protected Path getVideoPathFrom(final TestData testData) {
+ return testData.getDynamicVideoPath();
+ }
+
+ @Override
+ protected boolean isNewFrame(final byte[] screenshot, final TestData testData) {
+ final String displayName = testData.getDisplayName();
+
+ if (!displayName.equals(testData.getLastFrameDisplayName())) {
+ testData.setLastFrameDisplayName(displayName);
+ return true;
+ }
+
+ return super.isNewFrame(screenshot, testData);
+ }
+}
diff --git a/spectrum/src/main/java/io/github/giulong/spectrum/utils/events/video/VideoDynamicFinalizer.java b/spectrum/src/main/java/io/github/giulong/spectrum/utils/events/video/VideoDynamicFinalizer.java
new file mode 100644
index 00000000..ab29a9e1
--- /dev/null
+++ b/spectrum/src/main/java/io/github/giulong/spectrum/utils/events/video/VideoDynamicFinalizer.java
@@ -0,0 +1,16 @@
+package io.github.giulong.spectrum.utils.events.video;
+
+import com.fasterxml.jackson.annotation.JsonView;
+import io.github.giulong.spectrum.internals.jackson.views.Views.Internal;
+import io.github.giulong.spectrum.types.TestData;
+
+import java.nio.file.Path;
+
+@JsonView(Internal.class)
+public class VideoDynamicFinalizer extends VideoFinalizer {
+
+ @Override
+ protected Path getVideoPathFrom(final TestData testData) {
+ return testData.getDynamicVideoPath();
+ }
+}
diff --git a/spectrum/src/main/java/io/github/giulong/spectrum/utils/events/video/VideoDynamicInitConsumer.java b/spectrum/src/main/java/io/github/giulong/spectrum/utils/events/video/VideoDynamicInitConsumer.java
new file mode 100644
index 00000000..fb08656c
--- /dev/null
+++ b/spectrum/src/main/java/io/github/giulong/spectrum/utils/events/video/VideoDynamicInitConsumer.java
@@ -0,0 +1,16 @@
+package io.github.giulong.spectrum.utils.events.video;
+
+import com.fasterxml.jackson.annotation.JsonView;
+import io.github.giulong.spectrum.internals.jackson.views.Views.Internal;
+import io.github.giulong.spectrum.types.TestData;
+
+import java.nio.file.Path;
+
+@JsonView(Internal.class)
+public class VideoDynamicInitConsumer extends VideoInitConsumer {
+
+ @Override
+ protected Path getVideoPathFrom(final TestData testData) {
+ return testData.getDynamicVideoPath();
+ }
+}
diff --git a/spectrum/src/main/java/io/github/giulong/spectrum/utils/events/video/VideoFinalizer.java b/spectrum/src/main/java/io/github/giulong/spectrum/utils/events/video/VideoFinalizer.java
new file mode 100644
index 00000000..dddce197
--- /dev/null
+++ b/spectrum/src/main/java/io/github/giulong/spectrum/utils/events/video/VideoFinalizer.java
@@ -0,0 +1,42 @@
+package io.github.giulong.spectrum.utils.events.video;
+
+import com.fasterxml.jackson.annotation.JsonView;
+import io.github.giulong.spectrum.internals.jackson.views.Views.Internal;
+import io.github.giulong.spectrum.pojos.events.Event;
+import io.github.giulong.spectrum.types.TestData;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.jcodec.api.awt.AWTSequenceEncoder;
+
+import javax.imageio.ImageIO;
+import java.net.URL;
+import java.nio.file.Path;
+import java.util.Objects;
+
+import static io.github.giulong.spectrum.extensions.resolvers.TestDataResolver.TEST_DATA;
+import static org.junit.jupiter.api.extension.ExtensionContext.Namespace.GLOBAL;
+
+@Slf4j
+@JsonView(Internal.class)
+public class VideoFinalizer extends VideoBaseConsumer {
+
+ private final ClassLoader classLoader = VideoFinalizer.class.getClassLoader();
+
+ @Override
+ @SneakyThrows
+ public void accept(final Event event) {
+ final TestData testData = event.getContext().getStore(GLOBAL).get(TEST_DATA, TestData.class);
+ final Path videoPath = getVideoPathFrom(testData);
+ final AWTSequenceEncoder encoder = testData.getEncoders().get(videoPath);
+
+ log.debug("Finalizing video {}", videoPath.getFileName());
+
+ if (testData.getFrameNumber() == 0) {
+ log.debug("No frames were added to the video. Adding 'no-video.png'");
+ final URL noVideoPng = Objects.requireNonNull(classLoader.getResource("no-video.png"));
+ encoder.encodeImage(ImageIO.read(noVideoPng));
+ }
+
+ encoder.finish();
+ }
+}
diff --git a/spectrum/src/main/java/io/github/giulong/spectrum/utils/events/video/VideoInitConsumer.java b/spectrum/src/main/java/io/github/giulong/spectrum/utils/events/video/VideoInitConsumer.java
new file mode 100644
index 00000000..7ec2b513
--- /dev/null
+++ b/spectrum/src/main/java/io/github/giulong/spectrum/utils/events/video/VideoInitConsumer.java
@@ -0,0 +1,30 @@
+package io.github.giulong.spectrum.utils.events.video;
+
+import com.fasterxml.jackson.annotation.JsonView;
+import io.github.giulong.spectrum.internals.jackson.views.Views.Internal;
+import io.github.giulong.spectrum.pojos.events.Event;
+import io.github.giulong.spectrum.types.TestData;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+
+import java.nio.file.Path;
+
+import static io.github.giulong.spectrum.extensions.resolvers.TestDataResolver.TEST_DATA;
+import static org.jcodec.api.awt.AWTSequenceEncoder.createSequenceEncoder;
+import static org.junit.jupiter.api.extension.ExtensionContext.Namespace.GLOBAL;
+
+@Slf4j
+@JsonView(Internal.class)
+public class VideoInitConsumer extends VideoBaseConsumer {
+
+ @Override
+ @SneakyThrows
+ public void accept(final Event event) {
+ final TestData testData = event.getContext().getStore(GLOBAL).get(TEST_DATA, TestData.class);
+ final Path videoPath = getVideoPathFrom(testData);
+
+ log.info("Generating video for test {}", videoPath.getFileName());
+
+ testData.getEncoders().put(videoPath, createSequenceEncoder(videoPath.toFile(), 1));
+ }
+}
diff --git a/spectrum/src/main/java/io/github/giulong/spectrum/utils/web_driver_events/ScreenshotConsumer.java b/spectrum/src/main/java/io/github/giulong/spectrum/utils/web_driver_events/ScreenshotConsumer.java
index 31a92d27..5f489e3e 100644
--- a/spectrum/src/main/java/io/github/giulong/spectrum/utils/web_driver_events/ScreenshotConsumer.java
+++ b/spectrum/src/main/java/io/github/giulong/spectrum/utils/web_driver_events/ScreenshotConsumer.java
@@ -1,41 +1,43 @@
package io.github.giulong.spectrum.utils.web_driver_events;
import io.github.giulong.spectrum.enums.Frame;
-import io.github.giulong.spectrum.types.TestData;
-import io.github.giulong.spectrum.utils.FileUtils;
-import io.github.giulong.spectrum.utils.StatefulExtentTest;
+import io.github.giulong.spectrum.utils.HtmlUtils;
+import io.github.giulong.spectrum.utils.events.EventsDispatcher;
import io.github.giulong.spectrum.utils.video.Video;
-import lombok.SneakyThrows;
import lombok.experimental.SuperBuilder;
import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.extension.ExtensionContext;
import org.openqa.selenium.TakesScreenshot;
+import org.openqa.selenium.WebDriver;
-import java.nio.file.Files;
-import java.nio.file.Path;
+import java.util.Map;
+import static io.github.giulong.spectrum.extensions.resolvers.DriverResolver.DRIVER;
+import static io.github.giulong.spectrum.extensions.resolvers.TestContextResolver.EXTENSION_CONTEXT;
+import static org.junit.jupiter.api.extension.ExtensionContext.Namespace.GLOBAL;
import static org.openqa.selenium.OutputType.BYTES;
@Slf4j
@SuperBuilder
public class ScreenshotConsumer extends WebDriverEventConsumer {
- private final FileUtils fileUtils = FileUtils.getInstance();
+ public static final String SCREENSHOT = "screenshot";
+
+ private final HtmlUtils htmlUtils = HtmlUtils.getInstance();
+ private final EventsDispatcher eventsDispatcher = EventsDispatcher.getInstance();
- private TakesScreenshot driver;
- private StatefulExtentTest statefulExtentTest;
- private TestData testData;
private Video video;
+ private ExtensionContext context;
- @SneakyThrows
@Override
public void accept(final WebDriverEvent webDriverEvent) {
final Frame frame = webDriverEvent.getFrame();
if (video.shouldRecord(frame)) {
- final String fileName = fileUtils.getScreenshotNameFrom(frame, statefulExtentTest);
- final Path screenshotPath = testData.getScreenshotFolderPath().resolve(fileName);
- final Path path = Files.write(screenshotPath, driver.getScreenshotAs(BYTES));
- log.trace("Recording frame {} for event '{}' at {}", frame, webDriverEvent.getMessage(), path);
+ final byte[] screenshot = ((TakesScreenshot) context.getStore(GLOBAL).get(DRIVER, WebDriver.class)).getScreenshotAs(BYTES);
+
+ eventsDispatcher.fire(SCREENSHOT, SCREENSHOT, Map.of(EXTENSION_CONTEXT, context, SCREENSHOT, screenshot));
+ log.trace("Recording frame {} for event '{}'", frame, webDriverEvent.getMessage());
return;
}
diff --git a/spectrum/src/main/resources/yaml/configuration.default.yaml b/spectrum/src/main/resources/yaml/configuration.default.yaml
index c02405ed..052f1ff4 100644
--- a/spectrum/src/main/resources/yaml/configuration.default.yaml
+++ b/spectrum/src/main/resources/yaml/configuration.default.yaml
@@ -417,9 +417,9 @@ drivers:
message: Waiting for %2$s
afterImplicitlyWait:
message: Waited for %2$s
- beforeSetScriptTimeout:
+ beforeScriptTimeout:
message: Setting script timeout to %2$s
- afterSetScriptTimeout:
+ afterScriptTimeout:
message: Script timeout set to %2$s
beforePageLoadTimeout:
message: About to reach page load timeout of %2$s
@@ -459,6 +459,38 @@ drivers:
message: Setting fullscreen
afterFullscreen:
message: Window set to fullscreen
+ beforeAnyTargetLocatorCall:
+ message: Before target locator %1$s call
+ afterAnyTargetLocatorCall:
+ message: Result of target locator %1$s call is %2$s
+ beforeFrame:
+ message: Before frame %1$s %2$s
+ afterFrame:
+ message: After frame %1$s %2$s
+ beforeParentFrame:
+ message: Before parent frame %1$s
+ afterParentFrame:
+ message: After parent frame %1$s
+ beforeWindow:
+ message: Before window %1$s %2$s
+ afterWindow:
+ message: After window %1$s %2$s
+ beforeNewWindow:
+ message: Before new window %1$s %2$s
+ afterNewWindow:
+ message: After new window %1$s %2$s
+ beforeDefaultContent:
+ message: Before default content %1$s
+ afterDefaultContent:
+ message: After default content %1$s
+ beforeActiveElement:
+ message: Before active element %1$s
+ afterActiveElement:
+ message: After active element %1$s
+ beforeAlert:
+ message: Before alert %1$s
+ afterAlert:
+ message: After alert %1$s %2$s
# Data models
data:
@@ -476,23 +508,39 @@ faker:
# Internal events consumers. Cannot be removed, modified, nor added from client side configuration*.yaml, as lists' elements are appended when merging yaml files. You can add yours
eventsConsumers:
- - extentTest: # We need to add an entry to the Extent Report once each test is done
+ - videoInit: # initialize the video for each test
events:
- - reason: after
+ - reason: beforeExecution
tags: [ test ]
- - testbook: # We need to update the TestBook (if configured) once each test is done
+ - videoDynamicInit: # initialize the video for each dynamic test
+ events:
+ - reason: beforeExecution
+ tags: [ dynamicTest ]
+ - video: # add the screenshot to the video
+ events:
+ - primaryId: screenshot
+ reason: screenshot
+ - videoDynamic: # add the screenshot to the dynamic video
+ events:
+ - primaryId: screenshot
+ reason: screenshot
+ - extentTest: # add an entry to the Extent Report once each test is done
events:
- reason: after
tags: [ test ]
- - video: # We need to finalize the video once each test is done
+ - videoFinalizer: # finalize the video once each test is done
events:
- reason: after
- tags: [ test, dynamicTest ]
- - videoDynamic: # We need to finalize the video once each dynamic test is done
+ tags: [ test, testFactory ]
+ - videoDynamicFinalizer: # finalize the video once each dynamic test is done
events:
- reason: after
tags: [ dynamicTest ]
- - driver: # We need to close the driver once each test is done
+ - testbook: # update the TestBook once each test is done
+ events:
+ - reason: after
+ tags: [ test ]
+ - driver: # close the driver once each test is done
events:
- reason: after
tags: [ test, testFactory ]
diff --git a/spectrum/src/test/java/io/github/giulong/spectrum/SpectrumEntityTest.java b/spectrum/src/test/java/io/github/giulong/spectrum/SpectrumEntityTest.java
index fabab6ed..23a527d2 100644
--- a/spectrum/src/test/java/io/github/giulong/spectrum/SpectrumEntityTest.java
+++ b/spectrum/src/test/java/io/github/giulong/spectrum/SpectrumEntityTest.java
@@ -7,21 +7,20 @@
import io.github.giulong.spectrum.interfaces.Shared;
import io.github.giulong.spectrum.types.TestData;
import io.github.giulong.spectrum.utils.*;
+import io.github.giulong.spectrum.utils.events.EventsDispatcher;
import io.github.giulong.spectrum.utils.video.Video;
import lombok.SneakyThrows;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.*;
-import org.openqa.selenium.By;
-import org.openqa.selenium.TakesScreenshot;
-import org.openqa.selenium.WebDriver;
-import org.openqa.selenium.WebElement;
+import org.openqa.selenium.*;
import org.openqa.selenium.interactions.Actions;
import org.openqa.selenium.support.ui.WebDriverWait;
@@ -33,13 +32,18 @@
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.List;
+import java.util.Map;
import java.util.function.Function;
import java.util.stream.Stream;
import static com.aventstack.extentreports.Status.*;
import static io.github.giulong.spectrum.SpectrumEntity.HASH_ALGORITHM;
import static io.github.giulong.spectrum.enums.Frame.MANUAL;
+import static io.github.giulong.spectrum.extensions.resolvers.DriverResolver.DRIVER;
+import static io.github.giulong.spectrum.extensions.resolvers.TestContextResolver.EXTENSION_CONTEXT;
+import static io.github.giulong.spectrum.utils.web_driver_events.ScreenshotConsumer.SCREENSHOT;
import static org.junit.jupiter.api.Assertions.*;
+import static org.junit.jupiter.api.extension.ExtensionContext.Namespace.GLOBAL;
import static org.junit.jupiter.params.provider.Arguments.arguments;
import static org.mockito.Mockito.*;
import static org.openqa.selenium.OutputType.BYTES;
@@ -51,13 +55,17 @@ class SpectrumEntityTest {
private MockedStatic mediaEntityBuilderMockedStatic;
private MockedStatic messageDigestMockedStatic;
+ private final String screenshot = "screenshot";
+ private final String extension = ".png";
private final String msg = "msg";
private final String tag = "tag";
- private final String screenshotName = "screenshotName";
private final int frameNumber = 123;
private final byte[] bytes = new byte[]{1, 2, 3};
private final byte[] digest = new byte[]{4, 5, 6};
+ @Mock
+ private ContextManager contextManager;
+
@Mock
private WebDriverWait downloadWait;
@@ -73,9 +81,6 @@ class SpectrumEntityTest {
@Mock
private StatefulExtentTest statefulExtentTest;
- @Mock(extraInterfaces = TakesScreenshot.class)
- private WebDriver webDriver;
-
@Mock
private WebElement webElement;
@@ -97,9 +102,6 @@ class SpectrumEntityTest {
@Mock
private Path path;
- @Mock
- private Path reportsFolder;
-
@Mock
private Path downloadsFolderPath;
@@ -109,11 +111,29 @@ class SpectrumEntityTest {
@Mock
private File file;
+ @Mock
+ private EventsDispatcher eventsDispatcher;
+
@Mock
private MediaEntityBuilder mediaEntityBuilder;
@Mock
- private Media screenshot;
+ private TestContext testContext;
+
+ @Mock
+ private ExtensionContext context;
+
+ @Mock
+ private ExtensionContext.Store store;
+
+ @Mock(extraInterfaces = TakesScreenshot.class)
+ private WebDriver driver;
+
+ @Mock
+ private Media media;
+
+ @Mock
+ private Map screenshots;
@Mock
private MessageDigest messageDigest;
@@ -121,6 +141,9 @@ class SpectrumEntityTest {
@Mock
private TestData testData;
+ @Captor
+ private ArgumentCaptor byteArrayArgumentCaptor;
+
@Captor
private ArgumentCaptor> functionArgumentCaptor;
@@ -129,9 +152,11 @@ class SpectrumEntityTest {
@BeforeEach
void beforeEach() {
+ Reflections.setField("contextManager", spectrumEntity, contextManager);
Reflections.setField("configuration", spectrumEntity, configuration);
Reflections.setField("htmlUtils", spectrumEntity, htmlUtils);
Reflections.setField("fileUtils", spectrumEntity, fileUtils);
+ Reflections.setField("eventsDispatcher", spectrumEntity, eventsDispatcher);
pathMockedStatic = mockStatic(Path.class);
filesMockedStatic = mockStatic(Files.class);
@@ -149,19 +174,22 @@ void afterEach() {
@SneakyThrows
private void addScreenshotToReportStubs() {
- when(fileUtils.getScreenshotNameFrom(MANUAL, statefulExtentTest)).thenReturn(screenshotName);
- when(testData.getScreenshotFolderPath()).thenReturn(path);
- when(((TakesScreenshot) webDriver).getScreenshotAs(BYTES)).thenReturn(new byte[]{1, 2, 3});
+ when(testContext.get(EXTENSION_CONTEXT, ExtensionContext.class)).thenReturn(context);
+
+ when(context.getStore(GLOBAL)).thenReturn(store);
+ when(store.get(DRIVER, WebDriver.class)).thenReturn(driver);
+ when(((TakesScreenshot) driver).getScreenshotAs(BYTES)).thenReturn(bytes);
- when(fileUtils.getScreenshotNameFrom(MANUAL, statefulExtentTest)).thenReturn(screenshotName);
- when(testData.getScreenshotFolderPath()).thenReturn(reportsFolder);
- when(reportsFolder.resolve(screenshotName)).thenReturn(path);
when(statefulExtentTest.getCurrentNode()).thenReturn(extentTest);
when(configuration.getVideo()).thenReturn(video);
when(video.getAndIncrementFrameNumberFor(testData, MANUAL)).thenReturn(frameNumber);
when(htmlUtils.buildFrameTagFor(frameNumber, msg, testData, "screenshot-message")).thenReturn(tag);
when(MediaEntityBuilder.createScreenCaptureFromPath(path.toString())).thenReturn(mediaEntityBuilder);
- when(mediaEntityBuilder.build()).thenReturn(screenshot);
+ when(mediaEntityBuilder.build()).thenReturn(media);
+
+ when(fileUtils.writeTempFile(eq(screenshot), eq(extension), byteArrayArgumentCaptor.capture())).thenReturn(path);
+
+ when(contextManager.getScreenshots()).thenReturn(screenshots);
}
@Test
@@ -210,16 +238,19 @@ void hover() {
@Test
@DisplayName("screenshot should delegate to addScreenshotToReport")
void screenshot() {
- when(fileUtils.getScreenshotNameFrom(MANUAL, statefulExtentTest)).thenReturn(screenshotName);
- when(testData.getScreenshotFolderPath()).thenReturn(path);
- when(((TakesScreenshot) webDriver).getScreenshotAs(BYTES)).thenReturn(new byte[]{1, 2, 3});
+ when(testContext.get(EXTENSION_CONTEXT, ExtensionContext.class)).thenReturn(context);
- when(fileUtils.getScreenshotNameFrom(MANUAL, statefulExtentTest)).thenReturn(screenshotName);
- when(testData.getScreenshotFolderPath()).thenReturn(reportsFolder);
- when(reportsFolder.resolve(screenshotName)).thenReturn(path);
when(statefulExtentTest.getCurrentNode()).thenReturn(extentTest);
when(MediaEntityBuilder.createScreenCaptureFromPath(path.toString())).thenReturn(mediaEntityBuilder);
- when(mediaEntityBuilder.build()).thenReturn(screenshot);
+ when(mediaEntityBuilder.build()).thenReturn(media);
+
+ when(context.getStore(GLOBAL)).thenReturn(store);
+ when(store.get(DRIVER, WebDriver.class)).thenReturn(driver);
+ when(((TakesScreenshot) driver).getScreenshotAs(BYTES)).thenReturn(bytes);
+
+ when(fileUtils.writeTempFile(screenshot, extension, bytes)).thenReturn(path);
+
+ when(contextManager.getScreenshots()).thenReturn(screenshots);
assertEquals(spectrumEntity, spectrumEntity.screenshot());
@@ -273,23 +304,30 @@ void failWithScreenshot() {
void addScreenshotToReport() {
final Status status = INFO;
- when(fileUtils.getScreenshotNameFrom(MANUAL, statefulExtentTest)).thenReturn(screenshotName);
- when(testData.getScreenshotFolderPath()).thenReturn(reportsFolder);
- when(reportsFolder.resolve(screenshotName)).thenReturn(path);
+ when(testContext.get(EXTENSION_CONTEXT, ExtensionContext.class)).thenReturn(context);
when(statefulExtentTest.getCurrentNode()).thenReturn(extentTest);
when(configuration.getVideo()).thenReturn(video);
when(video.getAndIncrementFrameNumberFor(testData, MANUAL)).thenReturn(frameNumber);
when(htmlUtils.buildFrameTagFor(frameNumber, msg, testData, "screenshot-message")).thenReturn(tag);
- when(MediaEntityBuilder.createScreenCaptureFromPath(path.toString())).thenReturn(mediaEntityBuilder);
- when(mediaEntityBuilder.build()).thenReturn(screenshot);
- when(((TakesScreenshot) webDriver).getScreenshotAs(BYTES)).thenReturn(new byte[]{1, 2, 3});
+ when(context.getStore(GLOBAL)).thenReturn(store);
+ when(store.get(DRIVER, WebDriver.class)).thenReturn(driver);
+ when(((TakesScreenshot) driver).getScreenshotAs(BYTES)).thenReturn(bytes);
- final Media actual = spectrumEntity.addScreenshotToReport(msg, status);
+ when(fileUtils.writeTempFile(eq(screenshot), eq(extension), byteArrayArgumentCaptor.capture())).thenReturn(path);
+
+ when(contextManager.getScreenshots()).thenReturn(screenshots);
+
+ when(MediaEntityBuilder.createScreenCaptureFromPath(path.toString())).thenReturn(mediaEntityBuilder);
+ when(mediaEntityBuilder.build()).thenReturn(media);
- assertEquals(screenshot, actual);
+ spectrumEntity.addScreenshotToReport(msg, status);
- verify(extentTest).log(status, tag, actual);
+ assertArrayEquals(bytes, byteArrayArgumentCaptor.getValue());
+ verify(screenshots).put(path, bytes);
+ verify(eventsDispatcher).fire(SCREENSHOT, SCREENSHOT, Map.of(EXTENSION_CONTEXT, context, SCREENSHOT, bytes));
+ verify(extentTest).log(status, tag, media);
+ verifyNoMoreInteractions(eventsDispatcher);
}
@Test
@@ -318,7 +356,7 @@ void waitForDownloadOf() {
verify(downloadWait).until(functionArgumentCaptor.capture());
final Function function = functionArgumentCaptor.getValue();
- assertTrue(function.apply(webDriver));
+ assertTrue(function.apply(driver));
}
@Test
@@ -333,7 +371,7 @@ void waitForDownloadOfNotYetDone() {
verify(downloadWait).until(functionArgumentCaptor.capture());
final Function function = functionArgumentCaptor.getValue();
- assertFalse(function.apply(webDriver));
+ assertFalse(function.apply(driver));
}
@Test
@@ -346,7 +384,7 @@ void waitForDownloadOfNotYetCreated() {
verify(downloadWait).until(functionArgumentCaptor.capture());
final Function function = functionArgumentCaptor.getValue();
- assertFalse(function.apply(webDriver));
+ assertFalse(function.apply(driver));
}
@Test
@@ -437,7 +475,7 @@ void upload() {
@ParameterizedTest(name = "with list {0} we expect {1}")
@MethodSource("isPresentProvider")
void isPresent(final List webElements, final boolean expected) {
- when(webDriver.findElements(by)).thenReturn(webElements);
+ when(driver.findElements(by)).thenReturn(webElements);
assertEquals(expected, spectrumEntity.isPresent(by));
}
@@ -453,7 +491,7 @@ static Stream isPresentProvider() {
@ParameterizedTest(name = "with list {0} we expect {1}")
@MethodSource("isNotPresentProvider")
void isNotPresent(final List webElements, final boolean expected) {
- when(webDriver.findElements(by)).thenReturn(webElements);
+ when(driver.findElements(by)).thenReturn(webElements);
assertEquals(expected, spectrumEntity.isNotPresent(by));
}
diff --git a/spectrum/src/test/java/io/github/giulong/spectrum/extensions/interceptors/SpectrumInterceptorTest.java b/spectrum/src/test/java/io/github/giulong/spectrum/extensions/interceptors/SpectrumInterceptorTest.java
index f26fbd08..4e20feb3 100644
--- a/spectrum/src/test/java/io/github/giulong/spectrum/extensions/interceptors/SpectrumInterceptorTest.java
+++ b/spectrum/src/test/java/io/github/giulong/spectrum/extensions/interceptors/SpectrumInterceptorTest.java
@@ -122,6 +122,7 @@ private void commonStubs() {
@SuppressWarnings("checkstyle:IllegalThrows")
private void commonVerifications() throws Throwable {
+ verify(testData).setDynamic(true);
verify(testData).setFrameNumber(0);
verify(testData).setDisplayName(displayName);
verify(testData).setDynamicVideoPath(dynamicVideoPath);
@@ -130,6 +131,7 @@ private void commonVerifications() throws Throwable {
verify(statefulExtentTest).closeNode();
verify(eventsDispatcher).fire(className, displayName, BEFORE, null, Set.of(DYNAMIC_TEST), context);
+ verify(eventsDispatcher).fire(className, displayName, BEFORE_EXECUTION, null, Set.of(DYNAMIC_TEST), context);
verify(invocation).proceed();
verify(contextManager).initWithParentFor(context);
}
@@ -148,6 +150,7 @@ void interceptDynamicTest(final boolean videoDisabled, final boolean attach) thr
commonVerifications();
verify(eventsDispatcher).fire(className, displayName, AFTER, SUCCESSFUL, Set.of(DYNAMIC_TEST), context);
+ verifyNoMoreInteractions(eventsDispatcher);
verifyNoInteractions(extentReporter);
}
@@ -176,6 +179,7 @@ void interceptDynamicTestAttachVideo() throws Throwable {
commonVerifications();
verify(eventsDispatcher).fire(className, displayName, AFTER, SUCCESSFUL, Set.of(DYNAMIC_TEST), context);
verify(extentReporter).attachVideo(extentTest, videoExtentTest, String.format("%s-%s", testId, displayName), dynamicVideoPath);
+ verifyNoMoreInteractions(eventsDispatcher);
}
@Test
@@ -190,6 +194,7 @@ void interceptDynamicTestThrow() throws Throwable {
commonVerifications();
verify(eventsDispatcher).fire(className, displayName, AFTER, FAILED, Set.of(DYNAMIC_TEST), context);
+ verifyNoMoreInteractions(eventsDispatcher);
verifyNoInteractions(extentReporter);
}
}
diff --git a/spectrum/src/test/java/io/github/giulong/spectrum/extensions/resolvers/DriverResolverTest.java b/spectrum/src/test/java/io/github/giulong/spectrum/extensions/resolvers/DriverResolverTest.java
index 4567df63..035357cc 100644
--- a/spectrum/src/test/java/io/github/giulong/spectrum/extensions/resolvers/DriverResolverTest.java
+++ b/spectrum/src/test/java/io/github/giulong/spectrum/extensions/resolvers/DriverResolverTest.java
@@ -244,10 +244,8 @@ private void stubs() {
when(ScreenshotConsumer.builder()).thenReturn(screenshotConsumerBuilder);
when(screenshotConsumerBuilder.enabled(true)).thenReturn(screenshotConsumerBuilder);
- when(screenshotConsumerBuilder.driver((TakesScreenshot) webDriver)).thenReturn(screenshotConsumerBuilder);
- when(screenshotConsumerBuilder.statefulExtentTest(statefulExtentTest)).thenReturn(screenshotConsumerBuilder);
- when(screenshotConsumerBuilder.testData(testData)).thenReturn(screenshotConsumerBuilder);
when(screenshotConsumerBuilder.video(video)).thenReturn(screenshotConsumerBuilder);
+ when(screenshotConsumerBuilder.context(context)).thenReturn(screenshotConsumerBuilder);
when(screenshotConsumerBuilder.build()).thenReturn(screenshotConsumer);
when(TestStepBuilderConsumer.builder()).thenReturn(testStepBuilderConsumerBuilder);
diff --git a/spectrum/src/test/java/io/github/giulong/spectrum/extensions/resolvers/TestContextResolverTest.java b/spectrum/src/test/java/io/github/giulong/spectrum/extensions/resolvers/TestContextResolverTest.java
index 45f8687e..8d3cd1e1 100644
--- a/spectrum/src/test/java/io/github/giulong/spectrum/extensions/resolvers/TestContextResolverTest.java
+++ b/spectrum/src/test/java/io/github/giulong/spectrum/extensions/resolvers/TestContextResolverTest.java
@@ -11,11 +11,9 @@
import org.mockito.InjectMocks;
import org.mockito.Mock;
-import static io.github.giulong.spectrum.extensions.resolvers.TestContextResolver.TEST_CONTEXT;
+import static io.github.giulong.spectrum.extensions.resolvers.TestContextResolver.EXTENSION_CONTEXT;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.extension.ExtensionContext.Namespace.GLOBAL;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.*;
class TestContextResolverTest {
@@ -45,12 +43,12 @@ void beforeEach() {
@Test
@DisplayName("resolveParameter should return an instance of TestContext")
void resolveParameter() {
- when(context.getStore(GLOBAL)).thenReturn(store);
when(contextManager.initFor(context)).thenReturn(testContext);
final TestContext actual = testContextResolver.resolveParameter(parameterContext, context);
assertEquals(testContext, actual);
- verify(store).put(TEST_CONTEXT, actual);
+ verify(testContext).put(EXTENSION_CONTEXT, context);
+ verifyNoInteractions(store);
}
}
diff --git a/spectrum/src/test/java/io/github/giulong/spectrum/extensions/resolvers/TestDataResolverTest.java b/spectrum/src/test/java/io/github/giulong/spectrum/extensions/resolvers/TestDataResolverTest.java
index c64893d8..49b4485f 100644
--- a/spectrum/src/test/java/io/github/giulong/spectrum/extensions/resolvers/TestDataResolverTest.java
+++ b/spectrum/src/test/java/io/github/giulong/spectrum/extensions/resolvers/TestDataResolverTest.java
@@ -1,8 +1,9 @@
package io.github.giulong.spectrum.extensions.resolvers;
import io.github.giulong.spectrum.types.TestData;
-import io.github.giulong.spectrum.utils.*;
-import io.github.giulong.spectrum.utils.video.Video;
+import io.github.giulong.spectrum.utils.ContextManager;
+import io.github.giulong.spectrum.utils.FileUtils;
+import io.github.giulong.spectrum.utils.Reflections;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
@@ -16,22 +17,13 @@
import java.util.List;
import java.util.Optional;
-import static io.github.giulong.spectrum.extensions.resolvers.ConfigurationResolver.CONFIGURATION;
import static io.github.giulong.spectrum.extensions.resolvers.TestDataResolver.TEST_DATA;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.matchesPattern;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.extension.ExtensionContext.Namespace.GLOBAL;
import static org.mockito.Mockito.*;
class TestDataResolverTest {
- private static final String UUID_REGEX = "([a-f0-9]{8}(-[a-f0-9]{4}){4}[a-f0-9]{8})\\.mp4";
- private static final String CLASS_NAME = "className";
- private static final String METHOD_NAME = "methodName";
- private static final String REPORTS_FOLDER = "reportsFolder";
-
private static MockedStatic testDataMockedStatic;
@Mock
@@ -61,30 +53,12 @@ class TestDataResolverTest {
@Mock
private ExtensionContext.Store store;
- @Mock
- private ExtensionContext.Store rootStore;
-
- @Mock
- private Configuration configuration;
-
- @Mock
- private Configuration.Extent extent;
-
@Mock
private TestData.TestDataBuilder testDataBuilder;
@Mock
private TestData testData;
- @Mock
- private Video video;
-
- @Captor
- private ArgumentCaptor pathArgumentCaptor;
-
- @Captor
- private ArgumentCaptor stringArgumentCaptor;
-
@InjectMocks
private TestDataResolver testDataResolver;
@@ -112,39 +86,19 @@ void resolveParameter() throws NoSuchMethodException {
final String displayName = "displayName";
final String sanitizedDisplayName = "sanitizedDisplayName";
final String testId = "string-sanitizeddisplayname";
- final String fileName = "fileName";
- final String fileNameWithoutExtension = "fileNameWithoutExtension";
// joinTestDisplayNamesIn
when(context.getParent()).thenReturn(Optional.of(parentContext));
when(parentContext.getParent()).thenReturn(Optional.of(rootContext));
when(context.getDisplayName()).thenReturn(displayName);
- when(fileUtils.removeExtensionFrom(fileName)).thenReturn(fileNameWithoutExtension);
when(fileUtils.sanitize(classDisplayName)).thenReturn(sanitizedClassDisplayName);
when(fileUtils.sanitize(displayName)).thenReturn(sanitizedDisplayName);
-
- // getScreenshotFolderPathForCurrentTest
- when(fileUtils.deleteContentOf(
- Path.of(REPORTS_FOLDER, fileNameWithoutExtension, "screenshots", sanitizedClassDisplayName, sanitizedDisplayName).toAbsolutePath()))
- .thenReturn(path);
-
- // getVideoPathForCurrentTest
- when(fileUtils.deleteContentOf(
- Path.of(REPORTS_FOLDER, fileNameWithoutExtension, "videos", sanitizedClassDisplayName, sanitizedDisplayName).toAbsolutePath()))
- .thenReturn(path);
+ when(fileUtils.createTempFile("video", ".mp4")).thenReturn(path);
when(context.getStore(GLOBAL)).thenReturn(store);
- when(context.getRoot()).thenReturn(rootContext);
- when(rootContext.getStore(GLOBAL)).thenReturn(rootStore);
- when(rootStore.get(CONFIGURATION, Configuration.class)).thenReturn(configuration);
- when(configuration.getExtent()).thenReturn(extent);
- when(extent.getReportFolder()).thenReturn(REPORTS_FOLDER);
- when(extent.getFileName()).thenReturn(fileName);
doReturn(String.class).when(context).getRequiredTestClass();
when(context.getRequiredTestMethod()).thenReturn(getClass().getDeclaredMethod(methodName));
- when(configuration.getVideo()).thenReturn(video);
- when(video.isDisabled()).thenReturn(false);
when(TestData.builder()).thenReturn(testDataBuilder);
when(testDataBuilder.className(className)).thenReturn(testDataBuilder);
@@ -152,8 +106,7 @@ void resolveParameter() throws NoSuchMethodException {
when(testDataBuilder.classDisplayName(sanitizedClassDisplayName)).thenReturn(testDataBuilder);
when(testDataBuilder.displayName(sanitizedDisplayName)).thenReturn(testDataBuilder);
when(testDataBuilder.testId(testId)).thenReturn(testDataBuilder);
- when(testDataBuilder.screenshotFolderPath(path)).thenReturn(testDataBuilder);
- when(testDataBuilder.videoPath(pathArgumentCaptor.capture())).thenReturn(testDataBuilder);
+ when(testDataBuilder.videoPath(path)).thenReturn(testDataBuilder);
when(testDataBuilder.build()).thenReturn(testData);
final TestData actual = testDataResolver.resolveParameter(parameterContext, context);
@@ -163,33 +116,6 @@ void resolveParameter() throws NoSuchMethodException {
verify(contextManager).put(context, TEST_DATA, actual);
}
- @Test
- @DisplayName("getScreenshotFolderPathForCurrentTest should return the path for the current test and create the dirs")
- void getScreenshotFolderPathForCurrentTest() {
- final String extentFileName = "extentFileName";
-
- when(fileUtils.deleteContentOf(Path.of(REPORTS_FOLDER, extentFileName, "screenshots", CLASS_NAME, METHOD_NAME).toAbsolutePath())).thenReturn(path);
- assertEquals(path, testDataResolver.getScreenshotFolderPathForCurrentTest(REPORTS_FOLDER, extentFileName, CLASS_NAME, METHOD_NAME));
- }
-
- @Test
- @DisplayName("getVideoPathForCurrentTest should return the path for the current test and create the directories")
- void getVideoPathForCurrentTest() {
- final String extentFileName = "extentFileName";
-
- when(fileUtils.deleteContentOf(Path.of(REPORTS_FOLDER, extentFileName, "videos", CLASS_NAME, METHOD_NAME).toAbsolutePath())).thenReturn(path);
- when(path.resolve(stringArgumentCaptor.capture())).thenReturn(path);
-
- assertEquals(path, testDataResolver.getVideoPathForCurrentTest(false, REPORTS_FOLDER, extentFileName, CLASS_NAME, METHOD_NAME));
- assertThat(stringArgumentCaptor.getValue(), matchesPattern(UUID_REGEX));
- }
-
- @Test
- @DisplayName("getVideoPathForCurrentTest should return null if video is disabled")
- void getVideoPathForCurrentTestDisabled() {
- assertNull(testDataResolver.getVideoPathForCurrentTest(true, REPORTS_FOLDER, "extentFileName", CLASS_NAME, METHOD_NAME));
- }
-
@Test
@DisplayName("getDisplayNameOf should return the @DisplayName value")
void getDisplayNameOf() {
diff --git a/spectrum/src/test/java/io/github/giulong/spectrum/extensions/watchers/EventsWatcherTest.java b/spectrum/src/test/java/io/github/giulong/spectrum/extensions/watchers/EventsWatcherTest.java
index fa2e5a0d..2e9079df 100644
--- a/spectrum/src/test/java/io/github/giulong/spectrum/extensions/watchers/EventsWatcherTest.java
+++ b/spectrum/src/test/java/io/github/giulong/spectrum/extensions/watchers/EventsWatcherTest.java
@@ -5,6 +5,9 @@
import io.github.giulong.spectrum.utils.events.EventsDispatcher;
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockedStatic;
@@ -15,6 +18,8 @@
import static io.github.giulong.spectrum.enums.Result.*;
import static io.github.giulong.spectrum.utils.events.EventsDispatcher.*;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.params.provider.Arguments.arguments;
import static org.mockito.Mockito.*;
class EventsWatcherTest {
@@ -74,6 +79,7 @@ void testBeforeAll() {
notifyClassStubs();
eventsWatcher.beforeAll(extensionContext);
verify(eventsDispatcher).fire(className, null, BEFORE, null, Set.of(CLASS), extensionContext);
+ verifyNoMoreInteractions(eventsDispatcher);
}
@Test
@@ -82,6 +88,32 @@ void testBeforeEach() {
notifyTestStubs();
eventsWatcher.beforeEach(extensionContext);
verify(eventsDispatcher).fire(className, displayName, BEFORE, null, Set.of(TEST), extensionContext);
+ verifyNoMoreInteractions(eventsDispatcher);
+ }
+
+ @Test
+ @DisplayName("beforeTestExecution should dispatch only an event for regular tests")
+ void testBeforeTestExecution() throws NoSuchMethodException {
+ notifyTestStubs();
+
+ when(extensionContext.getRequiredTestMethod()).thenReturn(getClass().getDeclaredMethod("testAfterEach"));
+
+ eventsWatcher.beforeTestExecution(extensionContext);
+ verify(eventsDispatcher).fire(className, displayName, BEFORE_EXECUTION, null, Set.of(TEST), extensionContext);
+ verifyNoMoreInteractions(eventsDispatcher);
+ }
+
+ @Test
+ @DisplayName("beforeTestExecution should dispatch two events for TestFactories")
+ void testBeforeTestExecutionTestFactory() throws NoSuchMethodException {
+ notifyTestStubs();
+
+ when(extensionContext.getRequiredTestMethod()).thenReturn(getClass().getDeclaredMethod("testFactoryMethod"));
+
+ eventsWatcher.beforeTestExecution(extensionContext);
+ verify(eventsDispatcher).fire(className, displayName, BEFORE_EXECUTION, null, Set.of(TEST), extensionContext);
+ verify(eventsDispatcher).fire(className, displayName, BEFORE_EXECUTION, null, Set.of(TEST_FACTORY), extensionContext);
+ verifyNoMoreInteractions(eventsDispatcher);
}
@Test
@@ -93,6 +125,7 @@ void testAfterEachTestFactory() throws NoSuchMethodException {
eventsWatcher.afterEach(extensionContext);
verify(eventsDispatcher).fire(className, displayName, AFTER, SUCCESSFUL, Set.of(TEST_FACTORY), extensionContext);
+ verifyNoMoreInteractions(eventsDispatcher);
}
@Test
@@ -110,6 +143,7 @@ void testAfterAll() {
notifyClassStubs();
eventsWatcher.afterAll(extensionContext);
verify(eventsDispatcher).fire(className, null, AFTER, null, Set.of(CLASS), extensionContext);
+ verifyNoMoreInteractions(eventsDispatcher);
}
@Test
@@ -118,6 +152,7 @@ void testDisabled() {
notifyTestStubs();
eventsWatcher.testDisabled(extensionContext, Optional.of("reason"));
verify(eventsDispatcher).fire(className, displayName, AFTER, DISABLED, Set.of(TEST), extensionContext);
+ verifyNoMoreInteractions(eventsDispatcher);
}
@Test
@@ -126,6 +161,7 @@ void testSuccessful() {
notifyTestStubs();
eventsWatcher.testSuccessful(extensionContext);
verify(eventsDispatcher).fire(className, displayName, AFTER, SUCCESSFUL, Set.of(TEST), extensionContext);
+ verifyNoMoreInteractions(eventsDispatcher);
}
@Test
@@ -134,6 +170,7 @@ void testAborted() {
notifyTestStubs();
eventsWatcher.testAborted(extensionContext, new RuntimeException());
verify(eventsDispatcher).fire(className, displayName, AFTER, ABORTED, Set.of(TEST), extensionContext);
+ verifyNoMoreInteractions(eventsDispatcher);
}
@Test
@@ -142,6 +179,23 @@ void testFailed() {
notifyTestStubs();
eventsWatcher.testFailed(extensionContext, new RuntimeException());
verify(eventsDispatcher).fire(className, displayName, AFTER, FAILED, Set.of(TEST), extensionContext);
+ verifyNoMoreInteractions(eventsDispatcher);
+ }
+
+ @DisplayName("isTestFactory should check if the method is annotated with @TestFactory")
+ @ParameterizedTest(name = "with method {0} we expect {1}")
+ @MethodSource("valuesProvider")
+ void isTestFactory(final String methodName, final boolean expected) throws NoSuchMethodException {
+ when(extensionContext.getRequiredTestMethod()).thenReturn(getClass().getDeclaredMethod(methodName));
+
+ assertEquals(expected, eventsWatcher.isTestFactory(extensionContext));
+ }
+
+ static Stream valuesProvider() {
+ return Stream.of(
+ arguments("testFactoryMethod", true),
+ arguments("testAfterEach", false)
+ );
}
@Test
@@ -154,6 +208,7 @@ void testNotifyClass() {
notifyClassStubs();
eventsWatcher.notifyClass(extensionContext, reason, result, tags);
verify(eventsDispatcher).fire(className, null, reason, result, tags, extensionContext);
+ verifyNoMoreInteractions(eventsDispatcher);
}
@Test
@@ -166,5 +221,6 @@ void testNotifyTest() {
notifyTestStubs();
eventsWatcher.notifyTest(extensionContext, reason, result, tags);
verify(eventsDispatcher).fire(className, displayName, reason, result, tags, extensionContext);
+ verifyNoMoreInteractions(eventsDispatcher);
}
}
diff --git a/spectrum/src/test/java/io/github/giulong/spectrum/utils/FileUtilsTest.java b/spectrum/src/test/java/io/github/giulong/spectrum/utils/FileUtilsTest.java
index 73dca806..9b9ce610 100644
--- a/spectrum/src/test/java/io/github/giulong/spectrum/utils/FileUtilsTest.java
+++ b/spectrum/src/test/java/io/github/giulong/spectrum/utils/FileUtilsTest.java
@@ -1,11 +1,15 @@
package io.github.giulong.spectrum.utils;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
-import org.mockito.*;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.MockedStatic;
import java.io.File;
import java.io.IOException;
@@ -15,7 +19,6 @@
import java.nio.file.attribute.FileTime;
import java.util.stream.Stream;
-import static io.github.giulong.spectrum.enums.Frame.AUTO_AFTER;
import static java.lang.System.lineSeparator;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.matchesPattern;
@@ -25,8 +28,7 @@
class FileUtilsTest {
- private static final String DISPLAY_NAME = "displayName";
- private static final String UUID_REGEX = AUTO_AFTER.getValue() + "-" + DISPLAY_NAME + "-([a-f0-9]{8}(-[a-f0-9]{4}){4}[a-f0-9]{8})\\.png";
+ private static MockedStatic filesMockedStatic;
@Mock
private BasicFileAttributes basicFileAttributes;
@@ -43,12 +45,19 @@ class FileUtilsTest {
@Mock
private File file;
- @Mock
- private StatefulExtentTest statefulExtentTest;
-
@InjectMocks
private FileUtils fileUtils;
+ @BeforeEach
+ void beforeEach() {
+ filesMockedStatic = mockStatic(Files.class);
+ }
+
+ @AfterEach
+ void afterEach() {
+ filesMockedStatic.close();
+ }
+
@Test
@DisplayName("getInstance should return the singleton")
void getInstance() {
@@ -132,51 +141,64 @@ static Stream removeExtensionFromValuesProvider() {
);
}
- @DisplayName("delete should delete the provided folder and return the reference to it")
- @ParameterizedTest(name = "with value {0} which is existing? {1}")
- @MethodSource("deleteValuesProvider")
- void delete(final Path directory, final boolean existing) {
- directory.toFile().deleteOnExit();
- assertEquals(existing, Files.exists(directory));
+ @Test
+ @DisplayName("delete should do nothing if the provided folder doesn't exist and return the reference to it")
+ void deleteNotExisting() {
+ when(Files.notExists(path)).thenReturn(true);
- assertEquals(directory, fileUtils.delete(directory));
+ assertEquals(path, fileUtils.delete(path));
- assertFalse(Files.exists(directory));
+ //noinspection resource
+ filesMockedStatic.verify(() -> Files.walk(path), never());
}
- static Stream deleteValuesProvider() throws IOException {
- return Stream.of(
- arguments(Path.of("abc not existing"), false),
- arguments(Files.createTempDirectory("downloadsFolder"), true));
+ @Test
+ @DisplayName("delete should delete the provided folder and return the reference to it")
+ void delete() throws IOException {
+ final Path filePath = mock();
+
+ when(Files.notExists(path)).thenReturn(false);
+ when(Files.walk(path)).thenReturn(Stream.of(filePath));
+ when(filePath.toFile()).thenReturn(file);
+
+ assertEquals(path, fileUtils.delete(path));
+
+ verify(file).delete();
}
@Test
@DisplayName("overloaded delete that takes a file should delegate to the one taking its path")
void deleteFile() throws IOException {
- final File directory = Files.createTempDirectory("downloadsFolder").toFile();
- directory.deleteOnExit();
+ final Path filePath = mock();
- assertEquals(directory.toPath(), fileUtils.delete(directory));
+ when(file.toPath()).thenReturn(path);
+ when(Files.notExists(path)).thenReturn(false);
+ when(Files.walk(path)).thenReturn(Stream.of(filePath));
+ when(filePath.toFile()).thenReturn(file);
- assertFalse(directory.exists());
+ assertEquals(path, fileUtils.delete(file));
+
+ verify(file).delete();
}
+ @Test
@DisplayName("delete should delete the provided directory, recreate it, and return the reference to it")
- @ParameterizedTest(name = "with value {0} which is existing? {1}")
- @MethodSource("deleteValuesProvider")
- void deleteContentOf(final Path directory, final boolean existing) {
- directory.toFile().deleteOnExit();
- assertEquals(existing, Files.exists(directory));
+ void deleteContentOf() throws IOException {
+ final Path filePath = mock();
- assertEquals(directory, fileUtils.deleteContentOf(directory));
+ when(Files.createDirectories(path)).thenReturn(path);
- assertTrue(Files.exists(directory));
+ when(Files.notExists(path)).thenReturn(false);
+ when(Files.walk(path)).thenReturn(Stream.of(filePath));
+ when(filePath.toFile()).thenReturn(file);
+
+ assertEquals(path, fileUtils.deleteContentOf(path));
+ verify(file).delete();
}
@Test
@DisplayName("write should write the provided content to a file in the provided path, creating the parent folders if needed")
void write() {
- final MockedStatic filesMockedStatic = mockStatic(Files.class);
final String content = "content";
when(path.getParent()).thenReturn(parentPath);
@@ -186,14 +208,11 @@ void write() {
verify(file).mkdirs();
filesMockedStatic.verify(() -> Files.write(path, content.getBytes()));
-
- filesMockedStatic.close();
}
@Test
@DisplayName("write should write the provided content to a file in the provided string path, creating the parent folders if needed")
void writeString() {
- final MockedStatic filesMockedStatic = mockStatic(Files.class);
final MockedStatic pathMockedStatic = mockStatic(Path.class);
final String stringPath = "stringPath";
final String content = "content";
@@ -207,7 +226,6 @@ void writeString() {
verify(file).mkdirs();
filesMockedStatic.verify(() -> Files.write(path, content.getBytes()));
- filesMockedStatic.close();
pathMockedStatic.close();
}
@@ -223,22 +241,40 @@ void sanitize() {
@Test
@DisplayName("getCreationTimeOf should return the creation time of the provided file")
void getCreationTimeOf() throws IOException {
- final MockedStatic filesMockedStatic = mockStatic(Files.class);
-
when(file.toPath()).thenReturn(path);
when(Files.readAttributes(path, BasicFileAttributes.class)).thenReturn(basicFileAttributes);
when(basicFileAttributes.creationTime()).thenReturn(creationTime);
assertEquals(creationTime, fileUtils.getCreationTimeOf(file));
+ }
- filesMockedStatic.close();
+ @Test
+ @DisplayName("createTempFile should create a temp file with the provided prefix, suffix, and delete it on exit")
+ void createTempFile() throws IOException {
+ final String prefix = "prefix";
+ final String suffix = "suffix";
+
+ when(Files.createTempFile(prefix, suffix)).thenReturn(path);
+ when(path.toFile()).thenReturn(file);
+
+ assertEquals(path, fileUtils.createTempFile(prefix, suffix));
+
+ verify(file).deleteOnExit();
}
@Test
- @DisplayName("getScreenshotNameFrom should return the name for the provided frame and display name")
- void getScreenshotNameFrom() {
- when(statefulExtentTest.getDisplayName()).thenReturn(DISPLAY_NAME);
+ @DisplayName("writeTempFile should create a temp file with the provided prefix, suffix, and content and delete it on exit")
+ void writeTempFile() throws IOException {
+ final String prefix = "prefix";
+ final String suffix = "suffix";
+ final byte[] data = new byte[]{1, 2, 3};
+
+ when(Files.createTempFile(prefix, suffix)).thenReturn(path);
+ when(path.toFile()).thenReturn(file);
+ when(Files.write(path, data)).thenReturn(path);
+
+ assertEquals(path, fileUtils.writeTempFile(prefix, suffix, data));
- assertThat(fileUtils.getScreenshotNameFrom(AUTO_AFTER, statefulExtentTest), matchesPattern(UUID_REGEX));
+ verify(file).deleteOnExit();
}
}
diff --git a/spectrum/src/test/java/io/github/giulong/spectrum/utils/HtmlUtilsTest.java b/spectrum/src/test/java/io/github/giulong/spectrum/utils/HtmlUtilsTest.java
index 4ad1e1e4..5e19e248 100644
--- a/spectrum/src/test/java/io/github/giulong/spectrum/utils/HtmlUtilsTest.java
+++ b/spectrum/src/test/java/io/github/giulong/spectrum/utils/HtmlUtilsTest.java
@@ -5,12 +5,7 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.InjectMocks;
-import org.mockito.Mock;
-import org.mockito.MockedStatic;
-import org.mockito.Mockito;
+import org.mockito.*;
import java.io.IOException;
import java.nio.file.Files;
@@ -24,18 +19,21 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mockStatic;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.*;
class HtmlUtilsTest {
- private static MockedStatic pathMockedStatic;
- private static MockedStatic filesMockedStatic;
+ private MockedStatic pathMockedStatic;
+ private MockedStatic filesMockedStatic;
private final String testId = "testId";
+ @Mock
+ private ContextManager contextManager;
+
+ @Mock
+ private Map screenshots;
+
@Mock
private Path path;
@@ -62,6 +60,7 @@ void beforeEach() {
pathMockedStatic = mockStatic(Path.class);
filesMockedStatic = mockStatic(Files.class);
+ Reflections.setField("contextManager", htmlUtils, contextManager);
Reflections.setField("freeMarkerWrapper", htmlUtils, freeMarkerWrapper);
Reflections.setField("fileUtils", htmlUtils, fileUtils);
}
@@ -141,10 +140,10 @@ void buildFrameTagFor() {
@DisplayName("inline should call both the inlineImagesOf and inlineVideosOf on the provided html and return the one with all of those replaced")
void inline() throws IOException {
final String report = "abc