From 9fcc9dfd552eb096f71b7b94fb991749719e46bc Mon Sep 17 00:00:00 2001 From: Giulio Longfils Date: Sun, 4 Jan 2026 15:18:48 +0100 Subject: [PATCH 1/8] docs: adding Visual Regression Testing capability to README, while cleaning it up a bit --- README.md | 42 ++++++++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 6ae8b456..221e7a52 100644 --- a/README.md +++ b/README.md @@ -41,17 +41,23 @@ Spectrum is an **e2e test automation framework** that leverages **JUnit 6** and **Selenium 4** to provide these features automatically: -* **Driver** management with **auto-waiting**, to **highly reduce flakiness** -* Generation of a **html report** with the **execution video**, **coverage** and **several additional reports**. All **fully customisable** -* **Mail/Slack notifications** with reports as attachments -* It is fully configurable via a **declarative yaml file**, providing **out-of-the-box defaults** to let you run tests with no additional configuration -* It supports **browsers automation** via Selenium and **mobile and desktop automation** via Appium -* It supports **WebDriver BiDi** protocol - -Spectrum manages all the boilerplate code, allowing you to focus on test logic: -you just need to write a JUnit test using the native Selenium API as you would do in a vanilla Selenium test, -and Spectrum will enrich your suite transparently. -Be sure to check the [full documentation](https://giulong.github.io/spectrum/) to see all the available features. +* [Driver management](https://giulong.github.io/spectrum/#drivers-and-environments) with + [auto-waiting](https://giulong.github.io/spectrum/#auto-waiting), to **highly reduce flakiness**. +* [Generation of a html report](https://giulong.github.io/spectrum/#automatically-generated-reports) with the + [execution video](https://giulong.github.io/spectrum/#automatic-execution-video-generation), + [coverage report](https://giulong.github.io/spectrum/#testbook---coverage) and more. All **fully customisable**. +* [Visual Regression Testing](https://giulong.github.io/spectrum/#visual-regression-testing), + comparing visual snapshots of the AUT to identify regressions. +* [Mail/Slack notifications](https://giulong.github.io/spectrum/#event-sourcing---notifications) with reports as attachments. +* **No custom API**: it enriches plain Selenium tests transparently. +* Configurable via a **declarative yaml file**, with + [defaults](https://github.com/giulong/spectrum/blob/develop/spectrum/src/main/resources/yaml/configuration.default.yaml) + to run with no additional configuration. +* Supports **browsers automation** via Selenium and **mobile/desktop automation** via Appium. +* Supports [WebDriver BiDi](https://giulong.github.io/spectrum/#webdriver-bidi) protocol. + +Spectrum manages all the boilerplate code, allowing you to **focus on test logic**: +write a JUnit test using the vanilla Selenium API, and **Spectrum will enrich your suite transparently**. # Getting Started @@ -79,7 +85,8 @@ Here's an overview of the project created by the archetype, along with the gener https://github.com/giulong/spectrum/assets/27963644/df6b801e-91ca-415b-b510-a45b7392de20 -You can also configure Spectrum to produce additional reports, such as summary and coverage:

+You can also configure Spectrum to produce additional reports, such as [summary](https://giulong.github.io/spectrum/#execution-summary) +and [coverage](https://giulong.github.io/spectrum/#testbook---coverage):

summary  html testbook @@ -87,7 +94,7 @@ If you like Spectrum, please consider giving it a GitHub Star ⭐ # Usage -To start without the archetype, it's as simple as following these steps: +Starting without the archetype is as simple as following these steps: 1. Add the Spectrum dependency to your project, you can find the snippet for every build tool [here](https://central.sonatype.com/artifact/io.github.giulong/spectrum). @@ -119,14 +126,17 @@ To start without the archetype, it's as simple as following these steps: ```yaml application: - baseUrl: https://the-internet.herokuapp.com/ # Change this with your app's landing page + baseUrl: https://the-internet.herokuapp.com/ # Change it with your app's landing page - video: # video of the execution attached to the html report (will be empty since the test is doing nothing) + # video of the execution attached to the html report + # (will be empty since the test is doing nothing) + video: frames: - autoBefore + # the html report will open automatically in your browser after the execution extent: - openAtEnd: true # the html report will open automatically in your browser after the execution + openAtEnd: true ``` 4. Run the test! From 6faa3bdf387067205fcfa5c401c3ab8cb1e8b449 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Jan 2026 15:21:00 +0000 Subject: [PATCH 2/8] build(deps): bump com.puppycrawl.tools:checkstyle from 12.3.0 to 13.0.0 Bumps [com.puppycrawl.tools:checkstyle](https://github.com/checkstyle/checkstyle) from 12.3.0 to 13.0.0. - [Release notes](https://github.com/checkstyle/checkstyle/releases) - [Commits](https://github.com/checkstyle/checkstyle/compare/checkstyle-12.3.0...checkstyle-13.0.0) --- updated-dependencies: - dependency-name: com.puppycrawl.tools:checkstyle dependency-version: 13.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1e3f679a..37ec75e0 100644 --- a/pom.xml +++ b/pom.xml @@ -340,7 +340,7 @@ com.puppycrawl.tools checkstyle - 12.3.0 + 13.0.0 com.github.sevntu-checkstyle From 76a26abbb5effe09d1c7283ef4d71155cc2b1a8d Mon Sep 17 00:00:00 2001 From: Giulio Longfils Date: Tue, 6 Jan 2026 15:42:02 +0100 Subject: [PATCH 3/8] feat: adding takesScreenshot and javascriptExecutor fields to SpectrumEntity to avoid cumbersome castings in user code --- .../giulong/spectrum/SpectrumEntity.java | 11 +++-- .../github/giulong/spectrum/SpectrumTest.java | 4 ++ .../giulong/spectrum/SpectrumEntityTest.java | 4 +- .../giulong/spectrum/SpectrumTestTest.java | 46 +++++++++++-------- 4 files changed, 40 insertions(+), 25 deletions(-) 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 437f993a..a2ad8fa8 100644 --- a/spectrum/src/main/java/io/github/giulong/spectrum/SpectrumEntity.java +++ b/spectrum/src/main/java/io/github/giulong/spectrum/SpectrumEntity.java @@ -30,10 +30,7 @@ import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.extension.ExtensionContext; -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.bidi.browsingcontext.BrowsingContext; import org.openqa.selenium.bidi.module.BrowsingContextInspector; import org.openqa.selenium.bidi.module.LogInspector; @@ -71,6 +68,12 @@ public abstract class SpectrumEntity, Data> { @Shared protected WebDriver driver; + @Shared + protected TakesScreenshot takesScreenshot; + + @Shared + protected JavascriptExecutor javascriptExecutor; + @Shared protected WebDriverWait implicitWait; diff --git a/spectrum/src/main/java/io/github/giulong/spectrum/SpectrumTest.java b/spectrum/src/main/java/io/github/giulong/spectrum/SpectrumTest.java index 059ff681..5563ef77 100644 --- a/spectrum/src/main/java/io/github/giulong/spectrum/SpectrumTest.java +++ b/spectrum/src/main/java/io/github/giulong/spectrum/SpectrumTest.java @@ -35,6 +35,8 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.extension.RegisterExtension; +import org.openqa.selenium.JavascriptExecutor; +import org.openqa.selenium.TakesScreenshot; import org.openqa.selenium.WebDriver; import org.openqa.selenium.bidi.browsingcontext.BrowsingContext; import org.openqa.selenium.bidi.module.BrowsingContextInspector; @@ -128,6 +130,8 @@ void beforeEach(final TestContext testContext, final TestData testData, final St final Actions actions, final Js js, final JsWebElementProxyBuilder jsWebElementProxyBuilder, final LogInspector logInspector, final BrowsingContext browsingContext, final BrowsingContextInspector browsingContextInspector, final Network network, final Data data) { this.driver = driver; + this.takesScreenshot = (TakesScreenshot) driver; + this.javascriptExecutor = (JavascriptExecutor) driver; this.implicitWait = implicitWait; this.pageLoadWait = pageLoadWait; this.scriptWait = scriptWait; 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 bb982208..d7af75ed 100644 --- a/spectrum/src/test/java/io/github/giulong/spectrum/SpectrumEntityTest.java +++ b/spectrum/src/test/java/io/github/giulong/spectrum/SpectrumEntityTest.java @@ -185,7 +185,7 @@ void getSharedFields() { .toList(); // we're checking real size and names here, no mocks - assertEquals(21, actual.size()); + assertEquals(23, actual.size()); assertTrue(sharedFieldsNames.containsAll(List.of( "configuration", "extentReports", @@ -193,6 +193,8 @@ void getSharedFields() { "actions", "eventsDispatcher", "driver", + "takesScreenshot", + "javascriptExecutor", "implicitWait", "pageLoadWait", "scriptWait", diff --git a/spectrum/src/test/java/io/github/giulong/spectrum/SpectrumTestTest.java b/spectrum/src/test/java/io/github/giulong/spectrum/SpectrumTestTest.java index 8334bb34..2d309b63 100644 --- a/spectrum/src/test/java/io/github/giulong/spectrum/SpectrumTestTest.java +++ b/spectrum/src/test/java/io/github/giulong/spectrum/SpectrumTestTest.java @@ -32,6 +32,8 @@ import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; import org.mockito.Mock; +import org.openqa.selenium.JavascriptExecutor; +import org.openqa.selenium.TakesScreenshot; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.bidi.browsingcontext.BrowsingContext; @@ -45,8 +47,8 @@ class SpectrumTestTest { @Mock private TestContext testContext; - @Mock - private WebDriver webDriver; + @Mock(extraInterfaces = {TakesScreenshot.class, JavascriptExecutor.class}) + private WebDriver driver; @Mock private FakeData data; @@ -185,29 +187,33 @@ void testBeforeEach() { when(auto.getTimeout()).thenReturn(timeout); when(timeout.toSeconds()).thenReturn(seconds); + when(statefulExtentTest.getCurrentNode()).thenReturn(extentTest); + assertNull(childTestVoid.childTestPage); assertNull(childTestVoid.getParentTestPage()); - childTestVoid.beforeEach(testContext, testData, statefulExtentTest, webDriver, implicitWait, pageLoadWait, scriptWait, downloadWait, + childTestVoid.beforeEach(testContext, testData, statefulExtentTest, driver, implicitWait, pageLoadWait, scriptWait, downloadWait, actions, js, jsWebElementProxyBuilder, logInspector, browsingContext, browsingContextInspector, network, null); - assertEquals(webDriver, spectrumTest.driver); - assertEquals(implicitWait, spectrumTest.implicitWait); - assertEquals(pageLoadWait, spectrumTest.pageLoadWait); - assertEquals(scriptWait, spectrumTest.scriptWait); - assertEquals(downloadWait, spectrumTest.downloadWait); - assertEquals(statefulExtentTest, spectrumTest.statefulExtentTest); - assertEquals(extentTest, spectrumTest.extentTest); - assertEquals(actions, spectrumTest.actions); - assertEquals(testData, spectrumTest.testData); - assertEquals(js, spectrumTest.js); - assertEquals(logInspector, spectrumTest.logInspector); - assertEquals(browsingContext, spectrumTest.browsingContext); - assertEquals(browsingContextInspector, spectrumTest.browsingContextInspector); - assertEquals(network, spectrumTest.network); - assertEquals(jsWebElementProxyBuilder, spectrumTest.jsWebElementProxyBuilder); - assertEquals(data, spectrumTest.data); - assertEquals(testContext, spectrumTest.testContext); + assertEquals(driver, childTestVoid.driver); + assertEquals(driver, childTestVoid.takesScreenshot); + assertEquals(driver, childTestVoid.javascriptExecutor); + assertEquals(implicitWait, childTestVoid.implicitWait); + assertEquals(pageLoadWait, childTestVoid.pageLoadWait); + assertEquals(scriptWait, childTestVoid.scriptWait); + assertEquals(downloadWait, childTestVoid.downloadWait); + assertEquals(statefulExtentTest, childTestVoid.statefulExtentTest); + assertEquals(extentTest, childTestVoid.extentTest); + assertEquals(actions, childTestVoid.actions); + assertEquals(testData, childTestVoid.testData); + assertEquals(js, childTestVoid.js); + assertEquals(logInspector, childTestVoid.logInspector); + assertEquals(browsingContext, childTestVoid.browsingContext); + assertEquals(browsingContextInspector, childTestVoid.browsingContextInspector); + assertEquals(network, childTestVoid.network); + assertEquals(jsWebElementProxyBuilder, childTestVoid.jsWebElementProxyBuilder); + assertNull(childTestVoid.data); + assertEquals(testContext, childTestVoid.testContext); // injectPages assertNull(childTestVoid.toSkip); From 3b0ff3de28f5c5cee91a5c9700c51427584c97d0 Mon Sep 17 00:00:00 2001 From: Giulio Longfils Date: Tue, 6 Jan 2026 17:37:05 +0100 Subject: [PATCH 4/8] refactor: relying on afterGetScreenshotAs to allow using native Selenium take screenshot API --- .../giulong/spectrum/it/tests/CheckboxIT.java | 8 +- .../giulong/spectrum/SpectrumEntity.java | 15 +- .../extensions/resolvers/DriverResolver.java | 9 +- .../resolvers/TestContextResolver.java | 3 + .../EventsWebDriverListener.java | 38 +++- .../giulong/spectrum/utils/TestData.java | 21 +++ .../resources/yaml/configuration.default.yaml | 4 +- .../giulong/spectrum/SpectrumEntityTest.java | 47 ++--- .../resolvers/DriverResolverTest.java | 6 + .../resolvers/TestContextResolverTest.java | 6 +- .../EventsWebDriverListenerTest.java | 170 +++++++++++++++++- .../giulong/spectrum/utils/TestDataTest.java | 46 ++++- .../pages/ExtentReportPage.java | 3 + .../tests/ExtentReportVerifierIT.java | 9 +- 14 files changed, 314 insertions(+), 71 deletions(-) diff --git a/it/src/test/java/io/github/giulong/spectrum/it/tests/CheckboxIT.java b/it/src/test/java/io/github/giulong/spectrum/it/tests/CheckboxIT.java index 9afabb02..b881e37c 100644 --- a/it/src/test/java/io/github/giulong/spectrum/it/tests/CheckboxIT.java +++ b/it/src/test/java/io/github/giulong/spectrum/it/tests/CheckboxIT.java @@ -1,6 +1,9 @@ package io.github.giulong.spectrum.it.tests; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.openqa.selenium.OutputType.BYTES; import io.github.giulong.spectrum.SpectrumTest; import io.github.giulong.spectrum.it.pages.CheckboxPage; @@ -25,6 +28,9 @@ void testWithNoDisplayName() { driver.get(configuration.getApplication().getBaseUrl()); assertEquals("Welcome to the-internet", landingPage.getTitle().getText()); + // Taking a screenshot with the native Selenium API to prove it's the same as using the helper methods + takesScreenshot.getScreenshotAs(BYTES); + extentTest.info("Custom step that should not be highlighted on video playback"); landingPage.getCheckboxLink().click(); extentTest.info("Custom step that should not be highlighted on video playback"); 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 a2ad8fa8..5aee67ae 100644 --- a/spectrum/src/main/java/io/github/giulong/spectrum/SpectrumEntity.java +++ b/spectrum/src/main/java/io/github/giulong/spectrum/SpectrumEntity.java @@ -4,10 +4,8 @@ import static com.aventstack.extentreports.Status.INFO; import static com.aventstack.extentreports.Status.WARNING; import static io.github.giulong.spectrum.enums.Frame.MANUAL; -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.utils.web_driver_events.VideoAutoScreenshotProducer.SCREENSHOT; -import static org.junit.jupiter.api.extension.ExtensionContext.Namespace.GLOBAL; import static org.openqa.selenium.OutputType.BYTES; import java.nio.file.Files; @@ -232,17 +230,8 @@ public T screenshotFail(final WebElement webElement, final String message) { * @return the calling SpectrumEntity instance */ public T addScreenshotToReport(final String message, final Status status) { - final ExtensionContext context = testContext.get(EXTENSION_CONTEXT, ExtensionContext.class); - final TakesScreenshot originalDriver = (TakesScreenshot) context.getStore(GLOBAL).get(ORIGINAL_DRIVER, WebDriver.class); - final Payload payload = Payload - .builder() - .screenshot(originalDriver.getScreenshotAs(BYTES)) - .message(message) - .status(status) - .takesScreenshot(originalDriver) - .build(); - - eventsDispatcher.fire(MANUAL.getValue(), SCREENSHOT, context, payload); + testData.buildScreenshotFor(MANUAL, message, status); + takesScreenshot.getScreenshotAs(BYTES); return (T) this; } 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 46bd0d2b..f88f205b 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 @@ -2,6 +2,7 @@ import static io.github.giulong.spectrum.extensions.resolvers.ConfigurationResolver.CONFIGURATION; import static io.github.giulong.spectrum.extensions.resolvers.StatefulExtentTestResolver.STATEFUL_EXTENT_TEST; +import static io.github.giulong.spectrum.extensions.resolvers.TestContextResolver.TEST_CONTEXT; import static io.github.giulong.spectrum.extensions.resolvers.TestDataResolver.TEST_DATA; import static org.junit.jupiter.api.extension.ExtensionContext.Namespace.GLOBAL; @@ -11,11 +12,7 @@ import io.github.giulong.spectrum.internals.web_driver_listeners.AutoWaitWebDriverListener; import io.github.giulong.spectrum.internals.web_driver_listeners.EventsWebDriverListener; -import io.github.giulong.spectrum.utils.Configuration; -import io.github.giulong.spectrum.utils.ContextManager; -import io.github.giulong.spectrum.utils.FileUtils; -import io.github.giulong.spectrum.utils.StatefulExtentTest; -import io.github.giulong.spectrum.utils.TestData; +import io.github.giulong.spectrum.utils.*; import io.github.giulong.spectrum.utils.video.Video; import io.github.giulong.spectrum.utils.web_driver_events.*; @@ -57,6 +54,7 @@ public WebDriver resolveParameter(@NonNull final ParameterContext parameterConte final Configuration.Drivers.Waits.AutoWait autoWait = drivers.getWaits().getAuto(); final StatefulExtentTest statefulExtentTest = store.get(STATEFUL_EXTENT_TEST, StatefulExtentTest.class); final TestData testData = store.get(TEST_DATA, TestData.class); + final TestContext testContext = store.get(TEST_CONTEXT, TestContext.class); final Configuration.Application.Highlight highlight = configuration.getApplication().getHighlight(); final Pattern locatorPattern = Pattern.compile(configuration.getExtent().getLocatorRegex()); final Video video = configuration.getVideo(); @@ -110,6 +108,7 @@ public WebDriver resolveParameter(@NonNull final ParameterContext parameterConte .locatorPattern(locatorPattern) .events(events) .consumers(consumers) + .testContext(testContext) .build()); final WebDriver decoratedDriver = new EventFiringDecorator<>(webDriverListeners.toArray(new WebDriverListener[0])).decorate(driver); 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 b406e0e0..e2afaa46 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,5 +1,7 @@ package io.github.giulong.spectrum.extensions.resolvers; +import static org.junit.jupiter.api.extension.ExtensionContext.Namespace.GLOBAL; + import io.github.giulong.spectrum.utils.ContextManager; import io.github.giulong.spectrum.utils.TestContext; @@ -23,6 +25,7 @@ public TestContext resolveParameter(@NonNull final ParameterContext parameterCon 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/internals/web_driver_listeners/EventsWebDriverListener.java b/spectrum/src/main/java/io/github/giulong/spectrum/internals/web_driver_listeners/EventsWebDriverListener.java index 84e09481..e92e6684 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 @@ -1,7 +1,13 @@ package io.github.giulong.spectrum.internals.web_driver_listeners; +import static com.aventstack.extentreports.Status.INFO; import static io.github.giulong.spectrum.enums.Frame.AUTO_AFTER; import static io.github.giulong.spectrum.enums.Frame.AUTO_BEFORE; +import static io.github.giulong.spectrum.enums.Frame.MANUAL; +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.VideoAutoScreenshotProducer.SCREENSHOT; +import static org.junit.jupiter.api.extension.ExtensionContext.Namespace.GLOBAL; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -12,8 +18,13 @@ import java.util.regex.Pattern; import io.github.giulong.spectrum.enums.Frame; +import io.github.giulong.spectrum.pojos.events.Event.Payload; import io.github.giulong.spectrum.utils.Configuration; import io.github.giulong.spectrum.utils.Configuration.Drivers.Events; +import io.github.giulong.spectrum.utils.TestContext; +import io.github.giulong.spectrum.utils.TestData; +import io.github.giulong.spectrum.utils.TestData.Screenshot; +import io.github.giulong.spectrum.utils.events.EventsDispatcher; import io.github.giulong.spectrum.utils.web_driver_events.WebDriverEvent; import io.github.giulong.spectrum.utils.web_driver_events.WebDriverEventConsumer; @@ -22,6 +33,7 @@ import lombok.experimental.SuperBuilder; import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.extension.ExtensionContext; import org.openqa.selenium.*; import org.openqa.selenium.interactions.Sequence; import org.slf4j.event.Level; @@ -32,8 +44,11 @@ public class EventsWebDriverListener extends SpectrumWebDriverListener { private static final Pattern SECURED_PATTERN = Pattern.compile("@Secured@(?.*)@Secured@"); + private final EventsDispatcher eventsDispatcher = EventsDispatcher.getInstance(); + private Events events; private List consumers; + private TestContext testContext; List parse(final Object[] args) { return Arrays @@ -484,8 +499,29 @@ public void beforeGetScreenshotAs(final WebDriver driver, final OutputType void afterGetScreenshotAs(final WebDriver driver, final OutputType target, final X result) { + if (result instanceof byte[]) { + final ExtensionContext context = testContext.get(EXTENSION_CONTEXT, ExtensionContext.class); + final Screenshot screenshot = Optional + .ofNullable(context.getStore(GLOBAL).get(TEST_DATA, TestData.class).getScreenshot()) + .orElseGet(() -> Screenshot + .builder() + .frame(MANUAL) + .message("") + .status(INFO) + .build()); + + final Payload payload = Payload + .builder() + .screenshot((byte[]) result) + .message(screenshot.getMessage()) + .status(screenshot.getStatus()) + .takesScreenshot((TakesScreenshot) driver) + .build(); + + eventsDispatcher.fire(screenshot.getFrame().getValue(), SCREENSHOT, context, payload); + } + listenTo(AUTO_AFTER, events.getAfterGetScreenshotAs(), driver, target, result); } diff --git a/spectrum/src/main/java/io/github/giulong/spectrum/utils/TestData.java b/spectrum/src/main/java/io/github/giulong/spectrum/utils/TestData.java index 29a4b76a..fa1eefd4 100644 --- a/spectrum/src/main/java/io/github/giulong/spectrum/utils/TestData.java +++ b/spectrum/src/main/java/io/github/giulong/spectrum/utils/TestData.java @@ -4,6 +4,9 @@ import java.util.HashMap; import java.util.Map; +import com.aventstack.extentreports.Status; + +import io.github.giulong.spectrum.enums.Frame; import io.github.giulong.spectrum.exceptions.TestFailedException; import io.github.giulong.spectrum.exceptions.VisualRegressionException; @@ -26,6 +29,7 @@ public class TestData { private Path videoPath; private VisualRegression visualRegression; private TestFailedException testFailedException; + private Screenshot screenshot; @Builder.Default private Map encoders = new HashMap<>(); @@ -64,6 +68,15 @@ public void registerFailedVisualRegression() { testFailedException = new VisualRegressionException(String.format("There were %d visual regressions", ++visualRegression.count)); } + public void buildScreenshotFor(final Frame frame, final String message, final Status status) { + screenshot = Screenshot + .builder() + .frame(frame) + .message(message) + .status(status) + .build(); + } + @Getter @Builder public static class VisualRegression { @@ -74,4 +87,12 @@ public static class VisualRegression { @Setter private Path dynamicPath; } + + @Getter + @Builder + public static class Screenshot { + private Frame frame; + private String message; + private Status status; + } } diff --git a/spectrum/src/main/resources/yaml/configuration.default.yaml b/spectrum/src/main/resources/yaml/configuration.default.yaml index 9782e858..53045e6b 100644 --- a/spectrum/src/main/resources/yaml/configuration.default.yaml +++ b/spectrum/src/main/resources/yaml/configuration.default.yaml @@ -370,10 +370,10 @@ drivers: level: INFO message: Css property %2$s of element %1$s is %3$s beforeGetScreenshotAs: - level: INFO + level: DEBUG message: Getting screenshot as %2$s afterGetScreenshotAs: - level: ERROR + level: DEBUG message: Screenshot as %2$s resulted is %3$s beforeAnyNavigationCall: message: Calling navigation method %2$s with args %3$s 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 d7af75ed..0132fff1 100644 --- a/spectrum/src/test/java/io/github/giulong/spectrum/SpectrumEntityTest.java +++ b/spectrum/src/test/java/io/github/giulong/spectrum/SpectrumEntityTest.java @@ -4,13 +4,11 @@ import static com.aventstack.extentreports.Status.INFO; import static com.aventstack.extentreports.Status.WARNING; import static io.github.giulong.spectrum.enums.Frame.MANUAL; -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.utils.web_driver_events.VideoAutoScreenshotProducer.SCREENSHOT; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -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; @@ -29,10 +27,7 @@ import io.github.giulong.spectrum.interfaces.Shared; import io.github.giulong.spectrum.pojos.events.Event.Payload; -import io.github.giulong.spectrum.utils.Configuration; -import io.github.giulong.spectrum.utils.FileUtils; -import io.github.giulong.spectrum.utils.Reflections; -import io.github.giulong.spectrum.utils.TestContext; +import io.github.giulong.spectrum.utils.*; import io.github.giulong.spectrum.utils.events.EventsDispatcher; import org.junit.jupiter.api.AfterEach; @@ -97,6 +92,9 @@ class SpectrumEntityTest { @Mock private File file; + @Mock + private TestData testData; + @MockFinal @SuppressWarnings("unused") private EventsDispatcher eventsDispatcher; @@ -108,11 +106,11 @@ class SpectrumEntityTest { private ExtensionContext context; @Mock - private ExtensionContext.Store store; - - @Mock(extraInterfaces = TakesScreenshot.class) private WebDriver driver; + @Mock + private TakesScreenshot takesScreenshot; + @Captor private ArgumentCaptor> functionArgumentCaptor; @@ -135,31 +133,16 @@ void afterEach() { messageDigestMockedStatic.close(); } - private void screenshotStubs() { - when(testContext.get(EXTENSION_CONTEXT, ExtensionContext.class)).thenReturn(context); - - when(context.getStore(GLOBAL)).thenReturn(store); - when(store.get(ORIGINAL_DRIVER, WebDriver.class)).thenReturn(driver); - - when(((TakesScreenshot) driver).getScreenshotAs(BYTES)).thenReturn(bytes); - } - private void screenshotWebElementStubs() { when(testContext.get(EXTENSION_CONTEXT, ExtensionContext.class)).thenReturn(context); when(webElement.getScreenshotAs(BYTES)).thenReturn(bytes); } private void screenshotVerificationsFor(final String message, final Status status) { - final Payload payload = Payload - .builder() - .screenshot(bytes) - .message(message) - .status(status) - .takesScreenshot((TakesScreenshot) driver) - .build(); + verify(testData).buildScreenshotFor(MANUAL, message, status); + verify(takesScreenshot).getScreenshotAs(BYTES); - verify(eventsDispatcher).fire(MANUAL.getValue(), SCREENSHOT, context, payload); - verifyNoMoreInteractions(eventsDispatcher); + verifyNoInteractions(eventsDispatcher); } private void screenshotWebElementVerificationsFor(final String message, final Status status) { @@ -222,8 +205,6 @@ void hover() { @Test @DisplayName("screenshot should delegate to addScreenshotToReport") void screenshot() { - screenshotStubs(); - assertEquals(spectrumEntity, spectrumEntity.screenshot()); screenshotVerificationsFor("", INFO); @@ -242,8 +223,6 @@ void screenshotWebElement() { @Test @DisplayName("infoWithScreenshot should delegate to addScreenshotToReport") void infoWithScreenshot() { - screenshotStubs(); - assertEquals(spectrumEntity, spectrumEntity.screenshotInfo(msg)); screenshotVerificationsFor(msg, INFO); @@ -262,8 +241,6 @@ void infoWithScreenshotWebElement() { @Test @DisplayName("warningWithScreenshot should delegate to addScreenshotToReport") void warningWithScreenshot() { - screenshotStubs(); - assertEquals(spectrumEntity, spectrumEntity.screenshotWarning(msg)); screenshotVerificationsFor(msg, WARNING); @@ -282,8 +259,6 @@ void warningWithScreenshotWebElement() { @Test @DisplayName("failWithScreenshot should delegate to addScreenshotToReport") void failWithScreenshot() { - screenshotStubs(); - assertEquals(spectrumEntity, spectrumEntity.screenshotFail(msg)); screenshotVerificationsFor(msg, FAIL); @@ -304,8 +279,6 @@ void failWithScreenshotWebElement() { void addScreenshotToReport() { final Status status = INFO; - screenshotStubs(); - spectrumEntity.addScreenshotToReport(msg, status); screenshotVerificationsFor(msg, status); 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 c2fb21e0..5789ca2f 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 @@ -3,6 +3,7 @@ import static io.github.giulong.spectrum.extensions.resolvers.ConfigurationResolver.CONFIGURATION; import static io.github.giulong.spectrum.extensions.resolvers.DriverResolver.*; import static io.github.giulong.spectrum.extensions.resolvers.StatefulExtentTestResolver.STATEFUL_EXTENT_TEST; +import static io.github.giulong.spectrum.extensions.resolvers.TestContextResolver.TEST_CONTEXT; import static io.github.giulong.spectrum.extensions.resolvers.TestDataResolver.TEST_DATA; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -92,6 +93,9 @@ class DriverResolverTest { @Mock private Configuration.Drivers.Events events; + @Mock + private TestContext testContext; + @SuppressWarnings("rawtypes") @Mock private EventsWebDriverListener.EventsWebDriverListenerBuilder eventsWebDriverListenerBuilder; @@ -229,6 +233,7 @@ private void stubs() { when(store.get(STATEFUL_EXTENT_TEST, StatefulExtentTest.class)).thenReturn(statefulExtentTest); when(store.get(TEST_DATA, TestData.class)).thenReturn(testData); + when(store.get(TEST_CONTEXT, TestContext.class)).thenReturn(testContext); when(configuration.getVideo()).thenReturn(video); when(LogConsumer.builder()).thenReturn(logConsumerBuilder); @@ -266,6 +271,7 @@ private void stubs() { when(eventsWebDriverListenerBuilder.locatorPattern(pattern)).thenReturn(eventsWebDriverListenerBuilder); when(eventsWebDriverListenerBuilder.events(events)).thenReturn(eventsWebDriverListenerBuilder); when(eventsWebDriverListenerBuilder.consumers(consumersArgumentCaptor.capture())).thenReturn(eventsWebDriverListenerBuilder); + when(eventsWebDriverListenerBuilder.testContext(testContext)).thenReturn(eventsWebDriverListenerBuilder); when(eventsWebDriverListenerBuilder.build()).thenReturn(eventsWebDriverListener); } 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 8158fac5..9ff02ef4 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 @@ -1,7 +1,9 @@ package io.github.giulong.spectrum.extensions.resolvers; import static io.github.giulong.spectrum.extensions.resolvers.TestContextResolver.EXTENSION_CONTEXT; +import static io.github.giulong.spectrum.extensions.resolvers.TestContextResolver.TEST_CONTEXT; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.extension.ExtensionContext.Namespace.GLOBAL; import static org.mockito.Mockito.*; import io.github.giulong.spectrum.MockFinal; @@ -40,11 +42,13 @@ class TestContextResolverTest { @DisplayName("resolveParameter should return an instance of TestContext") void resolveParameter() { when(contextManager.initFor(context)).thenReturn(testContext); + when(context.getStore(GLOBAL)).thenReturn(store); final TestContext actual = testContextResolver.resolveParameter(parameterContext, context); assertEquals(testContext, actual); verify(testContext).put(EXTENSION_CONTEXT, context); - verifyNoInteractions(store); + verify(store).put(TEST_CONTEXT, testContext); + verifyNoInteractions(parameterContext); } } diff --git a/spectrum/src/test/java/io/github/giulong/spectrum/internals/web_driver_listeners/EventsWebDriverListenerTest.java b/spectrum/src/test/java/io/github/giulong/spectrum/internals/web_driver_listeners/EventsWebDriverListenerTest.java index cddb9411..2610b039 100644 --- a/spectrum/src/test/java/io/github/giulong/spectrum/internals/web_driver_listeners/EventsWebDriverListenerTest.java +++ b/spectrum/src/test/java/io/github/giulong/spectrum/internals/web_driver_listeners/EventsWebDriverListenerTest.java @@ -2,21 +2,45 @@ import static io.github.giulong.spectrum.enums.Frame.AUTO_AFTER; import static io.github.giulong.spectrum.enums.Frame.AUTO_BEFORE; -import static org.junit.jupiter.api.Assertions.*; +import static io.github.giulong.spectrum.enums.Frame.MANUAL; +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.VideoAutoScreenshotProducer.SCREENSHOT; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +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.slf4j.event.Level.*; +import static org.openqa.selenium.OutputType.BASE64; +import static org.openqa.selenium.OutputType.BYTES; +import static org.openqa.selenium.OutputType.FILE; +import static org.slf4j.event.Level.DEBUG; +import static org.slf4j.event.Level.INFO; +import static org.slf4j.event.Level.TRACE; +import static org.slf4j.event.Level.WARN; import java.util.Arrays; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Stream; import ch.qos.logback.classic.Level; import ch.qos.logback.classic.Logger; +import com.aventstack.extentreports.Status; + +import io.github.giulong.spectrum.MockFinal; +import io.github.giulong.spectrum.pojos.events.Event.Payload; import io.github.giulong.spectrum.utils.Configuration; import io.github.giulong.spectrum.utils.Configuration.Drivers.Event; import io.github.giulong.spectrum.utils.Reflections; +import io.github.giulong.spectrum.utils.TestContext; +import io.github.giulong.spectrum.utils.TestData; +import io.github.giulong.spectrum.utils.TestData.Screenshot; +import io.github.giulong.spectrum.utils.events.EventsDispatcher; import io.github.giulong.spectrum.utils.web_driver_events.WebDriverEvent; import io.github.giulong.spectrum.utils.web_driver_events.WebDriverEventConsumer; @@ -24,13 +48,16 @@ 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.api.extension.ExtensionContext.Store; 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.InjectMocks; import org.mockito.Mock; import org.mockito.MockedStatic; -import org.openqa.selenium.Keys; -import org.openqa.selenium.WebElement; +import org.openqa.selenium.*; import org.slf4j.LoggerFactory; class EventsWebDriverListenerTest { @@ -41,6 +68,24 @@ class EventsWebDriverListenerTest { private final String message = "message
%s
"; private final long wait = 1L; + @Mock(extraInterfaces = TakesScreenshot.class) + private WebDriver driver; + + @Mock + private TestContext testContext; + + @Mock + private ExtensionContext context; + + @Mock + private TestData testData; + + @Mock + private Screenshot screenshot; + + @Mock + private Store store; + @Mock private Event event; @@ -80,6 +125,10 @@ class EventsWebDriverListenerTest { @Mock private Configuration.Drivers.Events events; + @MockFinal + @SuppressWarnings("unused") + private EventsDispatcher eventsDispatcher; + @InjectMocks private EventsWebDriverListener eventsWebDriverListener = new EventsWebDriverListener(EventsWebDriverListener.builder()); @@ -475,6 +524,119 @@ void afterSendKeysSecured() { verify(consumer2).accept(webDriverEvent); } + @Test + @DisplayName("afterGetScreenshotAs should dispatch an event when the screenshot is in bytes") + void afterGetScreenshotAsBytes() { + final String message = "message"; + final Status status = Status.INFO; + final byte[] result = new byte[]{1, 2, 3}; + final Payload payload = Payload + .builder() + .screenshot(result) + .message(message) + .status(status) + .takesScreenshot((TakesScreenshot) driver) + .build(); + + when(testContext.get(EXTENSION_CONTEXT, ExtensionContext.class)).thenReturn(context); + when(context.getStore(GLOBAL)).thenReturn(store); + when(store.get(TEST_DATA, TestData.class)).thenReturn(testData); + when(testData.getScreenshot()).thenReturn(screenshot); + when(screenshot.getMessage()).thenReturn(message); + when(screenshot.getStatus()).thenReturn(status); + when(screenshot.getFrame()).thenReturn(MANUAL); + + when(WebDriverEvent.builder()).thenReturn(webDriverEventBuilder); + when(webDriverEventBuilder.frame(AUTO_AFTER)).thenReturn(webDriverEventBuilder); + when(webDriverEventBuilder.level(INFO)).thenReturn(webDriverEventBuilder); + when(webDriverEventBuilder.args(List.of(driver, BYTES, result))).thenReturn(webDriverEventBuilder); + when(webDriverEventBuilder.message(message)).thenReturn(webDriverEventBuilder); + when(webDriverEventBuilder.build()).thenReturn(webDriverEvent); + + when(events.getAfterGetScreenshotAs()).thenReturn(event); + + // listenTo + ((Logger) LoggerFactory.getLogger(EventsWebDriverListener.class)).setLevel(Level.INFO); + when(event.getMessage()).thenReturn(message); + when(event.getLevel()).thenReturn(INFO); + when(event.getWait()).thenReturn(wait); + + eventsWebDriverListener.afterGetScreenshotAs(driver, BYTES, result); + + verify(eventsDispatcher).fire(MANUAL.getValue(), SCREENSHOT, context, payload); + } + + @Test + @DisplayName("afterGetScreenshotAs should dispatch an event when the screenshot is in bytes, building a default screenshot if none is found in testData") + void afterGetScreenshotAsBytesNoScreenshot() { + final String message = ""; + final Status status = Status.INFO; + final byte[] result = new byte[]{1, 2, 3}; + final Payload payload = Payload + .builder() + .screenshot(result) + .message(message) + .status(status) + .takesScreenshot((TakesScreenshot) driver) + .build(); + + when(testContext.get(EXTENSION_CONTEXT, ExtensionContext.class)).thenReturn(context); + when(context.getStore(GLOBAL)).thenReturn(store); + when(store.get(TEST_DATA, TestData.class)).thenReturn(testData); + when(testData.getScreenshot()).thenReturn(null); + + when(WebDriverEvent.builder()).thenReturn(webDriverEventBuilder); + when(webDriverEventBuilder.frame(AUTO_AFTER)).thenReturn(webDriverEventBuilder); + when(webDriverEventBuilder.level(INFO)).thenReturn(webDriverEventBuilder); + when(webDriverEventBuilder.args(List.of(driver, BYTES, result))).thenReturn(webDriverEventBuilder); + when(webDriverEventBuilder.message(message)).thenReturn(webDriverEventBuilder); + when(webDriverEventBuilder.build()).thenReturn(webDriverEvent); + + when(events.getAfterGetScreenshotAs()).thenReturn(event); + + // listenTo + ((Logger) LoggerFactory.getLogger(EventsWebDriverListener.class)).setLevel(Level.INFO); + when(event.getMessage()).thenReturn(message); + when(event.getLevel()).thenReturn(INFO); + when(event.getWait()).thenReturn(wait); + + eventsWebDriverListener.afterGetScreenshotAs(driver, BYTES, result); + + verify(eventsDispatcher).fire(MANUAL.getValue(), SCREENSHOT, context, payload); + } + + @DisplayName("afterGetScreenshotAs should just delegates to listenTo when the screenshot is not in bytes") + @ParameterizedTest(name = "with output type {0}") + @MethodSource("valuesProvider") + void afterGetScreenshotAs(final OutputType outputType) { + final Object result = mock(); + + when(WebDriverEvent.builder()).thenReturn(webDriverEventBuilder); + when(webDriverEventBuilder.frame(AUTO_AFTER)).thenReturn(webDriverEventBuilder); + when(webDriverEventBuilder.level(INFO)).thenReturn(webDriverEventBuilder); + when(webDriverEventBuilder.args(List.of(driver, outputType, result))).thenReturn(webDriverEventBuilder); + when(webDriverEventBuilder.message("message
driver
")).thenReturn(webDriverEventBuilder); + when(webDriverEventBuilder.build()).thenReturn(webDriverEvent); + + when(events.getAfterGetScreenshotAs()).thenReturn(event); + + // listenTo + ((Logger) LoggerFactory.getLogger(EventsWebDriverListener.class)).setLevel(Level.INFO); + when(event.getMessage()).thenReturn(message); + when(event.getLevel()).thenReturn(INFO); + when(event.getWait()).thenReturn(wait); + + eventsWebDriverListener.afterGetScreenshotAs(driver, outputType, result); + + verifyNoInteractions(eventsDispatcher); + } + + static Stream valuesProvider() { + return Stream.of( + arguments(BASE64), + arguments(FILE)); + } + @Test @DisplayName("isSecured should unwrap the first keysToSend and return true check if it matches the secured pattern") void isSecuredTrue() { diff --git a/spectrum/src/test/java/io/github/giulong/spectrum/utils/TestDataTest.java b/spectrum/src/test/java/io/github/giulong/spectrum/utils/TestDataTest.java index a90af6d6..7b426677 100644 --- a/spectrum/src/test/java/io/github/giulong/spectrum/utils/TestDataTest.java +++ b/spectrum/src/test/java/io/github/giulong/spectrum/utils/TestDataTest.java @@ -1,27 +1,52 @@ package io.github.giulong.spectrum.utils; +import static com.aventstack.extentreports.Status.INFO; +import static io.github.giulong.spectrum.enums.Frame.MANUAL; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.mockito.Mockito.mockConstruction; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; import java.util.List; +import com.aventstack.extentreports.Status; + import io.github.giulong.spectrum.exceptions.VisualRegressionException; +import io.github.giulong.spectrum.utils.TestData.Screenshot; +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.mockito.InjectMocks; -import org.mockito.MockedConstruction; -import org.mockito.Spy; +import org.mockito.*; class TestDataTest { + private MockedStatic screenshotMockedStatic; + @Spy private TestData.VisualRegression visualRegression = TestData.VisualRegression.builder().build(); + @Mock + private Screenshot.ScreenshotBuilder screenshotBuilder; + + @Mock + private Screenshot screenshot; + @InjectMocks private TestData testData = TestData.builder().build(); + @BeforeEach + void beforeEach() { + screenshotMockedStatic = mockStatic(); + } + + @AfterEach + void afterEach() { + screenshotMockedStatic.close(); + } + @Test @DisplayName("incrementScreenshotNumber should return the incremented screenshotNumber") void incrementScreenshotNumber() { @@ -63,4 +88,19 @@ void registerFailedVisualRegression() { assertEquals(constructed.getLast(), testData.getTestFailedException()); } } + + @Test + @DisplayName("buildScreenshotFor should build a screenshot with the provided params") + void buildScreenshotFor() { + final String message = "message"; + final Status status = INFO; + + when(Screenshot.builder()).thenReturn(screenshotBuilder); + when(screenshotBuilder.frame(MANUAL)).thenReturn(screenshotBuilder); + when(screenshotBuilder.message(message)).thenReturn(screenshotBuilder); + when(screenshotBuilder.status(status)).thenReturn(screenshotBuilder); + when(screenshotBuilder.build()).thenReturn(screenshot); + + testData.buildScreenshotFor(MANUAL, message, status); + } } diff --git a/verify-browsers/src/test/java/io/github/giulong/spectrum/verify_browsers/pages/ExtentReportPage.java b/verify-browsers/src/test/java/io/github/giulong/spectrum/verify_browsers/pages/ExtentReportPage.java index 938c9971..5cf23963 100644 --- a/verify-browsers/src/test/java/io/github/giulong/spectrum/verify_browsers/pages/ExtentReportPage.java +++ b/verify-browsers/src/test/java/io/github/giulong/spectrum/verify_browsers/pages/ExtentReportPage.java @@ -279,6 +279,9 @@ public class ExtentReportPage extends SpectrumPage { @FindBy(css = "div[data-test-id='checkboxit-testwithnodisplayname()'][data-frame='4']") private List noDisplayNameFrame4; + @FindBy(css = "div[data-test-id='checkboxit-testwithnodisplayname()'][data-frame='5']") + private List noDisplayNameFrame5; + @FindBy(css = "div[data-test-id='dynamicit-navigation-to-prove-auto-wait-helps-a-lot'][data-frame='0']") private List dynamicFrame0; diff --git a/verify-browsers/src/test/java/io/github/giulong/spectrum/verify_browsers/tests/ExtentReportVerifierIT.java b/verify-browsers/src/test/java/io/github/giulong/spectrum/verify_browsers/tests/ExtentReportVerifierIT.java index 778bfff5..171172a4 100644 --- a/verify-browsers/src/test/java/io/github/giulong/spectrum/verify_browsers/tests/ExtentReportVerifierIT.java +++ b/verify-browsers/src/test/java/io/github/giulong/spectrum/verify_browsers/tests/ExtentReportVerifierIT.java @@ -86,7 +86,7 @@ private void commonChecksFor(final String url) { assertVideoDuration(extentReportPage.getVideoNavigationItTestToShowNavigationAndProducedVideo(), 15); - assertVideoDuration(extentReportPage.getVideoCheckboxItTestWithNoDisplayName(), 5); + assertVideoDuration(extentReportPage.getVideoCheckboxItTestWithNoDisplayName(), 6); assertVideoDuration(extentReportPage.getVideoDemoItSendingCustomEvents(), 1); assertVideoDuration(extentReportPage.getVideoDemoItThisOneShouldFailForDemonstrationPurposes(), 1); @@ -120,10 +120,11 @@ private void commonChecksFor(final String url) { // video data frames of Checkbox page testWithNoDisplayName() assertEquals("Text of tag name: h1 is 'Welcome to the-internet'", extentReportPage.getTextOf(extentReportPage.getNoDisplayNameFrame0())); - assertEquals("Element css selector: #checkboxes -> tag name: input is selected? false", extentReportPage.getTextOf(extentReportPage.getNoDisplayNameFrame1())); - assertEquals("Element css selector: #checkboxes -> tag name: input is selected? true", extentReportPage.getTextOf(extentReportPage.getNoDisplayNameFrame2())); + assertEquals("", extentReportPage.getTextOf(extentReportPage.getNoDisplayNameFrame1())); + assertEquals("Element css selector: #checkboxes -> tag name: input is selected? false", extentReportPage.getTextOf(extentReportPage.getNoDisplayNameFrame2())); assertEquals("Element css selector: #checkboxes -> tag name: input is selected? true", extentReportPage.getTextOf(extentReportPage.getNoDisplayNameFrame3())); - assertEquals("After checking the first checkbox", extentReportPage.getTextOf(extentReportPage.getNoDisplayNameFrame4())); + assertEquals("Element css selector: #checkboxes -> tag name: input is selected? true", extentReportPage.getTextOf(extentReportPage.getNoDisplayNameFrame4())); + assertEquals("After checking the first checkbox", extentReportPage.getTextOf(extentReportPage.getNoDisplayNameFrame5())); // video data frames of Dynamic elements navigation to prove auto-wait helps a lot extentReportPage.getDynamicItNavigationToProveAutoWaitHelpsALot().click(); From c4efd74fb54fcfd414ee28e53b7fb937e24de795 Mon Sep 17 00:00:00 2001 From: Giulio Longfils Date: Tue, 6 Jan 2026 17:44:00 +0100 Subject: [PATCH 5/8] docs: adding signatures of overloaded web element screenshot service methods to gh pages --- docs/README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/README.md b/docs/README.md index 20eb88a9..fd9f1a1c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -347,10 +347,15 @@ so you can directly use them in your tests/pages. * `T hover(WebElement)`: hovers on the provided WebElement, leveraging the `actions` field * `T screenshot()`: adds a screenshot at INFO level to the current test in the Extent Report +* `T screenshot(WebElement)`: adds a screenshot of the provided WebElement at INFO level to the current test in the Extent Report * `T screenshotInfo(String)`: adds a screenshot with the provided message and INFO status to the current test in the Extent Report +* `T screenshotInfo(WebElement, String)`: adds a screenshot of the provided WebElement with the provided message and INFO status to the current test in the Extent Report * `T screenshotWarning(String)`: adds a screenshot status with the provided message and WARN to the current test in the Extent Report +* `T screenshotWarning(WebElement, String)`: adds a screenshot of the provided WebElement status with the provided message and WARN to the current test in the Extent Report * `T screenshotFail(String)`: adds a screenshot with the provided message and FAIL status to the current test in the Extent Report +* `T screenshotFail(WebElement, String)`: adds a screenshot of the provided WebElement with the provided message and FAIL status to the current test in the Extent Report * `Media addScreenshotToReport(String, Status)`: adds a screenshot with the provided message and the provided status to the current test in the Extent Report +* `Media addScreenshotToReport(WebElement, String, Status)`: adds a screenshot of the provided WebElement with the provided message and the provided status to the current test in the Extent Report * `void deleteDownloadsFolder()`: deletes the download folder (its path is provided in the `configuration*.yaml`) * `T waitForDownloadOf(Path)`: leverages the configurable `downloadWait` to check fluently if the file at the provided path is fully downloaded * `boolean checkDownloadedFile(String, String)`: leverages the `waitForDownloadOf` method and then compares checksum of the two files provided. Check From 053003a09f8c860fa0ede993cfaba6e792b8f12b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Jan 2026 19:39:20 +0000 Subject: [PATCH 6/8] build(deps): bump ch.qos.logback:logback-classic from 1.5.23 to 1.5.24 Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.5.23 to 1.5.24. - [Release notes](https://github.com/qos-ch/logback/releases) - [Commits](https://github.com/qos-ch/logback/compare/v_1.5.23...v_1.5.24) --- updated-dependencies: - dependency-name: ch.qos.logback:logback-classic dependency-version: 1.5.24 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 37ec75e0..8d0748ae 100644 --- a/pom.xml +++ b/pom.xml @@ -217,7 +217,7 @@ ch.qos.logback logback-classic - 1.5.23 + 1.5.24 org.slf4j From 3c990332ea92d5820186871f08e0962d7b34ab11 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Jan 2026 19:39:33 +0000 Subject: [PATCH 7/8] build(deps): bump org.sonatype.central:central-publishing-maven-plugin Bumps [org.sonatype.central:central-publishing-maven-plugin](https://github.com/sonatype/central-publishing-maven-plugin) from 0.9.0 to 0.10.0. - [Commits](https://github.com/sonatype/central-publishing-maven-plugin/commits) --- updated-dependencies: - dependency-name: org.sonatype.central:central-publishing-maven-plugin dependency-version: 0.10.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- spectrum/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spectrum/pom.xml b/spectrum/pom.xml index a447379e..231e93fd 100644 --- a/spectrum/pom.xml +++ b/spectrum/pom.xml @@ -368,7 +368,7 @@ org.sonatype.central central-publishing-maven-plugin - 0.9.0 + 0.10.0 true central From 1d350cd106e1a14ae34b91534d57a6545d4331d1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Jan 2026 19:39:36 +0000 Subject: [PATCH 8/8] build(deps): bump junit.version from 6.0.1 to 6.0.2 Bumps `junit.version` from 6.0.1 to 6.0.2. Updates `org.junit.jupiter:junit-jupiter-api` from 6.0.1 to 6.0.2 - [Release notes](https://github.com/junit-team/junit-framework/releases) - [Commits](https://github.com/junit-team/junit-framework/compare/r6.0.1...r6.0.2) Updates `org.junit.jupiter:junit-jupiter-engine` from 6.0.1 to 6.0.2 - [Release notes](https://github.com/junit-team/junit-framework/releases) - [Commits](https://github.com/junit-team/junit-framework/compare/r6.0.1...r6.0.2) Updates `org.junit.jupiter:junit-jupiter-params` from 6.0.1 to 6.0.2 - [Release notes](https://github.com/junit-team/junit-framework/releases) - [Commits](https://github.com/junit-team/junit-framework/compare/r6.0.1...r6.0.2) Updates `org.junit.platform:junit-platform-launcher` from 6.0.1 to 6.0.2 - [Release notes](https://github.com/junit-team/junit-framework/releases) - [Commits](https://github.com/junit-team/junit-framework/compare/r6.0.1...r6.0.2) --- updated-dependencies: - dependency-name: org.junit.jupiter:junit-jupiter-api dependency-version: 6.0.2 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.junit.jupiter:junit-jupiter-engine dependency-version: 6.0.2 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.junit.jupiter:junit-jupiter-params dependency-version: 6.0.2 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.junit.platform:junit-platform-launcher dependency-version: 6.0.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 37ec75e0..4baee047 100644 --- a/pom.xml +++ b/pom.xml @@ -46,7 +46,7 @@ true true - 6.0.1 + 6.0.2 5.21.0 2.20.1 8.12.6