feat: roll driver, implement waitForUrl and new Video APIs (#382)
This commit is contained in:
@@ -491,6 +491,8 @@ public interface BrowserContext extends AutoCloseable {
|
||||
* <p> Page routes (set up with {@link Page#route Page.route()}) take precedence over browser context routes when request
|
||||
* matches both handlers.
|
||||
*
|
||||
* <p> To remove a route with its handler you can use {@link BrowserContext#unroute BrowserContext.unroute()}.
|
||||
*
|
||||
* <p> <strong>NOTE:</strong> Enabling routing disables http cache.
|
||||
*
|
||||
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while routing.
|
||||
@@ -522,6 +524,8 @@ public interface BrowserContext extends AutoCloseable {
|
||||
* <p> Page routes (set up with {@link Page#route Page.route()}) take precedence over browser context routes when request
|
||||
* matches both handlers.
|
||||
*
|
||||
* <p> To remove a route with its handler you can use {@link BrowserContext#unroute BrowserContext.unroute()}.
|
||||
*
|
||||
* <p> <strong>NOTE:</strong> Enabling routing disables http cache.
|
||||
*
|
||||
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while routing.
|
||||
@@ -553,6 +557,8 @@ public interface BrowserContext extends AutoCloseable {
|
||||
* <p> Page routes (set up with {@link Page#route Page.route()}) take precedence over browser context routes when request
|
||||
* matches both handlers.
|
||||
*
|
||||
* <p> To remove a route with its handler you can use {@link BrowserContext#unroute BrowserContext.unroute()}.
|
||||
*
|
||||
* <p> <strong>NOTE:</strong> Enabling routing disables http cache.
|
||||
*
|
||||
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while routing.
|
||||
|
||||
@@ -29,7 +29,7 @@ import java.util.*;
|
||||
* <p> Download event is emitted once the download starts. Download path becomes available once download completes:
|
||||
* <pre>{@code
|
||||
* // wait for download to start
|
||||
* Download download = page.waitForDownload(() -> page.click("a"));
|
||||
* Download download = page.waitForDownload(() -> page.click("a"));
|
||||
* // wait for download to complete
|
||||
* Path path = download.path();
|
||||
* }</pre>
|
||||
@@ -61,7 +61,7 @@ public interface Download {
|
||||
String failure();
|
||||
/**
|
||||
* Returns path to the downloaded file in case of successful download. The method will wait for the download to finish if
|
||||
* necessary.
|
||||
* necessary. The method throws when connected remotely via {@link BrowserType#connect BrowserType.connect()}.
|
||||
*/
|
||||
Path path();
|
||||
/**
|
||||
|
||||
@@ -888,6 +888,33 @@ public interface Frame {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
class WaitForURLOptions {
|
||||
/**
|
||||
* Maximum operation time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be
|
||||
* changed by using the {@link BrowserContext#setDefaultNavigationTimeout BrowserContext.setDefaultNavigationTimeout()},
|
||||
* {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()}, {@link Page#setDefaultNavigationTimeout
|
||||
* Page.setDefaultNavigationTimeout()} or {@link Page#setDefaultTimeout Page.setDefaultTimeout()} methods.
|
||||
*/
|
||||
public Double timeout;
|
||||
/**
|
||||
* When to consider operation succeeded, defaults to {@code load}. Events can be either:
|
||||
* <ul>
|
||||
* <li> {@code "domcontentloaded"} - consider operation to be finished when the {@code DOMContentLoaded} event is fired.</li>
|
||||
* <li> {@code "load"} - consider operation to be finished when the {@code load} event is fired.</li>
|
||||
* <li> {@code "networkidle"} - consider operation to be finished when there are no network connections for at least {@code 500} ms.</li>
|
||||
* </ul>
|
||||
*/
|
||||
public WaitUntilState waitUntil;
|
||||
|
||||
public WaitForURLOptions setTimeout(double timeout) {
|
||||
this.timeout = timeout;
|
||||
return this;
|
||||
}
|
||||
public WaitForURLOptions setWaitUntil(WaitUntilState waitUntil) {
|
||||
this.waitUntil = waitUntil;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Returns the added tag when the script's onload fires or when the script content was injected into frame.
|
||||
*
|
||||
@@ -2614,5 +2641,71 @@ public interface Frame {
|
||||
* @param timeout A timeout to wait for
|
||||
*/
|
||||
void waitForTimeout(double timeout);
|
||||
/**
|
||||
* Waits for the frame to navigate to the given URL.
|
||||
* <pre>{@code
|
||||
* frame.click("a.delayed-navigation"); // Clicking the link will indirectly cause a navigation
|
||||
* frame.waitForURL("**\/target.html");
|
||||
* }</pre>
|
||||
*
|
||||
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation.
|
||||
*/
|
||||
default void waitForURL(String url) {
|
||||
waitForURL(url, null);
|
||||
}
|
||||
/**
|
||||
* Waits for the frame to navigate to the given URL.
|
||||
* <pre>{@code
|
||||
* frame.click("a.delayed-navigation"); // Clicking the link will indirectly cause a navigation
|
||||
* frame.waitForURL("**\/target.html");
|
||||
* }</pre>
|
||||
*
|
||||
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation.
|
||||
*/
|
||||
void waitForURL(String url, WaitForURLOptions options);
|
||||
/**
|
||||
* Waits for the frame to navigate to the given URL.
|
||||
* <pre>{@code
|
||||
* frame.click("a.delayed-navigation"); // Clicking the link will indirectly cause a navigation
|
||||
* frame.waitForURL("**\/target.html");
|
||||
* }</pre>
|
||||
*
|
||||
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation.
|
||||
*/
|
||||
default void waitForURL(Pattern url) {
|
||||
waitForURL(url, null);
|
||||
}
|
||||
/**
|
||||
* Waits for the frame to navigate to the given URL.
|
||||
* <pre>{@code
|
||||
* frame.click("a.delayed-navigation"); // Clicking the link will indirectly cause a navigation
|
||||
* frame.waitForURL("**\/target.html");
|
||||
* }</pre>
|
||||
*
|
||||
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation.
|
||||
*/
|
||||
void waitForURL(Pattern url, WaitForURLOptions options);
|
||||
/**
|
||||
* Waits for the frame to navigate to the given URL.
|
||||
* <pre>{@code
|
||||
* frame.click("a.delayed-navigation"); // Clicking the link will indirectly cause a navigation
|
||||
* frame.waitForURL("**\/target.html");
|
||||
* }</pre>
|
||||
*
|
||||
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation.
|
||||
*/
|
||||
default void waitForURL(Predicate<String> url) {
|
||||
waitForURL(url, null);
|
||||
}
|
||||
/**
|
||||
* Waits for the frame to navigate to the given URL.
|
||||
* <pre>{@code
|
||||
* frame.click("a.delayed-navigation"); // Clicking the link will indirectly cause a navigation
|
||||
* frame.waitForURL("**\/target.html");
|
||||
* }</pre>
|
||||
*
|
||||
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation.
|
||||
*/
|
||||
void waitForURL(Predicate<String> url, WaitForURLOptions options);
|
||||
}
|
||||
|
||||
|
||||
@@ -1561,6 +1561,33 @@ public interface Page extends AutoCloseable {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
class WaitForURLOptions {
|
||||
/**
|
||||
* Maximum operation time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be
|
||||
* changed by using the {@link BrowserContext#setDefaultNavigationTimeout BrowserContext.setDefaultNavigationTimeout()},
|
||||
* {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()}, {@link Page#setDefaultNavigationTimeout
|
||||
* Page.setDefaultNavigationTimeout()} or {@link Page#setDefaultTimeout Page.setDefaultTimeout()} methods.
|
||||
*/
|
||||
public Double timeout;
|
||||
/**
|
||||
* When to consider operation succeeded, defaults to {@code load}. Events can be either:
|
||||
* <ul>
|
||||
* <li> {@code "domcontentloaded"} - consider operation to be finished when the {@code DOMContentLoaded} event is fired.</li>
|
||||
* <li> {@code "load"} - consider operation to be finished when the {@code load} event is fired.</li>
|
||||
* <li> {@code "networkidle"} - consider operation to be finished when there are no network connections for at least {@code 500} ms.</li>
|
||||
* </ul>
|
||||
*/
|
||||
public WaitUntilState waitUntil;
|
||||
|
||||
public WaitForURLOptions setTimeout(double timeout) {
|
||||
this.timeout = timeout;
|
||||
return this;
|
||||
}
|
||||
public WaitForURLOptions setWaitUntil(WaitUntilState waitUntil) {
|
||||
this.waitUntil = waitUntil;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
class WaitForWebSocketOptions {
|
||||
/**
|
||||
* Receives the {@code WebSocket} object and resolves to truthy value when the waiting should resolve.
|
||||
@@ -3079,6 +3106,8 @@ public interface Page extends AutoCloseable {
|
||||
* <p> Page routes take precedence over browser context routes (set up with {@link BrowserContext#route
|
||||
* BrowserContext.route()}) when request matches both handlers.
|
||||
*
|
||||
* <p> To remove a route with its handler you can use {@link Page#unroute Page.unroute()}.
|
||||
*
|
||||
* <p> <strong>NOTE:</strong> Enabling routing disables http cache.
|
||||
*
|
||||
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while routing.
|
||||
@@ -3111,6 +3140,8 @@ public interface Page extends AutoCloseable {
|
||||
* <p> Page routes take precedence over browser context routes (set up with {@link BrowserContext#route
|
||||
* BrowserContext.route()}) when request matches both handlers.
|
||||
*
|
||||
* <p> To remove a route with its handler you can use {@link Page#unroute Page.unroute()}.
|
||||
*
|
||||
* <p> <strong>NOTE:</strong> Enabling routing disables http cache.
|
||||
*
|
||||
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while routing.
|
||||
@@ -3143,6 +3174,8 @@ public interface Page extends AutoCloseable {
|
||||
* <p> Page routes take precedence over browser context routes (set up with {@link BrowserContext#route
|
||||
* BrowserContext.route()}) when request matches both handlers.
|
||||
*
|
||||
* <p> To remove a route with its handler you can use {@link Page#unroute Page.unroute()}.
|
||||
*
|
||||
* <p> <strong>NOTE:</strong> Enabling routing disables http cache.
|
||||
*
|
||||
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while routing.
|
||||
@@ -3494,6 +3527,7 @@ public interface Page extends AutoCloseable {
|
||||
* <li> {@link Page#reload Page.reload()}</li>
|
||||
* <li> {@link Page#setContent Page.setContent()}</li>
|
||||
* <li> {@link Page#waitForNavigation Page.waitForNavigation()}</li>
|
||||
* <li> {@link Page#waitForURL Page.waitForURL()}</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p> <strong>NOTE:</strong> {@link Page#setDefaultNavigationTimeout Page.setDefaultNavigationTimeout()} takes priority over {@link
|
||||
@@ -4410,6 +4444,84 @@ public interface Page extends AutoCloseable {
|
||||
* @param timeout A timeout to wait for
|
||||
*/
|
||||
void waitForTimeout(double timeout);
|
||||
/**
|
||||
* Waits for the main frame to navigate to the given URL.
|
||||
* <pre>{@code
|
||||
* page.click("a.delayed-navigation"); // Clicking the link will indirectly cause a navigation
|
||||
* page.waitForURL("**\/target.html");
|
||||
* }</pre>
|
||||
*
|
||||
* <p> Shortcut for main frame's {@link Frame#waitForURL Frame.waitForURL()}.
|
||||
*
|
||||
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation.
|
||||
*/
|
||||
default void waitForURL(String url) {
|
||||
waitForURL(url, null);
|
||||
}
|
||||
/**
|
||||
* Waits for the main frame to navigate to the given URL.
|
||||
* <pre>{@code
|
||||
* page.click("a.delayed-navigation"); // Clicking the link will indirectly cause a navigation
|
||||
* page.waitForURL("**\/target.html");
|
||||
* }</pre>
|
||||
*
|
||||
* <p> Shortcut for main frame's {@link Frame#waitForURL Frame.waitForURL()}.
|
||||
*
|
||||
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation.
|
||||
*/
|
||||
void waitForURL(String url, WaitForURLOptions options);
|
||||
/**
|
||||
* Waits for the main frame to navigate to the given URL.
|
||||
* <pre>{@code
|
||||
* page.click("a.delayed-navigation"); // Clicking the link will indirectly cause a navigation
|
||||
* page.waitForURL("**\/target.html");
|
||||
* }</pre>
|
||||
*
|
||||
* <p> Shortcut for main frame's {@link Frame#waitForURL Frame.waitForURL()}.
|
||||
*
|
||||
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation.
|
||||
*/
|
||||
default void waitForURL(Pattern url) {
|
||||
waitForURL(url, null);
|
||||
}
|
||||
/**
|
||||
* Waits for the main frame to navigate to the given URL.
|
||||
* <pre>{@code
|
||||
* page.click("a.delayed-navigation"); // Clicking the link will indirectly cause a navigation
|
||||
* page.waitForURL("**\/target.html");
|
||||
* }</pre>
|
||||
*
|
||||
* <p> Shortcut for main frame's {@link Frame#waitForURL Frame.waitForURL()}.
|
||||
*
|
||||
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation.
|
||||
*/
|
||||
void waitForURL(Pattern url, WaitForURLOptions options);
|
||||
/**
|
||||
* Waits for the main frame to navigate to the given URL.
|
||||
* <pre>{@code
|
||||
* page.click("a.delayed-navigation"); // Clicking the link will indirectly cause a navigation
|
||||
* page.waitForURL("**\/target.html");
|
||||
* }</pre>
|
||||
*
|
||||
* <p> Shortcut for main frame's {@link Frame#waitForURL Frame.waitForURL()}.
|
||||
*
|
||||
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation.
|
||||
*/
|
||||
default void waitForURL(Predicate<String> url) {
|
||||
waitForURL(url, null);
|
||||
}
|
||||
/**
|
||||
* Waits for the main frame to navigate to the given URL.
|
||||
* <pre>{@code
|
||||
* page.click("a.delayed-navigation"); // Clicking the link will indirectly cause a navigation
|
||||
* page.waitForURL("**\/target.html");
|
||||
* }</pre>
|
||||
*
|
||||
* <p> Shortcut for main frame's {@link Frame#waitForURL Frame.waitForURL()}.
|
||||
*
|
||||
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation.
|
||||
*/
|
||||
void waitForURL(Predicate<String> url, WaitForURLOptions options);
|
||||
/**
|
||||
* Performs action and waits for a new {@code WebSocket}. If predicate is provided, it passes {@code WebSocket} value into the
|
||||
* {@code predicate} function and waits for {@code predicate(webSocket)} to return a truthy value. Will throw an error if the page is
|
||||
|
||||
@@ -20,16 +20,28 @@ import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* When browser context is created with the {@code videosPath} option, each page has a video object associated with it.
|
||||
* When browser context is created with the {@code recordVideo} option, each page has a video object associated with it.
|
||||
* <pre>{@code
|
||||
* System.out.println(page.video().path());
|
||||
* }</pre>
|
||||
*/
|
||||
public interface Video {
|
||||
/**
|
||||
* Deletes the video file. Will wait for the video to finish if necessary.
|
||||
*/
|
||||
void delete();
|
||||
/**
|
||||
* Returns the file system path this video will be recorded to. The video is guaranteed to be written to the filesystem
|
||||
* upon closing the browser context.
|
||||
* upon closing the browser context. This method throws when connected remotely via {@link BrowserType#connect
|
||||
* BrowserType.connect()}.
|
||||
*/
|
||||
Path path();
|
||||
/**
|
||||
* Saves the video to a user-specified path. It is safe to call this method while the video is still in progress, or after
|
||||
* the page has closed. This method waits until the page is closed and the video is fully saved.
|
||||
*
|
||||
* @param path Path where the video should be saved.
|
||||
*/
|
||||
void saveAs(Path path);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.microsoft.playwright.PlaywrightException;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static com.microsoft.playwright.impl.Utils.writeToFile;
|
||||
|
||||
class ArtifactImpl extends ChannelOwner {
|
||||
boolean isRemote;
|
||||
public ArtifactImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
|
||||
super(parent, type, guid, initializer);
|
||||
}
|
||||
|
||||
public InputStream createReadStream() {
|
||||
JsonObject result = sendMessage("stream").getAsJsonObject();
|
||||
if (!result.has("stream")) {
|
||||
return null;
|
||||
}
|
||||
Stream stream = connection.getExistingObject(result.getAsJsonObject("stream").get("guid").getAsString());
|
||||
return stream.stream();
|
||||
}
|
||||
|
||||
public void delete() {
|
||||
sendMessage("delete");
|
||||
}
|
||||
|
||||
public String failure() {
|
||||
JsonObject result = sendMessage("failure").getAsJsonObject();
|
||||
if (result.has("error")) {
|
||||
return result.get("error").getAsString();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Path pathAfterFinished() {
|
||||
if (isRemote) {
|
||||
throw new PlaywrightException("Path is not available when using browserType.connect(). Use download.saveAs() to save a local copy.");
|
||||
}
|
||||
JsonObject json = sendMessage("pathAfterFinished").getAsJsonObject();
|
||||
return FileSystems.getDefault().getPath(json.get("value").getAsString());
|
||||
}
|
||||
|
||||
public void saveAs(Path path) {
|
||||
if (isRemote) {
|
||||
JsonObject jsonObject = sendMessage("saveAsStream").getAsJsonObject();
|
||||
Stream stream = connection.getExistingObject(jsonObject.getAsJsonObject("stream").get("guid").getAsString());
|
||||
writeToFile(stream.stream(), path);
|
||||
return;
|
||||
}
|
||||
|
||||
JsonObject params = new JsonObject();
|
||||
params.addProperty("path", path.toString());
|
||||
sendMessage("saveAs", params);
|
||||
}
|
||||
}
|
||||
@@ -255,6 +255,9 @@ public class Connection {
|
||||
case "AndroidDevice":
|
||||
// result = new AndroidDevice(parent, type, guid, initializer);
|
||||
break;
|
||||
case "Artifact":
|
||||
result = new ArtifactImpl(parent, type, guid, initializer);
|
||||
break;
|
||||
case "BindingCall":
|
||||
result = new BindingCall(parent, type, guid, initializer);
|
||||
break;
|
||||
@@ -273,9 +276,6 @@ public class Connection {
|
||||
case "Dialog":
|
||||
result = new DialogImpl(parent, type, guid, initializer);
|
||||
break;
|
||||
case "Download":
|
||||
result = new DownloadImpl(parent, type, guid, initializer);
|
||||
break;
|
||||
case "Electron":
|
||||
// result = new Playwright(parent, type, guid, initializer);
|
||||
break;
|
||||
|
||||
@@ -16,27 +16,19 @@
|
||||
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.microsoft.playwright.BrowserContext;
|
||||
import com.microsoft.playwright.Download;
|
||||
import com.microsoft.playwright.PlaywrightException;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static com.microsoft.playwright.impl.Utils.writeToFile;
|
||||
class DownloadImpl extends LoggingSupport implements Download {
|
||||
private final ArtifactImpl artifact;
|
||||
private final JsonObject initializer;
|
||||
|
||||
public class DownloadImpl extends ChannelOwner implements Download {
|
||||
private final BrowserImpl browser;
|
||||
|
||||
public DownloadImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
|
||||
super(parent, type, guid, initializer);
|
||||
browser = ((BrowserContextImpl) parent).browser();
|
||||
DownloadImpl(ArtifactImpl artifact, JsonObject initializer) {
|
||||
this.artifact = artifact;
|
||||
this.initializer = initializer;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -51,58 +43,26 @@ public class DownloadImpl extends ChannelOwner implements Download {
|
||||
|
||||
@Override
|
||||
public InputStream createReadStream() {
|
||||
return withLogging("Download.createReadStream", () -> {
|
||||
JsonObject result = sendMessage("stream").getAsJsonObject();
|
||||
if (!result.has("stream")) {
|
||||
return null;
|
||||
}
|
||||
Stream stream = connection.getExistingObject(result.getAsJsonObject("stream").get("guid").getAsString());
|
||||
return stream.stream();
|
||||
});
|
||||
return withLogging("Download.createReadStream", () -> artifact.createReadStream());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete() {
|
||||
withLogging("Download.delete", () -> {
|
||||
sendMessage("delete");
|
||||
});
|
||||
withLogging("Download.delete", () -> artifact.delete());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String failure() {
|
||||
return withLogging("Download.failure", () -> {
|
||||
JsonObject result = sendMessage("failure").getAsJsonObject();
|
||||
if (result.has("error")) {
|
||||
return result.get("error").getAsString();
|
||||
}
|
||||
return null;
|
||||
});
|
||||
return withLogging("Download.failure", () -> artifact.failure());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path path() {
|
||||
return withLogging("Download.path", () -> {
|
||||
if (browser != null && browser.isRemote) {
|
||||
throw new PlaywrightException("Path is not available when using browserType.connect(). Use download.saveAs() to save a local copy.");
|
||||
}
|
||||
JsonObject json = sendMessage("path").getAsJsonObject();
|
||||
return FileSystems.getDefault().getPath(json.get("value").getAsString());
|
||||
});
|
||||
return withLogging("Download.path", () -> artifact.pathAfterFinished());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveAs(Path path) {
|
||||
withLogging("Download.saveAs", () -> {
|
||||
if (browser != null && browser.isRemote) {
|
||||
JsonObject jsonObject = sendMessage("saveAsStream").getAsJsonObject();
|
||||
Stream stream = connection.getExistingObject(jsonObject.getAsJsonObject("stream").get("guid").getAsString());
|
||||
writeToFile(stream.stream(), path);
|
||||
return;
|
||||
}
|
||||
|
||||
JsonObject params = new JsonObject();
|
||||
params.addProperty("path", path.toString());
|
||||
sendMessage("saveAs", params);
|
||||
});
|
||||
withLogging("Download.saveAs", () -> artifact.saveAs(path));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,8 @@ import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static com.microsoft.playwright.options.LoadState.*;
|
||||
import static com.microsoft.playwright.impl.Serialization.*;
|
||||
@@ -856,10 +858,14 @@ public class FrameImpl extends ChannelOwner implements Frame {
|
||||
|
||||
@Override
|
||||
public Response waitForNavigation(WaitForNavigationOptions options, Runnable code) {
|
||||
return withLogging("Frame.waitForNavigation", () -> waitForNavigationImpl(code, options));
|
||||
return withLogging("Frame.waitForNavigation", () -> waitForNavigationImpl(code, options, null));
|
||||
}
|
||||
|
||||
Response waitForNavigationImpl(Runnable code, WaitForNavigationOptions options) {
|
||||
return waitForNavigationImpl(code, options, null);
|
||||
}
|
||||
|
||||
private Response waitForNavigationImpl(Runnable code, WaitForNavigationOptions options, UrlMatcher matcher) {
|
||||
if (options == null) {
|
||||
options = new WaitForNavigationOptions();
|
||||
}
|
||||
@@ -868,7 +874,9 @@ public class FrameImpl extends ChannelOwner implements Frame {
|
||||
}
|
||||
|
||||
List<Waitable<Response>> waitables = new ArrayList<>();
|
||||
UrlMatcher matcher = UrlMatcher.forOneOf(options.url);
|
||||
if (matcher == null) {
|
||||
matcher = UrlMatcher.forOneOf(options.url);
|
||||
}
|
||||
waitables.add(new WaitForNavigationHelper(matcher, convertViaJson(options.waitUntil, LoadState.class)));
|
||||
waitables.add(page.createWaitForCloseHelper());
|
||||
waitables.add(page.createWaitableFrameDetach(this));
|
||||
@@ -910,6 +918,37 @@ public class FrameImpl extends ChannelOwner implements Frame {
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void waitForURL(String url, WaitForURLOptions options) {
|
||||
waitForURL(new UrlMatcher(url), options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void waitForURL(Pattern url, WaitForURLOptions options) {
|
||||
waitForURL(new UrlMatcher(url), options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void waitForURL(Predicate<String> url, WaitForURLOptions options) {
|
||||
waitForURL(new UrlMatcher(url), options);
|
||||
}
|
||||
|
||||
private void waitForURL(UrlMatcher matcher, WaitForURLOptions options) {
|
||||
withLogging("Frame.waitForURL", () -> waitForURLImpl(matcher, options));
|
||||
}
|
||||
|
||||
void waitForURLImpl(UrlMatcher matcher, WaitForURLOptions options) {
|
||||
if (options == null) {
|
||||
options = new WaitForURLOptions();
|
||||
}
|
||||
if (matcher.test(url())) {
|
||||
waitForLoadStateImpl(convertViaJson(options.waitUntil, LoadState.class),
|
||||
convertViaJson(options, WaitForLoadStateOptions.class));
|
||||
return;
|
||||
}
|
||||
waitForNavigationImpl(() -> {}, convertViaJson(options, WaitForNavigationOptions.class), matcher);
|
||||
}
|
||||
|
||||
protected void handleEvent(String event, JsonObject params) {
|
||||
if ("loadstate".equals(event)) {
|
||||
JsonElement add = params.get("add");
|
||||
|
||||
@@ -44,6 +44,7 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
private final KeyboardImpl keyboard;
|
||||
private final MouseImpl mouse;
|
||||
private final TouchscreenImpl touchscreen;
|
||||
final Waitable<?> waitableClosedOrCrashed;
|
||||
private ViewportSize viewport;
|
||||
private final Router routes = new Router();
|
||||
private final Set<FrameImpl> frames = new LinkedHashSet<>();
|
||||
@@ -107,6 +108,7 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
touchscreen = new TouchscreenImpl(this);
|
||||
frames.add(mainFrame);
|
||||
timeoutSettings = new TimeoutSettings(browserContext.timeoutSettings);
|
||||
waitableClosedOrCrashed = createWaitForCloseHelper();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -138,8 +140,10 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
ConsoleMessageImpl message = connection.getExistingObject(guid);
|
||||
listeners.notify(EventType.CONSOLE, message);
|
||||
} else if ("download".equals(event)) {
|
||||
String guid = params.getAsJsonObject("download").get("guid").getAsString();
|
||||
DownloadImpl download = connection.getExistingObject(guid);
|
||||
String artifactGuid = params.getAsJsonObject("artifact").get("guid").getAsString();
|
||||
ArtifactImpl artifact = connection.getExistingObject(artifactGuid);
|
||||
artifact.isRemote = browserContext.browser() != null && browserContext.browser().isRemote;
|
||||
DownloadImpl download = new DownloadImpl(artifact, params);
|
||||
listeners.notify(EventType.DOWNLOAD, download);
|
||||
} else if ("fileChooser".equals(event)) {
|
||||
String guid = params.getAsJsonObject("element").get("guid").getAsString();
|
||||
@@ -211,7 +215,9 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
route.resume();
|
||||
}
|
||||
} else if ("video".equals(event)) {
|
||||
video().setRelativePath(params.get("relativePath").getAsString());
|
||||
String artifactGuid = params.getAsJsonObject("artifact").get("guid").getAsString();
|
||||
ArtifactImpl artifact = connection.getExistingObject(artifactGuid);
|
||||
forceVideo().setArtifact(artifact);
|
||||
} else if ("pageError".equals(event)) {
|
||||
SerializedError error = gson().fromJson(params.getAsJsonObject("error"), SerializedError.class);
|
||||
String errorStr = "";
|
||||
@@ -1144,20 +1150,23 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
return mainFrame.url();
|
||||
}
|
||||
|
||||
|
||||
private VideoImpl forceVideo() {
|
||||
if (video == null) {
|
||||
video = new VideoImpl(this);
|
||||
}
|
||||
return video;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VideoImpl video() {
|
||||
if (video != null) {
|
||||
return video;
|
||||
}
|
||||
// Note: we are creating Video object lazily, because we do not know
|
||||
// BrowserContextOptions when constructing the page - it is assigned
|
||||
// too late during launchPersistentContext.
|
||||
if (browserContext.videosDir == null) {
|
||||
return null;
|
||||
}
|
||||
video = new VideoImpl(this);
|
||||
// In case of persistent profile, we already have it.
|
||||
if (initializer.has("videoRelativePath")) {
|
||||
video.setRelativePath(initializer.get("videoRelativePath").getAsString());
|
||||
}
|
||||
return video;
|
||||
return forceVideo();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1320,6 +1329,25 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
withLogging("Page.waitForTimeout", () -> mainFrame.waitForTimeoutImpl(timeout));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void waitForURL(String url, WaitForURLOptions options) {
|
||||
waitForURL(new UrlMatcher(url), options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void waitForURL(Pattern url, WaitForURLOptions options) {
|
||||
waitForURL(new UrlMatcher(url), options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void waitForURL(Predicate<String> url, WaitForURLOptions options) {
|
||||
waitForURL(new UrlMatcher(url), options);
|
||||
}
|
||||
|
||||
private void waitForURL(UrlMatcher matcher, WaitForURLOptions options) {
|
||||
withLogging("Page.waitForURL", () -> mainFrame.waitForURLImpl(matcher, convertViaJson(options, Frame.WaitForURLOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Worker> workers() {
|
||||
return new ArrayList<>(workers);
|
||||
|
||||
@@ -16,27 +16,67 @@
|
||||
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.microsoft.playwright.PlaywrightException;
|
||||
import com.microsoft.playwright.Video;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
class VideoImpl implements Video {
|
||||
import static java.util.Arrays.asList;
|
||||
|
||||
class VideoImpl extends LoggingSupport implements Video {
|
||||
private final PageImpl page;
|
||||
private Path fullPath;
|
||||
private final WaitableResult<ArtifactImpl> waitableArtifact = new WaitableResult<>();
|
||||
private final boolean isRemote;
|
||||
|
||||
VideoImpl(PageImpl page) {
|
||||
this.page = page;
|
||||
BrowserImpl browser = page.context().browser();
|
||||
isRemote = browser != null && browser.isRemote;
|
||||
}
|
||||
|
||||
void setRelativePath(String path) {
|
||||
fullPath = page.context().videosDir.resolve(path);
|
||||
void setArtifact(ArtifactImpl artifact) {
|
||||
artifact.isRemote = isRemote;
|
||||
waitableArtifact.complete(artifact);
|
||||
}
|
||||
|
||||
private ArtifactImpl waitForArtifact() {
|
||||
Waitable<ArtifactImpl> waitable = new WaitableRace<>(asList(waitableArtifact, (Waitable<ArtifactImpl>) page.waitableClosedOrCrashed));
|
||||
return page.runUntil(() -> {}, waitable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete() {
|
||||
withLogging("Video.delete", () -> {
|
||||
try {
|
||||
waitForArtifact().delete();
|
||||
} catch (PlaywrightException e) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path path() {
|
||||
while (fullPath == null) {
|
||||
page.connection.processOneMessage();
|
||||
}
|
||||
return fullPath;
|
||||
return withLogging("Video.path", () -> {
|
||||
if (isRemote) {
|
||||
throw new PlaywrightException("Path is not available when using browserType.connect(). Use saveAs() to save a local copy.");
|
||||
}
|
||||
try {
|
||||
return Paths.get(waitForArtifact().initializer.get("absolutePath").getAsString());
|
||||
} catch (PlaywrightException e) {
|
||||
throw new PlaywrightException("Page did not produce any video frames", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveAs(Path path) {
|
||||
withLogging("Video.saveAs", () -> {
|
||||
try {
|
||||
waitForArtifact().saveAs(path);
|
||||
} catch (PlaywrightException e) {
|
||||
throw new PlaywrightException("Page did not produce any video frames", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,7 +213,7 @@ public class TestDownload extends TestBase {
|
||||
download.saveAs(userPath);
|
||||
fail("did not throw");
|
||||
} catch (PlaywrightException e) {
|
||||
assertTrue(e.getMessage().contains("Download already deleted. Save before deleting."));
|
||||
assertTrue(e.getMessage().contains("File already deleted. Save before deleting."), e.getMessage());
|
||||
}
|
||||
page.close();
|
||||
}
|
||||
|
||||
@@ -523,6 +523,7 @@ public class TestPageRoute extends TestBase {
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisabledIf(value="com.microsoft.playwright.TestBase#isChromium", disabledReason="https://github.com/microsoft/playwright/issues/6016")
|
||||
void shouldSupportCorsWithPOST() {
|
||||
page.navigate(server.EMPTY_PAGE);
|
||||
page.route("**/cars", route -> {
|
||||
@@ -545,6 +546,7 @@ public class TestPageRoute extends TestBase {
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisabledIf(value="com.microsoft.playwright.TestBase#isChromium", disabledReason="https://github.com/microsoft/playwright/issues/6016")
|
||||
void shouldSupportCorsWithCredentials() {
|
||||
page.navigate(server.EMPTY_PAGE);
|
||||
page.route("**/cars", route -> {
|
||||
@@ -597,6 +599,7 @@ public class TestPageRoute extends TestBase {
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisabledIf(value="com.microsoft.playwright.TestBase#isChromium", disabledReason="https://github.com/microsoft/playwright/issues/6016")
|
||||
void shouldSupportCorsForDifferentMethods() {
|
||||
page.navigate(server.EMPTY_PAGE);
|
||||
page.route("**/cars", route -> {
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* 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 com.microsoft.playwright.options.WaitUntilState;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class TestPageWaitForUrl extends TestBase {
|
||||
@Test
|
||||
void shouldWork() {
|
||||
page.navigate(server.EMPTY_PAGE);
|
||||
page.evaluate("url => window.location.href = url", server.PREFIX + "/grid.html");
|
||||
page.waitForURL("**/grid.html");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRespectTimeout() {
|
||||
page.navigate(server.EMPTY_PAGE);
|
||||
try {
|
||||
page.waitForURL("**/frame.html", new Page.WaitForURLOptions().setTimeout(2500));
|
||||
fail("did not throw");
|
||||
} catch (TimeoutError e) {
|
||||
assertTrue(e.getMessage().contains("Timeout 2500ms exceeded"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldWorkWithBothDomcontentloadedAndLoad() {
|
||||
page.navigate(server.PREFIX + "/one-style.html");
|
||||
page.waitForURL("**/one-style.html", new Page.WaitForURLOptions().setWaitUntil(WaitUntilState.DOMCONTENTLOADED));
|
||||
page.waitForURL("**/one-style.html", new Page.WaitForURLOptions().setWaitUntil(WaitUntilState.LOAD));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldWorkWithClickingOnAnchorLinks() {
|
||||
page.navigate(server.EMPTY_PAGE);
|
||||
page.setContent("<a href='#foobar'>foobar</a>");
|
||||
page.click("a");
|
||||
page.waitForURL("**/*#foobar");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldWorkWithHistoryPushState() {
|
||||
page.navigate(server.EMPTY_PAGE);
|
||||
page.setContent("<a onclick='javascript:pushState()'>SPA</a>\n" +
|
||||
"<script>\n" +
|
||||
" function pushState() { history.pushState({}, '', 'wow.html') }\n" +
|
||||
"</script>");
|
||||
page.click("a");
|
||||
page.waitForURL("**/wow.html");
|
||||
assertEquals(server.PREFIX + "/wow.html", page.url());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldWorkWithHistoryReplaceState() {
|
||||
page.navigate(server.EMPTY_PAGE);
|
||||
page.setContent(" <a onclick='javascript:replaceState()'>SPA</a>\n" +
|
||||
"<script>\n" +
|
||||
" function replaceState() { history.replaceState({}, '', '/replaced.html') }\n" +
|
||||
"</script>");
|
||||
page.click("a");
|
||||
page.waitForURL("**/replaced.html");
|
||||
assertEquals(server.PREFIX + "/replaced.html", page.url());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldWorkWithDOMHistoryBackHistoryForward() {
|
||||
page.navigate(server.EMPTY_PAGE);
|
||||
page.setContent("<a id=back onclick='javascript:goBack()'>back</a>\n" +
|
||||
"<a id=forward onclick='javascript:goForward()'>forward</a>\n" +
|
||||
"<script>\n" +
|
||||
" function goBack() { history.back(); }\n" +
|
||||
" function goForward() { history.forward(); }\n" +
|
||||
" history.pushState({}, '', '/first.html');\n" +
|
||||
" history.pushState({}, '', '/second.html');\n" +
|
||||
"</script>");
|
||||
assertEquals(server.PREFIX + "/second.html", page.url());
|
||||
|
||||
page.click("a#back");
|
||||
page.waitForURL("**/first.html");
|
||||
assertEquals(server.PREFIX + "/first.html", page.url());
|
||||
|
||||
page.click("a#forward");
|
||||
page.waitForURL("**/second.html");
|
||||
assertEquals(server.PREFIX + "/second.html", page.url());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldWorkOnFrame() {
|
||||
page.navigate(server.PREFIX + "/frames/one-frame.html");
|
||||
Frame frame = page.frames().get(1);
|
||||
frame.evaluate("url => window.location.href = url", server.PREFIX + "/grid.html");
|
||||
frame.waitForURL("**/grid.html");
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ import org.junit.jupiter.api.io.TempDir;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class TestScreencast extends TestBase {
|
||||
@Test
|
||||
@@ -36,5 +36,69 @@ public class TestScreencast extends TestBase {
|
||||
assertTrue(path.startsWith(videosDir));
|
||||
context.close();
|
||||
assertTrue(Files.exists(path));
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSaveAsVideo(@TempDir Path videosDir) {
|
||||
BrowserContext context = browser.newContext(
|
||||
new Browser.NewContextOptions()
|
||||
.setRecordVideoDir(videosDir)
|
||||
.setRecordVideoSize(320, 240)
|
||||
.setViewportSize(320, 240));
|
||||
Page page = context.newPage();
|
||||
page.evaluate("() => document.body.style.backgroundColor = 'red'");
|
||||
page.waitForTimeout(1000);
|
||||
context.close();
|
||||
|
||||
Path saveAsPath = videosDir.resolve("my-video.webm");
|
||||
page.video().saveAs(saveAsPath);
|
||||
assertTrue(Files.exists(saveAsPath));
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveAsShouldThrowWhenNoVideoFrames(@TempDir Path videosDir) {
|
||||
BrowserContext context = browser.newContext(
|
||||
new Browser.NewContextOptions()
|
||||
.setRecordVideoDir(videosDir)
|
||||
.setRecordVideoSize(320, 240)
|
||||
.setViewportSize(320, 240));
|
||||
|
||||
Page page = context.newPage();
|
||||
Page popup = context.waitForPage(() -> {
|
||||
page.evaluate("() => {\n" +
|
||||
" const win = window.open('about:blank');\n" +
|
||||
" win.close();\n" +
|
||||
"}");
|
||||
});
|
||||
page.close();
|
||||
|
||||
Path saveAsPath = videosDir.resolve("my-video.webm");
|
||||
try {
|
||||
popup.video().saveAs(saveAsPath);
|
||||
} catch (PlaywrightException e) {
|
||||
// WebKit pauses renderer before win.close() and actually writes something.
|
||||
if (isWebKit()) {
|
||||
assertTrue(Files.exists(saveAsPath));
|
||||
} else {
|
||||
assertTrue(e.getMessage().contains("Page did not produce any video frames"), e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldDeleteVideo(@TempDir Path videosDir) {
|
||||
BrowserContext context = browser.newContext(
|
||||
new Browser.NewContextOptions()
|
||||
.setRecordVideoDir(videosDir)
|
||||
.setRecordVideoSize(320, 240)
|
||||
.setViewportSize(320, 240));
|
||||
Page page = context.newPage();
|
||||
page.evaluate("() => document.body.style.backgroundColor = 'red'");
|
||||
page.waitForTimeout(1000);
|
||||
context.close();
|
||||
|
||||
page.video().delete();
|
||||
Path videoPath = page.video().path();
|
||||
assertFalse(Files.exists(videoPath));
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
1.11.0-next-1617087307000
|
||||
1.11.0-next-1617218236000
|
||||
|
||||
Reference in New Issue
Block a user