Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 23ad37122a | |||
| b448a1789a | |||
| 79b2c70513 | |||
| d476d0a98c | |||
| 2122c5690a | |||
| 3119102b10 | |||
| 838e7a40b3 | |||
| ea6ede4670 | |||
| 3cdefc2931 | |||
| 119700a678 | |||
| 17a4143a83 | |||
| c03f4a9384 | |||
| 85b671328e | |||
| 11f898ca7f | |||
| 2aef5c6742 | |||
| 897d441c02 | |||
| f4c69faad3 |
@@ -1,6 +1,7 @@
|
||||
name: Publish
|
||||
on:
|
||||
workflow_dispatch:
|
||||
release:
|
||||
types: [published]
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>parent-pom</artifactId>
|
||||
<version>1.18.0</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
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>parent-pom</artifactId>
|
||||
<version>1.18.0</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
@@ -6,7 +6,7 @@
|
||||
|
||||
<groupId>org.example</groupId>
|
||||
<artifactId>examples</artifactId>
|
||||
<version>1.18.0</version>
|
||||
<version>1.19.0</version>
|
||||
<name>Playwright Client Examples</name>
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>parent-pom</artifactId>
|
||||
<version>1.18.0</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;
|
||||
|
||||
+1
-1
@@ -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>
|
||||
|
||||
@@ -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() {
|
||||
@@ -295,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()) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
+2
-15
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+13
-22
@@ -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));
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>parent-pom</artifactId>
|
||||
<version>1.18.0</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
@@ -1 +1 @@
|
||||
1.18.0-beta-1642620709000
|
||||
1.19.0
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>api-generator</artifactId>
|
||||
<version>1.18.0</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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>test-local-installation</artifactId>
|
||||
<version>1.18.0</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>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
</parent>
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>test-spring-boot-starter</artifactId>
|
||||
<version>1.18.0</version>
|
||||
<version>1.19.0</version>
|
||||
<name>Test Playwright With Spring Boot</name>
|
||||
<properties>
|
||||
<spring.version>2.4.3</spring.version>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>update-version</artifactId>
|
||||
<version>1.18.0</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
|
||||
|
||||
Reference in New Issue
Block a user