mirror of
https://github.com/microsoft/playwright-java.git
synced 2025-12-28 10:20:45 +00:00
feat: support connectOverCDP (#387)
This commit is contained in:
parent
7f52faf400
commit
40c663ccce
@ -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.
|
||||
*/
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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."));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1 +1 @@
|
||||
1.11.0-next-1617387566000
|
||||
1.11.0-next-1617755629000
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user