Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
9adaf9b
refactor: screenshot emitted as event payload to optimize video gener…
giulong Jul 19, 2025
3541a74
refactor: leveraging screenshot byte array data instead of reading fr…
giulong Jul 20, 2025
e521b21
refactor: narrowing ExtentReporter default constructor visibility to …
giulong Jul 20, 2025
5758b68
build: upgrading maven version to 3.9.11 in maven wrapper
giulong Jul 20, 2025
e007271
Merge branch 'develop' of github.com:giulong/spectrum into feature/sc…
giulong Jul 22, 2025
030861f
refactor: introducing fileUtils.writeTempFile method
giulong Jul 22, 2025
275dff7
refactor: simplifying video path generation
giulong Jul 27, 2025
a66b07a
refactor: simplifying screenshot management
giulong Jul 28, 2025
213dbdd
Merge branch 'develop' into feature/screenshot-event
giulong Jul 28, 2025
ab6db3e
Merge pull request #444 from giulong/feature/screenshot-event
giulong Jul 28, 2025
46c4fbf
build(deps): bump org.codehaus.mojo:flatten-maven-plugin
dependabot[bot] Aug 4, 2025
278a999
Merge pull request #445 from giulong/dependabot/maven/develop/org.cod…
giulong Aug 5, 2025
d46206e
build(deps): bump com.puppycrawl.tools:checkstyle from 10.26.1 to 11.0.0
dependabot[bot] Aug 11, 2025
bf8e7fa
Merge pull request #446 from giulong/dependabot/maven/develop/com.pup…
giulong Aug 12, 2025
00264ad
build(deps): bump selenium.version from 4.34.0 to 4.35.0
dependabot[bot] Aug 26, 2025
9712e96
build(deps): bump org.apache.maven.plugins:maven-javadoc-plugin
dependabot[bot] Aug 26, 2025
ee41de0
build(deps): bump com.slack.api:slack-api-client from 1.45.3 to 1.45.4
dependabot[bot] Aug 26, 2025
4d347fc
build(deps): bump org.mockito:mockito-junit-jupiter
dependabot[bot] Aug 26, 2025
f582bca
Merge pull request #450 from giulong/dependabot/maven/develop/org.apa…
giulong Aug 26, 2025
babe2dc
Merge pull request #451 from giulong/dependabot/maven/develop/com.sla…
giulong Aug 26, 2025
9495568
Merge pull request #452 from giulong/dependabot/maven/develop/org.moc…
giulong Aug 26, 2025
70c1dc9
feat: overriding new WebDriverListener methods
giulong Aug 31, 2025
83d541f
Merge pull request #449 from giulong/dependabot/maven/develop/seleniu…
giulong Aug 31, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .mvn/wrapper/maven-wrapper.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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
12 changes: 6 additions & 6 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,12 @@
<maven.install.skip>true</maven.install.skip>

<junit.version>5.13.4</junit.version>
<mockito.version>5.18.0</mockito.version>
<mockito.version>5.19.0</mockito.version>
<jackson.version>2.19.2</jackson.version>
<simplejavamail.version>8.12.6</simplejavamail.version>
<jsonschemagenerator.version>4.38.0</jsonschemagenerator.version>
<slf4j.version>2.0.17</slf4j.version>
<selenium.version>4.34.0</selenium.version>
<selenium.version>4.35.0</selenium.version>
</properties>

<profiles>
Expand Down Expand Up @@ -183,7 +183,7 @@
<dependency>
<groupId>io.appium</groupId>
<artifactId>java-client</artifactId>
<version>9.5.0</version>
<version>10.0.0</version>
</dependency>

<dependency>
Expand Down Expand Up @@ -227,7 +227,7 @@
<dependency>
<groupId>com.slack.api</groupId>
<artifactId>slack-api-client</artifactId>
<version>1.45.3</version>
<version>1.45.4</version>
</dependency>

<dependency>
Expand Down Expand Up @@ -323,7 +323,7 @@
<dependency>
<groupId>com.puppycrawl.tools</groupId>
<artifactId>checkstyle</artifactId>
<version>10.26.1</version>
<version>11.0.0</version>
</dependency>
<dependency>
<groupId>com.github.sevntu-checkstyle</groupId>
Expand Down Expand Up @@ -485,7 +485,7 @@
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>flatten-maven-plugin</artifactId>
<version>1.7.1</version>
<version>1.7.2</version>
<configuration>
<updatePomFile>true</updatePomFile>
<flattenMode>ossrh</flattenMode>
Expand Down
2 changes: 1 addition & 1 deletion spectrum/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.11.2</version>
<version>3.11.3</version>
<configuration>
<additionalJOption>-Xdoclint:none</additionalJOption>
</configuration>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -28,17 +29,23 @@
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
public abstract class SpectrumEntity<T extends SpectrumEntity<T, Data>, 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();

Expand Down Expand Up @@ -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);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ public void interceptDynamicTest(final Invocation<Void> 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<String> tags = Set.of(DYNAMIC_TEST);

testData.setDynamic(true);
testData.setFrameNumber(0);
testData.setDisplayName(testName);
testData.setDynamicVideoPath(dynamicVideoPath);
Expand All @@ -56,12 +58,13 @@ public void interceptDynamicTest(final Invocation<Void> 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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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<TestContext> {

public static final String TEST_CONTEXT = "testContext";
public static final String EXTENSION_CONTEXT = "extensionContext";

private final ContextManager contextManager = ContextManager.getInstance();

Expand All @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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
Expand All @@ -30,31 +27,24 @@ public class TestDataResolver extends TypeBasedParameterResolver<TestData> {
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)
.methodName(methodName)
.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;
}
Expand All @@ -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();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand All @@ -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));
}
}
Expand Down Expand Up @@ -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<String> tags) {
final String className = context.getDisplayName();

Expand Down
Loading
Loading