feat: support connectOverCDP (#387)

This commit is contained in:
Yury Semikhatsky 2021-04-06 23:20:07 -07:00 committed by GitHub
parent 7f52faf400
commit 40c663ccce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 220 additions and 10 deletions

View File

@ -62,6 +62,27 @@ public interface BrowserType {
return this;
}
}
class ConnectOverCDPOptions {
/**
* Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on.
* Defaults to 0.
*/
public Double slowMo;
/**
* Maximum time in milliseconds to wait for the connection to be established. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to
* disable timeout.
*/
public Double timeout;
public ConnectOverCDPOptions setSlowMo(double slowMo) {
this.slowMo = slowMo;
return this;
}
public ConnectOverCDPOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
class LaunchOptions {
/**
* Additional arguments to pass to the browser instance. The list of Chromium flags can be found <a
@ -577,6 +598,28 @@ public interface BrowserType {
* @param wsEndpoint A browser websocket endpoint to connect to.
*/
Browser connect(String wsEndpoint, ConnectOptions options);
/**
* This methods attaches Playwright to an existing browser instance using the Chrome DevTools Protocol.
*
* <p> The default browser context is accessible via {@link Browser#contexts Browser.contexts()}.
*
* <p> <strong>NOTE:</strong> Connecting over the Chrome DevTools Protocol is only supported for Chromium-based browsers.
*
* @param wsEndpoint A CDP websocket endpoint to connect to.
*/
default Browser connectOverCDP(String wsEndpoint) {
return connectOverCDP(wsEndpoint, null);
}
/**
* This methods attaches Playwright to an existing browser instance using the Chrome DevTools Protocol.
*
* <p> The default browser context is accessible via {@link Browser#contexts Browser.contexts()}.
*
* <p> <strong>NOTE:</strong> Connecting over the Chrome DevTools Protocol is only supported for Chromium-based browsers.
*
* @param wsEndpoint A CDP websocket endpoint to connect to.
*/
Browser connectOverCDP(String wsEndpoint, ConnectOverCDPOptions options);
/**
* A path where Playwright expects to find a bundled browser executable.
*/

View File

@ -40,7 +40,8 @@ import static com.microsoft.playwright.impl.Utils.isSafeCloseError;
class BrowserImpl extends ChannelOwner implements Browser {
final Set<BrowserContextImpl> contexts = new HashSet<>();
private final ListenerCollection<EventType> listeners = new ListenerCollection<>();
public boolean isRemote;
boolean isRemote;
boolean isConnectedOverWebSocket;
private boolean isConnected = true;
enum EventType {
@ -67,7 +68,7 @@ class BrowserImpl extends ChannelOwner implements Browser {
}
private void closeImpl() {
if (isRemote) {
if (isConnectedOverWebSocket) {
try {
connection.close();
} catch (IOException e) {

View File

@ -69,12 +69,8 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
playwright.sharedSelectors.addChannel(selectors);
BrowserImpl browser = remoteBrowser.browser();
browser.isRemote = true;
Consumer<WebSocketTransport> connectionCloseListener = new Consumer<WebSocketTransport>() {
@Override
public void accept(WebSocketTransport t) {
browser.notifyRemoteClosed();
}
};
browser.isConnectedOverWebSocket = true;
Consumer<WebSocketTransport> connectionCloseListener = t -> browser.notifyRemoteClosed();
transport.onClose(connectionCloseListener);
browser.onDisconnected(b -> {
playwright.sharedSelectors.removeChannel(selectors);
@ -91,6 +87,34 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
}
}
@Override
public Browser connectOverCDP(String wsEndpoint, ConnectOverCDPOptions options) {
if (!"chromium".equals(name())) {
throw new PlaywrightException("Connecting over CDP is only supported in Chromium.");
}
return withLogging("BrowserType.connectOverCDP", () -> connectOverCDPImpl(wsEndpoint, options));
}
private Browser connectOverCDPImpl(String wsEndpoint, ConnectOverCDPOptions options) {
if (options == null) {
options = new ConnectOverCDPOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("sdkLanguage", "java");
params.addProperty("wsEndpoint", wsEndpoint);
JsonObject json = sendMessage("connectOverCDP", params).getAsJsonObject();
BrowserImpl browser = connection.getExistingObject(json.getAsJsonObject("browser").get("guid").getAsString());
browser.isRemote = true;
if (json.has("defaultContext")) {
String contextId = json.getAsJsonObject("defaultContext").get("guid").getAsString();
BrowserContextImpl defaultContext = connection.getExistingObject(contextId);
browser.contexts.add(defaultContext);
}
return browser;
}
public String executablePath() {
return initializer.get("executablePath").getAsString();
}

View File

@ -54,7 +54,7 @@ public class TestBase {
return "firefox".equals(getBrowserNameFromEnv());
}
private static BrowserChannel getBrowserChannelFromEnv() {
static BrowserChannel getBrowserChannelFromEnv() {
String channel = System.getenv("BROWSER_CHANNEL");
if (channel == null) {
return null;

View File

@ -0,0 +1,53 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed 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 com.microsoft.playwright;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledIf;
import java.nio.file.Files;
import java.nio.file.Paths;
import static com.microsoft.playwright.Utils.getBrowserNameFromEnv;
import static org.junit.jupiter.api.Assertions.*;
public class TestBrowserTypeBasic extends TestBase {
@Test
void browserTypeExecutablePathShouldWork() {
Assumptions.assumeTrue(getBrowserChannelFromEnv() == null);
Assumptions.assumeTrue(createLaunchOptions().executablePath == null, "Skip with custom executable path");
String executablePath = browserType.executablePath();
assertTrue(Files.exists(Paths.get(executablePath)));
}
@Test
void browserTypeNameShouldWork() {
assertEquals(getBrowserNameFromEnv(), browserType.name());
}
@Test
@DisabledIf(value="com.microsoft.playwright.TestBase#isChromium", disabledReason="Non-chromium behavior")
void shouldThrowWhenTryingToConnectWithNotChromium() {
try {
browserType.connectOverCDP("foo");
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Connecting over CDP is only supported in Chromium."));
}
}
}

View File

@ -0,0 +1,89 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed 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 com.microsoft.playwright;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledIf;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URL;
import java.net.URLConnection;
import java.util.List;
import static java.util.Arrays.asList;
import static org.junit.jupiter.api.Assertions.assertEquals;
@EnabledIf(value="com.microsoft.playwright.TestBase#isChromium", disabledReason="Chromium-specific API")
public class TestChromium extends TestBase {
@Override
void createContextAndPage() {
// Do not create anything.
}
private static String wsEndpointFromUrl(String urlString) throws IOException {
URL url = new URL(urlString);
URLConnection request = url.openConnection();
request.connect();
Reader reader = new InputStreamReader((InputStream) request.getContent());
JsonObject json = new Gson().fromJson(reader, JsonObject.class);
return json.get("webSocketDebuggerUrl").getAsString();
}
@Test
void shouldConnectToAnExistingCdpSession() throws IOException {
int port = 9339;
try (Browser browserServer = browserType.launch(createLaunchOptions()
.setArgs(asList("--remote-debugging-port=" + port)))) {
String wsEndpoint = wsEndpointFromUrl("http://localhost:" + port + "/json/version/");
Browser cdpBrowser = browserType.connectOverCDP(wsEndpoint);
List<BrowserContext> contexts = cdpBrowser.contexts();
assertEquals(1, contexts.size());
cdpBrowser.close();
}
}
@Test
void shouldConnectToAnExistingCdpSessionTwice() throws IOException {
int port = 9339;
try (Browser browserServer = browserType.launch(createLaunchOptions()
.setArgs(asList("--remote-debugging-port=" + port)))) {
String wsEndpoint = wsEndpointFromUrl("http://localhost:" + port + "/json/version/");
Browser cdpBrowser1 = browserType.connectOverCDP(wsEndpoint);
Browser cdpBrowser2 = browserType.connectOverCDP(wsEndpoint);
List<BrowserContext> contexts1 = cdpBrowser1.contexts();
assertEquals(1, contexts1.size());
Page page1 = contexts1.get(0).newPage();
page1.navigate(server.EMPTY_PAGE);
List<BrowserContext> contexts2 = cdpBrowser2.contexts();
assertEquals(1, contexts2.size());
Page page2 = contexts2.get(0).newPage();
page2.navigate(server.EMPTY_PAGE);
assertEquals(2, contexts1.get(0).pages().size());
assertEquals(2, contexts2.get(0).pages().size());
cdpBrowser1.close();
cdpBrowser2.close();
}
}
}

View File

@ -1 +1 @@
1.11.0-next-1617387566000
1.11.0-next-1617755629000