Skip to content

Commit ca99cd1

Browse files
committed
[java] Add command mapper that maps W3C WebDriver command to corresponding BiDi command
1 parent f1416f1 commit ca99cd1

File tree

2 files changed

+185
-162
lines changed

2 files changed

+185
-162
lines changed

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

Lines changed: 3 additions & 162 deletions
Original file line numberDiff line numberDiff line change
@@ -17,179 +17,20 @@
1717

1818
package org.openqa.selenium.remote;
1919

20-
import static org.openqa.selenium.remote.DriverCommand.GET;
21-
import static org.openqa.selenium.remote.DriverCommand.PRINT_PAGE;
22-
2320
import java.io.IOException;
24-
import java.io.StringReader;
25-
import java.util.ArrayList;
26-
import java.util.HashMap;
27-
import java.util.List;
28-
import java.util.Map;
29-
import java.util.concurrent.ConcurrentHashMap;
30-
import java.util.concurrent.atomic.AtomicReference;
31-
import java.util.function.BiFunction;
32-
import org.openqa.selenium.UnsupportedCommandException;
33-
import org.openqa.selenium.bidi.Network;
34-
import org.openqa.selenium.bidi.Script;
35-
import org.openqa.selenium.bidi.browsingcontext.BrowsingContext;
36-
import org.openqa.selenium.bidi.browsingcontext.ReadinessState;
37-
import org.openqa.selenium.json.Json;
38-
import org.openqa.selenium.json.JsonInput;
39-
import org.openqa.selenium.json.TypeToken;
40-
import org.openqa.selenium.print.PageMargin;
41-
import org.openqa.selenium.print.PageSize;
42-
import org.openqa.selenium.print.PrintOptions;
4321

4422
public class BiDiCommandExecutor implements CommandExecutor {
4523

46-
private static Json JSON = new Json();
47-
4824
private final RemoteWebDriver driver;
49-
50-
private final AtomicReference<BrowsingContext> currentContext = new AtomicReference<>();
51-
private final AtomicReference<Script> script = new AtomicReference<>();
52-
private final AtomicReference<Network> network = new AtomicReference<>();
53-
54-
private final Map<String, BiFunction<Command, RemoteWebDriver, Response>> commandHandlerMap =
55-
new HashMap<>();
56-
57-
// Each browsing context has an associated id, maintains state
58-
// We will need to maintain a map of all the browsingContext to run further commands
59-
// Switching between tabs etc might be tricky
60-
private final Map<String, BrowsingContext> browsingContextMap = new ConcurrentHashMap<>();
25+
private final BiDiCommandMapper commandMapper;
6126

6227
public BiDiCommandExecutor(RemoteWebDriver driver) {
6328
this.driver = driver;
64-
init(this.driver);
65-
}
66-
67-
private void init(RemoteWebDriver driver) {
68-
script.set(new Script(driver));
69-
network.set(new Network(driver));
70-
71-
BrowsingContext parentContext = new BrowsingContext(driver, driver.getWindowHandle());
72-
browsingContextMap.put(parentContext.getId(), parentContext);
73-
currentContext.set(parentContext);
74-
75-
commandHandlerMap.put(
76-
GET,
77-
(command, webDriver) -> {
78-
String pageLoadStrategy =
79-
(String) webDriver.getCapabilities().getCapability("pageLoadStrategy");
80-
currentContext
81-
.get()
82-
.navigate(
83-
(String) command.getParameters().get("url"),
84-
ReadinessState.getReadinessState(pageLoadStrategy));
85-
86-
// Required because W3C Classic sets the current browsing context to current top-level
87-
// context on navigation
88-
// This is crucial for tests that:
89-
// Switch to frame -> find element -> navigate url -> find element (in BiDi it will try to
90-
// find an element in the frame)
91-
// But in WebDriver Classic it will try to find the element on the page navigated to
92-
// So to avoid breaking changes, we need to add this step
93-
// Refer: Pt 9 of https://www.w3.org/TR/webdriver2/#navigate-to
94-
// Refer: https://w3c.github.io/webdriver-bidi/#command-browsingContext-navigate
95-
driver.switchTo().window(currentContext.get().getId());
96-
return new Response(webDriver.getSessionId());
97-
});
98-
99-
commandHandlerMap.put(
100-
PRINT_PAGE,
101-
(command, webDriver) -> {
102-
String result = currentContext.get().print(serializePrintOptions(command));
103-
Response response = new Response(webDriver.getSessionId());
104-
response.setValue(result);
105-
return response;
106-
});
29+
commandMapper = new BiDiCommandMapper(driver);
10730
}
10831

10932
@Override
11033
public Response execute(Command command) throws IOException {
111-
// This is not optimal
112-
// But each time Classic calls switchTo, we need to identify the current browsing context to run
113-
// commands
114-
// Maybe we need a way where every time switchTo commands are called, only then we update the
115-
// current browsing context
116-
117-
String currentWindowHandle = driver.getWindowHandle();
118-
BrowsingContext browsingContext =
119-
browsingContextMap.computeIfAbsent(
120-
currentWindowHandle, windowHandle -> new BrowsingContext(driver, windowHandle));
121-
122-
browsingContextMap.putIfAbsent(currentWindowHandle, browsingContext);
123-
currentContext.set(browsingContext);
124-
if (commandHandlerMap.containsKey(command.getName())) {
125-
return commandHandlerMap.get(command.getName()).apply(command, driver);
126-
} else {
127-
throw new UnsupportedCommandException();
128-
}
129-
}
130-
131-
private PrintOptions serializePrintOptions(Command command) {
132-
try (StringReader reader = new StringReader(JSON.toJson(command.getParameters()));
133-
JsonInput input = JSON.newInput(reader)) {
134-
PrintOptions printOptions = new PrintOptions();
135-
136-
input.beginObject();
137-
while (input.hasNext()) {
138-
switch (input.nextName()) {
139-
case "page":
140-
Map<String, Double> map = input.read(new TypeToken<Map<String, Double>>() {}.getType());
141-
if (map.size() != 0) {
142-
printOptions.setPageSize(new PageSize(map.get("height"), map.get("width")));
143-
}
144-
break;
145-
146-
case "orientation":
147-
String orientation = input.read(String.class);
148-
if (orientation.equals("portrait")) {
149-
printOptions.setOrientation(PrintOptions.Orientation.PORTRAIT);
150-
} else {
151-
printOptions.setOrientation(PrintOptions.Orientation.LANDSCAPE);
152-
}
153-
break;
154-
155-
case "scale":
156-
printOptions.setScale(input.read(Double.class));
157-
break;
158-
159-
case "shrinkToFit":
160-
printOptions.setShrinkToFit(input.read(Boolean.class));
161-
break;
162-
163-
case "background":
164-
printOptions.setBackground(input.read(Boolean.class));
165-
break;
166-
167-
case "pageRanges":
168-
List<String> pageRanges = input.read(new TypeToken<ArrayList<String>>() {}.getType());
169-
printOptions.setPageRanges(pageRanges);
170-
break;
171-
172-
case "margin":
173-
Map<String, Double> marginMap =
174-
input.read(new TypeToken<Map<String, Double>>() {}.getType());
175-
if (marginMap.size() != 0) {
176-
printOptions.setPageMargin(
177-
new PageMargin(
178-
marginMap.get("top"),
179-
marginMap.get("bottom"),
180-
marginMap.get("left"),
181-
marginMap.get("right")));
182-
}
183-
break;
184-
185-
default:
186-
input.skipValue();
187-
break;
188-
}
189-
}
190-
191-
input.endObject();
192-
return printOptions;
193-
}
34+
return commandMapper.map(command).apply(command, driver);
19435
}
19536
}
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
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 static org.openqa.selenium.remote.DriverCommand.GET;
21+
import static org.openqa.selenium.remote.DriverCommand.PRINT_PAGE;
22+
23+
import java.io.StringReader;
24+
import java.util.ArrayList;
25+
import java.util.HashMap;
26+
import java.util.List;
27+
import java.util.Map;
28+
import java.util.concurrent.ConcurrentHashMap;
29+
import java.util.concurrent.atomic.AtomicReference;
30+
import java.util.function.BiFunction;
31+
import org.openqa.selenium.UnsupportedCommandException;
32+
import org.openqa.selenium.bidi.Network;
33+
import org.openqa.selenium.bidi.Script;
34+
import org.openqa.selenium.bidi.browsingcontext.BrowsingContext;
35+
import org.openqa.selenium.bidi.browsingcontext.ReadinessState;
36+
import org.openqa.selenium.json.Json;
37+
import org.openqa.selenium.json.JsonInput;
38+
import org.openqa.selenium.json.TypeToken;
39+
import org.openqa.selenium.print.PageMargin;
40+
import org.openqa.selenium.print.PageSize;
41+
import org.openqa.selenium.print.PrintOptions;
42+
43+
public class BiDiCommandMapper {
44+
45+
private static Json JSON = new Json();
46+
47+
private final RemoteWebDriver driver;
48+
49+
private final AtomicReference<BrowsingContext> currentContext = new AtomicReference<>();
50+
private final AtomicReference<Script> script = new AtomicReference<>();
51+
private final AtomicReference<Network> network = new AtomicReference<>();
52+
53+
private final Map<String, BiFunction<Command, RemoteWebDriver, Response>> commandHandlerMap =
54+
new HashMap<>();
55+
56+
// Each browsing context has an associated id, maintains state
57+
// We will need to maintain a map of all the browsingContext to run further commands
58+
// Switching between tabs etc might be tricky
59+
private final Map<String, BrowsingContext> browsingContextMap = new ConcurrentHashMap<>();
60+
61+
public BiDiCommandMapper(RemoteWebDriver driver) {
62+
this.driver = driver;
63+
init(this.driver);
64+
}
65+
66+
private void init(RemoteWebDriver driver) {
67+
script.set(new Script(driver));
68+
network.set(new Network(driver));
69+
70+
BrowsingContext parentContext = new BrowsingContext(driver, driver.getWindowHandle());
71+
browsingContextMap.put(parentContext.getId(), parentContext);
72+
currentContext.set(parentContext);
73+
74+
commandHandlerMap.put(
75+
GET,
76+
(command, webDriver) -> {
77+
String pageLoadStrategy =
78+
(String) webDriver.getCapabilities().getCapability("pageLoadStrategy");
79+
currentContext
80+
.get()
81+
.navigate(
82+
(String) command.getParameters().get("url"),
83+
ReadinessState.getReadinessState(pageLoadStrategy));
84+
return new Response(webDriver.getSessionId());
85+
});
86+
87+
commandHandlerMap.put(
88+
PRINT_PAGE,
89+
(command, webDriver) -> {
90+
String result = currentContext.get().print(serializePrintOptions(command));
91+
Response response = new Response(webDriver.getSessionId());
92+
response.setValue(result);
93+
return response;
94+
});
95+
}
96+
97+
public BiFunction<Command, RemoteWebDriver, Response> map(Command command) {
98+
// This is not optimal
99+
// But each time Classic calls switchTo, we need to identify the current browsing context to run
100+
// commands
101+
// Maybe we need a way where every time switchTo commands are called, only then we update the
102+
// current browsing context
103+
String currentWindowHandle = driver.getWindowHandle();
104+
BrowsingContext browsingContext =
105+
browsingContextMap.computeIfAbsent(
106+
currentWindowHandle, windowHandle -> new BrowsingContext(driver, windowHandle));
107+
108+
browsingContextMap.putIfAbsent(currentWindowHandle, browsingContext);
109+
currentContext.set(browsingContext);
110+
111+
if (commandHandlerMap.containsKey(command.getName())) {
112+
return commandHandlerMap.get(command.getName());
113+
} else {
114+
throw new UnsupportedCommandException();
115+
}
116+
}
117+
118+
private PrintOptions serializePrintOptions(Command command) {
119+
try (StringReader reader = new StringReader(JSON.toJson(command.getParameters()));
120+
JsonInput input = JSON.newInput(reader)) {
121+
PrintOptions printOptions = new PrintOptions();
122+
123+
input.beginObject();
124+
while (input.hasNext()) {
125+
switch (input.nextName()) {
126+
case "page":
127+
Map<String, Double> map = input.read(new TypeToken<Map<String, Double>>() {}.getType());
128+
if (map.size() != 0) {
129+
printOptions.setPageSize(new PageSize(map.get("height"), map.get("width")));
130+
}
131+
break;
132+
133+
case "orientation":
134+
String orientation = input.read(String.class);
135+
if (orientation.equals("portrait")) {
136+
printOptions.setOrientation(PrintOptions.Orientation.PORTRAIT);
137+
} else {
138+
printOptions.setOrientation(PrintOptions.Orientation.LANDSCAPE);
139+
}
140+
break;
141+
142+
case "scale":
143+
printOptions.setScale(input.read(Double.class));
144+
break;
145+
146+
case "shrinkToFit":
147+
printOptions.setShrinkToFit(input.read(Boolean.class));
148+
break;
149+
150+
case "background":
151+
printOptions.setBackground(input.read(Boolean.class));
152+
break;
153+
154+
case "pageRanges":
155+
List<String> pageRanges = input.read(new TypeToken<ArrayList<String>>() {}.getType());
156+
printOptions.setPageRanges(pageRanges);
157+
break;
158+
159+
case "margin":
160+
Map<String, Double> marginMap =
161+
input.read(new TypeToken<Map<String, Double>>() {}.getType());
162+
if (marginMap.size() != 0) {
163+
printOptions.setPageMargin(
164+
new PageMargin(
165+
marginMap.get("top"),
166+
marginMap.get("bottom"),
167+
marginMap.get("left"),
168+
marginMap.get("right")));
169+
}
170+
break;
171+
172+
default:
173+
input.skipValue();
174+
break;
175+
}
176+
}
177+
178+
input.endObject();
179+
return printOptions;
180+
}
181+
}
182+
}

0 commit comments

Comments
 (0)