Skip to content

Commit 0e8059d

Browse files
authoredNov 13, 2024··
[bidi][java] Add network request handler APIs (#14424)
1 parent 25551ad commit 0e8059d

File tree

7 files changed

+422
-10
lines changed

7 files changed

+422
-10
lines changed
 

Diff for: ‎java/src/org/openqa/selenium/bidi/network/Header.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public class Header {
2525
private final String name;
2626
private final BytesValue value;
2727

28-
private Header(String name, BytesValue value) {
28+
public Header(String name, BytesValue value) {
2929
this.name = name;
3030
this.value = value;
3131
}

Diff for: ‎java/src/org/openqa/selenium/bidi/network/ResponseData.java

+5-5
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public class ResponseData {
2828
private final String url;
2929

3030
private final String protocol;
31-
private final long status;
31+
private final int status;
3232
private final String statusText;
3333
private final boolean fromCache;
3434
private final List<Header> headers;
@@ -42,7 +42,7 @@ public class ResponseData {
4242
private ResponseData(
4343
String url,
4444
String protocol,
45-
long status,
45+
int status,
4646
String statusText,
4747
boolean fromCache,
4848
List<Header> headers,
@@ -69,7 +69,7 @@ private ResponseData(
6969
public static ResponseData fromJson(JsonInput input) {
7070
String url = null;
7171
String protocol = null;
72-
long status = 0;
72+
int status = 0;
7373
String statusText = null;
7474
boolean fromCache = false;
7575
List<Header> headers = new ArrayList<>();
@@ -89,7 +89,7 @@ public static ResponseData fromJson(JsonInput input) {
8989
protocol = input.read(String.class);
9090
break;
9191
case "status":
92-
status = input.read(Long.class);
92+
status = input.read(Integer.class);
9393
break;
9494
case "statusText":
9595
statusText = input.read(String.class);
@@ -150,7 +150,7 @@ public String getProtocol() {
150150
return protocol;
151151
}
152152

153-
public long getStatus() {
153+
public int getStatus() {
154154
return status;
155155
}
156156

Diff for: ‎java/src/org/openqa/selenium/remote/Network.java

+8
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@
1919

2020
import java.net.URI;
2121
import java.util.function.Predicate;
22+
import java.util.function.UnaryOperator;
2223
import org.openqa.selenium.Beta;
2324
import org.openqa.selenium.UsernameAndPassword;
25+
import org.openqa.selenium.remote.http.HttpRequest;
2426

2527
@Beta
2628
public interface Network {
@@ -32,4 +34,10 @@ public interface Network {
3234
void removeAuthenticationHandler(long id);
3335

3436
void clearAuthenticationHandlers();
37+
38+
long addRequestHandler(Predicate<URI> filter, UnaryOperator<HttpRequest> handler);
39+
40+
void removeRequestHandler(long id);
41+
42+
void clearRequestHandlers();
3543
}

Diff for: ‎java/src/org/openqa/selenium/remote/RemoteNetwork.java

+114
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,28 @@
1818
package org.openqa.selenium.remote;
1919

2020
import java.net.URI;
21+
import java.util.ArrayList;
22+
import java.util.List;
2123
import java.util.Map;
2224
import java.util.Optional;
2325
import java.util.concurrent.ConcurrentHashMap;
2426
import java.util.concurrent.atomic.AtomicLong;
2527
import java.util.function.Predicate;
28+
import java.util.function.UnaryOperator;
2629
import org.openqa.selenium.Beta;
2730
import org.openqa.selenium.UsernameAndPassword;
2831
import org.openqa.selenium.WebDriver;
2932
import org.openqa.selenium.bidi.BiDi;
3033
import org.openqa.selenium.bidi.HasBiDi;
3134
import org.openqa.selenium.bidi.network.AddInterceptParameters;
35+
import org.openqa.selenium.bidi.network.BytesValue;
36+
import org.openqa.selenium.bidi.network.ContinueRequestParameters;
37+
import org.openqa.selenium.bidi.network.Header;
3238
import org.openqa.selenium.bidi.network.InterceptPhase;
39+
import org.openqa.selenium.bidi.network.RequestData;
40+
import org.openqa.selenium.remote.http.Contents;
41+
import org.openqa.selenium.remote.http.HttpMethod;
42+
import org.openqa.selenium.remote.http.HttpRequest;
3343

3444
@Beta
3545
class RemoteNetwork implements Network {
@@ -39,13 +49,16 @@ class RemoteNetwork implements Network {
3949

4050
private final Map<Long, AuthDetails> authHandlers = new ConcurrentHashMap<>();
4151

52+
private final Map<Long, RequestDetails> requestHandlers = new ConcurrentHashMap<>();
53+
4254
private final AtomicLong callBackId = new AtomicLong(1);
4355

4456
public RemoteNetwork(WebDriver driver) {
4557
this.biDi = ((HasBiDi) driver).getBiDi();
4658
this.network = new org.openqa.selenium.bidi.module.Network(driver);
4759

4860
interceptAuthTraffic();
61+
interceptRequest();
4962
}
5063

5164
private void interceptAuthTraffic() {
@@ -73,6 +86,71 @@ private Optional<UsernameAndPassword> getAuthCredentials(URI uri) {
7386
.findFirst();
7487
}
7588

89+
private void interceptRequest() {
90+
this.network.addIntercept(new AddInterceptParameters(InterceptPhase.BEFORE_REQUEST_SENT));
91+
92+
this.network.onBeforeRequestSent(
93+
beforeRequestSent -> {
94+
String requestId = beforeRequestSent.getRequest().getRequestId();
95+
URI uri = URI.create(beforeRequestSent.getRequest().getUrl());
96+
97+
ContinueRequestParameters continueRequestParameters =
98+
new ContinueRequestParameters(requestId);
99+
100+
Optional<UnaryOperator<HttpRequest>> requestHandler = getRequestHandler(uri);
101+
102+
if (requestHandler.isPresent()) {
103+
RequestData interceptedRequest = beforeRequestSent.getRequest();
104+
105+
// Build the originalRequest object from the intercepted request details.
106+
HttpRequest originalRequest =
107+
new HttpRequest(
108+
HttpMethod.getHttpMethod(interceptedRequest.getMethod()),
109+
interceptedRequest.getUrl());
110+
111+
// Populate the headers of the original request.
112+
interceptedRequest
113+
.getHeaders()
114+
.forEach(
115+
header ->
116+
originalRequest.addHeader(header.getName(), header.getValue().getValue()));
117+
118+
HttpRequest modifiedRequest = requestHandler.get().apply(originalRequest);
119+
120+
continueRequestParameters.method(modifiedRequest.getMethod());
121+
122+
if (!uri.toString().equals(modifiedRequest.getUri())) {
123+
continueRequestParameters.url(modifiedRequest.getUri());
124+
}
125+
126+
List<Header> headerList = new ArrayList<>();
127+
modifiedRequest.forEachHeader(
128+
(name, value) ->
129+
headerList.add(
130+
new Header(name, new BytesValue(BytesValue.Type.STRING, value))));
131+
132+
if (!headerList.isEmpty()) {
133+
continueRequestParameters.headers(headerList);
134+
}
135+
136+
Contents.Supplier content = modifiedRequest.getContent();
137+
138+
if (content.length() > 0) {
139+
continueRequestParameters.body(
140+
new BytesValue(BytesValue.Type.STRING, Contents.utf8String(content)));
141+
}
142+
}
143+
network.continueRequest(continueRequestParameters);
144+
});
145+
}
146+
147+
private Optional<UnaryOperator<HttpRequest>> getRequestHandler(URI uri) {
148+
return requestHandlers.values().stream()
149+
.filter(requestDetails -> requestDetails.getFilter().test(uri))
150+
.map(RequestDetails::getHandler)
151+
.findFirst();
152+
}
153+
76154
@Override
77155
public long addAuthenticationHandler(UsernameAndPassword usernameAndPassword) {
78156
return addAuthenticationHandler(url -> true, usernameAndPassword);
@@ -97,6 +175,24 @@ public void clearAuthenticationHandlers() {
97175
authHandlers.clear();
98176
}
99177

178+
@Override
179+
public long addRequestHandler(Predicate<URI> filter, UnaryOperator<HttpRequest> handler) {
180+
long id = this.callBackId.incrementAndGet();
181+
182+
requestHandlers.put(id, new RequestDetails(filter, handler));
183+
return id;
184+
}
185+
186+
@Override
187+
public void removeRequestHandler(long id) {
188+
requestHandlers.remove(id);
189+
}
190+
191+
@Override
192+
public void clearRequestHandlers() {
193+
requestHandlers.clear();
194+
}
195+
100196
private class AuthDetails {
101197
private final Predicate<URI> filter;
102198
private final UsernameAndPassword usernameAndPassword;
@@ -114,4 +210,22 @@ public UsernameAndPassword getUsernameAndPassword() {
114210
return usernameAndPassword;
115211
}
116212
}
213+
214+
private class RequestDetails {
215+
private final Predicate<URI> filter;
216+
private final UnaryOperator<HttpRequest> handler;
217+
218+
public RequestDetails(Predicate<URI> filter, UnaryOperator<HttpRequest> handler) {
219+
this.filter = filter;
220+
this.handler = handler;
221+
}
222+
223+
public Predicate<URI> getFilter() {
224+
return this.filter;
225+
}
226+
227+
public UnaryOperator<HttpRequest> getHandler() {
228+
return this.handler;
229+
}
230+
}
117231
}

Diff for: ‎java/src/org/openqa/selenium/remote/http/HttpMethod.java

+13-1
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,17 @@ public enum HttpMethod {
2626
PATCH,
2727
HEAD,
2828
CONNECT,
29-
TRACE,
29+
TRACE;
30+
31+
public static HttpMethod getHttpMethod(String method) {
32+
if (method == null) {
33+
throw new IllegalArgumentException("Method cannot be null");
34+
}
35+
36+
try {
37+
return HttpMethod.valueOf(method.toUpperCase());
38+
} catch (IllegalArgumentException e) {
39+
throw new IllegalArgumentException("No enum constant for method: " + method);
40+
}
41+
}
3042
}

Diff for: ‎java/test/org/openqa/selenium/WebNetworkTest.java

+278
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,24 @@
1818
package org.openqa.selenium;
1919

2020
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
21+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
2122
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
23+
import static org.openqa.selenium.remote.http.Contents.utf8String;
2224

2325
import java.net.URI;
26+
import java.util.List;
27+
import java.util.concurrent.CountDownLatch;
28+
import java.util.concurrent.TimeUnit;
2429
import java.util.function.Predicate;
2530
import org.junit.jupiter.api.Test;
31+
import org.openqa.selenium.bidi.module.Network;
32+
import org.openqa.selenium.bidi.network.Header;
33+
import org.openqa.selenium.environment.webserver.NettyAppServer;
2634
import org.openqa.selenium.remote.RemoteWebDriver;
35+
import org.openqa.selenium.remote.http.HttpMethod;
36+
import org.openqa.selenium.remote.http.HttpRequest;
37+
import org.openqa.selenium.remote.http.HttpResponse;
38+
import org.openqa.selenium.remote.http.Route;
2739
import org.openqa.selenium.testing.Ignore;
2840
import org.openqa.selenium.testing.JupiterTestBase;
2941
import org.openqa.selenium.testing.NeedsFreshDriver;
@@ -163,4 +175,270 @@ void canClearAuthenticationHandlers() {
163175
assertThatExceptionOfType(UnhandledAlertException.class)
164176
.isThrownBy(() -> driver.findElement(By.tagName("h1")));
165177
}
178+
179+
@Test
180+
@NeedsFreshDriver
181+
@Ignore(Browser.CHROME)
182+
@Ignore(Browser.EDGE)
183+
void canAddRequestHandler() {
184+
Predicate<URI> filter = uri -> uri.getPath().contains("logEntry");
185+
186+
page = appServer.whereIs("/bidi/logEntryAdded.html");
187+
188+
((RemoteWebDriver) driver).network().addRequestHandler(filter, httpRequest -> httpRequest);
189+
190+
driver.get(page);
191+
192+
assertThat(driver.findElement(By.tagName("h1")).getText()).isEqualTo("Long entry added events");
193+
}
194+
195+
@Test
196+
@NeedsFreshDriver
197+
@Ignore(Browser.CHROME)
198+
@Ignore(Browser.EDGE)
199+
void canAddRequestHandlerToModifyMethod() {
200+
Predicate<URI> filter = uri -> uri.getPath().contains("logEntry");
201+
202+
page = appServer.whereIs("/bidi/logEntryAdded.html");
203+
204+
((RemoteWebDriver) driver)
205+
.network()
206+
.addRequestHandler(filter, httpRequest -> new HttpRequest(HttpMethod.HEAD, page));
207+
208+
driver.get(page);
209+
210+
assertThatThrownBy(() -> driver.findElement(By.tagName("h1")))
211+
.isInstanceOf(NoSuchElementException.class);
212+
}
213+
214+
@Test
215+
@NeedsFreshDriver
216+
@Ignore(Browser.CHROME)
217+
@Ignore(Browser.EDGE)
218+
void canAddRequestHandlerToModifyHeaders() throws InterruptedException {
219+
Route route =
220+
Route.matching(req -> req.getUri().contains("network"))
221+
.to(
222+
() ->
223+
req -> {
224+
HttpResponse response = new HttpResponse();
225+
226+
req.getHeaderNames()
227+
.forEach(
228+
header -> {
229+
String value = req.getHeader(header);
230+
response.addHeader(header, value);
231+
});
232+
return response.setContent(utf8String("Received response for network"));
233+
});
234+
235+
appServer = new NettyAppServer(route);
236+
appServer.start();
237+
238+
Predicate<URI> filter = uri -> uri.getPath().contains("network");
239+
240+
CountDownLatch latch = new CountDownLatch(1);
241+
242+
page = appServer.whereIs("network.html");
243+
244+
((RemoteWebDriver) driver)
245+
.network()
246+
.addRequestHandler(
247+
filter,
248+
httpRequest ->
249+
new HttpRequest(HttpMethod.HEAD, page).addHeader("test", "network-intercept"));
250+
251+
Network network = new Network(driver);
252+
network.onResponseCompleted(
253+
responseDetails -> {
254+
List<Header> headers = responseDetails.getResponseData().getHeaders();
255+
headers.forEach(
256+
header -> {
257+
if (header.getName().equals("test")) {
258+
assertThat(header.getValue().getValue()).isEqualTo("network-intercept");
259+
latch.countDown();
260+
}
261+
});
262+
});
263+
264+
driver.get(page);
265+
266+
latch.await(5, TimeUnit.SECONDS);
267+
268+
assertThat(latch.getCount()).isEqualTo(0);
269+
}
270+
271+
@Test
272+
@NeedsFreshDriver
273+
@Ignore(Browser.CHROME)
274+
@Ignore(Browser.EDGE)
275+
void canAddRequestHandlerToModifyBody() throws InterruptedException {
276+
Route route =
277+
Route.matching(req -> req.getUri().contains("network"))
278+
.to(
279+
() ->
280+
req -> {
281+
HttpResponse response = new HttpResponse();
282+
return response.setContent(req.getContent());
283+
});
284+
285+
appServer = new NettyAppServer(route);
286+
appServer.start();
287+
288+
Predicate<URI> filter = uri -> uri.getPath().contains("network");
289+
290+
page = appServer.whereIs("network.html");
291+
292+
((RemoteWebDriver) driver)
293+
.network()
294+
.addRequestHandler(
295+
filter,
296+
httpRequest ->
297+
new HttpRequest(HttpMethod.POST, page)
298+
.setContent(utf8String("Received response for the request")));
299+
300+
driver.get(page);
301+
302+
assertThat(driver.getPageSource().contains("Received response for the request")).isTrue();
303+
}
304+
305+
@Test
306+
@NeedsFreshDriver
307+
@Ignore(Browser.CHROME)
308+
@Ignore(Browser.EDGE)
309+
void canAddMultipleRequestHandlers() {
310+
page = appServer.whereIs("/bidi/logEntryAdded.html");
311+
312+
((RemoteWebDriver) driver)
313+
.network()
314+
.addRequestHandler(uri -> uri.getPath().contains("logEntry"), httpRequest -> httpRequest);
315+
316+
((RemoteWebDriver) driver)
317+
.network()
318+
.addRequestHandler(
319+
uri -> uri.getPath().contains("hello"),
320+
httpRequest -> new HttpRequest(HttpMethod.HEAD, page));
321+
322+
driver.get(page);
323+
324+
assertThat(driver.findElement(By.tagName("h1")).getText()).isEqualTo("Long entry added events");
325+
}
326+
327+
@Test
328+
@NeedsFreshDriver
329+
@Ignore(Browser.CHROME)
330+
@Ignore(Browser.EDGE)
331+
void canAddMultipleRequestHandlersWithTheSameFilter() {
332+
((RemoteWebDriver) driver)
333+
.network()
334+
.addRequestHandler(uri -> uri.getPath().contains("logEntry"), httpRequest -> httpRequest);
335+
336+
((RemoteWebDriver) driver)
337+
.network()
338+
.addRequestHandler(uri -> uri.getPath().contains("logEntry"), httpRequest -> httpRequest);
339+
340+
page = appServer.whereIs("/bidi/logEntryAdded.html");
341+
342+
driver.get(page);
343+
344+
assertThat(driver.findElement(By.tagName("h1")).getText()).isEqualTo("Long entry added events");
345+
}
346+
347+
@Test
348+
@NeedsFreshDriver
349+
@Ignore(Browser.CHROME)
350+
@Ignore(Browser.EDGE)
351+
void canRemoveRequestHandler() throws InterruptedException {
352+
Route route =
353+
Route.matching(req -> req.getUri().contains("network"))
354+
.to(
355+
() ->
356+
req -> {
357+
HttpResponse response = new HttpResponse();
358+
359+
req.getHeaderNames()
360+
.forEach(
361+
header -> {
362+
String value = req.getHeader(header);
363+
response.addHeader(header, value);
364+
});
365+
return response.setContent(utf8String("Received response for network"));
366+
});
367+
368+
appServer = new NettyAppServer(route);
369+
appServer.start();
370+
371+
Predicate<URI> filter = uri -> uri.getPath().contains("network");
372+
373+
CountDownLatch latch = new CountDownLatch(1);
374+
375+
page = appServer.whereIs("network.html");
376+
377+
long id =
378+
((RemoteWebDriver) driver)
379+
.network()
380+
.addRequestHandler(
381+
filter,
382+
httpRequest ->
383+
new HttpRequest(HttpMethod.HEAD, page).addHeader("test", "network-intercept"));
384+
385+
((RemoteWebDriver) driver).network().removeRequestHandler(id);
386+
387+
Network network = new Network(driver);
388+
network.onResponseCompleted(
389+
responseDetails -> {
390+
List<Header> headers = responseDetails.getResponseData().getHeaders();
391+
headers.forEach(
392+
header -> {
393+
if (header.getName().equals("test")) {
394+
assertThat(header.getValue().getValue()).isEqualTo("network-intercept");
395+
latch.countDown();
396+
}
397+
});
398+
});
399+
400+
driver.get(page);
401+
402+
latch.await(5, TimeUnit.SECONDS);
403+
404+
assertThat(latch.getCount()).isEqualTo(1);
405+
}
406+
407+
@Test
408+
@NeedsFreshDriver
409+
@Ignore(Browser.CHROME)
410+
@Ignore(Browser.EDGE)
411+
void canRemoveRequestHandlerThatDoesNotExist() {
412+
((RemoteWebDriver) driver).network().removeAuthenticationHandler(5);
413+
page = appServer.whereIs("/bidi/logEntryAdded.html");
414+
driver.get(page);
415+
416+
assertThat(driver.findElement(By.tagName("h1")).getText()).isEqualTo("Long entry added events");
417+
}
418+
419+
@Test
420+
@NeedsFreshDriver
421+
@Ignore(Browser.CHROME)
422+
@Ignore(Browser.EDGE)
423+
void canClearRequestHandlers() {
424+
page = appServer.whereIs("/bidi/logEntryAdded.html");
425+
426+
((RemoteWebDriver) driver)
427+
.network()
428+
.addRequestHandler(
429+
uri -> uri.getPath().contains("logEntryAdded"),
430+
httpRequest -> new HttpRequest(HttpMethod.DELETE, page));
431+
432+
((RemoteWebDriver) driver)
433+
.network()
434+
.addRequestHandler(
435+
uri -> uri.getPath().contains("hello"),
436+
httpRequest -> new HttpRequest(HttpMethod.HEAD, page));
437+
438+
((RemoteWebDriver) driver).network().clearRequestHandlers();
439+
440+
driver.get(page);
441+
442+
assertThat(driver.findElement(By.tagName("h1")).getText()).isEqualTo("Long entry added events");
443+
}
166444
}

Diff for: ‎java/test/org/openqa/selenium/bidi/network/NetworkEventsTest.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ void canListenToResponseStartedEvent()
7777
assertThat(response.getRequest().getUrl()).isNotNull();
7878
assertThat(response.getResponseData().getHeaders().size()).isGreaterThanOrEqualTo(1);
7979
assertThat(response.getResponseData().getUrl()).contains("/bidi/logEntryAdded.html");
80-
assertThat(response.getResponseData().getStatus()).isEqualTo(200L);
80+
assertThat(response.getResponseData().getStatus()).isEqualTo(200);
8181
}
8282
}
8383

@@ -100,7 +100,7 @@ void canListenToResponseCompletedEvent()
100100
assertThat(response.getRequest().getUrl()).isNotNull();
101101
assertThat(response.getResponseData().getHeaders().size()).isGreaterThanOrEqualTo(1);
102102
assertThat(response.getResponseData().getUrl()).contains("/bidi/logEntryAdded.html");
103-
assertThat(response.getResponseData().getStatus()).isEqualTo(200L);
103+
assertThat(response.getResponseData().getStatus()).isEqualTo(200);
104104
}
105105
}
106106

@@ -147,7 +147,7 @@ void canListenToOnAuthRequiredEvent()
147147
assertThat(response.getRequest().getUrl()).isNotNull();
148148
assertThat(response.getResponseData().getHeaders().size()).isGreaterThanOrEqualTo(1);
149149
assertThat(response.getResponseData().getUrl()).contains("basicAuth");
150-
assertThat(response.getResponseData().getStatus()).isEqualTo(401L);
150+
assertThat(response.getResponseData().getStatus()).isEqualTo(401);
151151
}
152152
}
153153

0 commit comments

Comments
 (0)
Please sign in to comment.