Skip to content

Commit 8c97d29

Browse files
asolntsevdiemol
andauthored
make augmentation of HasBiDi/HasDevTools lazy-loaded (#16338)
* make augmentation of HasBiDi/HasDevTools lazy-loaded Otherwise, command `new Augmenter().augment(remoteWebDriver)` fails immediately (even if I don't want to use CDP or BiDi). * Augmenter should only augment (create instance of interfaces). * Augmenter should not perform any other actions (establish CDP connection, establish BiDi connection etc.) * avoid establishing CDP connection in `maybeGet*()` method 1. method `getBiDi`/`getDevTools` should establish a connection, BUT 1. method `maybeGet*()` returns connection only if the connection is already established, but should NOT establish a new connection. It's because method `maybeGet*()` is used from `WebDriver.close()` and `WebDriver.quite()`. At this moment, we don't want a new connection, we only want to close the existing connection. * extract reusable code to Lazy.java Now we have a convenient factory method `lazy` for declaring lazy-initialized values like BiDi or DevTools. This simplifies BiDiProvider and DevToolsProvider code. * add log informing user that CDP/BiDi connection is not established immediately anymore * reformat the code --------- Co-authored-by: Diego Molina <diemol@users.noreply.github.com>
1 parent ef9b910 commit 8c97d29

File tree

9 files changed

+217
-5
lines changed

9 files changed

+217
-5
lines changed

java/src/org/openqa/selenium/bidi/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ java_library(
1414
":bidi",
1515
"//java:auto-service",
1616
"//java/src/org/openqa/selenium:core",
17+
"//java/src/org/openqa/selenium/concurrent",
1718
"//java/src/org/openqa/selenium/remote:api",
1819
"//java/src/org/openqa/selenium/remote/http",
1920
],

java/src/org/openqa/selenium/bidi/BiDiProvider.java

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,17 @@
1717

1818
package org.openqa.selenium.bidi;
1919

20+
import static java.util.logging.Level.INFO;
21+
import static org.openqa.selenium.concurrent.Lazy.lazy;
22+
2023
import com.google.auto.service.AutoService;
2124
import java.net.URI;
2225
import java.net.URISyntaxException;
2326
import java.util.Optional;
2427
import java.util.function.Predicate;
28+
import java.util.logging.Logger;
2529
import org.openqa.selenium.Capabilities;
30+
import org.openqa.selenium.concurrent.Lazy;
2631
import org.openqa.selenium.remote.AugmenterProvider;
2732
import org.openqa.selenium.remote.ExecuteMethod;
2833
import org.openqa.selenium.remote.http.ClientConfig;
@@ -31,6 +36,7 @@
3136
@SuppressWarnings({"rawtypes", "RedundantSuppression"})
3237
@AutoService(AugmenterProvider.class)
3338
public class BiDiProvider implements AugmenterProvider<HasBiDi> {
39+
private static final Logger LOG = Logger.getLogger(BiDiProvider.class.getName());
3440

3541
@Override
3642
public Predicate<Capabilities> isApplicable() {
@@ -44,15 +50,35 @@ public Class<HasBiDi> getDescribedInterface() {
4450

4551
@Override
4652
public HasBiDi getImplementation(Capabilities caps, ExecuteMethod executeMethod) {
53+
final Lazy<BiDi> biDi = lazy(() -> establishBiDiConnection(caps));
54+
55+
LOG.log(
56+
INFO,
57+
"WebDriver augmented with BiDi interface; connection will not be verified until first"
58+
+ " use.");
59+
60+
return new HasBiDi() {
61+
@Override
62+
public Optional<BiDi> maybeGetBiDi() {
63+
return biDi.getIfInitialized();
64+
}
65+
66+
@Override
67+
public BiDi getBiDi() {
68+
return biDi.get();
69+
}
70+
};
71+
}
4772

73+
private BiDi establishBiDiConnection(Capabilities caps) {
4874
URI wsUri = getBiDiUrl(caps).orElseThrow(() -> new BiDiException("BiDi not supported"));
4975

5076
HttpClient.Factory clientFactory = HttpClient.Factory.createDefault();
5177
ClientConfig wsConfig = ClientConfig.defaultConfig().baseUri(wsUri);
5278
HttpClient wsClient = clientFactory.createClient(wsConfig);
5379
Connection connection = new Connection(wsClient, wsUri.toString());
5480

55-
return () -> Optional.of(new BiDi(connection));
81+
return new BiDi(connection);
5682
}
5783

5884
private Optional<URI> getBiDiUrl(Capabilities caps) {

java/src/org/openqa/selenium/concurrent/BUILD.bazel

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,15 @@ java_library(
44
name = "concurrent",
55
srcs = glob(["*.java"]),
66
visibility = [
7+
"//java/src/org/openqa/selenium/bidi:__subpackages__",
8+
"//java/src/org/openqa/selenium/devtools:__subpackages__",
79
"//java/src/org/openqa/selenium/events:__subpackages__",
810
"//java/src/org/openqa/selenium/grid:__subpackages__",
911
"//java/src/org/openqa/selenium/remote:__subpackages__",
12+
"//java/test/org/openqa/selenium/concurrent:__subpackages__",
1013
],
1114
deps = [
1215
"//java/src/org/openqa/selenium:core",
16+
"@maven//:org_jspecify_jspecify",
1317
],
1418
)
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Licensed to the Software Freedom Conservancy (SFC) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The SFC licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package org.openqa.selenium.concurrent;
19+
20+
import static java.util.Objects.requireNonNull;
21+
22+
import java.util.Optional;
23+
import java.util.function.Supplier;
24+
import org.jspecify.annotations.Nullable;
25+
26+
public class Lazy<T> {
27+
28+
@Nullable private volatile T value;
29+
private final Supplier<T> supplier;
30+
31+
private Lazy(Supplier<T> supplier) {
32+
this.supplier = supplier;
33+
}
34+
35+
public Optional<T> getIfInitialized() {
36+
return Optional.ofNullable(value);
37+
}
38+
39+
public T get() {
40+
if (value == null) {
41+
synchronized (this) {
42+
if (value == null) {
43+
value = supplier.get();
44+
}
45+
}
46+
}
47+
return requireNonNull(value);
48+
}
49+
50+
public static <T> Lazy<T> lazy(Supplier<T> supplier) {
51+
return new Lazy<>(supplier);
52+
}
53+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Licensed to the Software Freedom Conservancy (SFC) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The SFC licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
@NullMarked
19+
package org.openqa.selenium.concurrent;
20+
21+
import org.jspecify.annotations.NullMarked;

java/src/org/openqa/selenium/devtools/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ java_library(
4646
":devtools",
4747
"//java:auto-service",
4848
"//java/src/org/openqa/selenium:core",
49+
"//java/src/org/openqa/selenium/concurrent",
4950
"//java/src/org/openqa/selenium/remote:api",
5051
],
5152
)

java/src/org/openqa/selenium/devtools/DevToolsProvider.java

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,24 @@
1717

1818
package org.openqa.selenium.devtools;
1919

20+
import static java.util.logging.Level.INFO;
21+
import static org.openqa.selenium.concurrent.Lazy.lazy;
22+
2023
import com.google.auto.service.AutoService;
2124
import java.net.URI;
2225
import java.util.Optional;
2326
import java.util.function.Predicate;
27+
import java.util.logging.Logger;
2428
import org.openqa.selenium.Capabilities;
29+
import org.openqa.selenium.concurrent.Lazy;
2530
import org.openqa.selenium.devtools.noop.NoOpCdpInfo;
2631
import org.openqa.selenium.remote.AugmenterProvider;
2732
import org.openqa.selenium.remote.ExecuteMethod;
2833

2934
@SuppressWarnings({"rawtypes", "RedundantSuppression"})
3035
@AutoService(AugmenterProvider.class)
3136
public class DevToolsProvider implements AugmenterProvider<HasDevTools> {
37+
private static final Logger LOG = Logger.getLogger(DevToolsProvider.class.getName());
3238

3339
@Override
3440
public Predicate<Capabilities> isApplicable() {
@@ -42,14 +48,34 @@ public Class<HasDevTools> getDescribedInterface() {
4248

4349
@Override
4450
public HasDevTools getImplementation(Capabilities caps, ExecuteMethod executeMethod) {
51+
final Lazy<DevTools> devTools = lazy(() -> establishDevToolsConnection(caps));
52+
53+
LOG.log(
54+
INFO,
55+
"WebDriver augmented with DevTools interface; connection will not be verified until first"
56+
+ " use.");
57+
58+
return new HasDevTools() {
59+
@Override
60+
public Optional<DevTools> maybeGetDevTools() {
61+
return devTools.getIfInitialized();
62+
}
63+
64+
@Override
65+
public DevTools getDevTools() {
66+
return devTools.get();
67+
}
68+
};
69+
}
70+
71+
private DevTools establishDevToolsConnection(Capabilities caps) {
4572
Object cdpVersion = caps.getCapability("se:cdpVersion");
4673
String version = cdpVersion instanceof String ? (String) cdpVersion : caps.getBrowserVersion();
4774

4875
CdpInfo info = new CdpVersionFinder().match(version).orElseGet(NoOpCdpInfo::new);
49-
Optional<DevTools> devTools =
50-
SeleniumCdpConnection.create(caps).map(conn -> new DevTools(info::getDomains, conn));
51-
52-
return () -> devTools;
76+
return SeleniumCdpConnection.create(caps)
77+
.map(conn -> new DevTools(info::getDomains, conn))
78+
.orElseThrow(() -> new DevToolsException("Unable to create DevTools connection"));
5379
}
5480

5581
private String getCdpUrl(Capabilities caps) {
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
load("@rules_jvm_external//:defs.bzl", "artifact")
2+
load("//java:defs.bzl", "JUNIT5_DEPS", "java_test_suite")
3+
4+
java_test_suite(
5+
name = "SmallTests",
6+
size = "small",
7+
srcs = glob(["*Test.java"]),
8+
deps = [
9+
"//java/src/org/openqa/selenium/concurrent",
10+
"//java/test/org/openqa/selenium/testing:annotations",
11+
artifact("org.junit.jupiter:junit-jupiter-api"),
12+
artifact("org.assertj:assertj-core"),
13+
] + JUNIT5_DEPS,
14+
)
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Licensed to the Software Freedom Conservancy (SFC) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The SFC licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package org.openqa.selenium.concurrent;
19+
20+
import static org.assertj.core.api.Assertions.assertThat;
21+
22+
import java.util.concurrent.atomic.AtomicInteger;
23+
import org.junit.jupiter.api.Tag;
24+
import org.junit.jupiter.api.Test;
25+
26+
@Tag("UnitTests")
27+
public class LazyTest {
28+
private final AtomicInteger counter = new AtomicInteger(0);
29+
30+
@Test
31+
void trivialCase() {
32+
Lazy expression = Lazy.lazy(() -> "constant");
33+
assertThat(expression.get()).isEqualTo("constant");
34+
assertThat(expression.getIfInitialized()).contains("constant");
35+
}
36+
37+
@Test
38+
void getIfInitialized_returnsNothing_ifNotInitializedYet() {
39+
Lazy expression = Lazy.lazy(() -> "value#" + counter.incrementAndGet());
40+
assertThat(expression.getIfInitialized()).isEmpty();
41+
}
42+
43+
@Test
44+
void lazyEvaluatedExpression() {
45+
Lazy expression = Lazy.lazy(() -> "value#" + counter.incrementAndGet());
46+
assertThat(expression.get()).isEqualTo("value#1");
47+
assertThat(expression.get()).isEqualTo("value#1");
48+
assertThat(expression.getIfInitialized()).contains("value#1");
49+
assertThat(expression.getIfInitialized()).contains("value#1");
50+
}
51+
52+
@Test
53+
void differentLazyInstances_produce_differentValues() {
54+
Lazy expression1 = Lazy.lazy(() -> "one#" + counter.incrementAndGet());
55+
Lazy expression2 = Lazy.lazy(() -> "two#" + counter.incrementAndGet());
56+
assertThat(expression1.get()).isEqualTo("one#1");
57+
assertThat(expression1.getIfInitialized()).contains("one#1");
58+
assertThat(expression2.getIfInitialized()).isEmpty();
59+
60+
assertThat(expression2.get()).isEqualTo("two#2");
61+
assertThat(expression2.getIfInitialized()).contains("two#2");
62+
63+
assertThat(expression1.get()).isEqualTo("one#1");
64+
assertThat(expression2.get()).isEqualTo("two#2");
65+
}
66+
}

0 commit comments

Comments
 (0)