Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
42 changes: 26 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -79,15 +85,16 @@ 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:<br/><br/>
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):<br/><br/>
<img style="width: 49%; vertical-align: top;" src="docs/assets/images/readme-html-summary.png" alt="summary"/>
&nbsp;<img style="width: 49%; vertical-align: top;" src="docs/assets/images/readme-html-testbook.png" alt="html testbook"/>

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).

Expand Down Expand Up @@ -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!
Expand Down
5 changes: 5 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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");
Expand Down
6 changes: 3 additions & 3 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
<jar.skipIfEmpty>true</jar.skipIfEmpty>
<maven.install.skip>true</maven.install.skip>

<junit.version>6.0.1</junit.version>
<junit.version>6.0.2</junit.version>
<mockito.version>5.21.0</mockito.version>
<jackson.version>2.20.1</jackson.version>
<simplejavamail.version>8.12.6</simplejavamail.version>
Expand Down Expand Up @@ -217,7 +217,7 @@
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.5.23</version>
<version>1.5.24</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
Expand Down Expand Up @@ -340,7 +340,7 @@
<dependency>
<groupId>com.puppycrawl.tools</groupId>
<artifactId>checkstyle</artifactId>
<version>12.3.0</version>
<version>13.0.0</version>
</dependency>
<dependency>
<groupId>com.github.sevntu-checkstyle</groupId>
Expand Down
2 changes: 1 addition & 1 deletion spectrum/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@
<plugin>
<groupId>org.sonatype.central</groupId>
<artifactId>central-publishing-maven-plugin</artifactId>
<version>0.9.0</version>
<version>0.10.0</version>
<extensions>true</extensions>
<configuration>
<publishingServerId>central</publishingServerId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -30,10 +28,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;
Expand Down Expand Up @@ -71,6 +66,12 @@ public abstract class SpectrumEntity<T extends SpectrumEntity<T, Data>, Data> {
@Shared
protected WebDriver driver;

@Shared
protected TakesScreenshot takesScreenshot;

@Shared
protected JavascriptExecutor javascriptExecutor;

@Shared
protected WebDriverWait implicitWait;

Expand Down Expand Up @@ -229,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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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.*;

Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;

Expand All @@ -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;
Expand All @@ -32,8 +44,11 @@ public class EventsWebDriverListener extends SpectrumWebDriverListener {

private static final Pattern SECURED_PATTERN = Pattern.compile("@Secured@(?<key>.*)@Secured@");

private final EventsDispatcher eventsDispatcher = EventsDispatcher.getInstance();

private Events events;
private List<WebDriverEventConsumer> consumers;
private TestContext testContext;

List<String> parse(final Object[] args) {
return Arrays
Expand Down Expand Up @@ -484,8 +499,29 @@ public <X> void beforeGetScreenshotAs(final WebDriver driver, final OutputType<X
}

@Override
@Generated
public <X> void afterGetScreenshotAs(final WebDriver driver, final OutputType<X> 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);
}

Expand Down
Loading
Loading