Skip to content

Commit 7ff0fae

Browse files
authored
Add Selenium WebDriver BiDi log and network reporting (via #1270)
1 parent 621d24c commit 7ff0fae

17 files changed

Lines changed: 2127 additions & 7 deletions

File tree

.idea/vcs.xml

Lines changed: 1 addition & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

allure-java-commons-test/src/main/java/io/qameta/allure/test/AllureTestCommonsUtils.java

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@
4141
*/
4242
public final class AllureTestCommonsUtils {
4343

44+
private static final String DOT = ".";
45+
private static final String JSON_EXTENSION = "json";
46+
private static final String JSON_TYPE = "application/json";
47+
private static final String TEXT_EXTENSION = "txt";
48+
private static final String TEXT_TYPE = "text/plain";
4449
private static final ObjectWriter WRITER = JsonMapper
4550
.builder()
4651
.configure(USE_WRAPPER_NAME_AS_PROPERTY_NAME, true)
@@ -65,7 +70,9 @@ public static void attach(final AllureResults allureResults) {
6570
try {
6671
Allure.addAttachment(
6772
testResult.getUuid() + AllureConstants.TEST_RESULT_FILE_SUFFIX,
68-
WRITER.writeValueAsString(testResult)
73+
JSON_TYPE,
74+
WRITER.writeValueAsString(testResult),
75+
JSON_EXTENSION
6976
);
7077
} catch (JsonProcessingException e) {
7178
throw new UncheckedIOException(e);
@@ -76,7 +83,9 @@ public static void attach(final AllureResults allureResults) {
7683
try {
7784
Allure.addAttachment(
7885
container.getUuid() + AllureConstants.TEST_RESULT_CONTAINER_FILE_SUFFIX,
79-
WRITER.writeValueAsString(container)
86+
JSON_TYPE,
87+
WRITER.writeValueAsString(container),
88+
JSON_EXTENSION
8089
);
8190
} catch (JsonProcessingException e) {
8291
throw new UncheckedIOException(e);
@@ -86,11 +95,31 @@ public static void attach(final AllureResults allureResults) {
8695
allureResults.getAttachments().forEach((fileName, body) -> Allure
8796
.addAttachment(
8897
fileName,
89-
new ByteArrayInputStream(body)
98+
type(fileName),
99+
new ByteArrayInputStream(body),
100+
extension(fileName)
90101
)
91102
);
92103
}
93104

105+
private static String type(final String fileName) {
106+
if (fileName.endsWith(DOT + JSON_EXTENSION)) {
107+
return JSON_TYPE;
108+
}
109+
if (fileName.endsWith(DOT + TEXT_EXTENSION)) {
110+
return TEXT_TYPE;
111+
}
112+
return null;
113+
}
114+
115+
private static String extension(final String fileName) {
116+
final int index = fileName.lastIndexOf('.');
117+
if (index < 0 || index == fileName.length() - 1) {
118+
return null;
119+
}
120+
return fileName.substring(index + 1);
121+
}
122+
94123
/**
95124
* Parameter mode serializer.
96125
*/
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
description = "Allure Selenium WebDriver BiDi Integration"
2+
3+
val agent: Configuration by configurations.creating
4+
5+
val seleniumVersion = "4.23.0"
6+
val testcontainersVersion = "1.21.4"
7+
8+
dependencies {
9+
agent("org.aspectj:aspectjweaver")
10+
api(project(":allure-java-commons"))
11+
compileOnly("org.seleniumhq.selenium:selenium-java:$seleniumVersion")
12+
testAnnotationProcessor(project(":allure-descriptions-javadoc"))
13+
testImplementation("org.seleniumhq.selenium:selenium-java:$seleniumVersion")
14+
testImplementation("org.assertj:assertj-core")
15+
testImplementation("org.junit.jupiter:junit-jupiter-api")
16+
testImplementation("org.slf4j:slf4j-simple")
17+
testImplementation("org.testcontainers:junit-jupiter:$testcontainersVersion")
18+
testImplementation("org.testcontainers:testcontainers:$testcontainersVersion")
19+
testImplementation(project(":allure-assertj"))
20+
testImplementation(project(":allure-java-commons-test"))
21+
testImplementation(project(":allure-junit-platform"))
22+
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
23+
}
24+
25+
tasks {
26+
compileJava {
27+
options.release.set(17)
28+
}
29+
compileTestJava {
30+
options.release.set(17)
31+
}
32+
jar {
33+
manifest {
34+
attributes(
35+
mapOf(
36+
"Automatic-Module-Name" to "io.qameta.allure.seleniumbidi"
37+
)
38+
)
39+
}
40+
}
41+
test {
42+
useJUnitPlatform()
43+
jvmArgs("-javaagent:${agent.singleFile}")
44+
systemProperty("allure.model.indentOutput", "true")
45+
systemProperty("org.slf4j.simpleLogger.defaultLogLevel", "warn")
46+
}
47+
}
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
/*
2+
* Copyright 2016-2026 Qameta Software Inc
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.qameta.allure.seleniumbidi;
17+
18+
import io.qameta.allure.Allure;
19+
import io.qameta.allure.AllureLifecycle;
20+
import org.openqa.selenium.WebDriver;
21+
import org.openqa.selenium.support.events.EventFiringDecorator;
22+
import org.openqa.selenium.support.events.WebDriverListener;
23+
import org.slf4j.Logger;
24+
import org.slf4j.LoggerFactory;
25+
26+
import java.lang.reflect.Method;
27+
import java.util.ArrayList;
28+
import java.util.IdentityHashMap;
29+
import java.util.List;
30+
import java.util.Map;
31+
import java.util.concurrent.locks.Lock;
32+
import java.util.concurrent.locks.ReentrantLock;
33+
34+
/**
35+
* Selenium WebDriver BiDi listener that captures browser log and network events
36+
* as aggregated Allure attachments.
37+
*/
38+
@SuppressWarnings("unused")
39+
public class AllureWebDriverBiDi implements WebDriverListener, AutoCloseable {
40+
41+
private static final Logger LOGGER = LoggerFactory.getLogger(AllureWebDriverBiDi.class);
42+
43+
private final AllureLifecycle lifecycle;
44+
private final BiDiSessionFactory sessionFactory;
45+
private final BiDiConfiguration configuration = new BiDiConfiguration();
46+
private final Map<WebDriver, BiDiSessionState> sessions = new IdentityHashMap<>();
47+
private final Lock sessionsLock = new ReentrantLock();
48+
49+
public AllureWebDriverBiDi() {
50+
this(Allure.getLifecycle(), new SeleniumBiDiSessionFactory());
51+
}
52+
53+
AllureWebDriverBiDi(final AllureLifecycle lifecycle,
54+
final BiDiSessionFactory sessionFactory) {
55+
this.lifecycle = lifecycle;
56+
this.sessionFactory = sessionFactory;
57+
}
58+
59+
public <T extends WebDriver> T decorate(final T driver) {
60+
return new EventFiringDecorator<T>(this).decorate(driver);
61+
}
62+
63+
public AllureWebDriverBiDi logs(final boolean enabled) {
64+
configuration.setLogsEnabled(enabled);
65+
return this;
66+
}
67+
68+
public AllureWebDriverBiDi network(final boolean enabled) {
69+
configuration.setNetworkEnabled(enabled);
70+
return this;
71+
}
72+
73+
public AllureWebDriverBiDi maxLogEntries(final int maxLogEntries) {
74+
configuration.setMaxLogEntries(maxLogEntries);
75+
return this;
76+
}
77+
78+
public AllureWebDriverBiDi maxNetworkEvents(final int maxNetworkEvents) {
79+
configuration.setMaxNetworkEvents(maxNetworkEvents);
80+
return this;
81+
}
82+
83+
public AllureWebDriverBiDi redactHeaders(final String... headerNames) {
84+
configuration.redactHeaders(headerNames);
85+
return this;
86+
}
87+
88+
@Override
89+
public void beforeAnyWebDriverCall(final WebDriver driver,
90+
final Method method,
91+
final Object[] args) {
92+
captureActiveAllureContext(driver);
93+
}
94+
95+
@Override
96+
public void afterAnyWebDriverCall(final WebDriver driver,
97+
final Method method,
98+
final Object[] args,
99+
final Object result) {
100+
captureActiveAllureContext(driver);
101+
}
102+
103+
@Override
104+
public void beforeQuit(final WebDriver driver) {
105+
closeSession(driver);
106+
}
107+
108+
@Override
109+
public void afterQuit(final WebDriver driver) {
110+
closeSession(driver);
111+
}
112+
113+
@Override
114+
public void close() {
115+
final List<BiDiSessionState> activeSessions = new ArrayList<>();
116+
sessionsLock.lock();
117+
try {
118+
activeSessions.addAll(sessions.values());
119+
sessions.clear();
120+
} finally {
121+
sessionsLock.unlock();
122+
}
123+
activeSessions.forEach(this::safeFlushAndClose);
124+
}
125+
126+
private void captureActiveAllureContext(final WebDriver driver) {
127+
if (!configuration.isAnyEnabled()) {
128+
return;
129+
}
130+
131+
if (!lifecycle.getCurrentTestCaseOrStep().isPresent()) {
132+
return;
133+
}
134+
135+
getOrCreateSession(driver);
136+
}
137+
138+
private BiDiSessionState getOrCreateSession(final WebDriver driver) {
139+
sessionsLock.lock();
140+
try {
141+
final BiDiSessionState existing = sessions.get(driver);
142+
if (existing != null) {
143+
return existing;
144+
}
145+
146+
try {
147+
final BiDiSessionState created = BiDiSessionState.start(driver, configuration, sessionFactory);
148+
if (created != null) {
149+
sessions.put(driver, created);
150+
}
151+
return created;
152+
} catch (RuntimeException e) {
153+
LOGGER.debug("Could not start WebDriver BiDi capture", e);
154+
return null;
155+
}
156+
} finally {
157+
sessionsLock.unlock();
158+
}
159+
}
160+
161+
private void closeSession(final WebDriver driver) {
162+
final BiDiSessionState state;
163+
sessionsLock.lock();
164+
try {
165+
state = sessions.remove(driver);
166+
} finally {
167+
sessionsLock.unlock();
168+
}
169+
safeFlushAndClose(state);
170+
}
171+
172+
private void safeFlushAndClose(final BiDiSessionState state) {
173+
if (state == null) {
174+
return;
175+
}
176+
try {
177+
state.flushAndClose(lifecycle);
178+
} catch (RuntimeException e) {
179+
LOGGER.debug("Could not flush WebDriver BiDi capture", e);
180+
}
181+
}
182+
}

0 commit comments

Comments
 (0)