From 6d5724f16659fe78fdd0be4d1ffeefbfb0d9c176 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Thu, 22 Oct 2020 10:57:36 -0700 Subject: [PATCH] feat: support FileChooser (#33) --- .../playwright/tools/ApiGenerator.java | 49 +++++++ .../microsoft/playwright/ElementHandle.java | 13 +- .../com/microsoft/playwright/FileChooser.java | 25 +++- .../java/com/microsoft/playwright/Frame.java | 13 +- .../java/com/microsoft/playwright/Page.java | 13 +- .../playwright/impl/BrowserContextImpl.java | 2 +- .../playwright/impl/ChannelOwner.java | 1 + .../microsoft/playwright/impl/Connection.java | 3 - .../playwright/impl/ElementHandleImpl.java | 21 ++- .../playwright/impl/FileChooserImpl.java | 30 ++++- .../microsoft/playwright/impl/FrameImpl.java | 17 ++- .../playwright/impl/ListenerCollection.java | 31 +---- .../microsoft/playwright/impl/PageImpl.java | 61 +++++++-- .../playwright/impl/Serialization.java | 14 ++ .../com/microsoft/playwright/impl/Utils.java | 27 ++++ .../playwright/impl/WaitableEvent.java | 15 ++- .../microsoft/playwright/impl/WorkerImpl.java | 2 +- .../playwright/TestPageSetInputFiles.java | 122 ++++++++++++++++++ 18 files changed, 381 insertions(+), 78 deletions(-) create mode 100644 playwright/src/test/java/com/microsoft/playwright/TestPageSetInputFiles.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 88e7bfdb..ff983e9f 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 @@ -263,6 +263,38 @@ class Method extends Element { "void unroute(Pattern url, BiConsumer handler);", "void unroute(Predicate url, BiConsumer handler);", }); + customSignature.put("FileChooser.setFiles", new String[]{ + "default void setFiles(File file) { setFiles(file, null); }", + "default void setFiles(File file, SetFilesOptions options) { setFiles(new File[]{ file }, options); }", + "default void setFiles(File[] files) { setFiles(files, null); }", + "void setFiles(File[] files, SetFilesOptions options);", + "default void setFiles(FileChooser.FilePayload file) { setFiles(file, null); }", + "default void setFiles(FileChooser.FilePayload file, SetFilesOptions options) { setFiles(new FileChooser.FilePayload[]{ file }, options); }", + "default void setFiles(FileChooser.FilePayload[] files) { setFiles(files, null); }", + "void setFiles(FileChooser.FilePayload[] files, SetFilesOptions options);", + }); + customSignature.put("ElementHandle.setInputFiles", new String[]{ + "default void setInputFiles(File file) { setInputFiles(file, null); }", + "default void setInputFiles(File file, SetInputFilesOptions options) { setInputFiles(new File[]{ file }, options); }", + "default void setInputFiles(File[] files) { setInputFiles(files, null); }", + "void setInputFiles(File[] files, SetInputFilesOptions options);", + "default void setInputFiles(FileChooser.FilePayload file) { setInputFiles(file, null); }", + "default void setInputFiles(FileChooser.FilePayload file, SetInputFilesOptions options) { setInputFiles(new FileChooser.FilePayload[]{ file }, options); }", + "default void setInputFiles(FileChooser.FilePayload[] files) { setInputFiles(files, null); }", + "void setInputFiles(FileChooser.FilePayload[] files, SetInputFilesOptions options);", + }); + String[] setInputFilesWithSelector = { + "default void setInputFiles(String selector, File file) { setInputFiles(selector, file, null); }", + "default void setInputFiles(String selector, File file, SetInputFilesOptions options) { setInputFiles(selector, new File[]{ file }, options); }", + "default void setInputFiles(String selector, File[] files) { setInputFiles(selector, files, null); }", + "void setInputFiles(String selector, File[] files, SetInputFilesOptions options);", + "default void setInputFiles(String selector, FileChooser.FilePayload file) { setInputFiles(selector, file, null); }", + "default void setInputFiles(String selector, FileChooser.FilePayload file, SetInputFilesOptions options) { setInputFiles(selector, new FileChooser.FilePayload[]{ file }, options); }", + "default void setInputFiles(String selector, FileChooser.FilePayload[] files) { setInputFiles(selector, files, null); }", + "void setInputFiles(String selector, FileChooser.FilePayload[] files, SetInputFilesOptions options);", + }; + customSignature.put("Page.setInputFiles", setInputFilesWithSelector); + customSignature.put("Frame.setInputFiles", setInputFilesWithSelector); } Method(TypeDefinition parent, JsonObject jsonElement) { @@ -498,6 +530,9 @@ class Interface extends TypeDefinition { if (jsonName.equals("Route")) { output.add("import java.nio.charset.StandardCharsets;"); } + if (Arrays.asList("Page", "Frame", "ElementHandle", "FileChooser").contains(jsonName)) { + output.add("import java.io.File;"); + } output.add("import java.util.*;"); if (Arrays.asList("Page", "BrowserContext").contains(jsonName)) { output.add("import java.util.function.BiConsumer;"); @@ -632,6 +667,20 @@ class Interface extends TypeDefinition { output.add(offset + "}"); break; } + case "FileChooser": { + output.add(offset + "class FilePayload {"); + output.add(offset + " public final String name;"); + output.add(offset + " public final String mimeType;"); + output.add(offset + " public final byte[] buffer;"); + output.add(""); + output.add(offset + " public FilePayload(String name, String mimeType, byte[] buffer) {"); + output.add(offset + " this.name = name;"); + output.add(offset + " this.mimeType = mimeType;"); + output.add(offset + " this.buffer = buffer;"); + output.add(offset + " }"); + output.add(offset + "}"); + break; + } default: return; } output.add(""); diff --git a/playwright/src/main/java/com/microsoft/playwright/ElementHandle.java b/playwright/src/main/java/com/microsoft/playwright/ElementHandle.java index f974401c..5fe4447a 100644 --- a/playwright/src/main/java/com/microsoft/playwright/ElementHandle.java +++ b/playwright/src/main/java/com/microsoft/playwright/ElementHandle.java @@ -16,6 +16,7 @@ package com.microsoft.playwright; +import java.io.File; import java.util.*; public interface ElementHandle extends JSHandle { @@ -427,10 +428,14 @@ public interface ElementHandle extends JSHandle { selectText(null); } void selectText(SelectTextOptions options); - default void setInputFiles(String files) { - setInputFiles(files, null); - } - void setInputFiles(String files, SetInputFilesOptions options); + default void setInputFiles(File file) { setInputFiles(file, null); } + default void setInputFiles(File file, SetInputFilesOptions options) { setInputFiles(new File[]{ file }, options); } + default void setInputFiles(File[] files) { setInputFiles(files, null); } + void setInputFiles(File[] files, SetInputFilesOptions options); + default void setInputFiles(FileChooser.FilePayload file) { setInputFiles(file, null); } + default void setInputFiles(FileChooser.FilePayload file, SetInputFilesOptions options) { setInputFiles(new FileChooser.FilePayload[]{ file }, options); } + default void setInputFiles(FileChooser.FilePayload[] files) { setInputFiles(files, null); } + void setInputFiles(FileChooser.FilePayload[] files, SetInputFilesOptions options); String textContent(); String toString(); default void type(String text) { diff --git a/playwright/src/main/java/com/microsoft/playwright/FileChooser.java b/playwright/src/main/java/com/microsoft/playwright/FileChooser.java index 2c507b8f..ebcdd202 100644 --- a/playwright/src/main/java/com/microsoft/playwright/FileChooser.java +++ b/playwright/src/main/java/com/microsoft/playwright/FileChooser.java @@ -16,9 +16,22 @@ package com.microsoft.playwright; +import java.io.File; import java.util.*; public interface FileChooser { + class FilePayload { + public final String name; + public final String mimeType; + public final byte[] buffer; + + public FilePayload(String name, String mimeType, byte[] buffer) { + this.name = name; + this.mimeType = mimeType; + this.buffer = buffer; + } + } + class SetFilesOptions { public Boolean noWaitAfter; public Integer timeout; @@ -35,9 +48,13 @@ public interface FileChooser { ElementHandle element(); boolean isMultiple(); Page page(); - default void setFiles(String files) { - setFiles(files, null); - } - void setFiles(String files, SetFilesOptions options); + default void setFiles(File file) { setFiles(file, null); } + default void setFiles(File file, SetFilesOptions options) { setFiles(new File[]{ file }, options); } + default void setFiles(File[] files) { setFiles(files, null); } + void setFiles(File[] files, SetFilesOptions options); + default void setFiles(FileChooser.FilePayload file) { setFiles(file, null); } + default void setFiles(FileChooser.FilePayload file, SetFilesOptions options) { setFiles(new FileChooser.FilePayload[]{ file }, options); } + default void setFiles(FileChooser.FilePayload[] files) { setFiles(files, null); } + void setFiles(FileChooser.FilePayload[] files, SetFilesOptions options); } diff --git a/playwright/src/main/java/com/microsoft/playwright/Frame.java b/playwright/src/main/java/com/microsoft/playwright/Frame.java index c38a2253..8f07bce5 100644 --- a/playwright/src/main/java/com/microsoft/playwright/Frame.java +++ b/playwright/src/main/java/com/microsoft/playwright/Frame.java @@ -16,6 +16,7 @@ package com.microsoft.playwright; +import java.io.File; import java.util.*; import java.util.function.Predicate; import java.util.regex.Pattern; @@ -567,10 +568,14 @@ public interface Frame { setContent(html, null); } void setContent(String html, SetContentOptions options); - default void setInputFiles(String selector, String files) { - setInputFiles(selector, files, null); - } - void setInputFiles(String selector, String files, SetInputFilesOptions options); + default void setInputFiles(String selector, File file) { setInputFiles(selector, file, null); } + default void setInputFiles(String selector, File file, SetInputFilesOptions options) { setInputFiles(selector, new File[]{ file }, options); } + default void setInputFiles(String selector, File[] files) { setInputFiles(selector, files, null); } + void setInputFiles(String selector, File[] files, SetInputFilesOptions options); + default void setInputFiles(String selector, FileChooser.FilePayload file) { setInputFiles(selector, file, null); } + default void setInputFiles(String selector, FileChooser.FilePayload file, SetInputFilesOptions options) { setInputFiles(selector, new FileChooser.FilePayload[]{ file }, options); } + default void setInputFiles(String selector, FileChooser.FilePayload[] files) { setInputFiles(selector, files, null); } + void setInputFiles(String selector, FileChooser.FilePayload[] files, SetInputFilesOptions options); default String textContent(String selector) { return textContent(selector, null); } diff --git a/playwright/src/main/java/com/microsoft/playwright/Page.java b/playwright/src/main/java/com/microsoft/playwright/Page.java index 9c275af0..8df40f24 100644 --- a/playwright/src/main/java/com/microsoft/playwright/Page.java +++ b/playwright/src/main/java/com/microsoft/playwright/Page.java @@ -16,6 +16,7 @@ package com.microsoft.playwright; +import java.io.File; import java.util.*; import java.util.function.BiConsumer; import java.util.function.Predicate; @@ -929,10 +930,14 @@ public interface Page { void setDefaultNavigationTimeout(int timeout); void setDefaultTimeout(int timeout); void setExtraHTTPHeaders(Map headers); - default void setInputFiles(String selector, String files) { - setInputFiles(selector, files, null); - } - void setInputFiles(String selector, String files, SetInputFilesOptions options); + default void setInputFiles(String selector, File file) { setInputFiles(selector, file, null); } + default void setInputFiles(String selector, File file, SetInputFilesOptions options) { setInputFiles(selector, new File[]{ file }, options); } + default void setInputFiles(String selector, File[] files) { setInputFiles(selector, files, null); } + void setInputFiles(String selector, File[] files, SetInputFilesOptions options); + default void setInputFiles(String selector, FileChooser.FilePayload file) { setInputFiles(selector, file, null); } + default void setInputFiles(String selector, FileChooser.FilePayload file, SetInputFilesOptions options) { setInputFiles(selector, new FileChooser.FilePayload[]{ file }, options); } + default void setInputFiles(String selector, FileChooser.FilePayload[] files) { setInputFiles(selector, files, null); } + void setInputFiles(String selector, FileChooser.FilePayload[] files, SetInputFilesOptions options); void setViewportSize(int width, int height); default String textContent(String selector) { return textContent(selector, null); 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 2a94d730..9e720a1b 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java @@ -218,7 +218,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext { @Override public Deferred> waitForEvent(EventType event, String optionsOrPredicate) { - return listeners.waitForEvent(event, connection); + return toDeferred(new WaitableEvent<>(listeners, event)); } private void unroute(UrlMatcher matcher, BiConsumer handler) { diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/ChannelOwner.java b/playwright/src/main/java/com/microsoft/playwright/impl/ChannelOwner.java index 5a766e51..f7e91955 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/ChannelOwner.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/ChannelOwner.java @@ -76,6 +76,7 @@ class ChannelOwner { connection.sendMessageNoWait(guid, method, params); } + @SuppressWarnings("unchecked") Deferred toDeferred(Waitable waitable) { return () -> { while (!waitable.isDone()) { diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/Connection.java b/playwright/src/main/java/com/microsoft/playwright/impl/Connection.java index 570ca049..ec62a48a 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/Connection.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/Connection.java @@ -205,9 +205,6 @@ public class Connection { case "Frame": result = new FrameImpl(parent, type, guid, initializer); break; - case "FileChooser": - result = new FileChooserImpl(parent, type, guid, initializer); - break; case "JSHandle": result = new JSHandleImpl(parent, type, guid, initializer); break; 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 ba631a7b..a1e16d91 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/ElementHandleImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/ElementHandleImpl.java @@ -20,12 +20,17 @@ import com.google.gson.Gson; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; -import com.microsoft.playwright.*; +import com.microsoft.playwright.Deferred; +import com.microsoft.playwright.ElementHandle; +import com.microsoft.playwright.FileChooser; +import com.microsoft.playwright.Frame; +import java.io.File; import java.util.ArrayList; import java.util.List; -import static com.microsoft.playwright.impl.Serialization.*; +import static com.microsoft.playwright.impl.Serialization.deserialize; +import static com.microsoft.playwright.impl.Serialization.serializeArgument; import static com.microsoft.playwright.impl.Utils.isFunctionBody; class ElementHandleImpl extends JSHandleImpl implements ElementHandle { @@ -257,8 +262,18 @@ class ElementHandleImpl extends JSHandleImpl implements ElementHandle { } @Override - public void setInputFiles(String files, SetInputFilesOptions options) { + public void setInputFiles(File[] files, SetInputFilesOptions options) { + setInputFiles(Utils.toFilePayloads(files), options); + } + @Override + public void setInputFiles(FileChooser.FilePayload[] files, SetInputFilesOptions options) { + if (options == null) { + options = new SetInputFilesOptions(); + } + JsonObject params = new Gson().toJsonTree(options).getAsJsonObject(); + params.add("files", Serialization.toJsonArray(files)); + sendMessage("setInputFiles", params); } @Override diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/FileChooserImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/FileChooserImpl.java index c6169b28..822a9d56 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/FileChooserImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/FileChooserImpl.java @@ -16,33 +16,49 @@ package com.microsoft.playwright.impl; +import com.google.gson.Gson; import com.google.gson.JsonObject; import com.microsoft.playwright.ElementHandle; import com.microsoft.playwright.FileChooser; import com.microsoft.playwright.Page; -class FileChooserImpl extends ChannelOwner implements FileChooser { - FileChooserImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) { - super(parent, type, guid, initializer); +import java.io.File; + +import static com.microsoft.playwright.impl.Utils.convertViaJson; + +class FileChooserImpl implements FileChooser { + private final PageImpl page; + private final ElementHandle element; + private final boolean isMultiple; + + FileChooserImpl(PageImpl page, ElementHandle element, boolean isMultiple) { + this.page = page; + this.element = element; + this.isMultiple = isMultiple; } @Override public ElementHandle element() { - return null; + return element; } @Override public boolean isMultiple() { - return false; + return isMultiple; } @Override public Page page() { - return null; + return page; } @Override - public void setFiles(String files, SetFilesOptions options) { + public void setFiles(File[] files, SetFilesOptions options) { + setFiles(Utils.toFilePayloads(files), options); + } + @Override + public void setFiles(FilePayload[] files, SetFilesOptions options) { + element.setInputFiles(files, convertViaJson(options, ElementHandle.SetInputFilesOptions.class)); } } 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 09b1ee0c..c69245cd 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/FrameImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/FrameImpl.java @@ -22,6 +22,7 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.microsoft.playwright.*; +import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -29,8 +30,7 @@ import java.nio.file.Paths; import java.util.*; import static com.microsoft.playwright.Frame.LoadState.*; -import static com.microsoft.playwright.impl.Serialization.deserialize; -import static com.microsoft.playwright.impl.Serialization.serializeArgument; +import static com.microsoft.playwright.impl.Serialization.*; import static com.microsoft.playwright.impl.Utils.isFunctionBody; public class FrameImpl extends ChannelOwner implements Frame { @@ -388,8 +388,19 @@ public class FrameImpl extends ChannelOwner implements Frame { } @Override - public void setInputFiles(String selector, String files, SetInputFilesOptions options) { + public void setInputFiles(String selector, File[] files, SetInputFilesOptions options) { + setInputFiles(selector, Utils.toFilePayloads(files), options); + } + @Override + public void setInputFiles(String selector, FileChooser.FilePayload[] files, SetInputFilesOptions options) { + if (options == null) { + options = new SetInputFilesOptions(); + } + JsonObject params = new Gson().toJsonTree(options).getAsJsonObject(); + params.addProperty("selector", selector); + params.add("files", toJsonArray(files)); + sendMessage("setInputFiles", params); } @Override diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/ListenerCollection.java b/playwright/src/main/java/com/microsoft/playwright/impl/ListenerCollection.java index 72a332cf..314566bb 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/ListenerCollection.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/ListenerCollection.java @@ -71,34 +71,7 @@ class ListenerCollection { } } - private class DeferredEvent implements Listener, Deferred> { - private final EventType type; - private final Connection connection; - private Event event; - - DeferredEvent(EventType type, Connection connection) { - add(type, this); - this.type = type; - this.connection = connection; - } - - @Override - public void handle(Event e) { - event = e; - remove(type, this); - } - - @Override - public Event get() { - while (event == null) { - connection.processOneMessage(); - } - return event; - } + boolean hasListeners(EventType type) { + return listeners.containsKey(type); } - - Deferred> waitForEvent(EventType event, Connection connection) { - return new DeferredEvent(event, connection); - } - } 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 cbe7b803..007c3e9c 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/PageImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/PageImpl.java @@ -21,6 +21,8 @@ import com.google.gson.JsonArray; import com.google.gson.JsonObject; import com.microsoft.playwright.*; +import java.io.File; +import java.nio.file.Watchable; import java.util.*; import java.util.function.BiConsumer; import java.util.function.Predicate; @@ -83,7 +85,8 @@ public class PageImpl extends ChannelOwner implements Page { listeners.notify(EventType.DOWNLOAD, download); } else if ("fileChooser".equals(event)) { String guid = params.getAsJsonObject("element").get("guid").getAsString(); - FileChooser fileChooser = connection.getExistingObject(guid); + ElementHandle elementHandle = connection.getExistingObject(guid); + FileChooser fileChooser = new FileChooserImpl(this, elementHandle, params.get("isMultiple").getAsBoolean()); listeners.notify(EventType.FILECHOOSER, fileChooser); } else if ("bindingCall".equals(event)) { String guid = params.getAsJsonObject("binding").get("guid").getAsString(); @@ -162,14 +165,34 @@ public class PageImpl extends ChannelOwner implements Page { } } + private void willAddFileChooserListener() { + if (!listeners.hasListeners(EventType.FILECHOOSER)) { + updateFileChooserInterception(true); + } + } + + private void didRemoveFileChooserListener() { + if (!listeners.hasListeners(EventType.FILECHOOSER)) { + updateFileChooserInterception(false); + } + } + + private void updateFileChooserInterception(boolean enabled) { + JsonObject params = new JsonObject(); + params.addProperty("intercepted", enabled); + sendMessage("setFileChooserInterceptedNoReply", params); + } + @Override public void addListener(EventType type, Listener listener) { + willAddFileChooserListener(); listeners.add(type, listener); } @Override public void removeListener(EventType type, Listener listener) { listeners.remove(type, listener); + didRemoveFileChooserListener(); } @Override @@ -554,7 +577,12 @@ public class PageImpl extends ChannelOwner implements Page { } @Override - public void setInputFiles(String selector, String files, SetInputFilesOptions options) { + public void setInputFiles(String selector, File[] files, SetInputFilesOptions options) { + mainFrame.setInputFiles(selector, files, convertViaJson(options, Frame.SetInputFilesOptions.class)); + } + + @Override + public void setInputFiles(String selector, FileChooser.FilePayload[] files, SetInputFilesOptions options) { mainFrame.setInputFiles(selector, files, convertViaJson(options, Frame.SetInputFilesOptions.class)); } @@ -627,7 +655,20 @@ public class PageImpl extends ChannelOwner implements Page { @Override public Deferred> waitForEvent(EventType event, String optionsOrPredicate) { - return listeners.waitForEvent(event, connection); + Waitable> waitable; + if (event == EventType.FILECHOOSER) { + willAddFileChooserListener(); + waitable = new WaitableEvent(listeners, event) { + @Override + public void dispose() { + super.dispose(); + didRemoveFileChooserListener(); + } + }; + } else { + waitable = new WaitableEvent<>(listeners, event); + } + return toDeferred(waitable); } @Override @@ -680,19 +721,21 @@ public class PageImpl extends ChannelOwner implements Page { } } - private class WaitableFrameDetach extends WaitableEvent { + private class WaitableFrameDetach extends WaitableEvent { WaitableFrameDetach(Frame frame) { - super(listeners, EventType.FRAMEDETACHED, event -> frame.equals(event.data())); + super(PageImpl.this.listeners, EventType.FRAMEDETACHED, event -> frame.equals(event.data())); } @Override - public R get() { + public Event get() { throw new RuntimeException("Navigating frame was detached"); } } + @SuppressWarnings("unchecked") Waitable createWaitableFrameDetach(Frame frame) { - return new WaitableFrameDetach(frame); + // It is safe to cast as WaitableFrameDetach.get() always throws. + return (Waitable) new WaitableFrameDetach(frame); } Waitable createWaitForCloseHelper() { @@ -748,7 +791,7 @@ public class PageImpl extends ChannelOwner implements Page { return true; } return urlOrPredicate.equals(((Request) e.data()).url()); - })); + }).apply(event -> (Request) event.data())); waitables.add(createWaitForCloseHelper()); if (options != null && options.timeout != null) { waitables.add(new WaitableTimeout<>(options.timeout)); @@ -764,7 +807,7 @@ public class PageImpl extends ChannelOwner implements Page { return true; } return urlOrPredicate.equals(((Response) e.data()).url()); - })); + }).apply(event -> (Response) event.data())); waitables.add(createWaitForCloseHelper()); if (options != null && options.timeout != null) { waitables.add(new WaitableTimeout<>(options.timeout)); diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/Serialization.java b/playwright/src/main/java/com/microsoft/playwright/impl/Serialization.java index c6138eb4..7f6bcadf 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/Serialization.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/Serialization.java @@ -18,6 +18,8 @@ package com.microsoft.playwright.impl; import com.google.gson.Gson; import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.microsoft.playwright.FileChooser; import com.microsoft.playwright.JSHandle; import com.microsoft.playwright.Keyboard; import com.microsoft.playwright.Mouse; @@ -185,4 +187,16 @@ class Serialization { } return result; } + + static JsonArray toJsonArray(FileChooser.FilePayload[] files) { + JsonArray jsonFiles = new JsonArray(); + for (FileChooser.FilePayload p : files) { + JsonObject jsonFile = new JsonObject(); + jsonFile.addProperty("name", p.name); + jsonFile.addProperty("mimeType", p.mimeType); + jsonFile.addProperty("buffer", Base64.getEncoder().encodeToString(p.buffer)); + jsonFiles.add(jsonFile); + } + return jsonFiles; + } } 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 8d2bd9a9..8c4ee666 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/Utils.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/Utils.java @@ -17,7 +17,11 @@ package com.microsoft.playwright.impl; import com.google.gson.Gson; +import com.microsoft.playwright.FileChooser; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; import java.util.*; class Utils { @@ -90,4 +94,27 @@ class Utils { tokens.append('$'); return tokens.toString(); } + + static FileChooser.FilePayload[] toFilePayloads(File[] files) { + List payloads = new ArrayList<>(); + for (File file : files) { + String mimeType; + try { + mimeType = Files.probeContentType(file.toPath()); + } catch (IOException e) { + throw new RuntimeException("Failed to determine mime type", e); + } + if (mimeType == null) { + mimeType = "application/octet-stream"; + } + byte[] buffer; + try { + buffer = Files.readAllBytes(file.toPath()); + } catch (IOException e) { + throw new RuntimeException("Failed to read from file", e); + } + payloads.add(new FileChooser.FilePayload(file.getName(), mimeType, buffer)); + } + return payloads.toArray(new FileChooser.FilePayload[0]); + } } diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/WaitableEvent.java b/playwright/src/main/java/com/microsoft/playwright/impl/WaitableEvent.java index 8ab9710d..7d2020d7 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/WaitableEvent.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/WaitableEvent.java @@ -22,12 +22,16 @@ import com.microsoft.playwright.Page; import java.util.function.Predicate; -class WaitableEvent implements Waitable, Listener { - private final ListenerCollection listeners; +class WaitableEvent implements Waitable>, Listener { + final ListenerCollection listeners; private final EventType type; private final Predicate> predicate; private Event event; + WaitableEvent(ListenerCollection listeners, EventType type) { + this(listeners, type, null); + } + WaitableEvent(ListenerCollection listeners, EventType type, Predicate> predicate) { this.listeners = listeners; this.type = type; @@ -38,7 +42,7 @@ class WaitableEvent implements Waitable, List @Override public void handle(Event event) { assert type.equals(event.type()); - if (!predicate.test(event)) { + if (predicate != null && !predicate.test(event)) { return; } @@ -56,9 +60,8 @@ class WaitableEvent implements Waitable, List listeners.remove(type, this); } - @SuppressWarnings("unchecked") @Override - public ResultType get() { - return (ResultType) event.data(); + public Event get() { + return event; } } diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/WorkerImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/WorkerImpl.java index bb8698e5..0ae8b62a 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/WorkerImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/WorkerImpl.java @@ -71,7 +71,7 @@ class WorkerImpl extends ChannelOwner implements Worker { @Override public Deferred> waitForEvent(EventType event) { - return listeners.waitForEvent(event, connection); + return toDeferred(new WaitableEvent<>(listeners, event)); } @Override diff --git a/playwright/src/test/java/com/microsoft/playwright/TestPageSetInputFiles.java b/playwright/src/test/java/com/microsoft/playwright/TestPageSetInputFiles.java new file mode 100644 index 00000000..f3742bfd --- /dev/null +++ b/playwright/src/test/java/com/microsoft/playwright/TestPageSetInputFiles.java @@ -0,0 +1,122 @@ +/** + * 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 java.io.File; +import java.time.Duration; +import java.time.Instant; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + + +public class TestPageSetInputFiles extends TestBase { + static File FILE_TO_UPLOAD = new File("src/test/resources/file-to-upload.txt"); + + @Test + void shouldUploadTheFile() { + page.navigate(server.PREFIX + "/input/fileupload.html"); + ElementHandle input = page.querySelector("input"); + input.setInputFiles(FILE_TO_UPLOAD); + assertEquals("file-to-upload.txt", page.evaluate("e => e.files[0].name", input)); + assertEquals("contents of the file", page.evaluate("e => {\n" + + " const reader = new FileReader();\n" + + " const promise = new Promise(fulfill => reader.onload = fulfill);\n" + + " reader.readAsText(e.files[0]);\n" + + " return promise.then(() => reader.result);\n" + + "}", input)); + } + + @Test + void shouldWork() { + page.setContent(""); + page.setInputFiles("input", FILE_TO_UPLOAD); + assertEquals(1, page.evalOnSelector("input", "input => input.files.length")); + assertEquals("file-to-upload.txt", page.evalOnSelector("input", "input => input.files[0].name")); + } + + @Test + void shouldSetFromMemory() { + page.setContent(""); + page.setInputFiles("input", new FileChooser.FilePayload("test.txt","text/plain","this is a test".getBytes())); + assertEquals(1, page.evalOnSelector("input", "input => input.files.length")); + assertEquals("test.txt", page.evalOnSelector("input", "input => input.files[0].name")); + } + + @Test + void shouldEmitEventOnce() { + page.setContent(""); + Deferred> event = page.waitForEvent(Page.EventType.FILECHOOSER); + page.click("input"); + FileChooser chooser = (FileChooser) event.get().data(); + assertNotNull(chooser); + } + + void shouldEmitEventOnOff() { + // Not applicable in Java. + } + + @Test + void shouldEmitEventAddListenerRemoveListener() { + page.setContent(""); + FileChooser[] chooser = { null }; + page.addListener(Page.EventType.FILECHOOSER, new Listener() { + @Override + public void handle(Event event) { + chooser[0] = (FileChooser) event.data(); + page.removeListener(Page.EventType.FILECHOOSER, this); + } + }); + page.click("input"); + Instant start = Instant.now(); + while (chooser[0] == null && Duration.between(start, Instant.now()).toMillis() < 10_000) { + page.waitForTimeout(100).get(); + } + assertNotNull(chooser[0]); + } + + @Test + void shouldWorkWhenFileInputIsAttachedToDOM() { + page.setContent(""); + Deferred> chooser = page.waitForEvent(Page.EventType.FILECHOOSER); + page.click("input"); + assertNotNull(chooser.get()); + } + + @Test + void shouldWorkWhenFileInputIsNotAttachedToDOM() { + Deferred> chooser = page.waitForEvent(Page.EventType.FILECHOOSER); + page.evaluate("() => {\n" + + " const el = document.createElement('input');\n" + + " el.type = 'file';\n" + + " el.click();\n" + + "}"); + assertNotNull(chooser.get()); + } + + @Test + void shouldWorkWithCSP() { + server.setCSP("/empty.html", "default-src 'none'"); + page.navigate(server.EMPTY_PAGE); + page.setContent(""); + page.setInputFiles("input", FILE_TO_UPLOAD); + assertEquals(1, page.evalOnSelector("input", "input => input.files.length")); + assertEquals("file-to-upload.txt", page.evalOnSelector("input", "input => input.files[0].name")); + } +}