1
0
mirror of synced 2026-05-24 19:53:16 +00:00

Compare commits

...

17 Commits

Author SHA1 Message Date
Yury Semikhatsky 23ad37122a chore: set version to 1.19.0 (#814) 2022-02-15 16:06:10 -08:00
Yury Semikhatsky b448a1789a chore: roll 1.19.0 driver (#813) 2022-02-15 14:06:06 -08:00
Yury Semikhatsky 79b2c70513 fix: catch up with recent upstream changes (#812) 2022-02-15 08:39:27 -08:00
Max Schmitt d476d0a98c devops: trigger Java publish workflow on release (#811) 2022-02-14 22:44:03 +01:00
Yury Semikhatsky 2122c5690a fix: support multiple source dirs (#809) 2022-02-14 09:52:29 -08:00
Yury Semikhatsky 3119102b10 fix(assertions): include expected/actual values into error message (#808) 2022-02-11 13:02:27 -08:00
Yury Semikhatsky 838e7a40b3 feat: roll driver, support "has" option (#805) 2022-02-10 14:11:39 -08:00
Yury Semikhatsky ea6ede4670 fix: skip syntetic fields when converting options (#804) 2022-02-09 11:44:46 -08:00
Yury Semikhatsky 3cdefc2931 fix: normalize whitespaces in hasTitle (#803) 2022-02-09 09:54:46 -08:00
Yury Semikhatsky 119700a678 feat: add response param to route.fulfill (#797) 2022-01-28 08:38:24 -08:00
Yury Semikhatsky 17a4143a83 fix: throw if route is handled twice (#796) 2022-01-27 14:02:11 -08:00
Max Schmitt c03f4a9384 fix: CLI don't download browers automatically and set env accordingly (#790) 2022-01-24 12:54:49 -08:00
Andrey Lushnikov 85b671328e chore: roll to latest 1.18 (#785) 2022-01-19 16:42:07 -08:00
Yury Semikhatsky 11f898ca7f test: route.resume does not throw if page is closed (#781) 2022-01-19 13:19:33 -08:00
Yury Semikhatsky 2aef5c6742 fix: hide internal call from inspector log (#783) 2022-01-19 13:18:47 -08:00
Yury Semikhatsky 897d441c02 fix: include var name into tracing error message (#779) 2022-01-19 10:42:47 -08:00
Andrey Lushnikov f4c69faad3 chore: cut v1.18.0 (#777) 2022-01-19 08:42:53 -08:00
58 changed files with 581 additions and 160 deletions
+2 -1
View File
@@ -1,6 +1,7 @@
name: Publish
on:
workflow_dispatch:
release:
types: [published]
push:
branches:
- main
+2 -2
View File
@@ -11,9 +11,9 @@ Playwright is a Java library to automate [Chromium](https://www.chromium.org/Hom
| | Linux | macOS | Windows |
| :--- | :---: | :---: | :---: |
| Chromium <!-- GEN:chromium-version -->99.0.4812.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Chromium <!-- GEN:chromium-version -->100.0.4863.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| WebKit <!-- GEN:webkit-version -->15.4<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Firefox <!-- GEN:firefox-version -->95.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Firefox <!-- GEN:firefox-version -->96.0.1<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
Headless execution is supported for all the browsers on all platforms. Check out [system requirements](https://playwright.dev/java/docs/next/intro/#system-requirements) for details.
+1 -1
View File
@@ -6,7 +6,7 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.18.0-SNAPSHOT</version>
<version>1.19.0</version>
</parent>
<artifactId>driver-bundle</artifactId>
@@ -40,9 +40,10 @@ public class DriverJar extends Driver {
}
@Override
protected void initialize(Map<String, String> env) throws Exception {
protected void initialize(Map<String, String> env, Boolean installBrowsers) throws Exception {
extractDriverToTempDir();
installBrowsers(env);
if (installBrowsers)
installBrowsers(env);
}
private void installBrowsers(Map<String, String> env) throws IOException, InterruptedException {
@@ -60,6 +61,7 @@ public class DriverJar extends Driver {
}
ProcessBuilder pb = new ProcessBuilder(driver.toString(), "install");
pb.environment().putAll(env);
setRequiredEnvironmentVariables(pb);
pb.redirectError(ProcessBuilder.Redirect.INHERIT);
pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
Process p = pb.start();
@@ -40,7 +40,7 @@ public class TestInstall {
@Test
void playwrightCliInstalled() throws Exception {
Path cli = Driver.ensureDriverInstalled(Collections.emptyMap());
Path cli = Driver.ensureDriverInstalled(Collections.emptyMap(), false);
assertTrue(Files.exists(cli));
ProcessBuilder pb = new ProcessBuilder(cli.toString(), "install");
+1 -1
View File
@@ -6,7 +6,7 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.18.0-SNAPSHOT</version>
<version>1.19.0</version>
</parent>
<artifactId>driver</artifactId>
@@ -35,7 +35,7 @@ public abstract class Driver {
}
@Override
protected void initialize(Map<String, String> env) {
protected void initialize(Map<String, String> env, Boolean installBrowsers) {
// no-op
}
@@ -45,11 +45,11 @@ public abstract class Driver {
}
}
public static synchronized Path ensureDriverInstalled(Map<String, String> env) {
public static synchronized Path ensureDriverInstalled(Map<String, String> env, Boolean installBrowsers) {
if (instance == null) {
try {
instance = createDriver();
instance.initialize(env);
instance.initialize(env, installBrowsers);
} catch (Exception exception) {
throw new RuntimeException("Failed to create driver", exception);
}
@@ -57,7 +57,7 @@ public abstract class Driver {
return instance.driverPath();
}
protected abstract void initialize(Map<String, String> env) throws Exception;
protected abstract void initialize(Map<String, String> env, Boolean installBrowsers) throws Exception;
public Path driverPath() {
String cliFileName = System.getProperty("os.name").toLowerCase().contains("windows") ?
@@ -66,9 +66,11 @@ public abstract class Driver {
}
public static void setRequiredEnvironmentVariables(ProcessBuilder pb) {
if (!pb.environment().containsKey("PW_CLI_TARGET_LANG")) {
pb.environment().put("PW_CLI_TARGET_LANG", "java");
pb.environment().put("PW_CLI_TARGET_LANG_VERSION", getMajorJavaVersion());
pb.environment().put("PW_LANG_NAME", "java");
pb.environment().put("PW_LANG_NAME_VERSION", getMajorJavaVersion());
String version = Driver.class.getPackage().getImplementationVersion();
if (version != null) {
pb.environment().put("PW_CLI_DISPLAY_VERSION", version);
}
}
+1 -1
View File
@@ -6,7 +6,7 @@
<groupId>org.example</groupId>
<artifactId>examples</artifactId>
<version>1.18.0-SNAPSHOT</version>
<version>1.19.0</version>
<name>Playwright Client Examples</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+1 -1
View File
@@ -7,7 +7,7 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.18.0-SNAPSHOT</version>
<version>1.19.0</version>
</parent>
<artifactId>playwright</artifactId>
@@ -29,7 +29,7 @@ import static java.util.Arrays.asList;
*/
public class CLI {
public static void main(String[] args) throws IOException, InterruptedException {
Path driver = Driver.ensureDriverInstalled(Collections.emptyMap());
Path driver = Driver.ensureDriverInstalled(Collections.emptyMap(), false);
ProcessBuilder pb = new ProcessBuilder(driver.toString());
pb.command().addAll(asList(args));
Driver.setRequiredEnvironmentVariables(pb);
@@ -1218,12 +1218,29 @@ public interface Frame {
}
}
class LocatorOptions {
/**
* Matches elements containing an element that matches an inner locator. Inner locator is queried against the outer one.
* For example, {@code article} that has {@code text=Playwright} matches {@code <article><div>Playwright</div></article>}.
*
* <p> Note that outer and inner locators must belong to the same frame. Inner locator must not contain {@code FrameLocator}s.
*/
public Locator has;
/**
* Matches elements containing specified text somewhere inside, possibly in a child or a descendant element. For example,
* {@code "Playwright"} matches {@code <article><div>Playwright</div></article>}.
*/
public Object hasText;
/**
* Matches elements containing an element that matches an inner locator. Inner locator is queried against the outer one.
* For example, {@code article} that has {@code text=Playwright} matches {@code <article><div>Playwright</div></article>}.
*
* <p> Note that outer and inner locators must belong to the same frame. Inner locator must not contain {@code FrameLocator}s.
*/
public LocatorOptions setHas(Locator has) {
this.has = has;
return this;
}
/**
* Matches elements containing specified text somewhere inside, possibly in a child or a descendant element. For example,
* {@code "Playwright"} matches {@code <article><div>Playwright</div></article>}.
@@ -49,12 +49,29 @@ import java.util.regex.Pattern;
*/
public interface FrameLocator {
class LocatorOptions {
/**
* Matches elements containing an element that matches an inner locator. Inner locator is queried against the outer one.
* For example, {@code article} that has {@code text=Playwright} matches {@code <article><div>Playwright</div></article>}.
*
* <p> Note that outer and inner locators must belong to the same frame. Inner locator must not contain {@code FrameLocator}s.
*/
public Locator has;
/**
* Matches elements containing specified text somewhere inside, possibly in a child or a descendant element. For example,
* {@code "Playwright"} matches {@code <article><div>Playwright</div></article>}.
*/
public Object hasText;
/**
* Matches elements containing an element that matches an inner locator. Inner locator is queried against the outer one.
* For example, {@code article} that has {@code text=Playwright} matches {@code <article><div>Playwright</div></article>}.
*
* <p> Note that outer and inner locators must belong to the same frame. Inner locator must not contain {@code FrameLocator}s.
*/
public LocatorOptions setHas(Locator has) {
this.has = has;
return this;
}
/**
* Matches elements containing specified text somewhere inside, possibly in a child or a descendant element. For example,
* {@code "Playwright"} matches {@code <article><div>Playwright</div></article>}.
@@ -864,12 +864,29 @@ public interface Locator {
}
}
class LocatorOptions {
/**
* Matches elements containing an element that matches an inner locator. Inner locator is queried against the outer one.
* For example, {@code article} that has {@code text=Playwright} matches {@code <article><div>Playwright</div></article>}.
*
* <p> Note that outer and inner locators must belong to the same frame. Inner locator must not contain {@code FrameLocator}s.
*/
public Locator has;
/**
* Matches elements containing specified text somewhere inside, possibly in a child or a descendant element. For example,
* {@code "Playwright"} matches {@code <article><div>Playwright</div></article>}.
*/
public Object hasText;
/**
* Matches elements containing an element that matches an inner locator. Inner locator is queried against the outer one.
* For example, {@code article} that has {@code text=Playwright} matches {@code <article><div>Playwright</div></article>}.
*
* <p> Note that outer and inner locators must belong to the same frame. Inner locator must not contain {@code FrameLocator}s.
*/
public LocatorOptions setHas(Locator has) {
this.has = has;
return this;
}
/**
* Matches elements containing specified text somewhere inside, possibly in a child or a descendant element. For example,
* {@code "Playwright"} matches {@code <article><div>Playwright</div></article>}.
@@ -2180,6 +2197,10 @@ public interface Locator {
* Returns locator to the n-th matching element.
*/
Locator nth(int index);
/**
* A page this locator belongs to.
*/
Page page();
/**
* Focuses the element, and then uses {@link Keyboard#down Keyboard.down()} and {@link Keyboard#up Keyboard.up()}.
*
@@ -1639,12 +1639,29 @@ public interface Page extends AutoCloseable {
}
}
class LocatorOptions {
/**
* Matches elements containing an element that matches an inner locator. Inner locator is queried against the outer one.
* For example, {@code article} that has {@code text=Playwright} matches {@code <article><div>Playwright</div></article>}.
*
* <p> Note that outer and inner locators must belong to the same frame. Inner locator must not contain {@code FrameLocator}s.
*/
public Locator has;
/**
* Matches elements containing specified text somewhere inside, possibly in a child or a descendant element. For example,
* {@code "Playwright"} matches {@code <article><div>Playwright</div></article>}.
*/
public Object hasText;
/**
* Matches elements containing an element that matches an inner locator. Inner locator is queried against the outer one.
* For example, {@code article} that has {@code text=Playwright} matches {@code <article><div>Playwright</div></article>}.
*
* <p> Note that outer and inner locators must belong to the same frame. Inner locator must not contain {@code FrameLocator}s.
*/
public LocatorOptions setHas(Locator has) {
this.has = has;
return this;
}
/**
* Matches elements containing specified text somewhere inside, possibly in a child or a descendant element. For example,
* {@code "Playwright"} matches {@code <article><div>Playwright</div></article>}.
@@ -100,6 +100,11 @@ public interface Route {
* is resolved relative to the current working directory.
*/
public Path path;
/**
* {@code APIResponse} to fulfill route's request with. Individual fields of the response (such as headers) can be overridden
* using fulfill options.
*/
public APIResponse response;
/**
* Response status code, defaults to {@code 200}.
*/
@@ -141,6 +146,14 @@ public interface Route {
this.path = path;
return this;
}
/**
* {@code APIResponse} to fulfill route's request with. Individual fields of the response (such as headers) can be overridden
* using fulfill options.
*/
public FulfillOptions setResponse(APIResponse response) {
this.response = response;
return this;
}
/**
* Response status code, defaults to {@code 200}.
*/
@@ -47,7 +47,11 @@ public interface Tracing {
*/
public Boolean screenshots;
/**
* Whether to capture DOM snapshot on every action.
* If this option is true tracing will
* <ul>
* <li> capture DOM snapshot on every action</li>
* <li> record network activity</li>
* </ul>
*/
public Boolean snapshots;
/**
@@ -76,7 +80,11 @@ public interface Tracing {
return this;
}
/**
* Whether to capture DOM snapshot on every action.
* If this option is true tracing will
* <ul>
* <li> capture DOM snapshot on every action</li>
* <li> record network activity</li>
* </ul>
*/
public StartOptions setSnapshots(boolean snapshots) {
this.snapshots = snapshots;
@@ -39,7 +39,7 @@ package com.microsoft.playwright.assertions;
public interface APIResponseAssertions {
/**
* Makes the assertion check for the opposite condition. For example, this code tests that the response status is not
* successfull:
* successful:
* <pre>{@code
* assertThat(response).not().isOK();
* }</pre>
@@ -48,15 +48,6 @@ import com.microsoft.playwright.impl.PageAssertionsImpl;
* You can pass this timeout as an option.
*
* <p> By default, the timeout for assertions is set to 5 seconds.
*
* <p> To use Playwright assertions add the following dependency into the {@code pom.xml} of your Maven project:
* <pre>{@code
* <dependency>
* <groupId>com.microsoft.playwright</groupId>
* <artifactId>assertions</artifactId>
* <version>1.17.0</version>
* </dependency>
* }</pre>
*/
public interface PlaywrightAssertions {
/**
@@ -21,8 +21,11 @@ import static com.microsoft.playwright.impl.Serialization.*;
import static com.microsoft.playwright.impl.Utils.toFilePayload;
class APIRequestContextImpl extends ChannelOwner implements APIRequestContext {
private final TracingImpl tracing;
APIRequestContextImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
this.tracing = connection.getExistingObject(initializer.getAsJsonObject("tracing").get("guid").getAsString());
}
@Override
@@ -34,7 +34,7 @@ import static com.microsoft.playwright.impl.Utils.isSafeCloseError;
import static java.util.Arrays.asList;
class APIResponseImpl implements APIResponse {
private final APIRequestContextImpl context;
final APIRequestContextImpl context;
private final JsonObject initializer;
private final RawHeaders headers;
@@ -109,7 +109,7 @@ class APIResponseImpl implements APIResponse {
return initializer.get("url").getAsString();
}
private String fetchUid() {
String fetchUid() {
return initializer.get("fetchUid").getAsString();
}
@@ -67,7 +67,10 @@ class AssertionsBase {
if (expected == null) {
throw new AssertionFailedError(message + log);
}
throw new AssertionFailedError(message + log, formatValue(expected), formatValue(actual));
ValueWrapper expectedValue = formatValue(expected);
ValueWrapper actualValue = formatValue(actual);
message += ": " + expectedValue.getStringRepresentation() + "\nReceived: " + actualValue.getStringRepresentation() + "\n";
throw new AssertionFailedError(message + log, expectedValue, actualValue);
}
}
@@ -55,7 +55,6 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
Path videosDir;
URL baseUrl;
Path recordHarPath;
LocalUtils localUtils;
enum EventType {
CLOSE,
@@ -73,7 +72,8 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
} else {
browser = null;
}
this.tracing = new TracingImpl(this);
this.tracing = connection.getExistingObject(initializer.getAsJsonObject("tracing").get("guid").getAsString());
tracing.isRemote = browser != null && browser.isRemote;
this.request = connection.getExistingObject(initializer.getAsJsonObject("APIRequestContext").get("guid").getAsString());
}
@@ -429,7 +429,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
}
@Override
public Tracing tracing() {
public TracingImpl tracing() {
return tracing;
}
@@ -172,7 +172,7 @@ class BrowserImpl extends ChannelOwner implements Browser {
context.setBaseUrl(options.baseURL);
}
context.recordHarPath = options.recordHarPath;
context.localUtils = localUtils;
context.tracing().localUtils = localUtils;
contexts.add(context);
return context;
}
@@ -180,7 +180,7 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
context.setBaseUrl(options.baseURL);
}
context.recordHarPath = options.recordHarPath;
context.localUtils = localUtils;
context.tracing().localUtils = localUtils;
return context;
}
@@ -23,7 +23,6 @@ import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.TimeoutError;
import java.io.IOException;
import java.nio.file.Paths;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
@@ -85,8 +84,7 @@ public class Connection {
}
this.transport = transport;
root = new Root(this);
String srcRoot = System.getenv("PLAYWRIGHT_JAVA_SRC");
stackTraceCollector = (srcRoot == null) ? null: new StackTraceCollector(Paths.get(srcRoot));
stackTraceCollector = StackTraceCollector.createFromEnv();
}
boolean isCollectingStacks() {
@@ -121,11 +119,15 @@ public class Connection {
message.addProperty("method", method);
message.add("params", params);
JsonObject metadata = new JsonObject();
if (stackTraceCollector != null) {
metadata.add("stack", stackTraceCollector.currentStackTrace());
}
if (apiName != null) {
if (apiName == null) {
metadata.addProperty("internal", true);
} else {
metadata.addProperty("apiName", apiName);
// All but first message in an API call are considered internal and will be hidden from the inspector.
apiName = null;
if (stackTraceCollector != null) {
metadata.add("stack", stackTraceCollector.currentStackTrace());
}
}
message.add("metadata", metadata);
transport.send(message);
@@ -291,6 +293,9 @@ public class Connection {
case "Selectors":
result = new SelectorsImpl(parent, type, guid, initializer);
break;
case "Tracing":
result = new TracingImpl(parent, type, guid, initializer);
break;
case "WebSocket":
result = new WebSocketImpl(parent, type, guid, initializer);
break;
@@ -586,7 +586,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
@Override
public Page page() {
public PageImpl page() {
return page;
}
@@ -24,14 +24,24 @@ class LocatorImpl implements Locator {
public LocatorImpl(FrameImpl frame, String selector, LocatorOptions options) {
this.frame = frame;
if (options != null && options.hasText != null) {
if (options.hasText instanceof Pattern) {
Pattern pattern = (Pattern) options.hasText;
selector += " >> :scope:text-matches(" + escapeWithQuotes(pattern.pattern()) + ", \"" + toJsRegexFlags(pattern) + "\")";
} else if (options.hasText instanceof String) {
String text = (String) options.hasText;
selector += " >> :scope:has-text(" + escapeWithQuotes(text)+ ")";
if (options != null) {
if (options.hasText != null) {
if (options.hasText instanceof Pattern) {
Pattern pattern = (Pattern) options.hasText;
selector += " >> :scope:text-matches(" + escapeWithQuotes(pattern.pattern()) + ", \"" + toJsRegexFlags(pattern) + "\")";
} else if (options.hasText instanceof String) {
String text = (String) options.hasText;
selector += " >> :scope:has-text(" + escapeWithQuotes(text) + ")";
}
}
if (options.has != null) {
LocatorImpl has = (LocatorImpl) options.has;
if (has.frame != frame) {
throw new PlaywrightException("Inner 'has' locator must belong to the same frame.");
}
selector += " >> has=" + gson().toJson(has.selector);
}
}
this.selector = selector;
}
@@ -281,6 +291,11 @@ class LocatorImpl implements Locator {
return new LocatorImpl(frame, selector + " >> nth=" + index, null);
}
@Override
public Page page() {
return frame.page();
}
@Override
public void press(String key, PressOptions options) {
if (options == null) {
@@ -40,6 +40,7 @@ public class PageAssertionsImpl extends AssertionsBase implements PageAssertions
public void hasTitle(String title, HasTitleOptions options) {
ExpectedTextValue expected = new ExpectedTextValue();
expected.string = title;
expected.normalizeWhiteSpace = true;
expectImpl("to.have.title", expected, title, "Page title expected to be", convertType(options, FrameExpectOptions.class));
}
@@ -37,7 +37,7 @@ public class PlaywrightImpl extends ChannelOwner implements Playwright {
if (options != null && options.env != null) {
env = options.env;
}
Path driver = Driver.ensureDriverInstalled(env);
Path driver = Driver.ensureDriverInstalled(env, true);
ProcessBuilder pb = new ProcessBuilder(driver.toString(), "run-driver");
pb.redirectError(ProcessBuilder.Redirect.INHERIT);
pb.environment().putAll(env);
@@ -18,7 +18,6 @@ package com.microsoft.playwright.impl;
import com.google.gson.JsonObject;
import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.Request;
import com.microsoft.playwright.Route;
import java.io.IOException;
@@ -29,12 +28,15 @@ import java.util.LinkedHashMap;
import java.util.Map;
public class RouteImpl extends ChannelOwner implements Route {
private boolean handled;
public RouteImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
}
@Override
public void abort(String errorCode) {
startHandling();
withLogging("Route.abort", () -> {
JsonObject params = new JsonObject();
params.addProperty("errorCode", errorCode);
@@ -44,6 +46,7 @@ public class RouteImpl extends ChannelOwner implements Route {
@Override
public void resume(ResumeOptions options) {
startHandling();
withLogging("Route.resume", () -> resumeImpl(options));
}
@@ -78,6 +81,7 @@ public class RouteImpl extends ChannelOwner implements Route {
@Override
public void fulfill(FulfillOptions options) {
startHandling();
withLogging("Route.fulfill", () -> fulfillImpl(options));
}
@@ -86,16 +90,30 @@ public class RouteImpl extends ChannelOwner implements Route {
options = new FulfillOptions();
}
int status = options.status == null ? 200 : options.status;
String body = "";
Integer status = options.status;
Map<String, String> headersOption = options.headers;
String fetchResponseUid = null;
if (options.response != null) {
if (status == null) {
status = options.response.status();
}
if (headersOption == null) {
headersOption = options.response.headers();
}
}
if (status == null) {
status = 200;
}
String body = null;
boolean isBase64 = false;
int length = 0;
if (options.path != null) {
try {
byte[] buffer = Files.readAllBytes(options.path);
body = Base64.getEncoder().encodeToString(buffer);
isBase64 = true;
length = buffer.length;
byte[] buffer = Files.readAllBytes(options.path);
body = Base64.getEncoder().encodeToString(buffer);
isBase64 = true;
length = buffer.length;
} catch (IOException e) {
throw new PlaywrightException("Failed to read from file: " + options.path, e);
}
@@ -107,11 +125,22 @@ public class RouteImpl extends ChannelOwner implements Route {
body = Base64.getEncoder().encodeToString(options.bodyBytes);
isBase64 = true;
length = options.bodyBytes.length;
} else if (options.response != null) {
APIResponseImpl response = (APIResponseImpl) options.response;
if (response.context.connection == connection) {
fetchResponseUid = response.fetchUid();
} else {
byte[] bodyBytes = response.body();
body = Base64.getEncoder().encodeToString(bodyBytes);
isBase64 = true;
length = bodyBytes.length;
}
}
Map<String, String> headers = new LinkedHashMap<>();
if (options.headers != null) {
for (Map.Entry<String, String> h : options.headers.entrySet()) {
if (headersOption != null) {
for (Map.Entry<String, String> h : headersOption.entrySet()) {
headers.put(h.getKey().toLowerCase(), h.getValue());
}
}
@@ -128,11 +157,21 @@ public class RouteImpl extends ChannelOwner implements Route {
params.add("headers", Serialization.toProtocol(headers));
params.addProperty("isBase64", isBase64);
params.addProperty("body", body);
if (fetchResponseUid != null) {
params.addProperty("fetchResponseUid", fetchResponseUid);
}
sendMessageAsync("fulfill", params);
}
@Override
public Request request() {
public RequestImpl request() {
return connection.getExistingObject(initializer.getAsJsonObject("request").get("guid").getAsString());
}
private void startHandling() {
if (handled) {
throw new PlaywrightException("Route is already handled!");
}
handled = true;
}
}
@@ -23,15 +23,30 @@ import com.microsoft.playwright.PlaywrightException;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.stream.Collectors;
class StackTraceCollector {
private final Path srcDir;
private final List<Path> srcDirs;
private final Map<Path, String> classToSourceCache = new HashMap<>();
StackTraceCollector(Path srcDir) {
if (!Files.exists(srcDir.toAbsolutePath())) {
throw new PlaywrightException("Source location doesn't exist: '" + srcDir.toAbsolutePath() + "'");
static StackTraceCollector createFromEnv() {
String srcRoots = System.getenv("PLAYWRIGHT_JAVA_SRC");
if (srcRoots == null) {
return null;
}
this.srcDir = srcDir;
List<Path> srcDirs = Arrays.stream(srcRoots.split(File.pathSeparator)).map(p -> Paths.get(p)).collect(Collectors.toList());
for (Path srcDir: srcDirs) {
if (!Files.exists(srcDir.toAbsolutePath())) {
throw new PlaywrightException("Source location specified in PLAYWRIGHT_JAVA_SRC doesn't exist: '" + srcDir.toAbsolutePath() + "'");
}
}
return new StackTraceCollector(srcDirs);
}
private StackTraceCollector(List<Path> srcDirs) {
this.srcDirs = srcDirs;
}
private String sourceFile(StackTraceElement frame) {
@@ -47,7 +62,26 @@ class StackTraceCollector {
if (file == null) {
return "";
}
return srcDir.resolve(pkg).resolve(file).toString();
return resolveSourcePath(Paths.get(pkg).resolve(file));
}
private String resolveSourcePath(Path relativePath) {
String path = classToSourceCache.get(relativePath);
if (path == null) {
for (Path dir : srcDirs) {
Path absolutePath = dir.resolve(relativePath);
if (Files.exists(absolutePath)) {
path = absolutePath.toString();
classToSourceCache.put(relativePath, path);
break;
}
}
if (path == null) {
path = "";
classToSourceCache.put(relativePath, path);
}
}
return path;
}
JsonArray currentStackTrace() {
@@ -25,16 +25,16 @@ import java.nio.file.Path;
import static com.microsoft.playwright.impl.Serialization.gson;
class TracingImpl implements Tracing {
private final BrowserContextImpl context;
class TracingImpl extends ChannelOwner implements Tracing {
LocalUtils localUtils;
boolean isRemote;
private boolean includeSources;
TracingImpl(BrowserContextImpl context) {
this.context = context;
TracingImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
}
private void stopChunkImpl(Path path) {
boolean isRemote = context.browser() != null && context.browser().isRemote;
JsonObject params = new JsonObject();
String mode = "doNotSave";
if (path != null) {
@@ -45,11 +45,11 @@ class TracingImpl implements Tracing {
}
}
params.addProperty("mode", mode);
JsonObject json = context.sendMessage("tracingStopChunk", params).getAsJsonObject();
JsonObject json = sendMessage("tracingStopChunk", params).getAsJsonObject();
if (!json.has("artifact")) {
return;
}
ArtifactImpl artifact = context.connection.getExistingObject(json.getAsJsonObject("artifact").get("guid").getAsString());
ArtifactImpl artifact = connection.getExistingObject(json.getAsJsonObject("artifact").get("guid").getAsString());
// In case of CDP connection browser is null but since the connection is established by
// the driver it is safe to consider the artifact local.
if (isRemote) {
@@ -61,18 +61,18 @@ class TracingImpl implements Tracing {
// Add local sources to the remote trace if necessary.
if (isRemote && json.has("sourceEntries")) {
JsonArray entries = json.getAsJsonArray("sourceEntries");
context.localUtils.zip(path, entries);
localUtils.zip(path, entries);
}
}
@Override
public void start(StartOptions options) {
context.withLogging("Tracing.start", () -> startImpl(options));
withLogging("Tracing.start", () -> startImpl(options));
}
@Override
public void startChunk(StartChunkOptions options) {
context.withLogging("Tracing.startChunk", () -> {
withLogging("Tracing.startChunk", () -> {
startChunkImpl(options);
});
}
@@ -82,7 +82,7 @@ class TracingImpl implements Tracing {
options = new StartChunkOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
context.sendMessage("tracingStartChunk", params);
sendMessage("tracingStartChunk", params);
}
private void startImpl(StartOptions options) {
@@ -92,26 +92,26 @@ class TracingImpl implements Tracing {
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
includeSources = options.sources != null;
if (includeSources) {
if (!context.connection.isCollectingStacks()) {
throw new PlaywrightException("Source root directories must be provided to enable source collection");
if (!connection.isCollectingStacks()) {
throw new PlaywrightException("Source root directory must be specified via PLAYWRIGHT_JAVA_SRC environment variable when source collection is enabled");
}
params.addProperty("sources", true);
}
context.sendMessage("tracingStart", params);
context.sendMessage("tracingStartChunk");
sendMessage("tracingStart", params);
sendMessage("tracingStartChunk");
}
@Override
public void stop(StopOptions options) {
context.withLogging("Tracing.stop", () -> {
withLogging("Tracing.stop", () -> {
stopChunkImpl(options == null ? null : options.path);
context.sendMessage("tracingStop");
sendMessage("tracingStop");
});
}
@Override
public void stopChunk(StopChunkOptions options) {
context.withLogging("Tracing.stopChunk", () -> {
withLogging("Tracing.stopChunk", () -> {
stopChunkImpl(options == null ? null : options.path);
});
}
@@ -51,6 +51,10 @@ class Utils {
try {
T result = t.getDeclaredConstructor().newInstance();
for (Field toField : t.getDeclaredFields()) {
// Skip fields added by test coverage tools, see https://github.com/microsoft/playwright-java/issues/802
if (toField.isSynthetic()) {
continue;
}
if (Modifier.isStatic(toField.getModifiers())) {
throw new RuntimeException("Unexpected field modifiers: " + t.getCanonicalName() + "." + toField.getName() + ", modifiers: " + toField.getModifiers());
}
@@ -22,7 +22,4 @@ interface Waitable<T> {
boolean isDone();
T get();
void dispose();
default <U> Waitable<U> apply(Function<T, U> transform) {
return new WaitableAdapter<T, U>(this, transform);
}
}
@@ -57,6 +57,7 @@ public class TestBrowserContextAddCookies extends TestBase {
.setDomain(cookies.get(0).domain)
.setPath(cookies.get(0).path)
.setExpires(cookies.get(0).expires)
.setSameSite(cookies.get(0).sameSite)
));
assertJsonEquals(new Gson().toJson(cookies), context.cookies());
}
@@ -345,21 +346,7 @@ public class TestBrowserContextAddCookies extends TestBase {
"}", server.CROSS_PROCESS_PREFIX + "/grid.html");
page.frames().get(1).evaluate("document.cookie = 'username=John Doe'");
page.waitForTimeout(2000);
boolean allowsThirdParty = isFirefox();
List<Cookie> cookies = context.cookies(server.CROSS_PROCESS_PREFIX + "/grid.html");
if (allowsThirdParty) {
assertJsonEquals("[{\n" +
" 'domain': '127.0.0.1',\n" +
" 'expires': -1,\n" +
" 'httpOnly': false,\n" +
" 'name': 'username',\n" +
" 'path': '/',\n" +
" 'sameSite': 'NONE',\n" +
" 'secure': false,\n" +
" 'value': 'John Doe'\n" +
"}]", cookies);
} else {
assertEquals(0, cookies.size());
}
assertEquals(0, cookies.size());
}
}
@@ -50,7 +50,7 @@ public class TestBrowserContextCookies extends TestBase {
" expires: -1,\n" +
" httpOnly: false,\n" +
" secure: false,\n" +
" sameSite: '" + (isChromium() ? "LAX" : "NONE") +"'\n" +
" sameSite: '" + (isChromium() || isFirefox() ? "LAX" : "NONE") +"'\n" +
" }]", cookies);
}
@@ -74,7 +74,7 @@ public class TestBrowserContextCookies extends TestBase {
assertEquals(timestamp, cookie.expires);
assertEquals(false, cookie.httpOnly);
assertEquals(false, cookie.secure);
if (isChromium()) {
if (isChromium() || isFirefox()) {
assertEquals(SameSiteAttribute.LAX, cookie.sameSite);
} else {
assertEquals(SameSiteAttribute.NONE, cookie.sameSite);
@@ -146,7 +146,7 @@ public class TestBrowserContextCookies extends TestBase {
" expires: -1,\n" +
" httpOnly: false,\n" +
" secure: false,\n" +
" sameSite: '" + (isChromium() ? "LAX" : "NONE") +"'\n" +
" sameSite: '" + (isChromium() || isFirefox() ? "LAX" : "NONE") +"'\n" +
" },\n" +
" {\n" +
" name: 'username',\n" +
@@ -156,7 +156,7 @@ public class TestBrowserContextCookies extends TestBase {
" expires: -1,\n" +
" httpOnly: false,\n" +
" secure: false,\n" +
" sameSite: '" + (isChromium() ? "LAX" : "NONE") +"'\n" +
" sameSite: '" + (isChromium() || isFirefox() ? "LAX" : "NONE") +"'\n" +
" }\n" +
"]", cookies);
}
@@ -16,10 +16,13 @@
package com.microsoft.playwright;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.function.Consumer;
import static java.util.Arrays.asList;
@@ -134,4 +137,70 @@ public class TestBrowserContextRoute extends TestBase {
page.navigate(server.EMPTY_PAGE);
assertEquals(2, intercepted[0]);
}
@Test
void shouldOverwritePostBodyWithEmptyString() throws ExecutionException, InterruptedException {
context.route("**/empty.html", route -> {
route.resume(new Route.ResumeOptions().setPostData(""));
});
Future<Server.Request> req = server.futureRequest("/empty.html");
page.setContent("<script>\n" +
" (async () => {\n" +
" await fetch('" + server.EMPTY_PAGE + "', {\n" +
" method: 'POST',\n" +
" body: 'original',\n" +
" });\n" +
" })()\n" +
" </script>");
byte[] body = req.get().postBody;
assertEquals(0, body.length);
}
@Test
void shouldNotSwallowExceptionsInRoute() throws ExecutionException, InterruptedException {
context.route("**/empty.html", route -> {
throw new RuntimeException("My Exception");
});
try {
page.navigate(server.EMPTY_PAGE);
fail("did not throw");
} catch (RuntimeException e) {
assertTrue(e.getMessage().contains("My Exception"), e.getMessage());
}
}
@Test
@Disabled("Conflicts with https://github.com/microsoft/playwright-java/pull/680")
void shouldNotSwallowExceptionsInFulfill() throws ExecutionException, InterruptedException {
APIRequestContext request = playwright.request().newContext();
APIResponse response = request.get(server.EMPTY_PAGE);
response.dispose();
page.route("**/*", route -> {
// Fulfilling with dsiposed response will lead to a server-side exception.
route.fulfill(new Route.FulfillOptions().setResponse(response));
});
try {
page.navigate(server.EMPTY_PAGE);
fail("did not throw");
} catch (RuntimeException e) {
assertTrue(e.getMessage().contains("Fetch response has been disposed"), e.getMessage());
}
}
@Test
@Disabled("Conflicts with https://github.com/microsoft/playwright-java/pull/680")
void shouldNotSwallowExceptionsInResume() throws ExecutionException, InterruptedException {
page.route("**/*", route -> {
route.resume(new Route.ResumeOptions().setUrl("file:///tmp"));
});
try {
page.navigate(server.EMPTY_PAGE);
fail("did not throw");
} catch (RuntimeException e) {
assertTrue(e.getMessage().contains("New URL must have same protocol as overridden URL"), e.getMessage());
}
}
}
@@ -109,7 +109,7 @@ public class TestBrowserContextStorageState extends TestBase {
" 'expires':-1,\n" +
" 'httpOnly':false,\n" +
" 'secure':false,\n" +
" 'sameSite':'" + (isChromium() ? "Lax" : "None") + "'\n" +
" 'sameSite':'" + (isChromium() || isFirefox() ? "Lax" : "None") + "'\n" +
" }],\n" +
" 'origins':[\n" +
" {\n" +
@@ -60,7 +60,7 @@ public class TestBrowserTypeConnect extends TestBase {
private static BrowserServer launchBrowserServer(BrowserType browserType) {
try {
Path driver = Driver.ensureDriverInstalled(Collections.emptyMap());
Path driver = Driver.ensureDriverInstalled(Collections.emptyMap(), false);
Path dir = driver.getParent();
String node = dir.resolve(isWindows ? "node.exe" : "node").toString();
String cliJs = dir.resolve("package/lib/cli/cli.js").toString();
@@ -507,4 +507,15 @@ public class TestBrowserTypeConnect extends TestBase {
assertEquals(new String(thisFile, UTF_8), new String(sources.values().iterator().next(), UTF_8));
}
@Test
void shouldFulfillWithGlobalFetchResult() {
page.route("**/*", route -> {
APIRequestContext request = playwright.request().newContext();
APIResponse response = request.get(server.PREFIX + "/simple.json");
route.fulfill(new Route.FulfillOptions().setResponse(response));
});
Response response = page.navigate(server.EMPTY_PAGE);
assertEquals(200, response.status());
assertEquals("{\"foo\": \"bar\"}\n", response.text());
}
}
@@ -137,7 +137,7 @@ public class TestGlobalFetch extends TestBase {
Optional<HttpHeader> contentType = response.headersArray().stream().filter(h -> "content-type".equals(h.name.toLowerCase())).findFirst();
assertTrue(contentType.isPresent());
assertEquals("application/json", contentType.get().value);
assertEquals("{\"foo\": \"bar\"}\n", response.text());
assertEquals("", response.text());
}
@Test
@@ -190,7 +190,8 @@ public class TestLocatorAssertions extends TestBase {
} catch (AssertionFailedError e) {
assertEquals("[Text 1, Text 3, Extra]", e.getExpected().getStringRepresentation());
assertEquals("[Text 1, Text 3]", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected to have text"), e.getMessage());
assertTrue(e.getMessage().contains("Locator expected to have text: [Text 1, Text 3, Extra]"), e.getMessage());
assertTrue(e.getMessage().contains("Received: [Text 1, Text 3]"), e.getMessage());
}
}
@@ -234,7 +235,7 @@ public class TestLocatorAssertions extends TestBase {
} catch (AssertionFailedError e) {
assertEquals("foo", e.getExpected().getStringRepresentation());
assertEquals("node", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected to have attribute 'id'"), e.getMessage());
assertTrue(e.getMessage().contains("Locator expected to have attribute 'id': foo\nReceived: node"), e.getMessage());
}
}
@@ -255,7 +256,7 @@ public class TestLocatorAssertions extends TestBase {
} catch (AssertionFailedError e) {
assertEquals(".Nod..", e.getExpected().getStringRepresentation());
assertEquals("node", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected to have attribute 'id' matching regex"), e.getMessage());
assertTrue(e.getMessage().contains("Locator expected to have attribute 'id' matching regex: .Nod..\nReceived: node"), e.getMessage());
}
}
@@ -91,6 +91,12 @@ public class TestPageAssertions extends TestBase {
assertThat(page).hasTitle("Woof-Woof", new PageAssertions.HasTitleOptions().setTimeout(1_000));
}
@Test
void hasTitleTextNormalizeWhitespaces() {
page.setContent("<title> Foo Bar </title>");
assertThat(page).hasTitle(" Foo Bar", new PageAssertions.HasTitleOptions().setTimeout(1_000));
}
@Test
void hasTitleTextFail() {
page.navigate(server.PREFIX + "/title.html");
@@ -100,7 +106,7 @@ public class TestPageAssertions extends TestBase {
} catch (AssertionFailedError e) {
assertEquals("foo", e.getExpected().getValue());
assertEquals("Woof-Woof", e.getActual().getValue());
assertTrue(e.getMessage().contains("Page title expected to be"), e.getMessage());
assertTrue(e.getMessage().contains("Page title expected to be: foo\nReceived: Woof-Woof"), e.getMessage());
}
}
@@ -119,7 +125,7 @@ public class TestPageAssertions extends TestBase {
} catch (AssertionFailedError e) {
assertEquals("^foo[AB]", e.getExpected().getStringRepresentation());
assertEquals("Woof-Woof", e.getActual().getValue());
assertTrue(e.getMessage().contains("Page title expected to match regex"), e.getMessage());
assertTrue(e.getMessage().contains("Page title expected to match regex: ^foo[AB]\nReceived: Woof-Woof"), e.getMessage());
}
}
@@ -14,30 +14,21 @@
* limitations under the License.
*/
package com.microsoft.playwright.impl;
package com.microsoft.playwright;
import java.util.function.Function;
import org.junit.jupiter.api.Test;
class WaitableAdapter<F, T> implements Waitable<T> {
private final Waitable<F> waitable;
private final Function<F, T> transformation;
import static org.junit.jupiter.api.Assertions.assertEquals;
WaitableAdapter(Waitable<F> waitable, Function<F, T> transformation) {
this.waitable = waitable;
this.transformation = transformation;
}
@Override
public boolean isDone() {
return waitable.isDone();
}
@Override
public T get() {
return transformation.apply(waitable.get());
}
@Override
public void dispose() {
waitable.dispose();
public class TestPageLocatorConvenience extends TestBase {
@Test
void shouldReturnPage() {
page.navigate(server.PREFIX + "/frames/two-frames.html");
Locator outer = page.locator("#outer");
assertEquals(page, outer.page());
Locator inner = outer.locator("#inner");
assertEquals(page, inner.page());
Locator inFrame = page.frames().get(1).locator("div");
assertEquals(page, inFrame.page());
}
}
@@ -20,6 +20,7 @@ import org.junit.jupiter.api.Test;
import java.util.regex.Pattern;
import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;
public class TestPageLocatorQuery extends TestBase {
@@ -118,4 +119,19 @@ public class TestPageLocatorQuery extends TestBase {
assertEquals("Hello \"world\"", page.locator("div", new Page.LocatorOptions().setHasText(pattern)).textContent());
}
@Test
void shouldSupportHasLocator() {
page.setContent("<div><span>hello</span></div><div><span>world</span></div>");
assertThat(page.locator("div", new Page.LocatorOptions().setHas(page.locator("text=world")))).hasCount(1);
assertEquals("<div><span>world</span></div>", page.locator("div", new Page.LocatorOptions().setHas(page.locator("text=world"))).evaluate("e => e.outerHTML"));
assertThat(page.locator("div", new Page.LocatorOptions().setHas(page.locator("text='hello'")))).hasCount(1);
assertEquals("<div><span>hello</span></div>", page.locator("div", new Page.LocatorOptions().setHas(page.locator("text='hello'"))).evaluate("e => e.outerHTML"));
assertThat(page.locator("div", new Page.LocatorOptions().setHas(page.locator("xpath=./span")))).hasCount(2);
assertThat(page.locator("div", new Page.LocatorOptions().setHas(page.locator("span")))).hasCount(2);
assertThat(page.locator("div", new Page.LocatorOptions().setHas(page.locator("span", new Page.LocatorOptions().setHasText("wor"))))).hasCount(1);
assertEquals("<div><span>world</span></div>", page.locator("div", new Page.LocatorOptions().setHas(
page.locator("span", new Page.LocatorOptions().setHasText("wor")))).evaluate("e => e.outerHTML"));
assertThat(page.locator("div", new Page.LocatorOptions()
.setHas(page.locator("span")).setHasText("wor"))).hasCount(1);
}
}
@@ -24,7 +24,7 @@ import java.util.*;
import java.util.concurrent.Semaphore;
import java.util.stream.Collectors;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.*;
public class TestPageNetworkRequest extends TestBase {
@Test
@@ -0,0 +1,42 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
public class TestPageRequestContinue extends TestBase {
@Test
void shouldNotThrowWhenContinuingAfterPageIsClosed() {
boolean[] done = {false};
page.route("**/*", route -> {
page.close();
route.resume();
done[0] = true;
});
try {
page.navigate(server.EMPTY_PAGE);
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Navigation failed because page was closed") ||
e.getMessage().contains("frame was detached"), e.getMessage());
}
assertTrue(done[0]);
}
}
@@ -0,0 +1,63 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright;
import org.junit.jupiter.api.Test;
import static com.microsoft.playwright.Utils.mapOf;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class TestPageRequestFulfill extends TestBase {
@Test
void shouldFulfillWithGlobalFetchResult() {
page.route("**/*", route -> {
APIRequestContext request = playwright.request().newContext();
APIResponse response = request.get(server.PREFIX + "/simple.json");
route.fulfill(new Route.FulfillOptions().setResponse(response));
});
Response response = page.navigate(server.EMPTY_PAGE);
assertEquals("application/json", response.headers().get("content-type"));
assertEquals(200, response.status());
assertEquals("{\"foo\": \"bar\"}\n", response.text());
}
@Test
void shouldFulfillWithFetchResult() {
page.route("**/*", route -> {
APIResponse response = page.request().get(server.PREFIX + "/simple.json");
route.fulfill(new Route.FulfillOptions().setResponse(response));
});
Response response = page.navigate(server.EMPTY_PAGE);
assertEquals("application/json", response.headers().get("content-type"));
assertEquals(200, response.status());
assertEquals("{\"foo\": \"bar\"}\n", response.text());
}
@Test
void shouldFulfillWithFetchResultAndOverrides() {
page.route("**/*", route -> {
APIResponse response = page.request().get(server.PREFIX + "/simple.json");
route.fulfill(new Route.FulfillOptions().setResponse(response)
.setStatus(201).setHeaders(mapOf("Content-Type", "application/json", "foo", "bar")));
});
Response response = page.navigate(server.EMPTY_PAGE);
assertEquals("application/json", response.headers().get("content-type"));
assertEquals(201, response.status());
assertEquals("bar", response.allHeaders().get("foo"));
assertEquals("{\"foo\": \"bar\"}\n", response.text());
}
}
@@ -23,6 +23,8 @@ import java.io.OutputStreamWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.regex.Pattern;
import static com.microsoft.playwright.Utils.expectedSSLError;
@@ -239,15 +241,18 @@ public class TestPageWaitForNavigation extends TestBase {
server.setRoute("/empty.html", exchange -> {});
try {
frame.waitForNavigation(() -> {
page.evaluate("() => {\n" +
" frames[0].location.href = '/empty.html';\n" +
" setTimeout(() => document.querySelector('iframe').remove());\n" +
"}\n");
Future<Server.Request> req = server.futureRequest("/empty.html");
page.evalOnSelector("iframe", "frame => { frame.contentWindow.location.href = '/empty.html'; }");
try {
req.get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
page.evaluate("setTimeout(() => document.querySelector('iframe').remove());");
});
fail("did not throw");
} catch (PlaywrightException e) {
// assertTrue(e.getMessage().contains("waiting for navigation until \"load\""));
assertTrue(e.getMessage().contains("frame was detached"));
assertTrue(e.getMessage().contains("frame was detached"), e.getMessage());
}
}
@@ -24,7 +24,7 @@ import java.nio.file.Files;
import java.nio.file.Paths;
import static com.microsoft.playwright.Utils.mapOf;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.*;
public class TestRequestFulfill extends TestBase {
@Test
@@ -104,4 +104,45 @@ public class TestRequestFulfill extends TestBase {
}
@Test
void fulfillShouldThrowIfHandledTwice() {
try {
page.route("**/*", route -> {
route.fulfill();
route.fulfill();
});
page.navigate(server.EMPTY_PAGE);
fail("didn't throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Route is already handled!"), e.getMessage());
}
}
@Test
void abortShouldThrowIfHandledTwice() {
try {
page.route("**/*", route -> {
route.abort();
route.abort();
});
page.navigate(server.EMPTY_PAGE);
fail("didn't throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Route is already handled!"), e.getMessage());
}
}
@Test
void resumeShouldThrowIfHandledTwice() {
try {
page.route("**/*", route -> {
route.resume();
route.resume();
});
page.navigate(server.EMPTY_PAGE);
fail("didn't throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Route is already handled!"), e.getMessage());
}
}
}
@@ -119,7 +119,9 @@ public class TestTracing extends TestBase {
assertEquals(1, sources.size());
String path = getClass().getName().replace('.', File.separatorChar);
Path sourceFile = Paths.get(System.getenv("PLAYWRIGHT_JAVA_SRC"), path + ".java");
String[] srcRoots = System.getenv("PLAYWRIGHT_JAVA_SRC").split(File.pathSeparator);
// Resolve in the last specified source dir.
Path sourceFile = Paths.get(srcRoots[srcRoots.length - 1], path + ".java");
byte[] thisFile = Files.readAllBytes(sourceFile);
assertEquals(new String(thisFile, UTF_8), new String(sources.values().iterator().next(), UTF_8));
}
+1 -1
View File
@@ -6,7 +6,7 @@
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.18.0-SNAPSHOT</version>
<version>1.19.0</version>
<packaging>pom</packaging>
<name>Playwright Parent Project</name>
<description>Java library to automate Chromium, Firefox and WebKit with a single API.
+1 -1
View File
@@ -1 +1 @@
1.18.0-rc1
1.19.0
+1 -4
View File
@@ -16,10 +16,7 @@ cd "$(dirname $0)/.."
VERSION=$1
POM_FILES=(
pom.xml
tools/api-generator/pom.xml
tools/update-docs-version/pom.xml
tools/test-local-installation/pom.xml
tools/test-spring-boot-starter/pom.xml
tools/*/pom.xml
examples/pom.xml
)
+1 -1
View File
@@ -6,7 +6,7 @@
<groupId>com.microsoft.playwright</groupId>
<artifactId>api-generator</artifactId>
<version>1.18.0-SNAPSHOT</version>
<version>1.19.0</version>
<name>Playwright - API Generator</name>
<description>
This is an internal module used to generate Java API from the upstream Playwright
+1 -1
View File
@@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.microsoft.playwright</groupId>
<artifactId>test-cli-version</artifactId>
<version>1.18.0-SNAPSHOT</version>
<version>1.19.0</version>
<name>Test Playwright Command Line Version</name>
<properties>
<compiler.version>1.8</compiler.version>
+1 -1
View File
@@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.microsoft.playwright</groupId>
<artifactId>test-local-installation</artifactId>
<version>1.18.0-SNAPSHOT</version>
<version>1.19.0</version>
<name>Test local installation</name>
<description>Runs Playwright test suite (copied from playwright module) against locally cached Playwright</description>
<properties>
+1 -1
View File
@@ -9,7 +9,7 @@
</parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>test-spring-boot-starter</artifactId>
<version>1.18.0-SNAPSHOT</version>
<version>1.19.0</version>
<name>Test Playwright With Spring Boot</name>
<properties>
<spring.version>2.4.3</spring.version>
+1 -1
View File
@@ -6,7 +6,7 @@
<groupId>com.microsoft.playwright</groupId>
<artifactId>update-version</artifactId>
<version>1.18.0-SNAPSHOT</version>
<version>1.19.0</version>
<name>Playwright - Update Version in Documentation</name>
<description>
This is an internal module used to update versions in the documentation based on