From b18f723babe12c2c244632f19934d098a25dd325 Mon Sep 17 00:00:00 2001 From: Puja Jagani Date: Thu, 16 Nov 2023 12:55:34 +0530 Subject: [PATCH 01/15] [java] Port navigate and print commands to BiDi --- .../bidi/browsingcontext/ReadinessState.java | 34 +++- .../selenium/remote/BiDiCommandExecutor.java | 159 ++++++++++++++++++ .../org/openqa/selenium/remote/Delegator.java | 67 ++++++++ .../selenium/remote/RemoteWebDriver.java | 9 +- .../remote/service/DriverCommandExecutor.java | 3 +- .../selenium/bidi/PortClassicToBiDiTest.java | 72 ++++++++ 6 files changed, 334 insertions(+), 10 deletions(-) create mode 100644 java/src/org/openqa/selenium/remote/BiDiCommandExecutor.java create mode 100644 java/src/org/openqa/selenium/remote/Delegator.java create mode 100644 java/test/org/openqa/selenium/bidi/PortClassicToBiDiTest.java diff --git a/java/src/org/openqa/selenium/bidi/browsingcontext/ReadinessState.java b/java/src/org/openqa/selenium/bidi/browsingcontext/ReadinessState.java index c6bc0a0501417..473b2f39efd00 100644 --- a/java/src/org/openqa/selenium/bidi/browsingcontext/ReadinessState.java +++ b/java/src/org/openqa/selenium/bidi/browsingcontext/ReadinessState.java @@ -17,19 +17,39 @@ package org.openqa.selenium.bidi.browsingcontext; +import org.openqa.selenium.PageLoadStrategy; + public enum ReadinessState { - NONE("none"), - INTERACTIVE("interactive"), - COMPLETE("complete"); + NONE("none", "none"), + INTERACTIVE("interactive", "eager"), + COMPLETE("complete", "normal"); + + private final String readinessState; + + private final String pageLoadStrategy; - private final String text; + ReadinessState(String readinessState, String pageLoadStrategy) { + this.readinessState = readinessState; + this.pageLoadStrategy = pageLoadStrategy; + } + + public String getPageLoadStrategy() { + return pageLoadStrategy; + } - ReadinessState(String text) { - this.text = text; + public static ReadinessState getReadinessState(String pageLoadStrategy) { + if (pageLoadStrategy != null) { + for (ReadinessState b : ReadinessState.values()) { + if (pageLoadStrategy.equalsIgnoreCase(b.pageLoadStrategy)) { + return b; + } + } + } + return null; } @Override public String toString() { - return String.valueOf(text); + return String.valueOf(readinessState); } } diff --git a/java/src/org/openqa/selenium/remote/BiDiCommandExecutor.java b/java/src/org/openqa/selenium/remote/BiDiCommandExecutor.java new file mode 100644 index 0000000000000..db57222b1bf6d --- /dev/null +++ b/java/src/org/openqa/selenium/remote/BiDiCommandExecutor.java @@ -0,0 +1,159 @@ +// 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 static org.openqa.selenium.remote.DriverCommand.GET; +import static org.openqa.selenium.remote.DriverCommand.PRINT_PAGE; + +import java.io.IOException; +import java.io.StringReader; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; + +import org.openqa.selenium.UnsupportedCommandException; +import org.openqa.selenium.bidi.Network; +import org.openqa.selenium.bidi.Script; +import org.openqa.selenium.bidi.browsingcontext.BrowsingContext; +import org.openqa.selenium.bidi.browsingcontext.ReadinessState; +import org.openqa.selenium.json.Json; +import org.openqa.selenium.json.JsonInput; +import org.openqa.selenium.json.TypeToken; +import org.openqa.selenium.print.PageMargin; +import org.openqa.selenium.print.PageSize; +import org.openqa.selenium.print.PrintOptions; + +public class BiDiCommandExecutor implements CommandExecutor { + + private static Json JSON = new Json(); + + private final RemoteWebDriver driver; + + private Script script; + + private Network network; + + private AtomicReference currentContext = new AtomicReference<>(); + + // Each browsing context has an associated id, maintains state + // We will need to maintain a map of all the browsingContext to run further commands + // Switching between tabs etc might be tricky + private final Map browsingContextMap = new HashMap<>(); + + public BiDiCommandExecutor(RemoteWebDriver driver) { + this.driver = driver; + init(this.driver); + } + + private void init(RemoteWebDriver driver) { + // Add other modules + } + + @Override + public Response execute(Command command) throws IOException { + this.script = new Script(driver); + this.network = new Network(driver); + BrowsingContext parentContext = new BrowsingContext(driver, driver.getWindowHandle()); + browsingContextMap.put(parentContext.getId(), parentContext); + currentContext.set(parentContext); + + Response response = new Response(); + + switch (command.getName()) { + case GET: + String pageLoadStrategy = + (String) driver.getCapabilities().getCapability("pageLoadStrategy"); + currentContext + .get() + .navigate( + (String) command.getParameters().get("url"), + ReadinessState.getReadinessState(pageLoadStrategy)); + break; + + case PRINT_PAGE: + try (StringReader reader = new StringReader(JSON.toJson(command.getParameters())); + JsonInput input = JSON.newInput(reader)) { + PrintOptions printOptions = new PrintOptions(); + + input.beginObject(); + while (input.hasNext()) { + switch (input.nextName()) { + case "page": + Map map = input.read(String.class); + if (map.size() != 0) { + printOptions.setPageSize( + new PageSize((double) map.get("height"), (double) map.get("width"))); + } + break; + + case "orientation": + String orientation = input.read(String.class); + if (orientation.equals("portrait")) { + printOptions.setOrientation(PrintOptions.Orientation.PORTRAIT); + } else { + printOptions.setOrientation(PrintOptions.Orientation.LANDSCAPE); + } + break; + + case "scale": + printOptions.setScale(input.read(Double.class)); + break; + + case "shrinkToFit": + printOptions.setShrinkToFit(input.read(Boolean.class)); + break; + + case "background": + printOptions.setBackground(input.read(Boolean.class)); + break; + + case "pageRanges": + printOptions.setPageRanges(input.read(new TypeToken() {}.getType())); + break; + + case "margin": + Map marginMap = input.read(Map.class); + if (marginMap.size() != 0) { + printOptions.setPageMargin( + new PageMargin( + (double) marginMap.get("top"), + (double) marginMap.get("bottom"), + (double) marginMap.get("left"), + (double) marginMap.get("right"))); + } + break; + + default: + input.skipValue(); + break; + } + } + + input.endObject(); + String result = currentContext.get().print(printOptions); + response.setValue(result); + } + break; + + default: + throw new UnsupportedCommandException(); + } + + return response; + } +} diff --git a/java/src/org/openqa/selenium/remote/Delegator.java b/java/src/org/openqa/selenium/remote/Delegator.java new file mode 100644 index 0000000000000..74f5d93a95a50 --- /dev/null +++ b/java/src/org/openqa/selenium/remote/Delegator.java @@ -0,0 +1,67 @@ +// 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 static java.util.Collections.emptyMap; + +import java.io.IOException; +import java.net.URL; +import java.util.Map; +import org.openqa.selenium.internal.Require; +import org.openqa.selenium.remote.http.ClientConfig; +import org.openqa.selenium.remote.http.HttpClient; + +public class Delegator extends HttpCommandExecutor implements CommandExecutor { + + private BiDiCommandExecutor bidiCommandExecutor = null; + + public Delegator(ClientConfig config) { + super( + emptyMap(), + Require.nonNull("HTTP client configuration", config), + getDefaultClientFactory()); + } + + public Delegator( + Map additionalCommands, + ClientConfig config, + HttpClient.Factory httpClientFactory) { + super(additionalCommands, config, httpClientFactory); + } + + public Delegator( + Map additionalCommands, URL addressOfRemoteServer, ClientConfig config) { + super( + additionalCommands, + config.baseUrl(Require.nonNull("Server URL", addressOfRemoteServer)), + getDefaultClientFactory()); + } + + public void setBidiCommandExecutor(BiDiCommandExecutor bidiCommandExecutor) { + this.bidiCommandExecutor = bidiCommandExecutor; + } + + @Override + public Response execute(Command command) throws IOException { + if (bidiCommandExecutor != null && command.getName().equals(DriverCommand.GET)) { + return bidiCommandExecutor.execute(command); + } + + return super.execute(command); + } +} diff --git a/java/src/org/openqa/selenium/remote/RemoteWebDriver.java b/java/src/org/openqa/selenium/remote/RemoteWebDriver.java index 3462231c7f17b..b5394a528ee79 100644 --- a/java/src/org/openqa/selenium/remote/RemoteWebDriver.java +++ b/java/src/org/openqa/selenium/remote/RemoteWebDriver.java @@ -183,13 +183,13 @@ private static CommandExecutor createExecutor(URL remoteAddress, boolean enableT if (enableTracing) { Tracer tracer = OpenTelemetryTracer.getInstance(); CommandExecutor executor = - new HttpCommandExecutor( + new Delegator( Collections.emptyMap(), config, new TracedHttpClient.Factory(tracer, HttpClient.Factory.createDefault())); return new TracedCommandExecutor(executor, tracer); } else { - return new HttpCommandExecutor(config); + return new Delegator(config); } } @@ -251,6 +251,11 @@ protected void startSession(Capabilities capabilities) { @SuppressWarnings("unchecked") Map rawCapabilities = (Map) responseValue; + if (rawCapabilities.containsKey("webSocketUrl") && getCommandExecutor() instanceof Delegator) { + if (rawCapabilities.get("webSocketUrl") instanceof String) { + ((Delegator) (getCommandExecutor())).setBidiCommandExecutor(new BiDiCommandExecutor(this)); + } + } MutableCapabilities returnedCapabilities = new MutableCapabilities(rawCapabilities); String platformString = (String) rawCapabilities.get(PLATFORM_NAME); Platform platform; diff --git a/java/src/org/openqa/selenium/remote/service/DriverCommandExecutor.java b/java/src/org/openqa/selenium/remote/service/DriverCommandExecutor.java index 610fbf2589872..789ef107746cd 100644 --- a/java/src/org/openqa/selenium/remote/service/DriverCommandExecutor.java +++ b/java/src/org/openqa/selenium/remote/service/DriverCommandExecutor.java @@ -34,6 +34,7 @@ import org.openqa.selenium.WebDriverException; import org.openqa.selenium.remote.Command; import org.openqa.selenium.remote.CommandInfo; +import org.openqa.selenium.remote.Delegator; import org.openqa.selenium.remote.DriverCommand; import org.openqa.selenium.remote.HttpCommandExecutor; import org.openqa.selenium.remote.Response; @@ -44,7 +45,7 @@ * dies with a single WebDriver session. The service will be restarted upon each new session request * and shutdown after each quit command. */ -public class DriverCommandExecutor extends HttpCommandExecutor implements Closeable { +public class DriverCommandExecutor extends Delegator implements Closeable { private static final String NAME = "Driver Command Executor"; private final DriverService service; diff --git a/java/test/org/openqa/selenium/bidi/PortClassicToBiDiTest.java b/java/test/org/openqa/selenium/bidi/PortClassicToBiDiTest.java new file mode 100644 index 0000000000000..636c719b294b7 --- /dev/null +++ b/java/test/org/openqa/selenium/bidi/PortClassicToBiDiTest.java @@ -0,0 +1,72 @@ +// 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; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.openqa.selenium.testing.Safely.safelyCall; +import static org.openqa.selenium.testing.drivers.Browser.EDGE; +import static org.openqa.selenium.testing.drivers.Browser.IE; +import static org.openqa.selenium.testing.drivers.Browser.SAFARI; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.openqa.selenium.By; +import org.openqa.selenium.bidi.browsingcontext.BrowsingContext; +import org.openqa.selenium.bidi.browsingcontext.NavigationResult; +import org.openqa.selenium.bidi.browsingcontext.ReadinessState; +import org.openqa.selenium.bidi.log.JavascriptLogEntry; +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.testing.JupiterTestBase; +import org.openqa.selenium.testing.NotYetImplemented; + +class PortClassicToBiDiTest extends JupiterTestBase { + + String page; + private AppServer server; + + @BeforeEach + public void setUp() { + server = new NettyAppServer(); + server.start(); + } + + @Test + @NotYetImplemented(SAFARI) + @NotYetImplemented(IE) + @NotYetImplemented(EDGE) + void canNavigate() { + driver.get("http://google.com"); + + assertThat(driver.getTitle()).isEqualTo("Google"); + } + + @AfterEach + public void quitDriver() { + if (driver != null) { + driver.quit(); + } + safelyCall(server::stop); + } +} From 3955815f021e7e4ad9141f6a6c685ea92e6b3667 Mon Sep 17 00:00:00 2001 From: Puja Jagani Date: Thu, 16 Nov 2023 17:40:00 +0530 Subject: [PATCH 02/15] [java] Allow proper initializing of modules --- .../openqa/selenium/bidi/BiDiProvider.java | 10 ++++- .../src/org/openqa/selenium/bidi/HasBiDi.java | 28 +++++++++++++- .../selenium/chromium/ChromiumDriver.java | 10 ++++- .../selenium/firefox/FirefoxDriver.java | 38 +++++++++++-------- .../selenium/remote/BiDiCommandExecutor.java | 10 ++--- .../org/openqa/selenium/remote/Delegator.java | 4 +- .../selenium/remote/RemoteWebDriver.java | 32 +++++++++++++--- 7 files changed, 101 insertions(+), 31 deletions(-) diff --git a/java/src/org/openqa/selenium/bidi/BiDiProvider.java b/java/src/org/openqa/selenium/bidi/BiDiProvider.java index ae1ac58d7f901..bace8782dc8db 100644 --- a/java/src/org/openqa/selenium/bidi/BiDiProvider.java +++ b/java/src/org/openqa/selenium/bidi/BiDiProvider.java @@ -52,7 +52,15 @@ public HasBiDi getImplementation(Capabilities caps, ExecuteMethod executeMethod) HttpClient wsClient = clientFactory.createClient(wsConfig); Connection connection = new Connection(wsClient, wsUri.toString()); - return () -> Optional.of(new BiDi(connection)); + return new HasBiDi() { + @Override + public Optional maybeGetBiDi() { + return Optional.of(new BiDi(connection)); + } + + @Override + public void setBiDi(BiDi bidi) {} + }; } private Optional getBiDiUrl(Capabilities caps) { diff --git a/java/src/org/openqa/selenium/bidi/HasBiDi.java b/java/src/org/openqa/selenium/bidi/HasBiDi.java index fc9c3936ad94b..d13587b75fb2f 100644 --- a/java/src/org/openqa/selenium/bidi/HasBiDi.java +++ b/java/src/org/openqa/selenium/bidi/HasBiDi.java @@ -17,13 +17,39 @@ package org.openqa.selenium.bidi; +import org.openqa.selenium.remote.http.ClientConfig; +import org.openqa.selenium.remote.http.HttpClient; + +import java.net.URI; import java.util.Optional; public interface HasBiDi { + Optional maybeGetBiDi(); + + void setBiDi(BiDi bidi); + default BiDi getBiDi() { return maybeGetBiDi() .orElseThrow(() -> new BiDiException("Unable to create a BiDi connection")); } - Optional maybeGetBiDi(); + default Optional createBiDi(Optional biDiUri) { + if (!biDiUri.isPresent()) { + return Optional.empty(); + } + + URI wsUri = + biDiUri.orElseThrow( + () -> + new BiDiException("This version of Firefox or geckodriver does not support BiDi")); + + HttpClient.Factory clientFactory = HttpClient.Factory.createDefault(); + ClientConfig wsConfig = ClientConfig.defaultConfig().baseUri(wsUri); + HttpClient wsClient = clientFactory.createClient(wsConfig); + + org.openqa.selenium.bidi.Connection biDiConnection = + new org.openqa.selenium.bidi.Connection(wsClient, wsUri.toString()); + + return Optional.of(new BiDi(biDiConnection)); + } } diff --git a/java/src/org/openqa/selenium/chromium/ChromiumDriver.java b/java/src/org/openqa/selenium/chromium/ChromiumDriver.java index 7a87460878313..c951362e09746 100644 --- a/java/src/org/openqa/selenium/chromium/ChromiumDriver.java +++ b/java/src/org/openqa/selenium/chromium/ChromiumDriver.java @@ -104,7 +104,7 @@ public class ChromiumDriver extends RemoteWebDriver private Optional connection; private final Optional devTools; private final Optional biDiUri; - private final Optional biDi; + private Optional biDi; protected HasCasting casting; protected HasCdp cdp; private final Map scriptKeys = new HashMap<>(); @@ -346,7 +346,8 @@ public Optional maybeGetDevTools() { return devTools; } - private Optional createBiDi(Optional biDiUri) { + @Override + public Optional createBiDi(Optional biDiUri) { if (biDiUri.isEmpty()) { return Optional.empty(); } @@ -373,6 +374,11 @@ public Optional maybeGetBiDi() { return biDi; } + @Override + public void setBiDi(BiDi bidi) { + this.biDi = Optional.of(bidi); + } + @Override public List> getCastSinks() { return casting.getCastSinks(); diff --git a/java/src/org/openqa/selenium/firefox/FirefoxDriver.java b/java/src/org/openqa/selenium/firefox/FirefoxDriver.java index 1745564ffdaf8..c802045bff6d5 100644 --- a/java/src/org/openqa/selenium/firefox/FirefoxDriver.java +++ b/java/src/org/openqa/selenium/firefox/FirefoxDriver.java @@ -87,10 +87,11 @@ public class FirefoxDriver extends RemoteWebDriver private final HasFullPageScreenshot fullPageScreenshot; private final HasContext context; private final Optional cdpUri; - private final Optional biDiUri; + private Optional biDiUri = Optional.empty(); private Connection connection; private DevTools devTools; - private final Optional biDi; + + private Optional biDi = Optional.empty(); /** * Creates a new FirefoxDriver using the {@link GeckoDriverService#createDefaultService)} server @@ -192,18 +193,20 @@ private FirefoxDriver( Optional webSocketUrl = Optional.ofNullable((String) capabilities.getCapability("webSocketUrl")); - this.biDiUri = - webSocketUrl.map( - uri -> { - try { - return new URI(uri); - } catch (URISyntaxException e) { - LOG.warning(e.getMessage()); - } - return null; - }); - - this.biDi = createBiDi(biDiUri); + if (!this.biDi.isPresent()) { + this.biDiUri = + webSocketUrl.map( + uri -> { + try { + return new URI(uri); + } catch (URISyntaxException e) { + LOG.warning(e.getMessage()); + } + return null; + }); + + this.biDi = createBiDi(biDiUri); + } this.cdpUri = cdpUri; this.capabilities = @@ -353,7 +356,7 @@ public DevTools getDevTools() { .orElseThrow(() -> new DevToolsException("Unable to initialize CDP connection")); } - private Optional createBiDi(Optional biDiUri) { + public Optional createBiDi(Optional biDiUri) { if (biDiUri.isEmpty()) { return Optional.empty(); } @@ -380,7 +383,6 @@ public Optional maybeGetBiDi() { return biDi; } - @Override public BiDi getBiDi() { if (biDiUri.isEmpty()) { throw new BiDiException( @@ -392,6 +394,10 @@ public BiDi getBiDi() { .orElseThrow(() -> new BiDiException("Unable to initialize Bidi connection")); } + public void setBiDi(BiDi bidi) { + this.biDi = Optional.ofNullable(bidi); + } + @Override public void quit() { super.quit(); diff --git a/java/src/org/openqa/selenium/remote/BiDiCommandExecutor.java b/java/src/org/openqa/selenium/remote/BiDiCommandExecutor.java index db57222b1bf6d..6ad1b3037dfaf 100644 --- a/java/src/org/openqa/selenium/remote/BiDiCommandExecutor.java +++ b/java/src/org/openqa/selenium/remote/BiDiCommandExecutor.java @@ -48,7 +48,7 @@ public class BiDiCommandExecutor implements CommandExecutor { private Network network; - private AtomicReference currentContext = new AtomicReference<>(); + private final AtomicReference currentContext = new AtomicReference<>(); // Each browsing context has an associated id, maintains state // We will need to maintain a map of all the browsingContext to run further commands @@ -62,16 +62,16 @@ public BiDiCommandExecutor(RemoteWebDriver driver) { private void init(RemoteWebDriver driver) { // Add other modules - } - - @Override - public Response execute(Command command) throws IOException { this.script = new Script(driver); this.network = new Network(driver); + BrowsingContext parentContext = new BrowsingContext(driver, driver.getWindowHandle()); browsingContextMap.put(parentContext.getId(), parentContext); currentContext.set(parentContext); + } + @Override + public Response execute(Command command) throws IOException { Response response = new Response(); switch (command.getName()) { diff --git a/java/src/org/openqa/selenium/remote/Delegator.java b/java/src/org/openqa/selenium/remote/Delegator.java index 74f5d93a95a50..b2ba87ad4e715 100644 --- a/java/src/org/openqa/selenium/remote/Delegator.java +++ b/java/src/org/openqa/selenium/remote/Delegator.java @@ -58,7 +58,9 @@ public void setBidiCommandExecutor(BiDiCommandExecutor bidiCommandExecutor) { @Override public Response execute(Command command) throws IOException { - if (bidiCommandExecutor != null && command.getName().equals(DriverCommand.GET)) { + if (bidiCommandExecutor != null + && command.getName().equals(DriverCommand.GET) + && command.getName().equals(DriverCommand.PRINT_PAGE)) { return bidiCommandExecutor.execute(command); } diff --git a/java/src/org/openqa/selenium/remote/RemoteWebDriver.java b/java/src/org/openqa/selenium/remote/RemoteWebDriver.java index b5394a528ee79..0ee2aaa6b2a70 100644 --- a/java/src/org/openqa/selenium/remote/RemoteWebDriver.java +++ b/java/src/org/openqa/selenium/remote/RemoteWebDriver.java @@ -24,6 +24,8 @@ import java.io.IOException; import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; import java.net.URL; import java.nio.file.Path; import java.time.Duration; @@ -36,6 +38,7 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.function.BiFunction; @@ -159,6 +162,30 @@ public RemoteWebDriver(CommandExecutor executor, Capabilities capabilities) { try { startSession(capabilities); + + Optional webSocketUrl = + Optional.ofNullable((String) this.capabilities.getCapability("webSocketUrl")); + + // Firefox returns the capability back as it is if it is not supported + // So we need to check before proceeding + // Add a test to check + if (webSocketUrl.isPresent() && getCommandExecutor() instanceof Delegator) { + Optional biDi = + ((HasBiDi) this) + .createBiDi( + webSocketUrl.map( + uri -> { + try { + return new URI(uri); + } catch (URISyntaxException e) { + LOG.warning(e.getMessage()); + } + return null; + })); + + biDi.ifPresent(connection -> ((HasBiDi) this).setBiDi(connection)); + ((Delegator) (getCommandExecutor())).setBidiCommandExecutor(new BiDiCommandExecutor(this)); + } } catch (RuntimeException e) { try { quit(); @@ -251,11 +278,6 @@ protected void startSession(Capabilities capabilities) { @SuppressWarnings("unchecked") Map rawCapabilities = (Map) responseValue; - if (rawCapabilities.containsKey("webSocketUrl") && getCommandExecutor() instanceof Delegator) { - if (rawCapabilities.get("webSocketUrl") instanceof String) { - ((Delegator) (getCommandExecutor())).setBidiCommandExecutor(new BiDiCommandExecutor(this)); - } - } MutableCapabilities returnedCapabilities = new MutableCapabilities(rawCapabilities); String platformString = (String) rawCapabilities.get(PLATFORM_NAME); Platform platform; From df9a65167e6c38544722968cb14c2b2557e8e5bb Mon Sep 17 00:00:00 2001 From: Puja Jagani Date: Mon, 20 Nov 2023 14:06:49 +0530 Subject: [PATCH 03/15] [java] Fix case in name. Use list of supported commands to delegate. --- .../selenium/remote/BiDiCommandExecutor.java | 130 +++++++++--------- .../org/openqa/selenium/remote/Delegator.java | 33 +++-- .../selenium/remote/RemoteWebDriver.java | 2 +- 3 files changed, 89 insertions(+), 76 deletions(-) diff --git a/java/src/org/openqa/selenium/remote/BiDiCommandExecutor.java b/java/src/org/openqa/selenium/remote/BiDiCommandExecutor.java index 6ad1b3037dfaf..918359fa57837 100644 --- a/java/src/org/openqa/selenium/remote/BiDiCommandExecutor.java +++ b/java/src/org/openqa/selenium/remote/BiDiCommandExecutor.java @@ -61,7 +61,6 @@ public BiDiCommandExecutor(RemoteWebDriver driver) { } private void init(RemoteWebDriver driver) { - // Add other modules this.script = new Script(driver); this.network = new Network(driver); @@ -86,68 +85,8 @@ public Response execute(Command command) throws IOException { break; case PRINT_PAGE: - try (StringReader reader = new StringReader(JSON.toJson(command.getParameters())); - JsonInput input = JSON.newInput(reader)) { - PrintOptions printOptions = new PrintOptions(); - - input.beginObject(); - while (input.hasNext()) { - switch (input.nextName()) { - case "page": - Map map = input.read(String.class); - if (map.size() != 0) { - printOptions.setPageSize( - new PageSize((double) map.get("height"), (double) map.get("width"))); - } - break; - - case "orientation": - String orientation = input.read(String.class); - if (orientation.equals("portrait")) { - printOptions.setOrientation(PrintOptions.Orientation.PORTRAIT); - } else { - printOptions.setOrientation(PrintOptions.Orientation.LANDSCAPE); - } - break; - - case "scale": - printOptions.setScale(input.read(Double.class)); - break; - - case "shrinkToFit": - printOptions.setShrinkToFit(input.read(Boolean.class)); - break; - - case "background": - printOptions.setBackground(input.read(Boolean.class)); - break; - - case "pageRanges": - printOptions.setPageRanges(input.read(new TypeToken() {}.getType())); - break; - - case "margin": - Map marginMap = input.read(Map.class); - if (marginMap.size() != 0) { - printOptions.setPageMargin( - new PageMargin( - (double) marginMap.get("top"), - (double) marginMap.get("bottom"), - (double) marginMap.get("left"), - (double) marginMap.get("right"))); - } - break; - - default: - input.skipValue(); - break; - } - } - - input.endObject(); - String result = currentContext.get().print(printOptions); - response.setValue(result); - } + String result = currentContext.get().print(serializePrintOptions(command)); + response.setValue(result); break; default: @@ -156,4 +95,69 @@ public Response execute(Command command) throws IOException { return response; } + + private PrintOptions serializePrintOptions(Command command) { + try (StringReader reader = new StringReader(JSON.toJson(command.getParameters())); + JsonInput input = JSON.newInput(reader)) { + PrintOptions printOptions = new PrintOptions(); + + input.beginObject(); + while (input.hasNext()) { + switch (input.nextName()) { + case "page": + Map map = input.read(String.class); + if (map.size() != 0) { + printOptions.setPageSize( + new PageSize((double) map.get("height"), (double) map.get("width"))); + } + break; + + case "orientation": + String orientation = input.read(String.class); + if (orientation.equals("portrait")) { + printOptions.setOrientation(PrintOptions.Orientation.PORTRAIT); + } else { + printOptions.setOrientation(PrintOptions.Orientation.LANDSCAPE); + } + break; + + case "scale": + printOptions.setScale(input.read(Double.class)); + break; + + case "shrinkToFit": + printOptions.setShrinkToFit(input.read(Boolean.class)); + break; + + case "background": + printOptions.setBackground(input.read(Boolean.class)); + break; + + case "pageRanges": + printOptions.setPageRanges(input.read(new TypeToken() {}.getType())); + break; + + case "margin": + Map marginMap = input.read(Map.class); + if (marginMap.size() != 0) { + printOptions.setPageMargin( + new PageMargin( + (double) marginMap.get("top"), + (double) marginMap.get("bottom"), + (double) marginMap.get("left"), + (double) marginMap.get("right"))); + } + break; + + default: + input.skipValue(); + break; + } + } + + input.endObject(); + + return printOptions; + } + } } diff --git a/java/src/org/openqa/selenium/remote/Delegator.java b/java/src/org/openqa/selenium/remote/Delegator.java index b2ba87ad4e715..80acd18a516ec 100644 --- a/java/src/org/openqa/selenium/remote/Delegator.java +++ b/java/src/org/openqa/selenium/remote/Delegator.java @@ -21,14 +21,23 @@ import java.io.IOException; import java.net.URL; +import java.util.HashSet; import java.util.Map; +import java.util.Set; + import org.openqa.selenium.internal.Require; import org.openqa.selenium.remote.http.ClientConfig; import org.openqa.selenium.remote.http.HttpClient; public class Delegator extends HttpCommandExecutor implements CommandExecutor { - private BiDiCommandExecutor bidiCommandExecutor = null; + private BiDiCommandExecutor biDiCommandExecutor = null; + private static final Set biDiCommands = new HashSet<>(); + + static { + biDiCommands.add(DriverCommand.GET); + biDiCommands.add(DriverCommand.PRINT_PAGE); + } public Delegator(ClientConfig config) { super( @@ -36,7 +45,7 @@ public Delegator(ClientConfig config) { Require.nonNull("HTTP client configuration", config), getDefaultClientFactory()); } - + public Delegator( Map additionalCommands, ClientConfig config, @@ -45,25 +54,25 @@ public Delegator( } public Delegator( - Map additionalCommands, URL addressOfRemoteServer, ClientConfig config) { + Map additionalCommands, URL addressOfRemoteServer, ClientConfig config) { super( - additionalCommands, - config.baseUrl(Require.nonNull("Server URL", addressOfRemoteServer)), - getDefaultClientFactory()); + additionalCommands, + config.baseUrl(Require.nonNull("Server URL", addressOfRemoteServer)), + getDefaultClientFactory()); } - public void setBidiCommandExecutor(BiDiCommandExecutor bidiCommandExecutor) { - this.bidiCommandExecutor = bidiCommandExecutor; + public void setBiDiCommandExecutor(BiDiCommandExecutor biDiCommandExecutor) { + this.biDiCommandExecutor = biDiCommandExecutor; } @Override public Response execute(Command command) throws IOException { - if (bidiCommandExecutor != null - && command.getName().equals(DriverCommand.GET) - && command.getName().equals(DriverCommand.PRINT_PAGE)) { - return bidiCommandExecutor.execute(command); + if (biDiCommandExecutor != null && biDiCommands.contains(command.getName())) { + return biDiCommandExecutor.execute(command); } + // Add a mechanism to use HttpCommandExecutor if the command is not support by the browser + // But the browser supports W3C BiDi return super.execute(command); } } diff --git a/java/src/org/openqa/selenium/remote/RemoteWebDriver.java b/java/src/org/openqa/selenium/remote/RemoteWebDriver.java index 0ee2aaa6b2a70..a8f7f99c83835 100644 --- a/java/src/org/openqa/selenium/remote/RemoteWebDriver.java +++ b/java/src/org/openqa/selenium/remote/RemoteWebDriver.java @@ -184,7 +184,7 @@ public RemoteWebDriver(CommandExecutor executor, Capabilities capabilities) { })); biDi.ifPresent(connection -> ((HasBiDi) this).setBiDi(connection)); - ((Delegator) (getCommandExecutor())).setBidiCommandExecutor(new BiDiCommandExecutor(this)); + ((Delegator) (getCommandExecutor())).setBiDiCommandExecutor(new BiDiCommandExecutor(this)); } } catch (RuntimeException e) { try { From b8922a24a3f124d808e9b4dd150732b456d77d74 Mon Sep 17 00:00:00 2001 From: Puja Jagani Date: Tue, 21 Nov 2023 10:47:38 +0530 Subject: [PATCH 04/15] [java] Use a map of bidi command handlers --- .../selenium/remote/BiDiCommandExecutor.java | 63 ++++++++++--------- 1 file changed, 34 insertions(+), 29 deletions(-) diff --git a/java/src/org/openqa/selenium/remote/BiDiCommandExecutor.java b/java/src/org/openqa/selenium/remote/BiDiCommandExecutor.java index 918359fa57837..6431ebfce2cc5 100644 --- a/java/src/org/openqa/selenium/remote/BiDiCommandExecutor.java +++ b/java/src/org/openqa/selenium/remote/BiDiCommandExecutor.java @@ -25,6 +25,7 @@ import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.BiFunction; import org.openqa.selenium.UnsupportedCommandException; import org.openqa.selenium.bidi.Network; @@ -44,11 +45,12 @@ public class BiDiCommandExecutor implements CommandExecutor { private final RemoteWebDriver driver; - private Script script; - - private Network network; - private final AtomicReference currentContext = new AtomicReference<>(); + private final AtomicReference