Skip to content

Commit 48ebf7d

Browse files
authored
[bidi][java] Add dom mutation handler support (#14304)
1 parent 5d1b216 commit 48ebf7d

File tree

7 files changed

+198
-6
lines changed

7 files changed

+198
-6
lines changed

java/src/org/openqa/selenium/bidi/module/Script.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -343,11 +343,11 @@ public void removePreloadScript(String id) {
343343
this.bidi.send(new Command<>("script.removePreloadScript", Map.of("script", id)));
344344
}
345345

346-
public void onMessage(Consumer<Message> consumer) {
346+
public long onMessage(Consumer<Message> consumer) {
347347
if (browsingContextIds.isEmpty()) {
348-
this.bidi.addListener(messageEvent, consumer);
348+
return this.bidi.addListener(messageEvent, consumer);
349349
} else {
350-
this.bidi.addListener(browsingContextIds, messageEvent, consumer);
350+
return this.bidi.addListener(browsingContextIds, messageEvent, consumer);
351351
}
352352
}
353353

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

+8
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ java_library(
4444
name = "api",
4545
srcs = glob(["**/*.java"]),
4646
resources = [
47+
":bidi-mutation-listener",
4748
":get-attribute",
4849
":is-displayed",
4950
],
@@ -59,6 +60,7 @@ java_library(
5960
"//java/src/org/openqa/selenium/bidi",
6061
"//java/src/org/openqa/selenium/bidi/log",
6162
"//java/src/org/openqa/selenium/bidi/module",
63+
"//java/src/org/openqa/selenium/bidi/script",
6264
"//java/src/org/openqa/selenium/concurrent",
6365
"//java/src/org/openqa/selenium/devtools",
6466
"//java/src/org/openqa/selenium/json",
@@ -84,3 +86,9 @@ copy_file(
8486
src = "//javascript/atoms/fragments:is-displayed.js",
8587
out = "isDisplayed.js",
8688
)
89+
90+
copy_file(
91+
name = "bidi-mutation-listener",
92+
src = "//javascript/bidi-support:bidi-mutation-listener.js",
93+
out = "bidi-mutation-listener.js",
94+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
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.remote;
19+
20+
import org.openqa.selenium.WebElement;
21+
22+
public class DomMutation {
23+
24+
private final WebElement element;
25+
private final String attributeName;
26+
private final String currentValue;
27+
private final String oldValue;
28+
29+
public DomMutation(
30+
WebElement element, String attributeName, String currentValue, String oldValue) {
31+
this.element = element;
32+
this.attributeName = attributeName;
33+
this.currentValue = currentValue;
34+
this.oldValue = oldValue;
35+
}
36+
37+
public WebElement getElement() {
38+
return element;
39+
}
40+
41+
public String getAttributeName() {
42+
return attributeName;
43+
}
44+
45+
public String getCurrentValue() {
46+
return currentValue;
47+
}
48+
49+
public String getOldValue() {
50+
return oldValue;
51+
}
52+
}

java/src/org/openqa/selenium/remote/RemoteScript.java

+66
Original file line numberDiff line numberDiff line change
@@ -17,23 +17,41 @@
1717

1818
package org.openqa.selenium.remote;
1919

20+
import static java.nio.charset.StandardCharsets.UTF_8;
21+
import static org.openqa.selenium.json.Json.MAP_TYPE;
22+
23+
import java.io.IOException;
24+
import java.io.InputStream;
25+
import java.util.List;
26+
import java.util.Map;
2027
import java.util.function.Consumer;
2128
import org.openqa.selenium.Beta;
29+
import org.openqa.selenium.By;
2230
import org.openqa.selenium.WebDriver;
31+
import org.openqa.selenium.WebElement;
2332
import org.openqa.selenium.bidi.BiDi;
2433
import org.openqa.selenium.bidi.HasBiDi;
2534
import org.openqa.selenium.bidi.log.ConsoleLogEntry;
2635
import org.openqa.selenium.bidi.log.JavascriptLogEntry;
2736
import org.openqa.selenium.bidi.module.LogInspector;
37+
import org.openqa.selenium.bidi.script.ChannelValue;
38+
import org.openqa.selenium.json.Json;
2839

2940
@Beta
3041
class RemoteScript implements Script {
42+
43+
private static final Json JSON = new Json();
3144
private final BiDi biDi;
3245
private final LogInspector logInspector;
46+
private final org.openqa.selenium.bidi.module.Script script;
47+
48+
private final WebDriver driver;
3349

3450
public RemoteScript(WebDriver driver) {
51+
this.driver = driver;
3552
this.biDi = ((HasBiDi) driver).getBiDi();
3653
this.logInspector = new LogInspector(driver);
54+
this.script = new org.openqa.selenium.bidi.module.Script(driver);
3755
}
3856

3957
@Override
@@ -55,4 +73,52 @@ public long addJavaScriptErrorHandler(Consumer<JavascriptLogEntry> consumer) {
5573
public void removeJavaScriptErrorHandler(long id) {
5674
this.biDi.removeListener(id);
5775
}
76+
77+
@Override
78+
public long addDomMutationHandler(Consumer<DomMutation> consumer) {
79+
String scriptValue;
80+
try (InputStream stream =
81+
RemoteScript.class.getResourceAsStream(
82+
"/org/openqa/selenium/remote/bidi-mutation-listener.js")) {
83+
if (stream == null) {
84+
throw new IllegalStateException("Unable to find helper script");
85+
}
86+
scriptValue = new String(stream.readAllBytes(), UTF_8);
87+
} catch (IOException e) {
88+
throw new IllegalStateException("Unable to read helper script");
89+
}
90+
91+
this.script.addPreloadScript(scriptValue, List.of(new ChannelValue("channel_name")));
92+
93+
return this.script.onMessage(
94+
message -> {
95+
String value = message.getData().getValue().get().toString();
96+
97+
Map<String, Object> values = JSON.toType(value, MAP_TYPE);
98+
String id = (String) values.get("target");
99+
100+
List<WebElement> elements;
101+
102+
synchronized (this) {
103+
elements =
104+
this.driver.findElements(
105+
By.cssSelector(String.format("*[data-__webdriver_id='%s']", id)));
106+
}
107+
108+
if (!elements.isEmpty()) {
109+
DomMutation event =
110+
new DomMutation(
111+
elements.get(0),
112+
String.valueOf(values.get("name")),
113+
String.valueOf(values.get("value")),
114+
String.valueOf(values.get("oldValue")));
115+
consumer.accept(event);
116+
}
117+
});
118+
}
119+
120+
@Override
121+
public void removeDomMutationHandler(long id) {
122+
this.biDi.removeListener(id);
123+
}
58124
}

java/src/org/openqa/selenium/remote/Script.java

+4
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,8 @@ public interface Script {
3232
long addJavaScriptErrorHandler(Consumer<JavascriptLogEntry> consumer);
3333

3434
void removeJavaScriptErrorHandler(long id);
35+
36+
long addDomMutationHandler(Consumer<DomMutation> event);
37+
38+
void removeDomMutationHandler(long id);
3539
}

java/test/org/openqa/selenium/WebScriptTest.java

+62-3
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,17 @@
1717

1818
package org.openqa.selenium;
1919

20+
import static java.util.concurrent.TimeUnit.SECONDS;
2021
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
2122
import static org.assertj.core.api.AssertionsForClassTypes.fail;
23+
import static org.openqa.selenium.support.ui.ExpectedConditions.visibilityOf;
2224

23-
import java.util.concurrent.CompletableFuture;
24-
import java.util.concurrent.ExecutionException;
25-
import java.util.concurrent.TimeUnit;
25+
import java.time.Duration;
26+
import java.util.concurrent.*;
2627
import java.util.concurrent.TimeoutException;
28+
import java.util.concurrent.atomic.AtomicReference;
2729
import java.util.function.Consumer;
30+
import org.assertj.core.api.Assertions;
2831
import org.junit.jupiter.api.AfterEach;
2932
import org.junit.jupiter.api.BeforeEach;
3033
import org.junit.jupiter.api.Test;
@@ -33,7 +36,9 @@
3336
import org.openqa.selenium.bidi.log.LogLevel;
3437
import org.openqa.selenium.environment.webserver.AppServer;
3538
import org.openqa.selenium.environment.webserver.NettyAppServer;
39+
import org.openqa.selenium.remote.DomMutation;
3640
import org.openqa.selenium.remote.RemoteWebDriver;
41+
import org.openqa.selenium.support.ui.WebDriverWait;
3742
import org.openqa.selenium.testing.JupiterTestBase;
3843

3944
class WebScriptTest extends JupiterTestBase {
@@ -186,4 +191,58 @@ void canAddMultipleHandlers() throws ExecutionException, InterruptedException, T
186191
assertThat(logEntry2.getType()).isEqualTo("javascript");
187192
assertThat(logEntry2.getLevel()).isEqualTo(LogLevel.ERROR);
188193
}
194+
195+
@Test
196+
void canAddDomMutationHandler() throws InterruptedException {
197+
AtomicReference<DomMutation> seen = new AtomicReference<>();
198+
CountDownLatch latch = new CountDownLatch(1);
199+
200+
((RemoteWebDriver) driver)
201+
.script()
202+
.addDomMutationHandler(
203+
mutation -> {
204+
seen.set(mutation);
205+
latch.countDown();
206+
});
207+
208+
driver.get(pages.dynamicPage);
209+
210+
WebElement reveal = driver.findElement(By.id("reveal"));
211+
reveal.click();
212+
WebElement revealed = driver.findElement(By.id("revealed"));
213+
214+
new WebDriverWait(driver, Duration.ofSeconds(10)).until(visibilityOf(revealed));
215+
216+
Assertions.assertThat(latch.await(10, SECONDS)).isTrue();
217+
assertThat(seen.get().getAttributeName()).isEqualTo("style");
218+
assertThat(seen.get().getCurrentValue()).isEmpty();
219+
assertThat(seen.get().getOldValue()).isEqualTo("display:none;");
220+
}
221+
222+
@Test
223+
void canRemoveDomMutationHandler() throws InterruptedException {
224+
AtomicReference<DomMutation> seen = new AtomicReference<>();
225+
CountDownLatch latch = new CountDownLatch(1);
226+
227+
long id =
228+
((RemoteWebDriver) driver)
229+
.script()
230+
.addDomMutationHandler(
231+
mutation -> {
232+
seen.set(mutation);
233+
latch.countDown();
234+
});
235+
236+
driver.get(pages.dynamicPage);
237+
238+
((RemoteWebDriver) driver).script().removeDomMutationHandler(id);
239+
240+
WebElement reveal = driver.findElement(By.id("reveal"));
241+
reveal.click();
242+
WebElement revealed = driver.findElement(By.id("revealed"));
243+
244+
new WebDriverWait(driver, Duration.ofSeconds(10)).until(visibilityOf(revealed));
245+
246+
Assertions.assertThat(latch.await(10, SECONDS)).isFalse();
247+
}
189248
}

javascript/bidi-support/BUILD.bazel

+3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ package(default_visibility = [
22
"//dotnet/src/webdriver:__pkg__",
33
"//java/src/org/openqa/selenium/bidi:__pkg__",
44
"//java/src/org/openqa/selenium/remote:__pkg__",
5+
"//javascript:__pkg__",
6+
"//javascript:__subpackages__",
57
"//javascript/node/selenium-webdriver:__pkg__",
8+
"//javascript/node/selenium-webdriver/lib/atoms:__subpackages__",
69
])
710

811
exports_files([

0 commit comments

Comments
 (0)