feat: support timeout in waitForEvent, more FileChooser (#34)

This commit is contained in:
Yury Semikhatsky 2020-10-22 14:35:26 -07:00 committed by GitHub
parent 6d5724f166
commit a02e70ad84
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 433 additions and 37 deletions

View File

@ -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<Event<EventType>> waitForEvent(EventType event) {",
" return waitForEvent(event, (WaitForEventOptions) null);",
"}",
"default Deferred<Event<EventType>> waitForEvent(EventType event, Predicate<Event<EventType>> predicate) {",
" WaitForEventOptions options = new WaitForEventOptions();",
" options.predicate = predicate;",
" return waitForEvent(event, options);",
"}",
"Deferred<Event<EventType>> 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<String> allowedBaseInterfaces = new HashSet<>(Arrays.asList("Browser", "JSHandle", "BrowserContext"));
private static Set<String> 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,12 +700,27 @@ class Interface extends TypeDefinition {
output.add(offset + " this.buffer = buffer;");
output.add(offset + " }");
output.add(offset + "}");
output.add("");
break;
}
default: return;
}
if (asList("Page", "BrowserContext").contains(jsonName)){
output.add(offset + "class WaitForEventOptions {");
output.add(offset + " public Integer timeout;");
output.add(offset + " public Predicate<Event<EventType>> 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<Event<EventType>> predicate) {");
output.add(offset + " this.predicate = predicate;");
output.add(offset + " return this;");
output.add(offset + " }");
output.add(offset + "}");
output.add("");
}
}
}
class NestedClass extends TypeDefinition {

View File

@ -210,7 +210,7 @@ class Types {
add("Page.setInputFiles.files", "string|Array<string>|Object|Array<Object>", "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<Object>", "Deferred<Event<EventType>>", new Empty());
add("Page.waitForRequest.urlOrPredicate", "string|RegExp|Function", "String");
add("Page.waitForResponse.urlOrPredicate", "string|RegExp|Function", "String");

View File

@ -40,6 +40,19 @@ public interface BrowserContext {
}
}
class WaitForEventOptions {
public Integer timeout;
public Predicate<Event<EventType>> predicate;
public WaitForEventOptions withTimeout(int millis) {
timeout = millis;
return this;
}
public WaitForEventOptions withPredicate(Predicate<Event<EventType>> predicate) {
this.predicate = predicate;
return this;
}
}
enum EventType {
PAGE,
}
@ -120,8 +133,13 @@ public interface BrowserContext {
void unroute(Pattern url, BiConsumer<Route, Request> handler);
void unroute(Predicate<String> url, BiConsumer<Route, Request> handler);
default Deferred<Event<EventType>> waitForEvent(EventType event) {
return waitForEvent(event, null);
return waitForEvent(event, (WaitForEventOptions) null);
}
Deferred<Event<EventType>> waitForEvent(EventType event, String optionsOrPredicate);
default Deferred<Event<EventType>> waitForEvent(EventType event, Predicate<Event<EventType>> predicate) {
WaitForEventOptions options = new WaitForEventOptions();
options.predicate = predicate;
return waitForEvent(event, options);
}
Deferred<Event<EventType>> waitForEvent(EventType event, WaitForEventOptions options);
}

View File

@ -61,6 +61,18 @@ public interface Page {
String stack();
}
class WaitForEventOptions {
public Integer timeout;
public Predicate<Event<EventType>> predicate;
public WaitForEventOptions withTimeout(int millis) {
timeout = millis;
return this;
}
public WaitForEventOptions withPredicate(Predicate<Event<EventType>> predicate) {
this.predicate = predicate;
return this;
}
}
enum EventType {
CLOSE,
@ -962,9 +974,14 @@ public interface Page {
Video video();
Viewport viewportSize();
default Deferred<Event<EventType>> waitForEvent(EventType event) {
return waitForEvent(event, null);
return waitForEvent(event, (WaitForEventOptions) null);
}
Deferred<Event<EventType>> waitForEvent(EventType event, String optionsOrPredicate);
default Deferred<Event<EventType>> waitForEvent(EventType event, Predicate<Event<EventType>> predicate) {
WaitForEventOptions options = new WaitForEventOptions();
options.predicate = predicate;
return waitForEvent(event, options);
}
Deferred<Event<EventType>> waitForEvent(EventType event, WaitForEventOptions options);
default Deferred<JSHandle> waitForFunction(String pageFunction, Object arg) {
return waitForFunction(pageFunction, arg, null);
}

View File

@ -39,6 +39,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
final Map<String, Page.Binding> bindings = new HashMap<String, Page.Binding>();
PageImpl ownerPage;
private final ListenerCollection<EventType> 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<Event<EventType>> waitForEvent(EventType event, String optionsOrPredicate) {
return toDeferred(new WaitableEvent<>(listeners, event));
public Deferred<Event<EventType>> waitForEvent(EventType event, WaitForEventOptions options) {
if (options == null) {
options = new WaitForEventOptions();
}
List<Waitable<Event<EventType>>> 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<Route, Request> handler) {

View File

@ -457,6 +457,9 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override
public Deferred<Void> 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<Waitable<Void>> 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));
}

View File

@ -44,6 +44,7 @@ public class PageImpl extends ChannelOwner implements Page {
BrowserContextImpl ownedContext;
private boolean isClosed;
final Set<Worker> 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;
}
<T> Waitable<T> createWaitableNavigationTimeout(Integer timeout) {
return new WaitableTimeout<>(timeoutSettings.navigationTimeout(timeout));
}
<T> Waitable<T> createWaitableTimeout(Integer timeout) {
return timeoutSettings.createWaitable(timeout);
}
@Override
public Deferred<Event<EventType>> waitForEvent(EventType event, String optionsOrPredicate) {
Waitable<Event<EventType>> waitable;
public Deferred<Event<EventType>> waitForEvent(EventType event, WaitForEventOptions options) {
if (options == null) {
options = new WaitForEventOptions();
}
List<Waitable<Event<EventType>>> waitables = new ArrayList<>();
if (event == EventType.FILECHOOSER) {
willAddFileChooserListener();
waitable = new WaitableEvent<EventType>(listeners, event) {
waitables.add(new WaitableEvent<EventType>(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<Request> waitForRequest(String urlOrPredicate, WaitForRequestOptions options) {
if (options == null) {
options = new WaitForRequestOptions();
}
List<Waitable<Request>> 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<Response> waitForResponse(String urlOrPredicate, WaitForResponseOptions options) {
if (options == null) {
options = new WaitForResponseOptions();
}
List<Waitable<Response>> 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));
}

View File

@ -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;
}
<T> Waitable<T> createWaitable(Integer timeout) {
if (timeout != null && timeout == 0) {
return new WaitableNever<>();
}
return new WaitableTimeout<>(timeout(timeout));
}
}

View File

@ -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<T> implements Waitable<T> {
@Override
public boolean isDone() {
return false;
}
@Override
public T get() {
throw new IllegalStateException("Should never be called");
}
@Override
public void dispose() {
}
}

View File

@ -22,7 +22,7 @@ class WaitableTimeout<T> implements Waitable<T> {
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<T> implements Waitable<T> {
public void dispose() {
}
}

View File

@ -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.EventType>> 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.EventType>> 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.EventType>> 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.EventType>> 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("<input type=file>");
Deferred<Event<Page.EventType>> fileChooser1 = page.waitForEvent(Page.EventType.FILECHOOSER);
Deferred<Event<Page.EventType>> fileChooser2 = page.waitForEvent(Page.EventType.FILECHOOSER);
page.evalOnSelector("input", "input => input.click()");
assertEquals(fileChooser1.get().data(), fileChooser2.get().data());
}
@Test
void shouldAcceptSingleFile() {
page.setContent("<input type=file oninput='javascript:console.timeStamp()'>");
Deferred<Event<Page.EventType>> 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("<input type=file>");
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("<input type=file>");
page.addListener(Page.EventType.FILECHOOSER, new Listener<Page.EventType>() {
@Override
public void handle(Event<Page.EventType> 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<Page.EventType>() {
@Override
public void handle(Event<Page.EventType> 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("<input type=file>");
Deferred<Event<Page.EventType>> 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<Object> events = new ArrayList<>();
page.exposeFunction("eventHandled", args -> events.add(args[0]));
page.setContent("<input id=input type=file></input>\n" +
"<script>\n" +
" input.addEventListener('input', e => eventHandled(e.type));\n" +
" input.addEventListener('change', e => eventHandled(e.type));\n" +
"</script>");
page.querySelector("input").setInputFiles(FILE_TO_UPLOAD);
assertEquals(asList("input", "change"), events);
}
@Test
void shouldWorkForSingleFilePick() {
page.setContent("<input type=file>");
Deferred<Event<Page.EventType>> event = page.waitForEvent(Page.EventType.FILECHOOSER);
page.click("input");
FileChooser fileChooser = (FileChooser) event.get().data();
assertFalse(fileChooser.isMultiple());
}
@Test
void shouldWorkForMultiple() {
page.setContent("<input multiple type=file>");
Deferred<Event<Page.EventType>> event = page.waitForEvent(Page.EventType.FILECHOOSER);
page.click("input");
FileChooser fileChooser = (FileChooser) event.get().data();
assertTrue(fileChooser.isMultiple());
}
@Test
void shouldWorkForWebkitdirectory() {
page.setContent("<input multiple webkitdirectory type=file>");
Deferred<Event<Page.EventType>> event = page.waitForEvent(Page.EventType.FILECHOOSER);
page.click("input");
FileChooser fileChooser = (FileChooser) event.get().data();
assertTrue(fileChooser.isMultiple());
}
}