From dca206d28dba57b41f7056b8172eda538da512aa Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Wed, 28 Oct 2020 18:33:22 -0700 Subject: [PATCH] feat: launchPersistentContext (#53) --- .../playwright/tools/ApiGenerator.java | 6 + .../microsoft/playwright/BrowserServer.java | 27 ---- .../com/microsoft/playwright/BrowserType.java | 5 - .../playwright/impl/BrowserContextImpl.java | 6 +- .../playwright/impl/BrowserTypeImpl.java | 73 +++++++-- .../TestDefaultBrowserContext2.java | 146 ++++++++++++++++++ 6 files changed, 215 insertions(+), 48 deletions(-) delete mode 100644 playwright/src/main/java/com/microsoft/playwright/BrowserServer.java create mode 100644 playwright/src/test/java/com/microsoft/playwright/TestDefaultBrowserContext2.java diff --git a/api-generator/src/main/java/com/microsoft/playwright/tools/ApiGenerator.java b/api-generator/src/main/java/com/microsoft/playwright/tools/ApiGenerator.java index c5654cd0..6e4a4543 100644 --- a/api-generator/src/main/java/com/microsoft/playwright/tools/ApiGenerator.java +++ b/api-generator/src/main/java/com/microsoft/playwright/tools/ApiGenerator.java @@ -238,6 +238,9 @@ class Method extends Element { customSignature.put("Page.setViewportSize", new String[]{"void setViewportSize(int width, int height);"}); // The method is deprecated in ts, just remove it in Java. customSignature.put("BrowserContext.setHTTPCredentials", new String[0]); + // No connect for now. + customSignature.put("BrowserType.connect", new String[0]); + customSignature.put("BrowserType.launchServer", new String[0]); customSignature.put("BrowserContext.route", new String[]{ "void route(String url, BiConsumer handler);", "void route(Pattern url, BiConsumer handler);", @@ -928,6 +931,9 @@ public class ApiGenerator { System.out.println("Writing files to: " + dir.getCanonicalPath()); for (Map.Entry entry: api.entrySet()) { String name = entry.getKey(); + if ("BrowserServer".equals(name)) { + continue; + } List lines = new ArrayList<>(); new Interface(entry.getValue().getAsJsonObject()).writeTo(lines, ""); String text = String.join("\n", lines); diff --git a/playwright/src/main/java/com/microsoft/playwright/BrowserServer.java b/playwright/src/main/java/com/microsoft/playwright/BrowserServer.java deleted file mode 100644 index 7277fa0a..00000000 --- a/playwright/src/main/java/com/microsoft/playwright/BrowserServer.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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 java.util.*; - -public interface BrowserServer { - void close(); - void kill(); - Object process(); - String wsEndpoint(); -} - diff --git a/playwright/src/main/java/com/microsoft/playwright/BrowserType.java b/playwright/src/main/java/com/microsoft/playwright/BrowserType.java index 788dbbaa..fc56cdad 100644 --- a/playwright/src/main/java/com/microsoft/playwright/BrowserType.java +++ b/playwright/src/main/java/com/microsoft/playwright/BrowserType.java @@ -488,7 +488,6 @@ public interface BrowserType { return this; } } - Browser connect(ConnectOptions options); String executablePath(); default Browser launch() { return launch(null); @@ -498,10 +497,6 @@ public interface BrowserType { return launchPersistentContext(userDataDir, null); } BrowserContext launchPersistentContext(String userDataDir, LaunchPersistentContextOptions options); - default BrowserServer launchServer() { - return launchServer(null); - } - BrowserServer launchServer(LaunchServerOptions options); String name(); } diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java index e9373ddf..18aba368 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java @@ -42,7 +42,11 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext { protected BrowserContextImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) { super(parent, type, guid, initializer); - browser = (BrowserImpl) parent; + if (parent instanceof BrowserImpl) { + browser = (BrowserImpl) parent; + } else { + browser = null; + } } @Override diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserTypeImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserTypeImpl.java index 51aba52a..7152c3c7 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserTypeImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserTypeImpl.java @@ -16,24 +16,21 @@ package com.microsoft.playwright.impl; -import com.google.gson.Gson; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.microsoft.playwright.Browser; +import com.google.gson.*; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; import com.microsoft.playwright.BrowserContext; -import com.microsoft.playwright.BrowserServer; import com.microsoft.playwright.BrowserType; +import com.microsoft.playwright.PlaywrightException; + +import java.io.IOException; +import java.util.Map; class BrowserTypeImpl extends ChannelOwner implements BrowserType { BrowserTypeImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) { super(parent, type, guid, initializer); } - @Override - public Browser connect(ConnectOptions options) { - return null; - } - @Override public BrowserImpl launch(LaunchOptions options) { if (options == null) { @@ -48,14 +45,60 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType { return initializer.get("executablePath").getAsString(); } - @Override - public BrowserContext launchPersistentContext(String userDataDir, LaunchPersistentContextOptions options) { - return null; + + private static class ColorSchemeAdapter extends TypeAdapter { + @Override + public void write(JsonWriter out, LaunchPersistentContextOptions.ColorScheme value) throws IOException { + String stringValue; + switch (value) { + case DARK: + stringValue = "dark"; + break; + case LIGHT: + stringValue = "light"; + break; + case NO_PREFERENCE: + stringValue = "no-preference"; + break; + default: + throw new PlaywrightException("Unexpected value: " + value); + } + out.value(stringValue); + } + + @Override + public LaunchPersistentContextOptions.ColorScheme read(JsonReader in) throws IOException { + String value = in.nextString(); + switch (value) { + case "dark": return LaunchPersistentContextOptions.ColorScheme.DARK; + case "light": return LaunchPersistentContextOptions.ColorScheme.LIGHT; + case "no-preference": return LaunchPersistentContextOptions.ColorScheme.NO_PREFERENCE; + default: throw new PlaywrightException("Unexpected value: " + value); + } + } } @Override - public BrowserServer launchServer(LaunchServerOptions options) { - return null; + public BrowserContext launchPersistentContext(String userDataDir, LaunchPersistentContextOptions options) { + if (options == null) { + options = new LaunchPersistentContextOptions(); + } + Gson gson = new GsonBuilder().registerTypeAdapter(LaunchPersistentContextOptions.ColorScheme.class, new ColorSchemeAdapter().nullSafe()).create(); + JsonObject params = gson.toJsonTree(options).getAsJsonObject(); + if (options.extraHTTPHeaders != null) { + params.remove("extraHTTPHeaders"); + JsonArray jsonArray = new JsonArray(); + for (Map.Entry e : options.extraHTTPHeaders.entrySet()) { + JsonObject header = new JsonObject(); + header.addProperty("name", e.getKey()); + header.addProperty("value", e.getValue()); + jsonArray.add(header); + } + params.add("extraHTTPHeaders", jsonArray); + } + params.addProperty("userDataDir", userDataDir); + JsonObject json = sendMessage("launchPersistentContext", params).getAsJsonObject(); + return connection.getExistingObject(json.getAsJsonObject("context").get("guid").getAsString()); } public String name() { diff --git a/playwright/src/test/java/com/microsoft/playwright/TestDefaultBrowserContext2.java b/playwright/src/test/java/com/microsoft/playwright/TestDefaultBrowserContext2.java new file mode 100644 index 00000000..183fad61 --- /dev/null +++ b/playwright/src/test/java/com/microsoft/playwright/TestDefaultBrowserContext2.java @@ -0,0 +1,146 @@ +package com.microsoft.playwright; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; + +import static com.microsoft.playwright.BrowserType.LaunchPersistentContextOptions.ColorScheme.DARK; +import static com.microsoft.playwright.Utils.mapOf; +import static java.util.Arrays.asList; +import static org.junit.jupiter.api.Assertions.*; + +public class TestDefaultBrowserContext2 extends TestBase { + + + private BrowserContext persistentContext; + + @AfterEach + private void closePersistentContext() { + if (persistentContext != null) { + persistentContext.close(); + } + } + + private Page launchPersistent() { + return launchPersistent(null); + } + private Page launchPersistent(BrowserType.LaunchPersistentContextOptions options) { + Path userDataDir = null; + try { + userDataDir = Files.createTempDirectory("user-data-dir-"); + } catch (IOException e) { + throw new RuntimeException(e); + } + assertNull(persistentContext); + persistentContext = browserType.launchPersistentContext(userDataDir.toString(), options); + return persistentContext.pages().get(0); + } + + @Test + void shouldSupportHasTouchOption() { + Page page = launchPersistent(new BrowserType.LaunchPersistentContextOptions().withHasTouch(true)); + page.navigate(server.PREFIX + "/mobile.html"); + assertEquals(true, page.evaluate("() => 'ontouchstart' in window")); + } + + @Test + void shouldWorkInPersistentContext() { +// TODO: test.skip(browserName === "firefox"); + // Firefox does not support mobile. + Page page = launchPersistent(new BrowserType.LaunchPersistentContextOptions() + .withViewport(320,480).withIsMobile(true)); + page.navigate(server.PREFIX + "/empty.html"); + assertEquals(980, page.evaluate("() => window.innerWidth")); + } + + @Test + void shouldSupportColorSchemeOption() { + Page page = launchPersistent(new BrowserType.LaunchPersistentContextOptions().withColorScheme(DARK)); + assertEquals(false, page.evaluate("matchMedia('(prefers-color-scheme: light)').matches")); + assertEquals(true, page.evaluate("matchMedia('(prefers-color-scheme: dark)').matches")); + } + + @Test + void shouldSupportTimezoneIdOption() { + Page page = launchPersistent(new BrowserType.LaunchPersistentContextOptions() + .withLocale("en-US").withTimezoneId("America/Jamaica")); + assertEquals("Sat Nov 19 2016 13:12:34 GMT-0500 (Eastern Standard Time)", + page.evaluate("new Date(1479579154987).toString()")); + } + + @Test + void shouldSupportLocaleOption() { + Page page = launchPersistent(new BrowserType.LaunchPersistentContextOptions() + .withLocale("fr-CH")); + assertEquals("fr-CH", page.evaluate("navigator.language")); + } + + @Test + void shouldSupportGeolocationAndPermissionsOptions() { + Page page = launchPersistent(new BrowserType.LaunchPersistentContextOptions() + .withGeolocation(new Geolocation(10, 10)) + .withPermissions(asList("geolocation"))); + page.navigate(server.EMPTY_PAGE); + Object geolocation = page.evaluate("() => new Promise(resolve => navigator.geolocation.getCurrentPosition(position => {\n" + + " resolve({latitude: position.coords.latitude, longitude: position.coords.longitude});\n" + + "}))"); + assertEquals(mapOf("latitude", 10, "longitude", 10), geolocation); + } + +// @Test + void shouldSupportIgnoreHTTPSErrorsOption() { + // TODO: net::ERR_EMPTY_RESPONSE at https://localhost:8908/empty.html +// Page page = launchPersistent(new BrowserType.LaunchPersistentContextOptions().withIgnoreHTTPSErrors(true)); +// Response response = page.navigate(httpsServer.EMPTY_PAGE); +// assertTrue(response.ok()); + } + + @Test + void shouldSupportExtraHTTPHeadersOption() throws ExecutionException, InterruptedException { +// TODO: test.flaky(browserName === "firefox" && headful && platform === "linux", "Intermittent timeout on bots"); + Page page = launchPersistent(new BrowserType.LaunchPersistentContextOptions().withExtraHTTPHeaders(mapOf("foo", "bar"))); + Future request = server.waitForRequest("/empty.html"); + page.navigate(server.EMPTY_PAGE); + assertEquals(asList("bar"), request.get().headers.get("foo")); + } + + @Test + void shouldAcceptUserDataDir() throws IOException { +// TODO: test.flaky(browserName === "chromium"); + Path userDataDir = Files.createTempDirectory("user-data-dir-"); + BrowserContext context = browserType.launchPersistentContext(userDataDir.toString()); + assertTrue(userDataDir.toFile().listFiles().length > 0); + context.close(); + assertTrue(userDataDir.toFile().listFiles().length > 0); + } + + @Test + void shouldRestoreStateFromUserDataDir() throws IOException { +// TODO: test.slow(); + Path userDataDir = Files.createTempDirectory("user-data-dir-"); + BrowserType.LaunchPersistentContextOptions browserOptions = null; + BrowserContext browserContext = browserType.launchPersistentContext(userDataDir.toString(), browserOptions); + Page page = browserContext.newPage(); + page.navigate(server.EMPTY_PAGE); + page.evaluate("() => localStorage.hey = 'hello'"); + browserContext.close(); + + BrowserContext browserContext2 = browserType.launchPersistentContext(userDataDir.toString(), browserOptions); + Page page2 = browserContext2.newPage(); + page2.navigate(server.EMPTY_PAGE); + assertEquals("hello", page2.evaluate("localStorage.hey")); + browserContext2.close(); + + Path userDataDir2 = Files.createTempDirectory("user-data-dir-"); + BrowserContext browserContext3 = browserType.launchPersistentContext(userDataDir2.toString(), browserOptions); + Page page3 = browserContext3.newPage(); + page3.navigate(server.EMPTY_PAGE); + assertNotEquals("hello", page3.evaluate("localStorage.hey")); + browserContext3.close(); + } +}