Skip to content

chore: roll 1.53.0 #1801

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 28 commits into from
Jun 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ Playwright is a Java library to automate [Chromium](https://www.chromium.org/Hom

| | Linux | macOS | Windows |
| :--- | :---: | :---: | :---: |
| Chromium <!-- GEN:chromium-version -->137.0.7151.27<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| WebKit <!-- GEN:webkit-version -->18.4<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Firefox <!-- GEN:firefox-version -->137.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Chromium <!-- GEN:chromium-version -->138.0.7204.15<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| WebKit <!-- GEN:webkit-version -->18.5<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Firefox <!-- GEN:firefox-version -->139.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |

## Documentation

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2606,6 +2606,12 @@ default void dblclick() {
* Describes the locator, description is used in the trace viewer and reports. Returns the locator pointing to the same
* element.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* Locator button = page.getByTestId("btn-sub").describe("Subscribe button");
* button.click();
* }</pre>
*
* @param description Locator description.
* @since v1.53
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
*
* <p> <strong>Mocking</strong>
*
* <p> By default, the routed WebSocket will not connect to the server. This way, you can mock entire communcation over the
* <p> By default, the routed WebSocket will not connect to the server. This way, you can mock entire communication over the
* WebSocket. Here is an example that responds to a {@code "request"} with a {@code "response"}.
* <pre>{@code
* page.routeWebSocket("wss://example.com/ws", ws -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,27 +38,27 @@ class AssertionsBase {
this.isNot = isNot;
}

void expectImpl(String expression, ExpectedTextValue textValue, Object expected, String message, FrameExpectOptions options) {
expectImpl(expression, asList(textValue), expected, message, options);
void expectImpl(String expression, ExpectedTextValue textValue, Object expected, String message, FrameExpectOptions options, String title) {
expectImpl(expression, asList(textValue), expected, message, options, title);
}

void expectImpl(String expression, List<ExpectedTextValue> expectedText, Object expected, String message, FrameExpectOptions options) {
void expectImpl(String expression, List<ExpectedTextValue> expectedText, Object expected, String message, FrameExpectOptions options, String title) {
if (options == null) {
options = new FrameExpectOptions();
}
options.expectedText = expectedText;
expectImpl(expression, options, expected, message);
expectImpl(expression, options, expected, message, title);
}

void expectImpl(String expression, FrameExpectOptions expectOptions, Object expected, String message) {
void expectImpl(String expression, FrameExpectOptions expectOptions, Object expected, String message, String title) {
if (expectOptions.timeout == null) {
expectOptions.timeout = AssertionsTimeout.defaultTimeout;
}
expectOptions.isNot = isNot;
if (isNot) {
message = message.replace("expected to", "expected not to");
}
FrameExpectResult result = actualLocator.expect(expression, expectOptions);
FrameExpectResult result = actualLocator.expect(expression, expectOptions, title);
if (result.matches == isNot) {
Object actual = result.received == null ? null : Serialization.deserialize(result.received);
String log = (result.log == null) ? "" : String.join("\n", result.log);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
import static java.util.Arrays.asList;

class BrowserContextImpl extends ChannelOwner implements BrowserContext {
private final BrowserImpl browser;
protected BrowserImpl browser;
private final TracingImpl tracing;
private final APIRequestContextImpl request;
private final ClockImpl clock;
Expand All @@ -51,7 +51,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {

final Router routes = new Router();
final WebSocketRouter webSocketRoutes = new WebSocketRouter();
private boolean closeWasCalled;
private boolean closingOrClosed;
private final WaitableEvent<EventType, ?> closePromise;
final Map<String, BindingCallback> bindings = new HashMap<>();
PageImpl ownerPage;
Expand All @@ -69,8 +69,6 @@ private static final Map<EventType, String> eventSubscriptions() {
}
private final ListenerCollection<EventType> listeners = new ListenerCollection<>(eventSubscriptions(), this);
final TimeoutSettings timeoutSettings = new TimeoutSettings();
Path videosDir;
URL baseUrl;
final Map<String, HarRecorder> harRecorders = new HashMap<>();

static class HarRecorder {
Expand Down Expand Up @@ -98,32 +96,32 @@ enum EventType {

BrowserContextImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
if (parent instanceof BrowserImpl) {
browser = (BrowserImpl) parent;
} else {
browser = null;
}
tracing = connection.getExistingObject(initializer.getAsJsonObject("tracing").get("guid").getAsString());
request = connection.getExistingObject(initializer.getAsJsonObject("requestContext").get("guid").getAsString());
request.timeoutSettings = timeoutSettings;
clock = new ClockImpl(this);
closePromise = new WaitableEvent<>(listeners, EventType.CLOSE);
closePromise = new WaitableEvent<>(listeners, EventType.CLOSE);
}

void setRecordHar(Path path, HarContentPolicy policy) {
if (path != null) {
harRecorders.put("", new HarRecorder(path, policy));
Path videosDir() {
JsonObject recordVideo = initializer.getAsJsonObject("options").getAsJsonObject("recordVideo");
if (recordVideo == null) {
return null;
}
return Paths.get(recordVideo.get("dir").getAsString());
}

void setBaseUrl(String spec) {
try {
this.baseUrl = new URL(spec);
} catch (MalformedURLException e) {
this.baseUrl = null;
URL baseUrl() {
JsonElement url = initializer.getAsJsonObject("options").get("baseURL");
if (url != null) {
try {
return new URL(url.getAsString());
} catch (MalformedURLException e) {
}
}
return null;
}

String effectiveCloseReason() {
if (closeReason != null) {
return closeReason;
Expand Down Expand Up @@ -286,8 +284,8 @@ public List<Cookie> cookies(String url) {
}

private void closeImpl(CloseOptions options) {
if (!closeWasCalled) {
closeWasCalled = true;
if (!closingOrClosed) {
closingOrClosed = true;
if (options == null) {
options = new CloseOptions();
}
Expand Down Expand Up @@ -481,7 +479,7 @@ public APIRequestContextImpl request() {

@Override
public void route(String url, Consumer<Route> handler, RouteOptions options) {
route(UrlMatcher.forGlob(baseUrl, url, this.connection.localUtils, false), handler, options);
route(UrlMatcher.forGlob(baseUrl(), url, this.connection.localUtils, false), handler, options);
}

@Override
Expand All @@ -500,10 +498,10 @@ public void routeFromHAR(Path har, RouteFromHAROptions options) {
options = new RouteFromHAROptions();
}
if (options.update != null && options.update) {
recordIntoHar(null, har, options);
recordIntoHar(null, har, options, null);
return;
}
UrlMatcher matcher = UrlMatcher.forOneOf(baseUrl, options.url, this.connection.localUtils, false);
UrlMatcher matcher = UrlMatcher.forOneOf(baseUrl(), options.url, this.connection.localUtils, false);
HARRouter harRouter = new HARRouter(connection.localUtils, har, options.notFound);
onClose(context -> harRouter.dispose());
route(matcher, route -> harRouter.handle(route), null);
Expand All @@ -518,7 +516,7 @@ private void route(UrlMatcher matcher, Consumer<Route> handler, RouteOptions opt

@Override
public void routeWebSocket(String url, Consumer<WebSocketRoute> handler) {
routeWebSocketImpl(UrlMatcher.forGlob(baseUrl, url, this.connection.localUtils, true), handler);
routeWebSocketImpl(UrlMatcher.forGlob(baseUrl(), url, this.connection.localUtils, true), handler);
}

@Override
Expand All @@ -538,24 +536,28 @@ private void routeWebSocketImpl(UrlMatcher matcher, Consumer<WebSocketRoute> han
});
}

void recordIntoHar(PageImpl page, Path har, RouteFromHAROptions options) {
void recordIntoHar(PageImpl page, Path har, RouteFromHAROptions options, HarContentPolicy contentPolicy) {
if (contentPolicy == null) {
contentPolicy = Utils.convertType(options.updateContent, HarContentPolicy.class);;
}
if (contentPolicy == null) {
contentPolicy = HarContentPolicy.ATTACH;
}

JsonObject params = new JsonObject();
if (page != null) {
params.add("page", page.toProtocolRef());
}
JsonObject jsonOptions = new JsonObject();
jsonOptions.addProperty("path", har.toAbsolutePath().toString());
jsonOptions.addProperty("content", options.updateContent == null ?
HarContentPolicy.ATTACH.name().toLowerCase() :
options.updateContent.name().toLowerCase());
jsonOptions.addProperty("mode", options.updateMode == null ?
HarMode.MINIMAL.name().toLowerCase() :
options.updateMode.name().toLowerCase());
addHarUrlFilter(jsonOptions, options.url);
params.add("options", jsonOptions);
JsonObject recordHarArgs = new JsonObject();
recordHarArgs.addProperty("zip", har.toString().endsWith(".zip"));
recordHarArgs.addProperty("content", contentPolicy.name().toLowerCase());
recordHarArgs.addProperty("mode", (options.updateMode == null ? HarMode.MINIMAL : options.updateMode).name().toLowerCase());
addHarUrlFilter(recordHarArgs, options.url);

params.add("options", recordHarArgs);
JsonObject json = sendMessage("harStart", params).getAsJsonObject();
String harId = json.get("harId").getAsString();
harRecorders.put(harId, new HarRecorder(har, HarContentPolicy.ATTACH));
harRecorders.put(harId, new HarRecorder(har, contentPolicy));
}

@Override
Expand Down Expand Up @@ -639,7 +641,7 @@ public void unrouteAll() {

@Override
public void unroute(String url, Consumer<Route> handler) {
unroute(UrlMatcher.forGlob(this.baseUrl, url, this.connection.localUtils, false), handler);
unroute(UrlMatcher.forGlob(this.baseUrl(), url, this.connection.localUtils, false), handler);
}

@Override
Expand Down Expand Up @@ -849,8 +851,10 @@ protected void handleEvent(String event, JsonObject params) {
}

void didClose() {
closingOrClosed = true;
if (browser != null) {
browser.contexts.remove(this);
browser.browserType.playwright.selectors.contextsForSelectors.remove(this);
}
listeners.notify(EventType.CLOSE, this);
}
Expand All @@ -862,4 +866,46 @@ WritableStream createTempFile(String name, long lastModifiedMs) {
JsonObject json = sendMessage("createTempFile", params).getAsJsonObject();
return connection.getExistingObject(json.getAsJsonObject("writableStream").get("guid").getAsString());
}

protected void initializeHarFromOptions(Browser.NewContextOptions options) {
if (options.recordHarPath == null) {
if (options.recordHarOmitContent != null) {
throw new PlaywrightException("recordHarOmitContent is set but recordHarPath is null");
}
if (options.recordHarUrlFilter != null) {
throw new PlaywrightException("recordHarUrlFilter is set but recordHarPath is null");
}
if (options.recordHarMode != null) {
throw new PlaywrightException("recordHarMode is set but recordHarPath is null");
}
if (options.recordHarContent != null) {
throw new PlaywrightException("recordHarContent is set but recordHarPath is null");
}
return;
}

HarContentPolicy contentPolicy = options.recordHarContent;
if (contentPolicy == null && options.recordHarOmitContent != null && options.recordHarOmitContent == true) {
contentPolicy = HarContentPolicy.OMIT;
}
if (contentPolicy == null) {
contentPolicy = options.recordHarPath.endsWith(".zip") ? HarContentPolicy.ATTACH : HarContentPolicy.EMBED;
}
RouteFromHAROptions routeFromHAROptions = new RouteFromHAROptions();

if (options.recordHarUrlFilter instanceof String) {
routeFromHAROptions.setUrl((String) options.recordHarUrlFilter);
} else if (options.recordHarUrlFilter instanceof Pattern) {
routeFromHAROptions.setUrl((Pattern) options.recordHarUrlFilter);
}

if (options.recordHarMode != null) {
routeFromHAROptions.updateMode = options.recordHarMode;
} else {
routeFromHAROptions.updateMode = HarMode.FULL;
}
routeFromHAROptions.url = options.recordHarUrlFilter;

recordIntoHar(null, options.recordHarPath, routeFromHAROptions, contentPolicy);
}
}
Loading