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 ff983e9f..d37d3571 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 @@ -24,8 +24,11 @@ import java.io.*; import java.nio.file.FileSystems; import java.util.*; import java.util.List; +import java.util.function.Predicate; import java.util.stream.Collectors; +import static java.util.Arrays.asList; + abstract class Element { final String jsonName; final String jsonPath; @@ -295,6 +298,20 @@ class Method extends Element { }; customSignature.put("Page.setInputFiles", setInputFilesWithSelector); customSignature.put("Frame.setInputFiles", setInputFilesWithSelector); + + String[] waitForEvent = { + "default Deferred> waitForEvent(EventType event) {", + " return waitForEvent(event, (WaitForEventOptions) null);", + "}", + "default Deferred> waitForEvent(EventType event, Predicate> predicate) {", + " WaitForEventOptions options = new WaitForEventOptions();", + " options.predicate = predicate;", + " return waitForEvent(event, options);", + "}", + "Deferred> waitForEvent(EventType event, WaitForEventOptions options);", + }; + customSignature.put("Page.waitForEvent", waitForEvent); + customSignature.put("BrowserContext.waitForEvent", waitForEvent); } Method(TypeDefinition parent, JsonObject jsonElement) { @@ -503,7 +520,7 @@ class Interface extends TypeDefinition { "\n" + "package com.microsoft.playwright;\n"; - private static Set allowedBaseInterfaces = new HashSet<>(Arrays.asList("Browser", "JSHandle", "BrowserContext")); + private static Set allowedBaseInterfaces = new HashSet<>(asList("Browser", "JSHandle", "BrowserContext")); Interface(JsonObject jsonElement) { super(null, jsonElement); @@ -530,14 +547,14 @@ class Interface extends TypeDefinition { if (jsonName.equals("Route")) { output.add("import java.nio.charset.StandardCharsets;"); } - if (Arrays.asList("Page", "Frame", "ElementHandle", "FileChooser").contains(jsonName)) { + if (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)) { + if (asList("Page", "BrowserContext").contains(jsonName)) { output.add("import java.util.function.BiConsumer;"); } - if (Arrays.asList("Page", "Frame", "BrowserContext").contains(jsonName)) { + if (asList("Page", "Frame", "BrowserContext").contains(jsonName)) { output.add("import java.util.function.Predicate;"); output.add("import java.util.regex.Pattern;"); } @@ -589,10 +606,12 @@ class Interface extends TypeDefinition { switch (jsonName) { case "Mouse": { output.add(offset + "enum Button { LEFT, MIDDLE, RIGHT }"); + output.add(""); break; } case "Keyboard": { output.add(offset + "enum Modifier { ALT, CONTROL, META, SHIFT }"); + output.add(""); break; } case "Page": { @@ -656,6 +675,7 @@ class Interface extends TypeDefinition { output.add(offset + " return password;"); output.add(offset + " }"); output.add(offset + "}"); + output.add(""); break; } case "ElementHandle": { @@ -665,6 +685,7 @@ class Interface extends TypeDefinition { output.add(offset + " public double width;"); output.add(offset + " public double height;"); output.add(offset + "}"); + output.add(""); break; } case "FileChooser": { @@ -679,11 +700,26 @@ class Interface extends TypeDefinition { output.add(offset + " this.buffer = buffer;"); output.add(offset + " }"); output.add(offset + "}"); + output.add(""); break; } - default: return; } - output.add(""); + if (asList("Page", "BrowserContext").contains(jsonName)){ + output.add(offset + "class WaitForEventOptions {"); + output.add(offset + " public Integer timeout;"); + output.add(offset + " public Predicate> predicate;"); + + output.add(offset + " public WaitForEventOptions withTimeout(int millis) {"); + output.add(offset + " timeout = millis;"); + output.add(offset + " return this;"); + output.add(offset + " }"); + output.add(offset + " public WaitForEventOptions withPredicate(Predicate> predicate) {"); + output.add(offset + " this.predicate = predicate;"); + output.add(offset + " return this;"); + output.add(offset + " }"); + output.add(offset + "}"); + output.add(""); + } } } 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 194fa1be..6e46d380 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 @@ -210,7 +210,7 @@ class Types { add("Page.setInputFiles.files", "string|Array|Object|Array", "String"); add("Page.unroute.url", "string|RegExp|function(URL):boolean", "String"); add("Page.waitForEvent.event", "string", "EventType", new Empty()); - add("Page.waitForEvent.optionsOrPredicate", "Function|Object", "String"); + add("Page.waitForEvent.optionsOrPredicate", "Function|Object", "WaitForEventOptions"); add("Page.waitForEvent", "Promise", "Deferred>", new Empty()); add("Page.waitForRequest.urlOrPredicate", "string|RegExp|Function", "String"); add("Page.waitForResponse.urlOrPredicate", "string|RegExp|Function", "String"); diff --git a/playwright/src/main/java/com/microsoft/playwright/BrowserContext.java b/playwright/src/main/java/com/microsoft/playwright/BrowserContext.java index 40b56dc4..7667a5cd 100644 --- a/playwright/src/main/java/com/microsoft/playwright/BrowserContext.java +++ b/playwright/src/main/java/com/microsoft/playwright/BrowserContext.java @@ -40,6 +40,19 @@ public interface BrowserContext { } } + class WaitForEventOptions { + public Integer timeout; + public Predicate> predicate; + public WaitForEventOptions withTimeout(int millis) { + timeout = millis; + return this; + } + public WaitForEventOptions withPredicate(Predicate> predicate) { + this.predicate = predicate; + return this; + } + } + enum EventType { PAGE, } @@ -120,8 +133,13 @@ public interface BrowserContext { void unroute(Pattern url, BiConsumer handler); void unroute(Predicate url, BiConsumer handler); default Deferred> waitForEvent(EventType event) { - return waitForEvent(event, null); + return waitForEvent(event, (WaitForEventOptions) null); } - Deferred> waitForEvent(EventType event, String optionsOrPredicate); + default Deferred> waitForEvent(EventType event, Predicate> predicate) { + WaitForEventOptions options = new WaitForEventOptions(); + options.predicate = predicate; + return waitForEvent(event, options); + } + Deferred> waitForEvent(EventType event, WaitForEventOptions options); } diff --git a/playwright/src/main/java/com/microsoft/playwright/Page.java b/playwright/src/main/java/com/microsoft/playwright/Page.java index 8df40f24..e150d1e3 100644 --- a/playwright/src/main/java/com/microsoft/playwright/Page.java +++ b/playwright/src/main/java/com/microsoft/playwright/Page.java @@ -61,6 +61,18 @@ public interface Page { String stack(); } + class WaitForEventOptions { + public Integer timeout; + public Predicate> predicate; + public WaitForEventOptions withTimeout(int millis) { + timeout = millis; + return this; + } + public WaitForEventOptions withPredicate(Predicate> predicate) { + this.predicate = predicate; + return this; + } + } enum EventType { CLOSE, @@ -962,9 +974,14 @@ public interface Page { Video video(); Viewport viewportSize(); default Deferred> waitForEvent(EventType event) { - return waitForEvent(event, null); + return waitForEvent(event, (WaitForEventOptions) null); } - Deferred> waitForEvent(EventType event, String optionsOrPredicate); + default Deferred> waitForEvent(EventType event, Predicate> predicate) { + WaitForEventOptions options = new WaitForEventOptions(); + options.predicate = predicate; + return waitForEvent(event, options); + } + Deferred> waitForEvent(EventType event, WaitForEventOptions options); default Deferred waitForFunction(String pageFunction, Object arg) { return waitForFunction(pageFunction, arg, 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 9e720a1b..307341a7 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java @@ -39,6 +39,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext { final Map bindings = new HashMap(); PageImpl ownerPage; private final ListenerCollection listeners = new ListenerCollection<>(); + final TimeoutSettings timeoutSettings = new TimeoutSettings(); protected BrowserContextImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) { super(parent, type, guid, initializer); @@ -83,7 +84,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext { @Override public Browser browser() { - return null; + return browser; } @Override @@ -171,12 +172,18 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext { @Override public void setDefaultNavigationTimeout(int timeout) { - + timeoutSettings.setDefaultNavigationTimeout(timeout); + JsonObject params = new JsonObject(); + params.addProperty("timeout", timeout); + sendMessage("setDefaultNavigationTimeoutNoReply", params); } @Override public void setDefaultTimeout(int timeout) { - + timeoutSettings.setDefaultTimeout(timeout); + JsonObject params = new JsonObject(); + params.addProperty("timeout", timeout); + sendMessage("setDefaultTimeoutNoReply", params); } @Override @@ -217,8 +224,14 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext { } @Override - public Deferred> waitForEvent(EventType event, String optionsOrPredicate) { - return toDeferred(new WaitableEvent<>(listeners, event)); + public Deferred> waitForEvent(EventType event, WaitForEventOptions options) { + if (options == null) { + options = new WaitForEventOptions(); + } + List>> waitables = new ArrayList<>(); + waitables.add(new WaitableEvent<>(listeners, event, options.predicate)); + waitables.add(timeoutSettings.createWaitable(options.timeout)); + return toDeferred(new WaitableRace<>(waitables)); } private void unroute(UrlMatcher matcher, BiConsumer handler) { 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 c69245cd..55cde01a 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/FrameImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/FrameImpl.java @@ -457,6 +457,9 @@ public class FrameImpl extends ChannelOwner implements Frame { @Override public Deferred waitForLoadState(LoadState state, WaitForLoadStateOptions options) { + if (options == null) { + options = new WaitForLoadStateOptions(); + } if (state == null) { state = LOAD; } @@ -464,9 +467,7 @@ public class FrameImpl extends ChannelOwner implements Frame { List> waitables = new ArrayList<>(); waitables.add(new WaitForLoadStateHelper(state)); waitables.add(page.createWaitForCloseHelper()); - if (options != null && options.timeout != null) { - waitables.add(new WaitableTimeout<>(options.timeout)); - } + waitables.add(page.createWaitableTimeout(options.timeout)); return toDeferred(new WaitableRace<>(waitables)); } @@ -586,9 +587,7 @@ public class FrameImpl extends ChannelOwner implements Frame { waitables.add(new WaitForNavigationHelper(matcher, options.waitUntil)); waitables.add(page.createWaitForCloseHelper()); waitables.add(page.createWaitableFrameDetach(this)); - if (options.timeout != null) { - waitables.add(new WaitableTimeout<>(options.timeout)); - } + waitables.add(page.createWaitableNavigationTimeout(options.timeout)); return toDeferred(new WaitableRace<>(waitables)); } 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 007c3e9c..edd4a263 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/PageImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/PageImpl.java @@ -44,6 +44,7 @@ public class PageImpl extends ChannelOwner implements Page { BrowserContextImpl ownedContext; private boolean isClosed; final Set workers = new HashSet<>(); + private final TimeoutSettings timeoutSettings; PageImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) { super(parent, type, guid, initializer); @@ -53,6 +54,7 @@ public class PageImpl extends ChannelOwner implements Page { keyboard = new KeyboardImpl(this); mouse = new MouseImpl(this); frames.add(mainFrame); + timeoutSettings = new TimeoutSettings(browserContext.timeoutSettings); } @Override @@ -550,6 +552,7 @@ public class PageImpl extends ChannelOwner implements Page { @Override public void setDefaultNavigationTimeout(int timeout) { + timeoutSettings.setDefaultNavigationTimeout(timeout); JsonObject params = new JsonObject(); params.addProperty("timeout", timeout); sendMessage("setDefaultNavigationTimeoutNoReply", params); @@ -557,6 +560,7 @@ public class PageImpl extends ChannelOwner implements Page { @Override public void setDefaultTimeout(int timeout) { + timeoutSettings.setDefaultTimeout(timeout); JsonObject params = new JsonObject(); params.addProperty("timeout", timeout); sendMessage("setDefaultTimeoutNoReply", params); @@ -653,22 +657,34 @@ public class PageImpl extends ChannelOwner implements Page { return viewport; } + Waitable createWaitableNavigationTimeout(Integer timeout) { + return new WaitableTimeout<>(timeoutSettings.navigationTimeout(timeout)); + } + + Waitable createWaitableTimeout(Integer timeout) { + return timeoutSettings.createWaitable(timeout); + } + @Override - public Deferred> waitForEvent(EventType event, String optionsOrPredicate) { - Waitable> waitable; + public Deferred> waitForEvent(EventType event, WaitForEventOptions options) { + if (options == null) { + options = new WaitForEventOptions(); + } + List>> waitables = new ArrayList<>(); if (event == EventType.FILECHOOSER) { willAddFileChooserListener(); - waitable = new WaitableEvent(listeners, event) { + waitables.add(new WaitableEvent(listeners, event, options.predicate) { @Override public void dispose() { super.dispose(); didRemoveFileChooserListener(); } - }; + }); } else { - waitable = new WaitableEvent<>(listeners, event); + waitables.add(new WaitableEvent<>(listeners, event, options.predicate)); } - return toDeferred(waitable); + waitables.add(createWaitableTimeout(options.timeout)); + return toDeferred(new WaitableRace<>(waitables)); } @Override @@ -785,6 +801,9 @@ public class PageImpl extends ChannelOwner implements Page { @Override public Deferred waitForRequest(String urlOrPredicate, WaitForRequestOptions options) { + if (options == null) { + options = new WaitForRequestOptions(); + } List> waitables = new ArrayList<>(); waitables.add(new WaitableEvent<>(listeners, EventType.REQUEST,e -> { if (urlOrPredicate == null) { @@ -793,14 +812,15 @@ public class PageImpl extends ChannelOwner implements Page { 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)); - } + waitables.add(createWaitableTimeout(options.timeout)); return toDeferred(new WaitableRace<>(waitables)); } @Override public Deferred waitForResponse(String urlOrPredicate, WaitForResponseOptions options) { + if (options == null) { + options = new WaitForResponseOptions(); + } List> waitables = new ArrayList<>(); waitables.add(new WaitableEvent<>(listeners, EventType.RESPONSE, e -> { if (urlOrPredicate == null) { @@ -809,9 +829,7 @@ public class PageImpl extends ChannelOwner implements Page { 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)); - } + waitables.add(createWaitableTimeout(options.timeout)); return toDeferred(new WaitableRace<>(waitables)); } diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/TimeoutSettings.java b/playwright/src/main/java/com/microsoft/playwright/impl/TimeoutSettings.java new file mode 100644 index 00000000..177554ce --- /dev/null +++ b/playwright/src/main/java/com/microsoft/playwright/impl/TimeoutSettings.java @@ -0,0 +1,77 @@ +/** + * 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.impl; + +class TimeoutSettings { + private static final int DEFAULT_TIMEOUT_MS = 30_000; + + private final TimeoutSettings parent; + private Integer defaultTimeout ; + private Integer defaultNavigationTimeout; + + TimeoutSettings() { + this(null); + } + TimeoutSettings(TimeoutSettings parent) { + this.parent = parent; + } + + void setDefaultTimeout(int timeout) { + defaultTimeout = timeout; + } + + void setDefaultNavigationTimeout(int timeout) { + defaultNavigationTimeout = timeout; + } + + int timeout(Integer timeout) { + if (timeout != null) { + return timeout; + } + if (defaultTimeout != null) { + return defaultTimeout; + } + if (parent != null) { + return parent.timeout(timeout); + } + return DEFAULT_TIMEOUT_MS; + } + + int navigationTimeout(Integer timeout) { + if (timeout != null) { + return timeout; + } + if (defaultNavigationTimeout != null) { + return defaultNavigationTimeout; + } + if (defaultTimeout != null) { + return defaultTimeout; + } + if (parent != null) { + return parent.navigationTimeout(timeout); + } + return DEFAULT_TIMEOUT_MS; + } + + Waitable createWaitable(Integer timeout) { + if (timeout != null && timeout == 0) { + return new WaitableNever<>(); + } + return new WaitableTimeout<>(timeout(timeout)); + } + +} diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/WaitableNever.java b/playwright/src/main/java/com/microsoft/playwright/impl/WaitableNever.java new file mode 100644 index 00000000..7fb70d47 --- /dev/null +++ b/playwright/src/main/java/com/microsoft/playwright/impl/WaitableNever.java @@ -0,0 +1,33 @@ +/** + * 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.impl; + +public class WaitableNever implements Waitable { + @Override + public boolean isDone() { + return false; + } + + @Override + public T get() { + throw new IllegalStateException("Should never be called"); + } + + @Override + public void dispose() { + } +} diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/WaitableTimeout.java b/playwright/src/main/java/com/microsoft/playwright/impl/WaitableTimeout.java index 23affb35..5fe304a1 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/WaitableTimeout.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/WaitableTimeout.java @@ -22,7 +22,7 @@ class WaitableTimeout implements Waitable { WaitableTimeout(int millis) { timeout = millis; - deadline = System.nanoTime() + millis * 1_000_000; + deadline = System.nanoTime() + (long) millis * 1_000_000; } @Override @@ -39,3 +39,4 @@ class WaitableTimeout implements Waitable { public void dispose() { } } + diff --git a/playwright/src/test/java/com/microsoft/playwright/TestPageSetInputFiles.java b/playwright/src/test/java/com/microsoft/playwright/TestPageSetInputFiles.java index f3742bfd..cc3e5c47 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestPageSetInputFiles.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestPageSetInputFiles.java @@ -21,9 +21,12 @@ import org.junit.jupiter.api.Test; import java.io.File; import java.time.Duration; import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutionException; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; +import static java.util.Arrays.asList; +import static org.junit.jupiter.api.Assertions.*; public class TestPageSetInputFiles extends TestBase { @@ -119,4 +122,185 @@ public class TestPageSetInputFiles extends TestBase { assertEquals(1, page.evalOnSelector("input", "input => input.files.length")); assertEquals("file-to-upload.txt", page.evalOnSelector("input", "input => input.files[0].name")); } + + @Test + void shouldRespectTimeout() { + try { + Deferred> event = page.waitForEvent(Page.EventType.FILECHOOSER, new Page.WaitForEventOptions().withTimeout(1)); + event.get(); + fail("did not throw"); + } catch (RuntimeException e) { + assertTrue(e.getMessage().contains("Timeout 1ms exceeded")); + } + } + + @Test + void shouldRespectDefaultTimeoutWhenThereIsNoCustomTimeout() { + page.setDefaultTimeout(1); + try { + Deferred> event = page.waitForEvent(Page.EventType.FILECHOOSER); + event.get(); + fail("did not throw"); + } catch (RuntimeException e) { + assertTrue(e.getMessage().contains("Timeout 1ms exceeded")); + } + } + + @Test + void shouldPrioritizeExactTimeoutOverDefaultTimeout() { + page.setDefaultTimeout(0); + Deferred> event = page.waitForEvent(Page.EventType.FILECHOOSER, + new Page.WaitForEventOptions().withTimeout(1)); + try { + event.get(); + fail("did not throw"); + } catch (RuntimeException e) { + assertTrue(e.getMessage().contains("Timeout 1ms exceeded")); + } + } + + @Test + void shouldWorkWithNoTimeout() { + Deferred> event = page.waitForEvent(Page.EventType.FILECHOOSER, + new Page.WaitForEventOptions().withTimeout(0)); + page.evaluate("() => setTimeout(() => {\n" + + " const el = document.createElement('input');\n" + + " el.type = 'file';\n" + + " el.click();\n" + + "}, 50)"); + assertNotNull(event.get().data()); + } + + @Test + void shouldReturnTheSameFileChooserWhenThereAreManyWatchdogsSimultaneously() { + page.setContent(""); + Deferred> fileChooser1 = page.waitForEvent(Page.EventType.FILECHOOSER); + Deferred> fileChooser2 = page.waitForEvent(Page.EventType.FILECHOOSER); + page.evalOnSelector("input", "input => input.click()"); + assertEquals(fileChooser1.get().data(), fileChooser2.get().data()); + } + + @Test + void shouldAcceptSingleFile() { + page.setContent(""); + Deferred> event = page.waitForEvent(Page.EventType.FILECHOOSER); + page.click("input"); + FileChooser fileChooser = (FileChooser) event.get().data(); + assertEquals(page, fileChooser.page()); + assertNotNull(fileChooser.element()); + fileChooser.setFiles(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 shouldDetectMimeType() throws ExecutionException, InterruptedException { + // TODO: Parse form fields on server + } + + @Test + void shouldBeAbleToReadSelectedFile() { + page.setContent(""); + page.addListener(Page.EventType.FILECHOOSER, event -> { + FileChooser fileChooser = (FileChooser) event.data(); + fileChooser.setFiles(FILE_TO_UPLOAD); + }); + Object content = page.evalOnSelector("input", "async picker => {\n" + + " picker.click();\n" + + " await new Promise(x => picker.oninput = x);\n" + + " const reader = new FileReader();\n" + + " const promise = new Promise(fulfill => reader.onload = fulfill);\n" + + " reader.readAsText(picker.files[0]);\n" + + " return promise.then(() => reader.result);\n" + + "}"); + assertEquals("contents of the file", content); + } + + @Test + void shouldBeAbleToResetSelectedFilesWithEmptyFileList() { + page.setContent(""); + page.addListener(Page.EventType.FILECHOOSER, new Listener() { + @Override + public void handle(Event event) { + FileChooser fileChooser = (FileChooser) event.data(); + fileChooser.setFiles(FILE_TO_UPLOAD); + page.removeListener(Page.EventType.FILECHOOSER, this); + } + }); + Object fileLength1 = page.evalOnSelector("input", "async picker => {\n" + + " picker.click();\n" + + " await new Promise(x => picker.oninput = x);\n" + + " return picker.files.length;\n" + + "}"); + assertEquals(1, fileLength1); + + page.addListener(Page.EventType.FILECHOOSER, new Listener() { + @Override + public void handle(Event event) { + FileChooser fileChooser = (FileChooser) event.data(); + fileChooser.setFiles(new File[0]); + page.removeListener(Page.EventType.FILECHOOSER, this); + } + }); + Object fileLength2 = page.evalOnSelector("input", "async picker => {\n" + + " picker.click();\n" + + " await new Promise(x => picker.oninput = x);\n" + + " return picker.files.length;\n" + + "}"); + assertEquals(0, fileLength2); + } + + @Test + void shouldNotAcceptMultipleFilesForSingleFileInput() { + page.setContent(""); + Deferred> event = page.waitForEvent(Page.EventType.FILECHOOSER); + page.click("input"); + FileChooser fileChooser = (FileChooser) event.get().data(); + try { + fileChooser.setFiles(new File[]{FILE_TO_UPLOAD, new File("src/test/resources/pptr.png")}); + fail("did not throw"); + } catch (RuntimeException e) { + assertTrue(e.getMessage().contains("Non-multiple file input can only accept single file")); + } + } + @Test + void shouldEmitInputAndChangeEvents() { + List events = new ArrayList<>(); + page.exposeFunction("eventHandled", args -> events.add(args[0])); + page.setContent("\n" + + ""); + page.querySelector("input").setInputFiles(FILE_TO_UPLOAD); + assertEquals(asList("input", "change"), events); + } + + @Test + void shouldWorkForSingleFilePick() { + page.setContent(""); + Deferred> event = page.waitForEvent(Page.EventType.FILECHOOSER); + page.click("input"); + FileChooser fileChooser = (FileChooser) event.get().data(); + assertFalse(fileChooser.isMultiple()); + } + + @Test + void shouldWorkForMultiple() { + page.setContent(""); + Deferred> event = page.waitForEvent(Page.EventType.FILECHOOSER); + page.click("input"); + FileChooser fileChooser = (FileChooser) event.get().data(); + assertTrue(fileChooser.isMultiple()); + } + + @Test + void shouldWorkForWebkitdirectory() { + page.setContent(""); + Deferred> event = page.waitForEvent(Page.EventType.FILECHOOSER); + page.click("input"); + FileChooser fileChooser = (FileChooser) event.get().data(); + assertTrue(fileChooser.isMultiple()); + } } +