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
5 changes: 1 addition & 4 deletions .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@
*/
public final class AllureTestCommonsUtils {

private static final String DOT = ".";
private static final String JSON_EXTENSION = "json";
private static final String JSON_TYPE = "application/json";
private static final String TEXT_EXTENSION = "txt";
private static final String TEXT_TYPE = "text/plain";
private static final ObjectWriter WRITER = JsonMapper
.builder()
.configure(USE_WRAPPER_NAME_AS_PROPERTY_NAME, true)
Expand All @@ -65,7 +70,9 @@ public static void attach(final AllureResults allureResults) {
try {
Allure.addAttachment(
testResult.getUuid() + AllureConstants.TEST_RESULT_FILE_SUFFIX,
WRITER.writeValueAsString(testResult)
JSON_TYPE,
WRITER.writeValueAsString(testResult),
JSON_EXTENSION
);
} catch (JsonProcessingException e) {
throw new UncheckedIOException(e);
Expand All @@ -76,7 +83,9 @@ public static void attach(final AllureResults allureResults) {
try {
Allure.addAttachment(
container.getUuid() + AllureConstants.TEST_RESULT_CONTAINER_FILE_SUFFIX,
WRITER.writeValueAsString(container)
JSON_TYPE,
WRITER.writeValueAsString(container),
JSON_EXTENSION
);
} catch (JsonProcessingException e) {
throw new UncheckedIOException(e);
Expand All @@ -86,11 +95,31 @@ public static void attach(final AllureResults allureResults) {
allureResults.getAttachments().forEach((fileName, body) -> Allure
.addAttachment(
fileName,
new ByteArrayInputStream(body)
type(fileName),
new ByteArrayInputStream(body),
extension(fileName)
)
);
}

private static String type(final String fileName) {
if (fileName.endsWith(DOT + JSON_EXTENSION)) {
return JSON_TYPE;
}
if (fileName.endsWith(DOT + TEXT_EXTENSION)) {
return TEXT_TYPE;
}
return null;
}

private static String extension(final String fileName) {
final int index = fileName.lastIndexOf('.');
if (index < 0 || index == fileName.length() - 1) {
return null;
}
return fileName.substring(index + 1);
}

/**
* Parameter mode serializer.
*/
Expand Down
47 changes: 47 additions & 0 deletions allure-selenium-bidi/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
description = "Allure Selenium WebDriver BiDi Integration"

val agent: Configuration by configurations.creating

val seleniumVersion = "4.23.0"
val testcontainersVersion = "1.21.4"

dependencies {
agent("org.aspectj:aspectjweaver")
api(project(":allure-java-commons"))
compileOnly("org.seleniumhq.selenium:selenium-java:$seleniumVersion")
testAnnotationProcessor(project(":allure-descriptions-javadoc"))
testImplementation("org.seleniumhq.selenium:selenium-java:$seleniumVersion")
testImplementation("org.assertj:assertj-core")
testImplementation("org.junit.jupiter:junit-jupiter-api")
testImplementation("org.slf4j:slf4j-simple")
testImplementation("org.testcontainers:junit-jupiter:$testcontainersVersion")
testImplementation("org.testcontainers:testcontainers:$testcontainersVersion")
testImplementation(project(":allure-assertj"))
testImplementation(project(":allure-java-commons-test"))
testImplementation(project(":allure-junit-platform"))
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
}

tasks {
compileJava {
options.release.set(17)
}
compileTestJava {
options.release.set(17)
}
jar {
manifest {
attributes(
mapOf(
"Automatic-Module-Name" to "io.qameta.allure.seleniumbidi"
)
)
}
}
test {
useJUnitPlatform()
jvmArgs("-javaagent:${agent.singleFile}")
systemProperty("allure.model.indentOutput", "true")
systemProperty("org.slf4j.simpleLogger.defaultLogLevel", "warn")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
/*
* Copyright 2016-2026 Qameta Software Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.qameta.allure.seleniumbidi;

import io.qameta.allure.Allure;
import io.qameta.allure.AllureLifecycle;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.support.events.EventFiringDecorator;
import org.openqa.selenium.support.events.WebDriverListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
* Selenium WebDriver BiDi listener that captures browser log and network events
* as aggregated Allure attachments.
*/
@SuppressWarnings("unused")
public class AllureWebDriverBiDi implements WebDriverListener, AutoCloseable {

private static final Logger LOGGER = LoggerFactory.getLogger(AllureWebDriverBiDi.class);

private final AllureLifecycle lifecycle;
private final BiDiSessionFactory sessionFactory;
private final BiDiConfiguration configuration = new BiDiConfiguration();
private final Map<WebDriver, BiDiSessionState> sessions = new IdentityHashMap<>();
private final Lock sessionsLock = new ReentrantLock();

public AllureWebDriverBiDi() {
this(Allure.getLifecycle(), new SeleniumBiDiSessionFactory());
}

AllureWebDriverBiDi(final AllureLifecycle lifecycle,
final BiDiSessionFactory sessionFactory) {
this.lifecycle = lifecycle;
this.sessionFactory = sessionFactory;
}

public <T extends WebDriver> T decorate(final T driver) {
return new EventFiringDecorator<T>(this).decorate(driver);
}

public AllureWebDriverBiDi logs(final boolean enabled) {
configuration.setLogsEnabled(enabled);
return this;
}

public AllureWebDriverBiDi network(final boolean enabled) {
configuration.setNetworkEnabled(enabled);
return this;
}

public AllureWebDriverBiDi maxLogEntries(final int maxLogEntries) {
configuration.setMaxLogEntries(maxLogEntries);
return this;
}

public AllureWebDriverBiDi maxNetworkEvents(final int maxNetworkEvents) {
configuration.setMaxNetworkEvents(maxNetworkEvents);
return this;
}

public AllureWebDriverBiDi redactHeaders(final String... headerNames) {
configuration.redactHeaders(headerNames);
return this;
}

@Override
public void beforeAnyWebDriverCall(final WebDriver driver,
final Method method,
final Object[] args) {
captureActiveAllureContext(driver);
}

@Override
public void afterAnyWebDriverCall(final WebDriver driver,
final Method method,
final Object[] args,
final Object result) {
captureActiveAllureContext(driver);
}

@Override
public void beforeQuit(final WebDriver driver) {
closeSession(driver);
}

@Override
public void afterQuit(final WebDriver driver) {
closeSession(driver);
}

@Override
public void close() {
final List<BiDiSessionState> activeSessions = new ArrayList<>();
sessionsLock.lock();
try {
activeSessions.addAll(sessions.values());
sessions.clear();
} finally {
sessionsLock.unlock();
}
activeSessions.forEach(this::safeFlushAndClose);
}

private void captureActiveAllureContext(final WebDriver driver) {
if (!configuration.isAnyEnabled()) {
return;
}

if (!lifecycle.getCurrentTestCaseOrStep().isPresent()) {
return;
}

getOrCreateSession(driver);
}

private BiDiSessionState getOrCreateSession(final WebDriver driver) {
sessionsLock.lock();
try {
final BiDiSessionState existing = sessions.get(driver);
if (existing != null) {
return existing;
}

try {
final BiDiSessionState created = BiDiSessionState.start(driver, configuration, sessionFactory);
if (created != null) {
sessions.put(driver, created);
}
return created;
} catch (RuntimeException e) {
LOGGER.debug("Could not start WebDriver BiDi capture", e);
return null;
}
} finally {
sessionsLock.unlock();
}
}

private void closeSession(final WebDriver driver) {
final BiDiSessionState state;
sessionsLock.lock();
try {
state = sessions.remove(driver);
} finally {
sessionsLock.unlock();
}
safeFlushAndClose(state);
}

private void safeFlushAndClose(final BiDiSessionState state) {
if (state == null) {
return;
}
try {
state.flushAndClose(lifecycle);
} catch (RuntimeException e) {
LOGGER.debug("Could not flush WebDriver BiDi capture", e);
}
}
}
Loading
Loading