Skip to content

Commit

Permalink
[bidi][java] Add dom mutation handler support (SeleniumHQ#14304)
Browse files Browse the repository at this point in the history
  • Loading branch information
pujagani authored Jul 25, 2024
1 parent 5d1b216 commit 48ebf7d
Show file tree
Hide file tree
Showing 7 changed files with 198 additions and 6 deletions.
6 changes: 3 additions & 3 deletions java/src/org/openqa/selenium/bidi/module/Script.java
Original file line number Diff line number Diff line change
Expand Up @@ -343,11 +343,11 @@ public void removePreloadScript(String id) {
this.bidi.send(new Command<>("script.removePreloadScript", Map.of("script", id)));
}

public void onMessage(Consumer<Message> consumer) {
public long onMessage(Consumer<Message> consumer) {
if (browsingContextIds.isEmpty()) {
this.bidi.addListener(messageEvent, consumer);
return this.bidi.addListener(messageEvent, consumer);
} else {
this.bidi.addListener(browsingContextIds, messageEvent, consumer);
return this.bidi.addListener(browsingContextIds, messageEvent, consumer);
}
}

Expand Down
8 changes: 8 additions & 0 deletions java/src/org/openqa/selenium/remote/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ java_library(
name = "api",
srcs = glob(["**/*.java"]),
resources = [
":bidi-mutation-listener",
":get-attribute",
":is-displayed",
],
Expand All @@ -59,6 +60,7 @@ java_library(
"//java/src/org/openqa/selenium/bidi",
"//java/src/org/openqa/selenium/bidi/log",
"//java/src/org/openqa/selenium/bidi/module",
"//java/src/org/openqa/selenium/bidi/script",
"//java/src/org/openqa/selenium/concurrent",
"//java/src/org/openqa/selenium/devtools",
"//java/src/org/openqa/selenium/json",
Expand All @@ -84,3 +86,9 @@ copy_file(
src = "//javascript/atoms/fragments:is-displayed.js",
out = "isDisplayed.js",
)

copy_file(
name = "bidi-mutation-listener",
src = "//javascript/bidi-support:bidi-mutation-listener.js",
out = "bidi-mutation-listener.js",
)
52 changes: 52 additions & 0 deletions java/src/org/openqa/selenium/remote/DomMutation.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The SFC licenses this file
// to you 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 org.openqa.selenium.remote;

import org.openqa.selenium.WebElement;

public class DomMutation {

private final WebElement element;
private final String attributeName;
private final String currentValue;
private final String oldValue;

public DomMutation(
WebElement element, String attributeName, String currentValue, String oldValue) {
this.element = element;
this.attributeName = attributeName;
this.currentValue = currentValue;
this.oldValue = oldValue;
}

public WebElement getElement() {
return element;
}

public String getAttributeName() {
return attributeName;
}

public String getCurrentValue() {
return currentValue;
}

public String getOldValue() {
return oldValue;
}
}
66 changes: 66 additions & 0 deletions java/src/org/openqa/selenium/remote/RemoteScript.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,41 @@

package org.openqa.selenium.remote;

import static java.nio.charset.StandardCharsets.UTF_8;
import static org.openqa.selenium.json.Json.MAP_TYPE;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import org.openqa.selenium.Beta;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.bidi.BiDi;
import org.openqa.selenium.bidi.HasBiDi;
import org.openqa.selenium.bidi.log.ConsoleLogEntry;
import org.openqa.selenium.bidi.log.JavascriptLogEntry;
import org.openqa.selenium.bidi.module.LogInspector;
import org.openqa.selenium.bidi.script.ChannelValue;
import org.openqa.selenium.json.Json;

@Beta
class RemoteScript implements Script {

private static final Json JSON = new Json();
private final BiDi biDi;
private final LogInspector logInspector;
private final org.openqa.selenium.bidi.module.Script script;

private final WebDriver driver;

public RemoteScript(WebDriver driver) {
this.driver = driver;
this.biDi = ((HasBiDi) driver).getBiDi();
this.logInspector = new LogInspector(driver);
this.script = new org.openqa.selenium.bidi.module.Script(driver);
}

@Override
Expand All @@ -55,4 +73,52 @@ public long addJavaScriptErrorHandler(Consumer<JavascriptLogEntry> consumer) {
public void removeJavaScriptErrorHandler(long id) {
this.biDi.removeListener(id);
}

@Override
public long addDomMutationHandler(Consumer<DomMutation> consumer) {
String scriptValue;
try (InputStream stream =
RemoteScript.class.getResourceAsStream(
"/org/openqa/selenium/remote/bidi-mutation-listener.js")) {
if (stream == null) {
throw new IllegalStateException("Unable to find helper script");
}
scriptValue = new String(stream.readAllBytes(), UTF_8);
} catch (IOException e) {
throw new IllegalStateException("Unable to read helper script");
}

this.script.addPreloadScript(scriptValue, List.of(new ChannelValue("channel_name")));

return this.script.onMessage(
message -> {
String value = message.getData().getValue().get().toString();

Map<String, Object> values = JSON.toType(value, MAP_TYPE);
String id = (String) values.get("target");

List<WebElement> elements;

synchronized (this) {
elements =
this.driver.findElements(
By.cssSelector(String.format("*[data-__webdriver_id='%s']", id)));
}

if (!elements.isEmpty()) {
DomMutation event =
new DomMutation(
elements.get(0),
String.valueOf(values.get("name")),
String.valueOf(values.get("value")),
String.valueOf(values.get("oldValue")));
consumer.accept(event);
}
});
}

@Override
public void removeDomMutationHandler(long id) {
this.biDi.removeListener(id);
}
}
4 changes: 4 additions & 0 deletions java/src/org/openqa/selenium/remote/Script.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,8 @@ public interface Script {
long addJavaScriptErrorHandler(Consumer<JavascriptLogEntry> consumer);

void removeJavaScriptErrorHandler(long id);

long addDomMutationHandler(Consumer<DomMutation> event);

void removeDomMutationHandler(long id);
}
65 changes: 62 additions & 3 deletions java/test/org/openqa/selenium/WebScriptTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,17 @@

package org.openqa.selenium;

import static java.util.concurrent.TimeUnit.SECONDS;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.assertj.core.api.AssertionsForClassTypes.fail;
import static org.openqa.selenium.support.ui.ExpectedConditions.visibilityOf;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.time.Duration;
import java.util.concurrent.*;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
Expand All @@ -33,7 +36,9 @@
import org.openqa.selenium.bidi.log.LogLevel;
import org.openqa.selenium.environment.webserver.AppServer;
import org.openqa.selenium.environment.webserver.NettyAppServer;
import org.openqa.selenium.remote.DomMutation;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.openqa.selenium.testing.JupiterTestBase;

class WebScriptTest extends JupiterTestBase {
Expand Down Expand Up @@ -186,4 +191,58 @@ void canAddMultipleHandlers() throws ExecutionException, InterruptedException, T
assertThat(logEntry2.getType()).isEqualTo("javascript");
assertThat(logEntry2.getLevel()).isEqualTo(LogLevel.ERROR);
}

@Test
void canAddDomMutationHandler() throws InterruptedException {
AtomicReference<DomMutation> seen = new AtomicReference<>();
CountDownLatch latch = new CountDownLatch(1);

((RemoteWebDriver) driver)
.script()
.addDomMutationHandler(
mutation -> {
seen.set(mutation);
latch.countDown();
});

driver.get(pages.dynamicPage);

WebElement reveal = driver.findElement(By.id("reveal"));
reveal.click();
WebElement revealed = driver.findElement(By.id("revealed"));

new WebDriverWait(driver, Duration.ofSeconds(10)).until(visibilityOf(revealed));

Assertions.assertThat(latch.await(10, SECONDS)).isTrue();
assertThat(seen.get().getAttributeName()).isEqualTo("style");
assertThat(seen.get().getCurrentValue()).isEmpty();
assertThat(seen.get().getOldValue()).isEqualTo("display:none;");
}

@Test
void canRemoveDomMutationHandler() throws InterruptedException {
AtomicReference<DomMutation> seen = new AtomicReference<>();
CountDownLatch latch = new CountDownLatch(1);

long id =
((RemoteWebDriver) driver)
.script()
.addDomMutationHandler(
mutation -> {
seen.set(mutation);
latch.countDown();
});

driver.get(pages.dynamicPage);

((RemoteWebDriver) driver).script().removeDomMutationHandler(id);

WebElement reveal = driver.findElement(By.id("reveal"));
reveal.click();
WebElement revealed = driver.findElement(By.id("revealed"));

new WebDriverWait(driver, Duration.ofSeconds(10)).until(visibilityOf(revealed));

Assertions.assertThat(latch.await(10, SECONDS)).isFalse();
}
}
3 changes: 3 additions & 0 deletions javascript/bidi-support/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ package(default_visibility = [
"//dotnet/src/webdriver:__pkg__",
"//java/src/org/openqa/selenium/bidi:__pkg__",
"//java/src/org/openqa/selenium/remote:__pkg__",
"//javascript:__pkg__",
"//javascript:__subpackages__",
"//javascript/node/selenium-webdriver:__pkg__",
"//javascript/node/selenium-webdriver/lib/atoms:__subpackages__",
])

exports_files([
Expand Down

0 comments on commit 48ebf7d

Please sign in to comment.