From 5f578aae4116e0d877f5f69589885ca833a85734 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Thu, 22 Oct 2020 22:41:34 -0700 Subject: [PATCH] feat: implement {Page,ElementHandle}.screenshot methods (#36) --- README.md | 8 +-- .../playwright/tools/ApiGenerator.java | 2 +- .../com/microsoft/playwright/tools/Types.java | 11 ++++ .../microsoft/playwright/ChromiumBrowser.java | 5 +- .../microsoft/playwright/ElementHandle.java | 4 +- .../java/com/microsoft/playwright/Frame.java | 8 +-- .../java/com/microsoft/playwright/Page.java | 16 +++--- .../java/com/microsoft/playwright/Route.java | 5 +- .../microsoft/playwright/example/Main.java | 8 +-- .../playwright/impl/ElementHandleImpl.java | 40 ++++++++++++-- .../microsoft/playwright/impl/FrameImpl.java | 8 +-- .../microsoft/playwright/impl/PageImpl.java | 33 +++++++++++- .../com/microsoft/playwright/impl/Utils.java | 18 +++++++ .../playwright/TestPageScreenshot.java | 52 +++++++++++++++++++ 14 files changed, 182 insertions(+), 36 deletions(-) create mode 100644 playwright/src/test/java/com/microsoft/playwright/TestPageScreenshot.java diff --git a/README.md b/README.md index 4ee8ba17..c8afcac6 100644 --- a/README.md +++ b/README.md @@ -15,16 +15,18 @@ package com.microsoft.playwright.example; import com.microsoft.playwright.*; +import java.io.File; + public class Main { public static void main(String[] args) { Playwright playwright = Playwright.create(); - Browser browser = playwright.chromium().launch( - new BrowserType.LaunchOptions().withHeadless(false)); + Browser browser = playwright.chromium().launch(); BrowserContext context = browser.newContext( new Browser.NewContextOptions().withViewport(800, 600)); Page page = context.newPage(); - page.navigate("https://webkit.org", null); + page.navigate("https://webkit.org"); page.click("text=check feature status"); + page.screenshot(new Page.ScreenshotOptions().withPath(new File("s.png"))); browser.close(); } } 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 d37d3571..3e41d004 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 @@ -547,7 +547,7 @@ class Interface extends TypeDefinition { if (jsonName.equals("Route")) { output.add("import java.nio.charset.StandardCharsets;"); } - if (asList("Page", "Frame", "ElementHandle", "FileChooser").contains(jsonName)) { + if (asList("Page", "Frame", "ElementHandle", "FileChooser", "ChromiumBrowser", "Route").contains(jsonName)) { output.add("import java.io.File;"); } output.add("import java.util.*;"); 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 6e46d380..c726f7b5 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 @@ -94,6 +94,17 @@ class Types { add("Mouse.up.options.button", "\"left\"|\"middle\"|\"right\"", "Button", new Empty()); add("BrowserType.launchPersistentContext.options.colorScheme", "\"dark\"|\"light\"|\"no-preference\"", "ColorScheme"); + // File + add("Page.addScriptTag.options.path", "string", "File"); + add("Page.addStyleTag.options.path", "string", "File"); + add("Page.pdf.options.path", "string", "File"); + add("Page.screenshot.options.path", "string", "File"); + add("Frame.addScriptTag.options.path", "string", "File"); + add("Frame.addStyleTag.options.path", "string", "File"); + add("ElementHandle.screenshot.options.path", "string", "File"); + add("Route.fulfill.response.path", "string", "File"); + add("ChromiumBrowser.startTracing.options.path", "string", "File"); + // Route add("BrowserContext.route.handler", "function(Route, Request)", "BiConsumer"); add("BrowserContext.unroute.handler", "function(Route, Request)", "BiConsumer"); diff --git a/playwright/src/main/java/com/microsoft/playwright/ChromiumBrowser.java b/playwright/src/main/java/com/microsoft/playwright/ChromiumBrowser.java index 53fed925..63a6a088 100644 --- a/playwright/src/main/java/com/microsoft/playwright/ChromiumBrowser.java +++ b/playwright/src/main/java/com/microsoft/playwright/ChromiumBrowser.java @@ -16,15 +16,16 @@ package com.microsoft.playwright; +import java.io.File; import java.util.*; public interface ChromiumBrowser extends Browser { class StartTracingOptions { - public String path; + public File path; public Boolean screenshots; public List categories; - public StartTracingOptions withPath(String path) { + public StartTracingOptions withPath(File path) { this.path = path; return this; } diff --git a/playwright/src/main/java/com/microsoft/playwright/ElementHandle.java b/playwright/src/main/java/com/microsoft/playwright/ElementHandle.java index 5fe4447a..3e1d3058 100644 --- a/playwright/src/main/java/com/microsoft/playwright/ElementHandle.java +++ b/playwright/src/main/java/com/microsoft/playwright/ElementHandle.java @@ -240,13 +240,13 @@ public interface ElementHandle extends JSHandle { } class ScreenshotOptions { public enum Type { JPEG, PNG } - public String path; + public File path; public Type type; public Integer quality; public Boolean omitBackground; public Integer timeout; - public ScreenshotOptions withPath(String path) { + public ScreenshotOptions withPath(File path) { this.path = path; return this; } diff --git a/playwright/src/main/java/com/microsoft/playwright/Frame.java b/playwright/src/main/java/com/microsoft/playwright/Frame.java index 8f07bce5..d31c8e3d 100644 --- a/playwright/src/main/java/com/microsoft/playwright/Frame.java +++ b/playwright/src/main/java/com/microsoft/playwright/Frame.java @@ -25,7 +25,7 @@ public interface Frame { enum LoadState { DOMCONTENTLOADED, LOAD, NETWORKIDLE } class AddScriptTagOptions { public String url; - public String path; + public File path; public String content; public String type; @@ -33,7 +33,7 @@ public interface Frame { this.url = url; return this; } - public AddScriptTagOptions withPath(String path) { + public AddScriptTagOptions withPath(File path) { this.path = path; return this; } @@ -48,14 +48,14 @@ public interface Frame { } class AddStyleTagOptions { public String url; - public String path; + public File path; public String content; public AddStyleTagOptions withUrl(String url) { this.url = url; return this; } - public AddStyleTagOptions withPath(String path) { + public AddStyleTagOptions withPath(File path) { this.path = path; return this; } diff --git a/playwright/src/main/java/com/microsoft/playwright/Page.java b/playwright/src/main/java/com/microsoft/playwright/Page.java index e150d1e3..a5fa09ac 100644 --- a/playwright/src/main/java/com/microsoft/playwright/Page.java +++ b/playwright/src/main/java/com/microsoft/playwright/Page.java @@ -108,7 +108,7 @@ public interface Page { } class AddScriptTagOptions { public String url; - public String path; + public File path; public String content; public String type; @@ -116,7 +116,7 @@ public interface Page { this.url = url; return this; } - public AddScriptTagOptions withPath(String path) { + public AddScriptTagOptions withPath(File path) { this.path = path; return this; } @@ -131,14 +131,14 @@ public interface Page { } class AddStyleTagOptions { public String url; - public String path; + public File path; public String content; public AddStyleTagOptions withUrl(String url) { this.url = url; return this; } - public AddStyleTagOptions withPath(String path) { + public AddStyleTagOptions withPath(File path) { this.path = path; return this; } @@ -476,7 +476,7 @@ public interface Page { return this; } } - public String path; + public File path; public Integer scale; public Boolean displayHeaderFooter; public String headerTemplate; @@ -490,7 +490,7 @@ public interface Page { public Margin margin; public Boolean preferCSSPageSize; - public PdfOptions withPath(String path) { + public PdfOptions withPath(File path) { this.path = path; return this; } @@ -605,7 +605,7 @@ public interface Page { return this; } } - public String path; + public File path; public Type type; public Integer quality; public Boolean fullPage; @@ -613,7 +613,7 @@ public interface Page { public Boolean omitBackground; public Integer timeout; - public ScreenshotOptions withPath(String path) { + public ScreenshotOptions withPath(File path) { this.path = path; return this; } diff --git a/playwright/src/main/java/com/microsoft/playwright/Route.java b/playwright/src/main/java/com/microsoft/playwright/Route.java index cc37d3f9..8303f2bd 100644 --- a/playwright/src/main/java/com/microsoft/playwright/Route.java +++ b/playwright/src/main/java/com/microsoft/playwright/Route.java @@ -17,6 +17,7 @@ package com.microsoft.playwright; import java.nio.charset.StandardCharsets; +import java.io.File; import java.util.*; public interface Route { @@ -47,7 +48,7 @@ public interface Route { public Map headers; public String contentType; public String body; - public String path; + public File path; public FulfillResponse withStatus(Integer status) { this.status = status; @@ -65,7 +66,7 @@ public interface Route { this.body = body; return this; } - public FulfillResponse withPath(String path) { + public FulfillResponse withPath(File path) { this.path = path; return this; } diff --git a/playwright/src/main/java/com/microsoft/playwright/example/Main.java b/playwright/src/main/java/com/microsoft/playwright/example/Main.java index a81e514e..0cea90d8 100644 --- a/playwright/src/main/java/com/microsoft/playwright/example/Main.java +++ b/playwright/src/main/java/com/microsoft/playwright/example/Main.java @@ -17,16 +17,18 @@ package com.microsoft.playwright.example; import com.microsoft.playwright.*; +import java.io.File; + public class Main { public static void main(String[] args) { Playwright playwright = Playwright.create(); - Browser browser = playwright.chromium().launch( - new BrowserType.LaunchOptions().withHeadless(false).withSlowMo(1000)); + Browser browser = playwright.chromium().launch(); BrowserContext context = browser.newContext( new Browser.NewContextOptions().withViewport(800, 600)); Page page = context.newPage(); - page.navigate("https://webkit.org", null); + page.navigate("https://webkit.org"); page.click("text=check feature status"); + page.screenshot(new Page.ScreenshotOptions().withPath(new File("s.png"))); browser.close(); } } diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/ElementHandleImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/ElementHandleImpl.java index a1e16d91..60207a65 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/ElementHandleImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/ElementHandleImpl.java @@ -20,13 +20,14 @@ import com.google.gson.Gson; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; -import com.microsoft.playwright.Deferred; -import com.microsoft.playwright.ElementHandle; -import com.microsoft.playwright.FileChooser; -import com.microsoft.playwright.Frame; +import com.microsoft.playwright.*; +import java.io.DataOutputStream; import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; import java.util.ArrayList; +import java.util.Base64; import java.util.List; import static com.microsoft.playwright.impl.Serialization.deserialize; @@ -232,9 +233,38 @@ class ElementHandleImpl extends JSHandleImpl implements ElementHandle { sendMessage("press", params); } + private static String toProtocol(ScreenshotOptions.Type type) { + return type.toString().toLowerCase(); + } + @Override public byte[] screenshot(ScreenshotOptions options) { - return new byte[0]; + if (options == null) { + options = new ScreenshotOptions(); + } + if (options.type == null) { + options.type = ScreenshotOptions.Type.PNG; + if (options.path != null) { + int extStart = options.path.getName().lastIndexOf('.'); + if (extStart != -1) { + String extension = options.path.getName().substring(extStart).toLowerCase(); + if (".jpeg".equals(extension) || ".jpg".equals(extension)) { + options.type = ScreenshotOptions.Type.JPEG; + } + } + } + } + JsonObject params = new Gson().toJsonTree(options).getAsJsonObject(); + params.remove("type"); + params.addProperty("type", toProtocol(options.type)); + params.remove("path"); + JsonObject json = sendMessage("screenshot", params).getAsJsonObject(); + + byte[] buffer = Base64.getDecoder().decode(json.get("binary").getAsString()); + if (options.path != null) { + Utils.writeToFile(buffer, options.path); + } + return buffer; } @Override diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/FrameImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/FrameImpl.java index 55cde01a..f1cf95c3 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/FrameImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/FrameImpl.java @@ -143,12 +143,12 @@ public class FrameImpl extends ChannelOwner implements Frame { params.remove("path"); byte[] encoded = new byte[0]; try { - encoded = Files.readAllBytes(Paths.get(options.path)); + encoded = Files.readAllBytes(options.path.toPath()); } catch (IOException e) { throw new RuntimeException("Failed to read from file", e); } String content = new String(encoded, StandardCharsets.UTF_8); - content += "//# sourceURL=" + options.path.replace("\n", ""); + content += "//# sourceURL=" + options.path.getPath().replace("\n", ""); params.addProperty("content", content); } JsonElement json = sendMessage("addScriptTag", params); @@ -165,12 +165,12 @@ public class FrameImpl extends ChannelOwner implements Frame { params.remove("path"); byte[] encoded = new byte[0]; try { - encoded = Files.readAllBytes(Paths.get(options.path)); + encoded = Files.readAllBytes(options.path.toPath()); } catch (IOException e) { throw new RuntimeException("Failed to read from file", e); } String content = new String(encoded, StandardCharsets.UTF_8); - content += "/*# sourceURL=" + options.path.replace("\n", "") + "*/"; + content += "/*# sourceURL=" + options.path.getPath().replace("\n", "") + "*/"; params.addProperty("content", content); } JsonElement json = sendMessage("addStyleTag", params); diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/PageImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/PageImpl.java index edd4a263..d2815d50 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/PageImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/PageImpl.java @@ -21,7 +21,7 @@ import com.google.gson.JsonArray; import com.google.gson.JsonObject; import com.microsoft.playwright.*; -import java.io.File; +import java.io.*; import java.nio.file.Watchable; import java.util.*; import java.util.function.BiConsumer; @@ -535,9 +535,38 @@ public class PageImpl extends ChannelOwner implements Page { } } + private static String toProtocol(ScreenshotOptions.Type type) { + return type.toString().toLowerCase(); + } + @Override public byte[] screenshot(ScreenshotOptions options) { - return new byte[0]; + if (options == null) { + options = new ScreenshotOptions(); + } + if (options.type == null) { + options.type = ScreenshotOptions.Type.PNG; + if (options.path != null) { + int extStart = options.path.getName().lastIndexOf('.'); + if (extStart != -1) { + String extension = options.path.getName().substring(extStart).toLowerCase(); + if (".jpeg".equals(extension) || ".jpg".equals(extension)) { + options.type = ScreenshotOptions.Type.JPEG; + } + } + } + } + JsonObject params = new Gson().toJsonTree(options).getAsJsonObject(); + params.remove("type"); + params.addProperty("type", toProtocol(options.type)); + params.remove("path"); + JsonObject json = sendMessage("screenshot", params).getAsJsonObject(); + + byte[] buffer = Base64.getDecoder().decode(json.get("binary").getAsString()); + if (options.path != null) { + Utils.writeToFile(buffer, options.path); + } + return buffer; } @Override diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/Utils.java b/playwright/src/main/java/com/microsoft/playwright/impl/Utils.java index 8c4ee666..50b6d763 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/Utils.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/Utils.java @@ -19,7 +19,9 @@ package com.microsoft.playwright.impl; import com.google.gson.Gson; import com.microsoft.playwright.FileChooser; +import java.io.DataOutputStream; import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; import java.nio.file.Files; import java.util.*; @@ -117,4 +119,20 @@ class Utils { } return payloads.toArray(new FileChooser.FilePayload[0]); } + + static void writeToFile(byte[] buffer, File path) { + File dir = path.getParentFile(); + if (dir != null) { + if (!dir.exists()) { + if (!dir.mkdirs()) { + throw new RuntimeException("Failed to create parent directory: " + dir.getPath()); + } + } + } + try (DataOutputStream out = new DataOutputStream(new FileOutputStream(path));) { + out.write(buffer); + } catch (IOException e) { + throw new RuntimeException("Failed to write to file", e); + } + } } diff --git a/playwright/src/test/java/com/microsoft/playwright/TestPageScreenshot.java b/playwright/src/test/java/com/microsoft/playwright/TestPageScreenshot.java new file mode 100644 index 00000000..5dba0a1f --- /dev/null +++ b/playwright/src/test/java/com/microsoft/playwright/TestPageScreenshot.java @@ -0,0 +1,52 @@ +/** + * 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.Test; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.IOException; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +// TODO: suite.skip(browserName === "firefox" && headful"); +public class TestPageScreenshot extends TestBase { + @Test + void shouldWork() throws IOException { + page.setViewportSize(500, 500); + page.navigate(server.PREFIX + "/grid.html"); + byte[] screenshot = page.screenshot(); + BufferedImage image = ImageIO.read(new ByteArrayInputStream(screenshot)); + assertEquals(500, image.getWidth()); + assertEquals(500, image.getHeight()); +// expect(screenshot).toMatchSnapshot("screenshot-sanity.png"); + } + + @Test + void shouldClipRect() throws IOException { + page.setViewportSize(500, 500); + page.navigate(server.PREFIX + "/grid.html"); + byte[] screenshot = page.screenshot(new Page.ScreenshotOptions() + .setClip().withX(50).withY(100).withWidth(150).withHeight(100).done()); + BufferedImage image = ImageIO.read(new ByteArrayInputStream(screenshot)); + assertEquals(150, image.getWidth()); + assertEquals(100, image.getHeight()); +// expect(screenshot).toMatchSnapshot("screenshot-clip-rect.png"); + } +}