diff --git a/.github/workflows/verify_api.yml b/.github/workflows/verify_api.yml index 22414c9e..69a1ca3c 100644 --- a/.github/workflows/verify_api.yml +++ b/.github/workflows/verify_api.yml @@ -24,12 +24,6 @@ jobs: path: ~/.m2 key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} restore-keys: ${{ runner.os }}-m2 - - name: Cache Downloaded Drivers - uses: actions/cache@v2 - with: - path: driver-bundle/src/main/resources/driver - key: ${{ runner.os }}-drivers-${{ hashFiles('scripts/*') }} - restore-keys: ${{ runner.os }}-drivers - name: Download drivers run: scripts/download_driver_for_all_platforms.sh - name: Regenerate APIs 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 b99139c5..4a39ef93 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 @@ -608,10 +608,16 @@ class Field extends Element { return; } if (asList("Page.emulateMedia.params.media", - "Page.emulateMedia.params.colorScheme").contains(jsonPath)) { + "Page.emulateMedia.params.colorScheme").contains(jsonPath)) { output.add(offset + access + "Optional<" + type.toJava() + "> " + name + ";"); return; } + if (asList("Browser.newContext.options.storageState", + "Browser.newPage.options.storageState").contains(jsonPath)) { + output.add(offset + access + type.toJava() + " " + name + ";"); + output.add(offset + access + "Path " + name + "Path;"); + return; + } output.add(offset + access + type.toJava() + " " + name + ";"); } @@ -676,6 +682,20 @@ class Field extends Element { output.add(offset + "}"); return; } + if (asList("Browser.newContext.options.storageState", + "Browser.newPage.options.storageState").contains(jsonPath)) { + output.add(offset + "public " + parentClass + " withStorageState(BrowserContext.StorageState storageState) {"); + output.add(offset + " this.storageState = storageState;"); + output.add(offset + " this.storageStatePath = null;"); + output.add(offset + " return this;"); + output.add(offset + "}"); + output.add(offset + "public " + parentClass + " withStorageState(Path storageStatePath) {"); + output.add(offset + " this.storageState = null;"); + output.add(offset + " this.storageStatePath = storageStatePath;"); + output.add(offset + " return this;"); + output.add(offset + "}"); + return; + } if ("Route.continue.overrides.postData".equals(jsonPath)) { output.add(offset + "public ContinueOverrides withPostData(String postData) {"); output.add(offset + " this.postData = postData.getBytes(StandardCharsets.UTF_8);"); @@ -762,7 +782,7 @@ class Interface extends TypeDefinition { if ("Download".equals(jsonName)) { output.add("import java.io.InputStream;"); } - if (asList("Page", "Frame", "ElementHandle", "FileChooser", "Browser", "BrowserType", "Download", "Route", "Selectors").contains(jsonName)) { + if (asList("Page", "Frame", "ElementHandle", "FileChooser", "Browser", "BrowserContext", "BrowserType", "Download", "Route", "Selectors").contains(jsonName)) { output.add("import java.nio.file.Path;"); } output.add("import java.util.*;"); @@ -1159,9 +1179,9 @@ public class ApiGenerator { List lines = new ArrayList<>(); new Interface(entry.getValue().getAsJsonObject()).writeTo(lines, ""); String text = String.join("\n", lines); - FileWriter writer = new FileWriter(new File(dir, name + ".java")); - writer.write(text); - writer.close(); + try (FileWriter writer = new FileWriter(new File(dir, name + ".java"))) { + writer.write(text); + } } } diff --git a/api-generator/src/main/java/com/microsoft/playwright/tools/Types.java b/api-generator/src/main/java/com/microsoft/playwright/tools/Types.java index 70719408..ae1888d3 100644 --- a/api-generator/src/main/java/com/microsoft/playwright/tools/Types.java +++ b/api-generator/src/main/java/com/microsoft/playwright/tools/Types.java @@ -119,6 +119,7 @@ class Types { add("BrowserType.launchPersistentContext.options.downloadsPath", "string", "Path"); add("BrowserType.launch.options.executablePath", "string", "Path"); add("BrowserType.launch.options.downloadsPath", "string", "Path"); + add("BrowserContext.storageState.options.path", "string", "Path"); add("ChromiumBrowser.startTracing.options.path", "string", "Path"); // Route @@ -298,8 +299,8 @@ class Types { add("BrowserContext.setGeolocation.geolocation", "null|Object", "Geolocation", new Empty()); add("Browser.newContext.options.geolocation", "Object", "Geolocation", new Empty()); - add("Browser.newContext.options.storageState", "Object", "BrowserContext.StorageState", new Empty()); - add("Browser.newPage.options.storageState", "Object", "BrowserContext.StorageState", new Empty()); + add("Browser.newContext.options.storageState", "string|Object", "BrowserContext.StorageState", new Empty()); + add("Browser.newPage.options.storageState", "string|Object", "BrowserContext.StorageState", new Empty()); add("Browser.newPage.options.geolocation", "Object", "Geolocation", new Empty()); add("BrowserType.launchPersistentContext.options.geolocation", "Object", "Geolocation", new Empty()); add("Download.saveAs.path", "string", "Path", new Empty()); diff --git a/playwright/src/main/java/com/microsoft/playwright/Browser.java b/playwright/src/main/java/com/microsoft/playwright/Browser.java index 3e52aaa6..63a52d48 100644 --- a/playwright/src/main/java/com/microsoft/playwright/Browser.java +++ b/playwright/src/main/java/com/microsoft/playwright/Browser.java @@ -227,9 +227,10 @@ public interface Browser { */ public Proxy proxy; /** - * Populates context with given storage state. This method can be used to initialize context with logged-in information obtained via browserContext.storageState(). + * Populates context with given storage state. This method can be used to initialize context with logged-in information obtained via browserContext.storageState([options]). Either a path to the file with saved storage, or an object with the following fields: */ public BrowserContext.StorageState storageState; + public Path storageStatePath; public NewContextOptions withAcceptDownloads(Boolean acceptDownloads) { this.acceptDownloads = acceptDownloads; @@ -317,6 +318,12 @@ public interface Browser { } public NewContextOptions withStorageState(BrowserContext.StorageState storageState) { this.storageState = storageState; + this.storageStatePath = null; + return this; + } + public NewContextOptions withStorageState(Path storageStatePath) { + this.storageState = null; + this.storageStatePath = storageStatePath; return this; } } @@ -494,9 +501,10 @@ public interface Browser { */ public Proxy proxy; /** - * Populates context with given storage state. This method can be used to initialize context with logged-in information obtained via browserContext.storageState(). + * Populates context with given storage state. This method can be used to initialize context with logged-in information obtained via browserContext.storageState([options]). Either a path to the file with saved storage, or an object with the following fields: */ public BrowserContext.StorageState storageState; + public Path storageStatePath; public NewPageOptions withAcceptDownloads(Boolean acceptDownloads) { this.acceptDownloads = acceptDownloads; @@ -584,6 +592,12 @@ public interface Browser { } public NewPageOptions withStorageState(BrowserContext.StorageState storageState) { this.storageState = storageState; + this.storageStatePath = null; + return this; + } + public NewPageOptions withStorageState(Path storageStatePath) { + this.storageState = null; + this.storageStatePath = storageStatePath; return this; } } diff --git a/playwright/src/main/java/com/microsoft/playwright/BrowserContext.java b/playwright/src/main/java/com/microsoft/playwright/BrowserContext.java index 195999bc..aa2bf376 100644 --- a/playwright/src/main/java/com/microsoft/playwright/BrowserContext.java +++ b/playwright/src/main/java/com/microsoft/playwright/BrowserContext.java @@ -16,6 +16,7 @@ package com.microsoft.playwright; +import java.nio.file.Path; import java.util.*; import java.util.function.Consumer; import java.util.function.Predicate; @@ -240,6 +241,17 @@ public interface BrowserContext { return this; } } + class StorageStateOptions { + /** + * The file path to save the storage state to. If {@code path} is a relative path, then it is resolved relative to current working directory. If no path is provided, storage state is still returned, but won't be saved to the disk. + */ + public Path path; + + public StorageStateOptions withPath(Path path) { + this.path = path; + return this; + } + } /** * Adds cookies into this browser context. All pages within this context will have these cookies installed. Cookies can be *

@@ -441,10 +453,13 @@ public interface BrowserContext { * @param offline Whether to emulate network being offline for the browser context. */ void setOffline(boolean offline); + default StorageState storageState() { + return storageState(null); + } /** * Returns storage state for this browser context, contains current cookies and local storage snapshot. */ - StorageState storageState(); + StorageState storageState(StorageStateOptions options); default void unroute(String url) { unroute(url, null); } default void unroute(Pattern url) { unroute(url, null); } default void unroute(Predicate url) { unroute(url, null); } diff --git a/playwright/src/main/java/com/microsoft/playwright/Page.java b/playwright/src/main/java/com/microsoft/playwright/Page.java index 2af6a699..478cf10c 100644 --- a/playwright/src/main/java/com/microsoft/playwright/Page.java +++ b/playwright/src/main/java/com/microsoft/playwright/Page.java @@ -1540,11 +1540,9 @@ public interface Page { Frame frameByUrl(String glob); Frame frameByUrl(Pattern pattern); /** - * Returns frame matching the criteria. Returns {@code null} if no frame matches. + * Returns frame matching the specified criteria. Either {@code name} or {@code url} must be specified. *

* - *

- * Returns frame matching the specified criteria. Either {@code name} or {@code url} must be specified. * @param frameSelector Frame name or other frame lookup options. */ Frame frameByUrl(Predicate predicate); 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 ebbe9b5f..ee4dd4fc 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java @@ -21,6 +21,10 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.microsoft.playwright.*; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Files; import java.util.*; import java.util.function.Consumer; import java.util.function.Predicate; @@ -242,9 +246,20 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext { } @Override - public StorageState storageState() { + public StorageState storageState(StorageStateOptions options) { JsonElement json = sendMessage("storageState"); - return gson().fromJson(json, StorageState.class); + StorageState storageState = gson().fromJson(json, StorageState.class); + if (options != null && options.path != null) { + try { + Files.createDirectories(options.path.getParent()); + try (FileWriter writer = new FileWriter(options.path.toFile())) { + writer.write(json.toString()); + } + } catch (IOException e) { + throw new PlaywrightException("Failed to write storage state to file", e); + } + } + return storageState; } @Override diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserImpl.java index 4dec9c72..79553a1a 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserImpl.java @@ -20,6 +20,9 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.microsoft.playwright.*; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -74,6 +77,14 @@ class BrowserImpl extends ChannelOwner implements Browser { if (options == null) { options = new NewContextOptions(); } + if (options.storageStatePath != null) { + try (FileReader reader = new FileReader(options.storageStatePath.toFile())) { + options.storageState = gson().fromJson(reader, BrowserContext.StorageState.class); + options.storageStatePath = null; + } catch (IOException e) { + throw new PlaywrightException("Failed to read storage state from file", e); + } + } JsonObject params = gson().toJsonTree(options).getAsJsonObject(); if (options.extraHTTPHeaders != null) { params.remove("extraHTTPHeaders"); diff --git a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextStorageState.java b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextStorageState.java index 62e3fe22..6e9d1a19 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextStorageState.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextStorageState.java @@ -16,8 +16,14 @@ 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.io.TempDir; +import java.io.FileReader; +import java.io.IOException; +import java.nio.file.Path; import java.util.Arrays; import static com.microsoft.playwright.Utils.assertJsonEquals; @@ -68,4 +74,57 @@ public class TestBrowserContextStorageState extends TestBase { assertEquals(mapOf("name1", "value1"), localStorage); context.close(); } + + @Test + void shouldRoundTripThroughTheFile(@TempDir Path tempDir) throws IOException { + Page page1 = context.newPage(); + page1.route("**/*", route -> { + route.fulfill(new Route.FulfillResponse().withBody("")); + }); + page1.navigate("https://www.example.com"); + page1.evaluate("() => {\n" + + " localStorage['name1'] = 'value1';\n" + + " document.cookie = 'username=John Doe';\n" + + " return document.cookie;\n" + + "}"); + Path path = tempDir.resolve("storage-state.json"); + BrowserContext.StorageState state = context.storageState(new BrowserContext.StorageStateOptions().withPath(path)); + JsonObject expected = new Gson().fromJson( + "{\n" + + " \"cookies\":[\n" + + " { \n" + + " \"name\":\"username\",\n" + + " \"value\":\"John Doe\",\n" + + " \"domain\":\"www.example.com\",\n" + + " \"path\":\"/\",\n" + + " \"expires\":-1,\n" + + " \"httpOnly\":false,\n" + + " \"secure\":false,\n" + + " \"sameSite\":\"None\"\n" + + " }],\n" + + " \"origins\":[\n" + + " {\n" + + " \"origin\":\"https://www.example.com\",\n" + + " \"localStorage\":[\n" + + " {\n" + + " \"name\":\"name1\",\n" + + " \"value\":\"value1\"\n" + + " }]\n" + + " }]\n" + + "}\n", JsonObject.class); + try (FileReader reader = new FileReader(path.toFile())) { + assertEquals(expected, new Gson().fromJson(reader, JsonObject.class)); + } + BrowserContext context2 = browser.newContext(new Browser.NewContextOptions().withStorageState(path)); + Page page2 = context2.newPage(); + page2.route("**/*", route -> { + route.fulfill(new Route.FulfillResponse().withBody("")); + }); + page2.navigate("https://www.example.com"); + Object localStorage = page2.evaluate("window.localStorage"); + assertEquals(mapOf("name1", "value1"), localStorage); + Object cookie = page2.evaluate("document.cookie"); + assertEquals("username=John Doe", cookie); + context2.close(); + } } diff --git a/scripts/CLI_VERSION b/scripts/CLI_VERSION index f8f35e0a..1ccb5ad0 100644 --- a/scripts/CLI_VERSION +++ b/scripts/CLI_VERSION @@ -1 +1 @@ -0.170.0-next.1607623793189 +0.170.0-next.1608058598043 diff --git a/scripts/generate_api.sh b/scripts/generate_api.sh index db77cb9a..cfb13fee 100755 --- a/scripts/generate_api.sh +++ b/scripts/generate_api.sh @@ -6,8 +6,10 @@ set +x trap "cd $(pwd -P)" EXIT cd "$(dirname $0)/.." -echo "Updating api.json" -./driver-bundle/src/main/resources/driver/linux/playwright-cli print-api-json > ./api-generator/src/main/resources/api.json +PLAYWRIGHT_CLI=./driver-bundle/src/main/resources/driver/linux/playwright-cli +echo "Updating api.json from $($PLAYWRIGHT_CLI --version)" + +$PLAYWRIGHT_CLI print-api-json > ./api-generator/src/main/resources/api.json mvn compile -projects api-generator --no-transfer-progress