diff --git a/java/src/org/openqa/selenium/bidi/Command.java b/java/src/org/openqa/selenium/bidi/Command.java index 8a120a3369cf7..dc99d5eec1124 100644 --- a/java/src/org/openqa/selenium/bidi/Command.java +++ b/java/src/org/openqa/selenium/bidi/Command.java @@ -17,7 +17,10 @@ package org.openqa.selenium.bidi; +import static java.util.Collections.unmodifiableMap; + import java.lang.reflect.Type; +import java.util.HashMap; import java.util.Map; import java.util.function.Function; import org.openqa.selenium.internal.Require; @@ -48,7 +51,8 @@ public Command( Function mapper, boolean sendsResponse) { this.method = Require.nonNull("Method name", method); - this.params = Map.copyOf(Require.nonNull("Command parameters", params)); + this.params = + unmodifiableMap(new HashMap(Require.nonNull("Command parameters", params))); this.mapper = Require.nonNull("Mapper for result", mapper); this.sendsResponse = sendsResponse; } diff --git a/java/src/org/openqa/selenium/bidi/browsingcontext/BUILD.bazel b/java/src/org/openqa/selenium/bidi/browsingcontext/BUILD.bazel index fbf0948a2d6eb..f613f2b689506 100644 --- a/java/src/org/openqa/selenium/bidi/browsingcontext/BUILD.bazel +++ b/java/src/org/openqa/selenium/bidi/browsingcontext/BUILD.bazel @@ -22,5 +22,6 @@ java_library( "//java/src/org/openqa/selenium/json", "//java/src/org/openqa/selenium/remote/http", artifact("com.google.auto.service:auto-service-annotations"), + "@maven//:org_jspecify_jspecify", ], ) diff --git a/java/src/org/openqa/selenium/bidi/browsingcontext/BrowsingContext.java b/java/src/org/openqa/selenium/bidi/browsingcontext/BrowsingContext.java index 6be01cbefafa0..e72251326dfae 100644 --- a/java/src/org/openqa/selenium/bidi/browsingcontext/BrowsingContext.java +++ b/java/src/org/openqa/selenium/bidi/browsingcontext/BrowsingContext.java @@ -24,6 +24,8 @@ import java.util.List; import java.util.Map; import java.util.function.Function; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WindowType; import org.openqa.selenium.bidi.BiDi; @@ -36,6 +38,7 @@ import org.openqa.selenium.json.TypeToken; import org.openqa.selenium.print.PrintOptions; +@NullMarked public class BrowsingContext { private static final Json JSON = new Json(); @@ -91,6 +94,7 @@ public BrowsingContext(WebDriver driver, String id) { public BrowsingContext(WebDriver driver, WindowType type) { Require.nonNull("WebDriver", driver); + Require.nonNull("WindowType", type); if (!(driver instanceof HasBiDi)) { throw new IllegalArgumentException("WebDriver instance must support BiDi protocol"); @@ -103,6 +107,7 @@ public BrowsingContext(WebDriver driver, WindowType type) { public BrowsingContext(WebDriver driver, CreateContextParameters parameters) { Require.nonNull("WebDriver", driver); + Require.nonNull("CreateContextParameters", parameters); if (!(driver instanceof HasBiDi)) { throw new IllegalArgumentException("WebDriver instance must support BiDi protocol"); @@ -302,31 +307,68 @@ public String captureElementScreenshot(String elementId, String handle) { })); } - public void setViewport(double width, double height) { - Require.positive("Viewport width", width); - Require.positive("Viewport height", height); + public void setViewport(int width, int height) { + setViewport((double) width, (double) height); + } - this.bidi.send( - new Command<>( - "browsingContext.setViewport", - Map.of(CONTEXT, id, "viewport", Map.of("width", width, "height", height)))); + public void setViewport(int width, int height, double devicePixelRatio) { + setViewport((double) width, (double) height, devicePixelRatio); } - public void setViewport(double width, double height, double devicePixelRatio) { - Require.positive("Viewport width", width); - Require.positive("Viewport height", height); - Require.positive("Device pixel ratio.", devicePixelRatio); + /** + * Set viewport size to given width and height (aka "mobile emulation" mode). + * + *

If both {@code width} and {@code height} are null, then resets viewport to the initial size + * (aka "desktop" mode). + * + * @param width null or positive + * @param height null or positive + */ + public void setViewport(@Nullable Double width, @Nullable Double height) { + validate(width, height); - this.bidi.send( - new Command<>( - "browsingContext.setViewport", - Map.of( - CONTEXT, - id, - "viewport", - Map.of("width", width, "height", height), - "devicePixelRatio", - devicePixelRatio))); + Map params = new HashMap<>(); + params.put(CONTEXT, id); + params.put("viewport", width == null ? null : Map.of("width", width, "height", height)); + this.bidi.send(new Command<>("browsingContext.setViewport", params)); + } + + /** + * Set viewport's size and pixel ratio (aka "mobile emulation" mode). + * + *

If both {@code width} and {@code height} are null then resets viewport to the initial size + * (aka "desktop" mode). + * + *

If {@code devicePixelRatio} is null then resets DPR to browser’s default DPR (usually 1.0 on + * desktop). + * + * @param width null or positive + * @param height null or positive + * @param devicePixelRatio null or positive + */ + public void setViewport( + @Nullable Double width, @Nullable Double height, @Nullable Double devicePixelRatio) { + validate(width, height); + validate(devicePixelRatio); + + Map params = new HashMap<>(); + params.put(CONTEXT, id); + params.put("viewport", width == null ? null : Map.of("width", width, "height", height)); + params.put("devicePixelRatio", devicePixelRatio); + this.bidi.send(new Command<>("browsingContext.setViewport", params)); + } + + private void validate(@Nullable Double width, @Nullable Double height) { + if (width != null || height != null) { + Require.positive("Viewport width", width); + Require.positive("Viewport height", height); + } + } + + private void validate(@Nullable Double devicePixelRatio) { + if (devicePixelRatio != null) { + Require.positive("Device pixel ratio.", devicePixelRatio); + } } public void activate() { diff --git a/java/src/org/openqa/selenium/bidi/browsingcontext/BrowsingContextInfo.java b/java/src/org/openqa/selenium/bidi/browsingcontext/BrowsingContextInfo.java index 0414e6284f8ef..1edb9627798bc 100644 --- a/java/src/org/openqa/selenium/bidi/browsingcontext/BrowsingContextInfo.java +++ b/java/src/org/openqa/selenium/bidi/browsingcontext/BrowsingContextInfo.java @@ -134,4 +134,9 @@ public static BrowsingContextInfo fromJson(JsonInput input) { return new BrowsingContextInfo( id, url, children, clientWindow, originalOpener, userContext, parentBrowsingContext); } + + @Override + public String toString() { + return String.format("BrowsingContextInfo(%s %s)", id, url); + } } diff --git a/java/test/org/openqa/selenium/bidi/browsingcontext/BUILD.bazel b/java/test/org/openqa/selenium/bidi/browsingcontext/BUILD.bazel index 71b67efb132f2..27ee17ef974d2 100644 --- a/java/test/org/openqa/selenium/bidi/browsingcontext/BUILD.bazel +++ b/java/test/org/openqa/selenium/bidi/browsingcontext/BUILD.bazel @@ -14,6 +14,7 @@ java_selenium_test_suite( "selenium-remote", ], deps = [ + "//java/src/org/openqa/selenium:core", "//java/src/org/openqa/selenium/bidi", "//java/src/org/openqa/selenium/bidi/browsingcontext", "//java/src/org/openqa/selenium/bidi/log", diff --git a/java/test/org/openqa/selenium/bidi/browsingcontext/BrowsingContextTest.java b/java/test/org/openqa/selenium/bidi/browsingcontext/BrowsingContextTest.java index 7e57600542f47..0ee75e4e5eed0 100644 --- a/java/test/org/openqa/selenium/bidi/browsingcontext/BrowsingContextTest.java +++ b/java/test/org/openqa/selenium/bidi/browsingcontext/BrowsingContextTest.java @@ -17,8 +17,8 @@ package org.openqa.selenium.bidi.browsingcontext; -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -import static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.openqa.selenium.support.ui.ExpectedConditions.alertIsPresent; import static org.openqa.selenium.support.ui.ExpectedConditions.titleIs; import static org.openqa.selenium.support.ui.ExpectedConditions.visibilityOfElementLocated; @@ -26,6 +26,7 @@ import java.util.List; import org.junit.jupiter.api.Test; import org.openqa.selenium.By; +import org.openqa.selenium.Dimension; import org.openqa.selenium.JavascriptExecutor; import org.openqa.selenium.Rectangle; import org.openqa.selenium.WebElement; @@ -136,9 +137,9 @@ void canGetTreeWithAChild() { List contextInfoList = parentWindow.getTree(); - assertThat(contextInfoList.size()).isEqualTo(1); + assertThat(contextInfoList).hasSize(1); BrowsingContextInfo info = contextInfoList.get(0); - assertThat(info.getChildren().size()).isEqualTo(1); + assertThat(info.getChildren()).hasSize(1); assertThat(info.getId()).isEqualTo(referenceContextId); assertThat(info.getChildren().get(0).getUrl()).contains("formPage.html"); } @@ -155,7 +156,7 @@ void canGetTreeWithDepth() { List contextInfoList = parentWindow.getTree(0); - assertThat(contextInfoList.size()).isEqualTo(1); + assertThat(contextInfoList).hasSize(1); BrowsingContextInfo info = contextInfoList.get(0); assertThat(info.getChildren()).isNull(); // since depth is 0 assertThat(info.getId()).isEqualTo(referenceContextId); @@ -176,7 +177,7 @@ void canGetTreeWithRootAndDepth() { List contextInfoList = parentWindow.getTree(referenceContextId, 1); - assertThat(contextInfoList.size()).isEqualTo(1); + assertThat(contextInfoList).hasSize(1); BrowsingContextInfo info = contextInfoList.get(0); assertThat(info.getChildren()).isNotNull(); // since depth is 1 assertThat(info.getId()).isEqualTo(referenceContextId); @@ -199,7 +200,7 @@ void canGetTreeWithRoot() { List contextInfoList = parentWindow.getTree(tab.getId()); - assertThat(contextInfoList.size()).isEqualTo(1); + assertThat(contextInfoList).hasSize(1); BrowsingContextInfo info = contextInfoList.get(0); assertThat(info.getId()).isEqualTo(tab.getId()); assertThat(info.getOriginalOpener()).isNull(); @@ -215,7 +216,7 @@ void canGetAllTopLevelContexts() { List contextInfoList = window1.getTopLevelContexts(); - assertThat(contextInfoList.size()).isEqualTo(2); + assertThat(contextInfoList).hasSize(2); } @Test @@ -226,7 +227,9 @@ void canCloseAWindow() { window2.close(); - assertThatExceptionOfType(BiDiException.class).isThrownBy(window2::getTree); + assertThatThrownBy(window2::getTree) + .isInstanceOf(BiDiException.class) + .hasMessageContaining("not found"); } @Test @@ -237,7 +240,9 @@ void canCloseATab() { tab2.close(); - assertThatExceptionOfType(BiDiException.class).isThrownBy(tab2::getTree); + assertThatThrownBy(tab2::getTree) + .isInstanceOf(BiDiException.class) + .hasMessageContaining("not found"); } @Test @@ -397,7 +402,7 @@ void canCaptureScreenshot() { String screenshot = browsingContext.captureScreenshot(); - assertThat(screenshot.length()).isPositive(); + assertThat(screenshot).isNotEmpty(); } @Test @@ -423,7 +428,7 @@ void canCaptureScreenshotWithAllParameters() { .origin(CaptureScreenshotParameters.Origin.DOCUMENT) .clipRectangle(clipRectangle)); - assertThat(screenshot.length()).isPositive(); + assertThat(screenshot).isNotEmpty(); } @Test @@ -440,7 +445,7 @@ void canCaptureScreenshotOfViewport() { browsingContext.captureBoxScreenshot( elementRectangle.getX(), elementRectangle.getY(), 5, 5); - assertThat(screenshot.length()).isPositive(); + assertThat(screenshot).isNotEmpty(); } @Test @@ -455,46 +460,41 @@ void canCaptureElementScreenshot() { String screenshot = browsingContext.captureElementScreenshot(((RemoteWebElement) element).getId()); - assertThat(screenshot.length()).isPositive(); + assertThat(screenshot).isNotEmpty(); } @Test @NeedsFreshDriver void canSetViewport() { + Dimension initialViewportSize = getViewportSize(); + BrowsingContext browsingContext = new BrowsingContext(driver, driver.getWindowHandle()); driver.get(appServer.whereIs("formPage.html")); browsingContext.setViewport(250, 300); + assertThat(getViewportSize()).isEqualTo(new Dimension(250, 300)); - List newViewportSize = - (List) - ((JavascriptExecutor) driver) - .executeScript("return [window.innerWidth, window.innerHeight];"); - - assertThat(newViewportSize.get(0)).isEqualTo(250); - assertThat(newViewportSize.get(1)).isEqualTo(300); + browsingContext.setViewport(null, null); + assertThat(getViewportSize()).isEqualTo(initialViewportSize); } @Test @NeedsFreshDriver void canSetViewportWithDevicePixelRatio() { + Dimension initialViewportSize = getViewportSize(); + double initialPixelRation = getDevicePixelRatio(); + BrowsingContext browsingContext = new BrowsingContext(driver, driver.getWindowHandle()); driver.get(appServer.whereIs("formPage.html")); - browsingContext.setViewport(250, 300, 5); + browsingContext.setViewport(250, 300, 5.5); - List newViewportSize = - (List) - ((JavascriptExecutor) driver) - .executeScript("return [window.innerWidth, window.innerHeight];"); + assertThat(getViewportSize()).isEqualTo(new Dimension(250, 300)); + assertThat(getDevicePixelRatio()).isEqualTo(5.5); - assertThat(newViewportSize.get(0)).isEqualTo(250); - assertThat(newViewportSize.get(1)).isEqualTo(300); - - Long newDevicePixelRatio = - (Long) ((JavascriptExecutor) driver).executeScript("return window.devicePixelRatio"); - - assertThat(newDevicePixelRatio).isEqualTo(5); + browsingContext.setViewport(null, null, null); + assertThat(getViewportSize()).isEqualTo(initialViewportSize); + assertThat(getDevicePixelRatio()).isEqualTo(initialPixelRation); } @Test @@ -507,7 +507,7 @@ void canPrintPage() { String printPage = browsingContext.print(printOptions); - assertThat(printPage.length()).isPositive(); + assertThat(printPage).isNotEmpty(); // Comparing expected PDF is a hard problem. // As long as we are sending the parameters correctly it should be fine. // Trusting the browsers to do the right thing. @@ -568,7 +568,20 @@ private String promptPage() { "

")); } + private T executeScript(String js) { + return (T) ((JavascriptExecutor) driver).executeScript(js); + } + private boolean getDocumentFocus() { - return (boolean) ((JavascriptExecutor) driver).executeScript("return document.hasFocus();"); + return executeScript("return document.hasFocus();"); + } + + private Dimension getViewportSize() { + List dimensions = executeScript("return [window.innerWidth, window.innerHeight];"); + return new Dimension(dimensions.get(0).intValue(), dimensions.get(1).intValue()); + } + + private double getDevicePixelRatio() { + return ((Number) executeScript("return window.devicePixelRatio")).doubleValue(); } }