mirror of
https://github.com/microsoft/playwright-java.git
synced 2025-09-08 21:01:00 +00:00
feat: support timeout in waitForEvent, more FileChooser (#34)
This commit is contained in:
parent
6d5724f166
commit
a02e70ad84
@ -24,8 +24,11 @@ import java.io.*;
|
|||||||
import java.nio.file.FileSystems;
|
import java.nio.file.FileSystems;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.function.Predicate;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static java.util.Arrays.asList;
|
||||||
|
|
||||||
abstract class Element {
|
abstract class Element {
|
||||||
final String jsonName;
|
final String jsonName;
|
||||||
final String jsonPath;
|
final String jsonPath;
|
||||||
@ -295,6 +298,20 @@ class Method extends Element {
|
|||||||
};
|
};
|
||||||
customSignature.put("Page.setInputFiles", setInputFilesWithSelector);
|
customSignature.put("Page.setInputFiles", setInputFilesWithSelector);
|
||||||
customSignature.put("Frame.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) {
|
Method(TypeDefinition parent, JsonObject jsonElement) {
|
||||||
@ -503,7 +520,7 @@ class Interface extends TypeDefinition {
|
|||||||
"\n" +
|
"\n" +
|
||||||
"package com.microsoft.playwright;\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) {
|
Interface(JsonObject jsonElement) {
|
||||||
super(null, jsonElement);
|
super(null, jsonElement);
|
||||||
@ -530,14 +547,14 @@ class Interface extends TypeDefinition {
|
|||||||
if (jsonName.equals("Route")) {
|
if (jsonName.equals("Route")) {
|
||||||
output.add("import java.nio.charset.StandardCharsets;");
|
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.io.File;");
|
||||||
}
|
}
|
||||||
output.add("import java.util.*;");
|
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;");
|
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.function.Predicate;");
|
||||||
output.add("import java.util.regex.Pattern;");
|
output.add("import java.util.regex.Pattern;");
|
||||||
}
|
}
|
||||||
@ -589,10 +606,12 @@ class Interface extends TypeDefinition {
|
|||||||
switch (jsonName) {
|
switch (jsonName) {
|
||||||
case "Mouse": {
|
case "Mouse": {
|
||||||
output.add(offset + "enum Button { LEFT, MIDDLE, RIGHT }");
|
output.add(offset + "enum Button { LEFT, MIDDLE, RIGHT }");
|
||||||
|
output.add("");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "Keyboard": {
|
case "Keyboard": {
|
||||||
output.add(offset + "enum Modifier { ALT, CONTROL, META, SHIFT }");
|
output.add(offset + "enum Modifier { ALT, CONTROL, META, SHIFT }");
|
||||||
|
output.add("");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "Page": {
|
case "Page": {
|
||||||
@ -656,6 +675,7 @@ class Interface extends TypeDefinition {
|
|||||||
output.add(offset + " return password;");
|
output.add(offset + " return password;");
|
||||||
output.add(offset + " }");
|
output.add(offset + " }");
|
||||||
output.add(offset + "}");
|
output.add(offset + "}");
|
||||||
|
output.add("");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "ElementHandle": {
|
case "ElementHandle": {
|
||||||
@ -665,6 +685,7 @@ class Interface extends TypeDefinition {
|
|||||||
output.add(offset + " public double width;");
|
output.add(offset + " public double width;");
|
||||||
output.add(offset + " public double height;");
|
output.add(offset + " public double height;");
|
||||||
output.add(offset + "}");
|
output.add(offset + "}");
|
||||||
|
output.add("");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "FileChooser": {
|
case "FileChooser": {
|
||||||
@ -679,12 +700,27 @@ class Interface extends TypeDefinition {
|
|||||||
output.add(offset + " this.buffer = buffer;");
|
output.add(offset + " this.buffer = buffer;");
|
||||||
output.add(offset + " }");
|
output.add(offset + " }");
|
||||||
output.add(offset + "}");
|
output.add(offset + "}");
|
||||||
|
output.add("");
|
||||||
break;
|
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("");
|
output.add("");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class NestedClass extends TypeDefinition {
|
class NestedClass extends TypeDefinition {
|
||||||
|
@ -210,7 +210,7 @@ class Types {
|
|||||||
add("Page.setInputFiles.files", "string|Array<string>|Object|Array<Object>", "String");
|
add("Page.setInputFiles.files", "string|Array<string>|Object|Array<Object>", "String");
|
||||||
add("Page.unroute.url", "string|RegExp|function(URL):boolean", "String");
|
add("Page.unroute.url", "string|RegExp|function(URL):boolean", "String");
|
||||||
add("Page.waitForEvent.event", "string", "EventType", new Empty());
|
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.waitForEvent", "Promise<Object>", "Deferred<Event<EventType>>", new Empty());
|
||||||
add("Page.waitForRequest.urlOrPredicate", "string|RegExp|Function", "String");
|
add("Page.waitForRequest.urlOrPredicate", "string|RegExp|Function", "String");
|
||||||
add("Page.waitForResponse.urlOrPredicate", "string|RegExp|Function", "String");
|
add("Page.waitForResponse.urlOrPredicate", "string|RegExp|Function", "String");
|
||||||
|
@ -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 {
|
enum EventType {
|
||||||
PAGE,
|
PAGE,
|
||||||
}
|
}
|
||||||
@ -120,8 +133,13 @@ public interface BrowserContext {
|
|||||||
void unroute(Pattern url, BiConsumer<Route, Request> handler);
|
void unroute(Pattern url, BiConsumer<Route, Request> handler);
|
||||||
void unroute(Predicate<String> url, BiConsumer<Route, Request> handler);
|
void unroute(Predicate<String> url, BiConsumer<Route, Request> handler);
|
||||||
default Deferred<Event<EventType>> waitForEvent(EventType event) {
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,6 +61,18 @@ public interface Page {
|
|||||||
String stack();
|
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 {
|
enum EventType {
|
||||||
CLOSE,
|
CLOSE,
|
||||||
@ -962,9 +974,14 @@ public interface Page {
|
|||||||
Video video();
|
Video video();
|
||||||
Viewport viewportSize();
|
Viewport viewportSize();
|
||||||
default Deferred<Event<EventType>> waitForEvent(EventType event) {
|
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) {
|
default Deferred<JSHandle> waitForFunction(String pageFunction, Object arg) {
|
||||||
return waitForFunction(pageFunction, arg, null);
|
return waitForFunction(pageFunction, arg, null);
|
||||||
}
|
}
|
||||||
|
@ -39,6 +39,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
|
|||||||
final Map<String, Page.Binding> bindings = new HashMap<String, Page.Binding>();
|
final Map<String, Page.Binding> bindings = new HashMap<String, Page.Binding>();
|
||||||
PageImpl ownerPage;
|
PageImpl ownerPage;
|
||||||
private final ListenerCollection<EventType> listeners = new ListenerCollection<>();
|
private final ListenerCollection<EventType> listeners = new ListenerCollection<>();
|
||||||
|
final TimeoutSettings timeoutSettings = new TimeoutSettings();
|
||||||
|
|
||||||
protected BrowserContextImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
|
protected BrowserContextImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
|
||||||
super(parent, type, guid, initializer);
|
super(parent, type, guid, initializer);
|
||||||
@ -83,7 +84,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Browser browser() {
|
public Browser browser() {
|
||||||
return null;
|
return browser;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -171,12 +172,18 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setDefaultNavigationTimeout(int timeout) {
|
public void setDefaultNavigationTimeout(int timeout) {
|
||||||
|
timeoutSettings.setDefaultNavigationTimeout(timeout);
|
||||||
|
JsonObject params = new JsonObject();
|
||||||
|
params.addProperty("timeout", timeout);
|
||||||
|
sendMessage("setDefaultNavigationTimeoutNoReply", params);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setDefaultTimeout(int timeout) {
|
public void setDefaultTimeout(int timeout) {
|
||||||
|
timeoutSettings.setDefaultTimeout(timeout);
|
||||||
|
JsonObject params = new JsonObject();
|
||||||
|
params.addProperty("timeout", timeout);
|
||||||
|
sendMessage("setDefaultTimeoutNoReply", params);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -217,8 +224,14 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Deferred<Event<EventType>> waitForEvent(EventType event, String optionsOrPredicate) {
|
public Deferred<Event<EventType>> waitForEvent(EventType event, WaitForEventOptions options) {
|
||||||
return toDeferred(new WaitableEvent<>(listeners, event));
|
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) {
|
private void unroute(UrlMatcher matcher, BiConsumer<Route, Request> handler) {
|
||||||
|
@ -457,6 +457,9 @@ public class FrameImpl extends ChannelOwner implements Frame {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Deferred<Void> waitForLoadState(LoadState state, WaitForLoadStateOptions options) {
|
public Deferred<Void> waitForLoadState(LoadState state, WaitForLoadStateOptions options) {
|
||||||
|
if (options == null) {
|
||||||
|
options = new WaitForLoadStateOptions();
|
||||||
|
}
|
||||||
if (state == null) {
|
if (state == null) {
|
||||||
state = LOAD;
|
state = LOAD;
|
||||||
}
|
}
|
||||||
@ -464,9 +467,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
|
|||||||
List<Waitable<Void>> waitables = new ArrayList<>();
|
List<Waitable<Void>> waitables = new ArrayList<>();
|
||||||
waitables.add(new WaitForLoadStateHelper(state));
|
waitables.add(new WaitForLoadStateHelper(state));
|
||||||
waitables.add(page.createWaitForCloseHelper());
|
waitables.add(page.createWaitForCloseHelper());
|
||||||
if (options != null && options.timeout != null) {
|
waitables.add(page.createWaitableTimeout(options.timeout));
|
||||||
waitables.add(new WaitableTimeout<>(options.timeout));
|
|
||||||
}
|
|
||||||
return toDeferred(new WaitableRace<>(waitables));
|
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(new WaitForNavigationHelper(matcher, options.waitUntil));
|
||||||
waitables.add(page.createWaitForCloseHelper());
|
waitables.add(page.createWaitForCloseHelper());
|
||||||
waitables.add(page.createWaitableFrameDetach(this));
|
waitables.add(page.createWaitableFrameDetach(this));
|
||||||
if (options.timeout != null) {
|
waitables.add(page.createWaitableNavigationTimeout(options.timeout));
|
||||||
waitables.add(new WaitableTimeout<>(options.timeout));
|
|
||||||
}
|
|
||||||
return toDeferred(new WaitableRace<>(waitables));
|
return toDeferred(new WaitableRace<>(waitables));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,6 +44,7 @@ public class PageImpl extends ChannelOwner implements Page {
|
|||||||
BrowserContextImpl ownedContext;
|
BrowserContextImpl ownedContext;
|
||||||
private boolean isClosed;
|
private boolean isClosed;
|
||||||
final Set<Worker> workers = new HashSet<>();
|
final Set<Worker> workers = new HashSet<>();
|
||||||
|
private final TimeoutSettings timeoutSettings;
|
||||||
|
|
||||||
PageImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
|
PageImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
|
||||||
super(parent, type, guid, initializer);
|
super(parent, type, guid, initializer);
|
||||||
@ -53,6 +54,7 @@ public class PageImpl extends ChannelOwner implements Page {
|
|||||||
keyboard = new KeyboardImpl(this);
|
keyboard = new KeyboardImpl(this);
|
||||||
mouse = new MouseImpl(this);
|
mouse = new MouseImpl(this);
|
||||||
frames.add(mainFrame);
|
frames.add(mainFrame);
|
||||||
|
timeoutSettings = new TimeoutSettings(browserContext.timeoutSettings);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -550,6 +552,7 @@ public class PageImpl extends ChannelOwner implements Page {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setDefaultNavigationTimeout(int timeout) {
|
public void setDefaultNavigationTimeout(int timeout) {
|
||||||
|
timeoutSettings.setDefaultNavigationTimeout(timeout);
|
||||||
JsonObject params = new JsonObject();
|
JsonObject params = new JsonObject();
|
||||||
params.addProperty("timeout", timeout);
|
params.addProperty("timeout", timeout);
|
||||||
sendMessage("setDefaultNavigationTimeoutNoReply", params);
|
sendMessage("setDefaultNavigationTimeoutNoReply", params);
|
||||||
@ -557,6 +560,7 @@ public class PageImpl extends ChannelOwner implements Page {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setDefaultTimeout(int timeout) {
|
public void setDefaultTimeout(int timeout) {
|
||||||
|
timeoutSettings.setDefaultTimeout(timeout);
|
||||||
JsonObject params = new JsonObject();
|
JsonObject params = new JsonObject();
|
||||||
params.addProperty("timeout", timeout);
|
params.addProperty("timeout", timeout);
|
||||||
sendMessage("setDefaultTimeoutNoReply", params);
|
sendMessage("setDefaultTimeoutNoReply", params);
|
||||||
@ -653,22 +657,34 @@ public class PageImpl extends ChannelOwner implements Page {
|
|||||||
return viewport;
|
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
|
@Override
|
||||||
public Deferred<Event<EventType>> waitForEvent(EventType event, String optionsOrPredicate) {
|
public Deferred<Event<EventType>> waitForEvent(EventType event, WaitForEventOptions options) {
|
||||||
Waitable<Event<EventType>> waitable;
|
if (options == null) {
|
||||||
|
options = new WaitForEventOptions();
|
||||||
|
}
|
||||||
|
List<Waitable<Event<EventType>>> waitables = new ArrayList<>();
|
||||||
if (event == EventType.FILECHOOSER) {
|
if (event == EventType.FILECHOOSER) {
|
||||||
willAddFileChooserListener();
|
willAddFileChooserListener();
|
||||||
waitable = new WaitableEvent<EventType>(listeners, event) {
|
waitables.add(new WaitableEvent<EventType>(listeners, event, options.predicate) {
|
||||||
@Override
|
@Override
|
||||||
public void dispose() {
|
public void dispose() {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
didRemoveFileChooserListener();
|
didRemoveFileChooserListener();
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
} else {
|
} 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
|
@Override
|
||||||
@ -785,6 +801,9 @@ public class PageImpl extends ChannelOwner implements Page {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Deferred<Request> waitForRequest(String urlOrPredicate, WaitForRequestOptions options) {
|
public Deferred<Request> waitForRequest(String urlOrPredicate, WaitForRequestOptions options) {
|
||||||
|
if (options == null) {
|
||||||
|
options = new WaitForRequestOptions();
|
||||||
|
}
|
||||||
List<Waitable<Request>> waitables = new ArrayList<>();
|
List<Waitable<Request>> waitables = new ArrayList<>();
|
||||||
waitables.add(new WaitableEvent<>(listeners, EventType.REQUEST,e -> {
|
waitables.add(new WaitableEvent<>(listeners, EventType.REQUEST,e -> {
|
||||||
if (urlOrPredicate == null) {
|
if (urlOrPredicate == null) {
|
||||||
@ -793,14 +812,15 @@ public class PageImpl extends ChannelOwner implements Page {
|
|||||||
return urlOrPredicate.equals(((Request) e.data()).url());
|
return urlOrPredicate.equals(((Request) e.data()).url());
|
||||||
}).apply(event -> (Request) event.data()));
|
}).apply(event -> (Request) event.data()));
|
||||||
waitables.add(createWaitForCloseHelper());
|
waitables.add(createWaitForCloseHelper());
|
||||||
if (options != null && options.timeout != null) {
|
waitables.add(createWaitableTimeout(options.timeout));
|
||||||
waitables.add(new WaitableTimeout<>(options.timeout));
|
|
||||||
}
|
|
||||||
return toDeferred(new WaitableRace<>(waitables));
|
return toDeferred(new WaitableRace<>(waitables));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Deferred<Response> waitForResponse(String urlOrPredicate, WaitForResponseOptions options) {
|
public Deferred<Response> waitForResponse(String urlOrPredicate, WaitForResponseOptions options) {
|
||||||
|
if (options == null) {
|
||||||
|
options = new WaitForResponseOptions();
|
||||||
|
}
|
||||||
List<Waitable<Response>> waitables = new ArrayList<>();
|
List<Waitable<Response>> waitables = new ArrayList<>();
|
||||||
waitables.add(new WaitableEvent<>(listeners, EventType.RESPONSE, e -> {
|
waitables.add(new WaitableEvent<>(listeners, EventType.RESPONSE, e -> {
|
||||||
if (urlOrPredicate == null) {
|
if (urlOrPredicate == null) {
|
||||||
@ -809,9 +829,7 @@ public class PageImpl extends ChannelOwner implements Page {
|
|||||||
return urlOrPredicate.equals(((Response) e.data()).url());
|
return urlOrPredicate.equals(((Response) e.data()).url());
|
||||||
}).apply(event -> (Response) event.data()));
|
}).apply(event -> (Response) event.data()));
|
||||||
waitables.add(createWaitForCloseHelper());
|
waitables.add(createWaitForCloseHelper());
|
||||||
if (options != null && options.timeout != null) {
|
waitables.add(createWaitableTimeout(options.timeout));
|
||||||
waitables.add(new WaitableTimeout<>(options.timeout));
|
|
||||||
}
|
|
||||||
return toDeferred(new WaitableRace<>(waitables));
|
return toDeferred(new WaitableRace<>(waitables));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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() {
|
||||||
|
}
|
||||||
|
}
|
@ -22,7 +22,7 @@ class WaitableTimeout<T> implements Waitable<T> {
|
|||||||
|
|
||||||
WaitableTimeout(int millis) {
|
WaitableTimeout(int millis) {
|
||||||
timeout = millis;
|
timeout = millis;
|
||||||
deadline = System.nanoTime() + millis * 1_000_000;
|
deadline = System.nanoTime() + (long) millis * 1_000_000;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -39,3 +39,4 @@ class WaitableTimeout<T> implements Waitable<T> {
|
|||||||
public void dispose() {
|
public void dispose() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,9 +21,12 @@ import org.junit.jupiter.api.Test;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.Instant;
|
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 java.util.Arrays.asList;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
|
||||||
public class TestPageSetInputFiles extends TestBase {
|
public class TestPageSetInputFiles extends TestBase {
|
||||||
@ -119,4 +122,185 @@ public class TestPageSetInputFiles extends TestBase {
|
|||||||
assertEquals(1, page.evalOnSelector("input", "input => input.files.length"));
|
assertEquals(1, page.evalOnSelector("input", "input => input.files.length"));
|
||||||
assertEquals("file-to-upload.txt", page.evalOnSelector("input", "input => input.files[0].name"));
|
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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user