diff --git a/java/src/org/openqa/selenium/bidi/Command.java b/java/src/org/openqa/selenium/bidi/Command.java index 8a120a3369cf7..a2e141a11792f 100644 --- a/java/src/org/openqa/selenium/bidi/Command.java +++ b/java/src/org/openqa/selenium/bidi/Command.java @@ -18,6 +18,8 @@ package org.openqa.selenium.bidi; import java.lang.reflect.Type; +import java.util.Collections; +import java.util.HashMap; import java.util.Map; import java.util.function.Function; import org.openqa.selenium.internal.Require; @@ -48,7 +50,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 = + Collections.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/emulation/AbstractOverrideParameters.java b/java/src/org/openqa/selenium/bidi/emulation/AbstractOverrideParameters.java new file mode 100644 index 0000000000000..e9ed75ac2c4a0 --- /dev/null +++ b/java/src/org/openqa/selenium/bidi/emulation/AbstractOverrideParameters.java @@ -0,0 +1,59 @@ +// 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.bidi.emulation; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public abstract class AbstractOverrideParameters implements OverrideParameters { + protected final Map map = new HashMap<>(); + + @Override + public OverrideParameters contexts(List contexts) { + if (contexts == null || contexts.isEmpty()) { + throw new IllegalArgumentException("Contexts cannot be null or empty"); + } + if (map.containsKey("userContexts")) { + throw new IllegalArgumentException("Cannot specify both contexts and userContexts"); + } + map.put("contexts", contexts); + return this; + } + + @Override + public OverrideParameters userContexts(List userContexts) { + if (userContexts == null || userContexts.isEmpty()) { + throw new IllegalArgumentException("User contexts cannot be null or empty"); + } + if (map.containsKey("contexts")) { + throw new IllegalArgumentException("Cannot specify both contexts and userContexts"); + } + map.put("userContexts", userContexts); + return this; + } + + @Override + public Map toMap() { + // Validate that either contexts or userContexts is set + if (!map.containsKey("contexts") && !map.containsKey("userContexts")) { + throw new IllegalStateException("Must specify either contexts or userContexts"); + } + return new HashMap<>(map); + } +} diff --git a/java/src/org/openqa/selenium/bidi/emulation/Emulation.java b/java/src/org/openqa/selenium/bidi/emulation/Emulation.java index 6aa3fda1b09c2..efc3823a1bf85 100644 --- a/java/src/org/openqa/selenium/bidi/emulation/Emulation.java +++ b/java/src/org/openqa/selenium/bidi/emulation/Emulation.java @@ -43,4 +43,10 @@ public Map setGeolocationOverride(SetGeolocationOverrideParamete return bidi.send( new Command<>("emulation.setGeolocationOverride", parameters.toMap(), Map.class)); } + + public Map setTimezoneOverride(SetTimezoneOverrideParameters parameters) { + Require.nonNull("SetTimezoneOverride parameters", parameters); + + return bidi.send(new Command<>("emulation.setTimezoneOverride", parameters.toMap(), Map.class)); + } } diff --git a/java/src/org/openqa/selenium/bidi/emulation/OverrideParameters.java b/java/src/org/openqa/selenium/bidi/emulation/OverrideParameters.java new file mode 100644 index 0000000000000..115c3e773f4ee --- /dev/null +++ b/java/src/org/openqa/selenium/bidi/emulation/OverrideParameters.java @@ -0,0 +1,29 @@ +// 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.bidi.emulation; + +import java.util.List; +import java.util.Map; + +public interface OverrideParameters { + OverrideParameters contexts(List contexts); + + OverrideParameters userContexts(List userContexts); + + Map toMap(); +} diff --git a/java/src/org/openqa/selenium/bidi/emulation/SetGeolocationOverrideParameters.java b/java/src/org/openqa/selenium/bidi/emulation/SetGeolocationOverrideParameters.java index 8913ea4d49f51..5f05bcbdf6c44 100644 --- a/java/src/org/openqa/selenium/bidi/emulation/SetGeolocationOverrideParameters.java +++ b/java/src/org/openqa/selenium/bidi/emulation/SetGeolocationOverrideParameters.java @@ -17,12 +17,7 @@ package org.openqa.selenium.bidi.emulation; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class SetGeolocationOverrideParameters { - private final Map map = new HashMap<>(); +public class SetGeolocationOverrideParameters extends AbstractOverrideParameters { // Constructor for coordinates - must specify either contexts or userContexts later public SetGeolocationOverrideParameters(GeolocationCoordinates coordinates) { @@ -40,33 +35,15 @@ public SetGeolocationOverrideParameters(GeolocationPositionError error) { map.put("error", error.toMap()); } - public SetGeolocationOverrideParameters contexts(List contexts) { - if (contexts == null || contexts.isEmpty()) { - throw new IllegalArgumentException("Contexts cannot be null or empty"); - } - if (map.containsKey("userContexts")) { - throw new IllegalArgumentException("Cannot specify both contexts and userContexts"); - } - map.put("contexts", contexts); + @Override + public SetGeolocationOverrideParameters contexts(java.util.List contexts) { + super.contexts(contexts); return this; } - public SetGeolocationOverrideParameters userContexts(List userContexts) { - if (userContexts == null || userContexts.isEmpty()) { - throw new IllegalArgumentException("User contexts cannot be null or empty"); - } - if (map.containsKey("contexts")) { - throw new IllegalArgumentException("Cannot specify both contexts and userContexts"); - } - map.put("userContexts", userContexts); + @Override + public SetGeolocationOverrideParameters userContexts(java.util.List userContexts) { + super.userContexts(userContexts); return this; } - - public Map toMap() { - // Validate that either contexts or userContexts is set - if (!map.containsKey("contexts") && !map.containsKey("userContexts")) { - throw new IllegalStateException("Must specify either contexts or userContexts"); - } - return Map.copyOf(map); - } } diff --git a/java/src/org/openqa/selenium/bidi/emulation/SetTimezoneOverrideParameters.java b/java/src/org/openqa/selenium/bidi/emulation/SetTimezoneOverrideParameters.java new file mode 100644 index 0000000000000..d29f9329c995c --- /dev/null +++ b/java/src/org/openqa/selenium/bidi/emulation/SetTimezoneOverrideParameters.java @@ -0,0 +1,37 @@ +// 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.bidi.emulation; + +public class SetTimezoneOverrideParameters extends AbstractOverrideParameters { + + public SetTimezoneOverrideParameters(String timezone) { + map.put("timezone", timezone); + } + + @Override + public SetTimezoneOverrideParameters contexts(java.util.List contexts) { + super.contexts(contexts); + return this; + } + + @Override + public SetTimezoneOverrideParameters userContexts(java.util.List userContexts) { + super.userContexts(userContexts); + return this; + } +} diff --git a/java/test/org/openqa/selenium/bidi/emulation/EmulationTest.java b/java/test/org/openqa/selenium/bidi/emulation/SetGeolocationOverrideTest.java similarity index 99% rename from java/test/org/openqa/selenium/bidi/emulation/EmulationTest.java rename to java/test/org/openqa/selenium/bidi/emulation/SetGeolocationOverrideTest.java index 366f2498c679b..d183c996cb4f9 100644 --- a/java/test/org/openqa/selenium/bidi/emulation/EmulationTest.java +++ b/java/test/org/openqa/selenium/bidi/emulation/SetGeolocationOverrideTest.java @@ -38,7 +38,7 @@ import org.openqa.selenium.testing.NeedsSecureServer; @NeedsSecureServer -class EmulationTest extends JupiterTestBase { +class SetGeolocationOverrideTest extends JupiterTestBase { Object getBrowserGeolocation(WebDriver driver, String userContext, String origin) { JavascriptExecutor executor = (JavascriptExecutor) driver; Permission permission = new Permission(driver); diff --git a/java/test/org/openqa/selenium/bidi/emulation/SetTimezoneOverrideTest.java b/java/test/org/openqa/selenium/bidi/emulation/SetTimezoneOverrideTest.java new file mode 100644 index 0000000000000..206354a1aa07a --- /dev/null +++ b/java/test/org/openqa/selenium/bidi/emulation/SetTimezoneOverrideTest.java @@ -0,0 +1,158 @@ +// 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.bidi.emulation; + +import static org.openqa.selenium.testing.drivers.Browser.FIREFOX; + +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.openqa.selenium.JavascriptExecutor; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WindowType; +import org.openqa.selenium.bidi.browsingcontext.BrowsingContext; +import org.openqa.selenium.bidi.browsingcontext.CreateContextParameters; +import org.openqa.selenium.bidi.browsingcontext.ReadinessState; +import org.openqa.selenium.bidi.module.Browser; +import org.openqa.selenium.testing.Ignore; +import org.openqa.selenium.testing.JupiterTestBase; +import org.openqa.selenium.testing.NeedsFreshDriver; + +public class SetTimezoneOverrideTest extends JupiterTestBase { + + int getExpectedTimezoneOffset(String timezoneId) { + ZoneId zone = ZoneId.of(timezoneId); + ZonedDateTime now = ZonedDateTime.now(zone); + return now.getOffset().getTotalSeconds() + / 60 + * -1; // Negate to match JavaScript getTimezoneOffset behavior + } + + String getTimezoneString(WebDriver driver, String context) { + JavascriptExecutor executor = (JavascriptExecutor) driver; + + driver.switchTo().window(context); + return (String) + executor.executeScript("return Intl.DateTimeFormat().resolvedOptions().timeZone;"); + } + + Number getTimezoneOffset(WebDriver driver, String context) { + JavascriptExecutor executor = (JavascriptExecutor) driver; + + driver.switchTo().window(context); + return (Number) executor.executeScript("return new Date().getTimezoneOffset()"); + } + + @Test + @NeedsFreshDriver + void canSetTimezoneOverrideInContext() { + BrowsingContext context = new BrowsingContext(driver, driver.getWindowHandle()); + String contextId = context.getId(); + + String url = appServer.whereIs("blank.html"); + context.navigate(url, ReadinessState.COMPLETE); + + Emulation emul = new Emulation(driver); + String timezone = "America/Los_Angeles"; + String tzOrg = getTimezoneString(driver, contextId); + emul.setTimezoneOverride( + new SetTimezoneOverrideParameters(timezone).contexts(List.of(contextId))); + + String tzString = getTimezoneString(driver, contextId); + Number tzOffset = getTimezoneOffset(driver, contextId); + + int expectedOffset = getExpectedTimezoneOffset(timezone); + + assert tzString.equals(timezone) + : "Timezone string mismatch: expected " + timezone + ", got " + tzString; + assert tzOffset.intValue() == expectedOffset + : "Timezone offset mismatch: expected " + expectedOffset + ", got " + tzOffset; + + emul.setTimezoneOverride(new SetTimezoneOverrideParameters(null).contexts(List.of(contextId))); + String TzNew = getTimezoneString(driver, contextId); + assert TzNew.equals(tzOrg) : "Timezone reset failed: expected " + tzOrg + ", got " + TzNew; + } + + @Test + @NeedsFreshDriver + void canSetTimeZoneOverrideInUserContext() { + Browser browser = new Browser(driver); + String userContext = browser.createUserContext(); + + BrowsingContext context = + new BrowsingContext( + driver, new CreateContextParameters(WindowType.TAB).userContext(userContext)); + String contextId = context.getId(); + + String url = appServer.whereIs("blank.html"); + context.navigate(url, ReadinessState.COMPLETE); + + Emulation emul = new Emulation(driver); + String timezone = "Europe/London"; + String tzOrg = getTimezoneString(driver, contextId); + emul.setTimezoneOverride( + new SetTimezoneOverrideParameters(timezone).userContexts(List.of(userContext))); + + String tzString = getTimezoneString(driver, contextId); + Number tzOffset = getTimezoneOffset(driver, contextId); + + int expectedOffset = getExpectedTimezoneOffset(timezone); + + assert tzString.equals(timezone) + : "Timezone string mismatch: expected " + timezone + ", got " + tzString; + assert tzOffset.intValue() == expectedOffset + : "Timezone offset mismatch: expected " + expectedOffset + ", got " + tzOffset; + + emul.setTimezoneOverride( + new SetTimezoneOverrideParameters(null).userContexts(List.of(userContext))); + String TzNew = getTimezoneString(driver, contextId); + assert TzNew.equals(tzOrg) : "Timezone reset failed: expected " + tzOrg + ", got " + TzNew; + + context.close(); + browser.removeUserContext(userContext); + } + + @Test + @NeedsFreshDriver + @Ignore(FIREFOX) + void canSetTimezoneOverrideUsingOffset() { + BrowsingContext context = new BrowsingContext(driver, driver.getWindowHandle()); + String contextId = context.getId(); + + String url = appServer.whereIs("blank.html"); + context.navigate(url, ReadinessState.COMPLETE); + + Emulation emul = new Emulation(driver); + String timezone = "+05:30"; + String tzOrg = getTimezoneString(driver, contextId); + + emul.setTimezoneOverride( + new SetTimezoneOverrideParameters(timezone).contexts(List.of(contextId))); + + String tzString = getTimezoneString(driver, contextId); + Number tzOffset = getTimezoneOffset(driver, contextId); + + assert tzOffset.intValue() == -330 : "Expected timezone offset -330, got " + tzOffset; + assert tzString.equals("+05:30") : "Expected timezone '+05:30', got " + tzString; + + emul.setTimezoneOverride(new SetTimezoneOverrideParameters(null).contexts(List.of(contextId))); + String tzNew = getTimezoneString(driver, contextId); + assert tzNew.equals(tzOrg) : "Timezone reset failed: expected " + tzOrg + ", got " + tzNew; + } +}