From 393a9c04dca6215de3428de87807915ec3ea0f73 Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Wed, 11 Jun 2025 09:36:22 +0200 Subject: [PATCH 01/28] selectors --- README.md | 6 +- .../com/microsoft/playwright/Locator.java | 6 ++ .../microsoft/playwright/WebSocketRoute.java | 2 +- .../playwright/impl/BrowserTypeImpl.java | 2 - .../microsoft/playwright/impl/Connection.java | 3 - .../playwright/impl/PlaywrightImpl.java | 18 +---- .../playwright/impl/RemoteBrowser.java | 4 -- .../playwright/impl/SelectorsImpl.java | 44 ------------- .../playwright/impl/SharedSelectors.java | 66 ++++++++----------- scripts/DRIVER_VERSION | 2 +- 10 files changed, 40 insertions(+), 113 deletions(-) delete mode 100644 playwright/src/main/java/com/microsoft/playwright/impl/SelectorsImpl.java diff --git a/README.md b/README.md index c4dcdc856..3e32fce0b 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,9 @@ Playwright is a Java library to automate [Chromium](https://www.chromium.org/Hom | | Linux | macOS | Windows | | :--- | :---: | :---: | :---: | -| Chromium 137.0.7151.27 | :white_check_mark: | :white_check_mark: | :white_check_mark: | -| WebKit 18.4 | ✅ | ✅ | ✅ | -| Firefox 137.0 | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| Chromium 138.0.7204.4 | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| WebKit 18.5 | ✅ | ✅ | ✅ | +| Firefox 139.0 | :white_check_mark: | :white_check_mark: | :white_check_mark: | ## Documentation diff --git a/playwright/src/main/java/com/microsoft/playwright/Locator.java b/playwright/src/main/java/com/microsoft/playwright/Locator.java index 593ebe2e2..54b494ae3 100644 --- a/playwright/src/main/java/com/microsoft/playwright/Locator.java +++ b/playwright/src/main/java/com/microsoft/playwright/Locator.java @@ -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. * + *

Usage + *

{@code
+   * Locator button = page.getByTestId("btn-sub").describe("Subscribe button");
+   * button.click();
+   * }
+ * * @param description Locator description. * @since v1.53 */ diff --git a/playwright/src/main/java/com/microsoft/playwright/WebSocketRoute.java b/playwright/src/main/java/com/microsoft/playwright/WebSocketRoute.java index e20fe41a0..d8303830b 100644 --- a/playwright/src/main/java/com/microsoft/playwright/WebSocketRoute.java +++ b/playwright/src/main/java/com/microsoft/playwright/WebSocketRoute.java @@ -27,7 +27,7 @@ * *

Mocking * - *

By default, the routed WebSocket will not connect to the server. This way, you can mock entire communcation over the + *

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"}. *

{@code
  * page.routeWebSocket("wss://example.com/ws", ws -> {
diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserTypeImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserTypeImpl.java
index a00e42d00..6750751a1 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserTypeImpl.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserTypeImpl.java
@@ -101,14 +101,12 @@ private Browser connectImpl(String wsEndpoint, ConnectOptions options) {
       }
       throw new PlaywrightException("Malformed endpoint. Did you use launchServer method?");
     }
-    playwright.initSharedSelectors(this.connection.getExistingObject("Playwright"));
     BrowserImpl browser = connection.getExistingObject(playwright.initializer.getAsJsonObject("preLaunchedBrowser").get("guid").getAsString());
     browser.isConnectedOverWebSocket = true;
     browser.browserType = this;
     Consumer connectionCloseListener = t -> browser.notifyRemoteClosed();
     pipe.onClose(connectionCloseListener);
     browser.onDisconnected(b -> {
-      playwright.unregisterSelectors();
       pipe.offClose(connectionCloseListener);
       try {
         connection.close();
diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/Connection.java b/playwright/src/main/java/com/microsoft/playwright/impl/Connection.java
index f0d3ca5f0..419119598 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/Connection.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/Connection.java
@@ -373,9 +373,6 @@ private ChannelOwner createRemoteObject(String parentGuid, JsonObject params) {
       case "Stream":
         result = new Stream(parent, type, guid, initializer);
         break;
-      case "Selectors":
-        result = new SelectorsImpl(parent, type, guid, initializer);
-        break;
       case "SocksSupport":
         break;
       case "Tracing":
diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/PlaywrightImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/PlaywrightImpl.java
index 38863c8e8..2f90e6273 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/PlaywrightImpl.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/PlaywrightImpl.java
@@ -52,7 +52,6 @@ public static PlaywrightImpl createImpl(CreateOptions options, boolean forceNewD
       Connection connection = new Connection(new PipeTransport(p.getInputStream(), p.getOutputStream()), env);
       PlaywrightImpl result = connection.initializePlaywright();
       result.driverProcess = p;
-      result.initSharedSelectors(null);
       return result;
     } catch (IOException e) {
       throw new PlaywrightException("Failed to launch driver", e);
@@ -62,7 +61,6 @@ public static PlaywrightImpl createImpl(CreateOptions options, boolean forceNewD
   private final BrowserTypeImpl chromium;
   private final BrowserTypeImpl firefox;
   private final BrowserTypeImpl webkit;
-  private final SelectorsImpl selectors;
   private final APIRequestImpl apiRequest;
   private SharedSelectors sharedSelectors;
 
@@ -72,24 +70,10 @@ public static PlaywrightImpl createImpl(CreateOptions options, boolean forceNewD
     firefox = parent.connection.getExistingObject(initializer.getAsJsonObject("firefox").get("guid").getAsString());
     webkit = parent.connection.getExistingObject(initializer.getAsJsonObject("webkit").get("guid").getAsString());
 
-    selectors = connection.getExistingObject(initializer.getAsJsonObject("selectors").get("guid").getAsString());
+    sharedSelectors = new SharedSelectors();
     apiRequest = new APIRequestImpl(this);
   }
 
-  void initSharedSelectors(PlaywrightImpl parent) {
-    assert sharedSelectors == null;
-    if (parent == null) {
-      sharedSelectors = new SharedSelectors();
-    } else {
-      sharedSelectors = parent.sharedSelectors;
-    }
-    sharedSelectors.addChannel(selectors);
-  }
-
-  void unregisterSelectors() {
-    sharedSelectors.removeChannel(selectors);
-  }
-
   public LocalUtils localUtils() {
     return connection.localUtils;
   }
diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/RemoteBrowser.java b/playwright/src/main/java/com/microsoft/playwright/impl/RemoteBrowser.java
index 0dc5011d9..05941a86e 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/RemoteBrowser.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/RemoteBrowser.java
@@ -26,8 +26,4 @@ public class RemoteBrowser extends ChannelOwner {
   BrowserImpl browser() {
     return connection.getExistingObject(initializer.getAsJsonObject("browser").get("guid").getAsString());
   }
-
-  SelectorsImpl selectors() {
-    return connection.getExistingObject(initializer.getAsJsonObject("selectors").get("guid").getAsString());
-  }
 }
diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/SelectorsImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/SelectorsImpl.java
deleted file mode 100644
index 77977bba8..000000000
--- a/playwright/src/main/java/com/microsoft/playwright/impl/SelectorsImpl.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (c) Microsoft Corporation.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.microsoft.playwright.impl;
-
-import com.google.gson.JsonObject;
-import com.microsoft.playwright.Selectors;
-
-import static com.microsoft.playwright.impl.Serialization.gson;
-
-class SelectorsImpl extends ChannelOwner {
-  SelectorsImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
-    super(parent, type, guid, initializer);
-  }
-
-  void register(String name, String script, Selectors.RegisterOptions options) {
-    if (options == null) {
-      options = new Selectors.RegisterOptions();
-    }
-    JsonObject params = gson().toJsonTree(options).getAsJsonObject();
-    params.addProperty("name", name);
-    params.addProperty("source", script);
-    sendMessage("register", params);
-  }
-
-  void setTestIdAttributeName(String name) {
-    JsonObject params = new JsonObject();
-    params.addProperty("testIdAttributeName", name);
-    sendMessageAsync("setTestIdAttributeName", params);
-  }
-}
diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/SharedSelectors.java b/playwright/src/main/java/com/microsoft/playwright/impl/SharedSelectors.java
index b654ed251..c03a70e75 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/SharedSelectors.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/SharedSelectors.java
@@ -16,6 +16,7 @@
 
 package com.microsoft.playwright.impl;
 
+import com.google.gson.JsonObject;
 import com.microsoft.playwright.PlaywrightException;
 import com.microsoft.playwright.Selectors;
 
@@ -28,20 +29,24 @@
 import static java.nio.charset.StandardCharsets.UTF_8;
 
 public class SharedSelectors extends LoggingSupport implements Selectors {
-  private final List channels = new ArrayList<>();
-  private final List registrations = new ArrayList<>();
+  private final List contextsForSelectors = new ArrayList<>();
+  private final List selectorEngines = new ArrayList<>(); // FIXME: do we need this? is it ever read?
 
   String testIdAttributeName = "data-testid";
 
-  private static class Registration {
-    final String name;
-    final String script;
-    final RegisterOptions options;
-
-    Registration(String name, String script, RegisterOptions options) {
-      this.name = name;
-      this.script = script;
-      this.options = options;
+  @Override
+  public void setTestIdAttribute(String attributeName) {
+    if (attributeName == null) {
+      throw new PlaywrightException("Test id attribute cannot be null");
+    }
+    testIdAttributeName = attributeName;
+    for (BrowserContextImpl context : contextsForSelectors) {
+      try {
+        JsonObject params = new JsonObject();
+        params.addProperty("testIdAttributeName", attributeName);
+        context.sendMessageAsync("setTestIdAttributeName", params);
+      } catch (PlaywrightException e) {  
+      }
     }
   }
 
@@ -63,33 +68,18 @@ public void register(String name, Path path, RegisterOptions options) {
     });
   }
 
-  @Override
-  public void setTestIdAttribute(String attributeName) {
-    if (attributeName == null) {
-      throw new PlaywrightException("Test id attribute cannot be null");
-    }
-    testIdAttributeName = attributeName;
-    channels.forEach(channel -> channel.setTestIdAttributeName(testIdAttributeName));
-  }
-
-  void addChannel(SelectorsImpl channel) {
-    registrations.forEach(r -> {
-      try {
-        channel.register(r.name, r.script, r.options);
-      } catch (PlaywrightException e) {
-        // This should not fail except for connection closure, but just in case we catch.
-      }
-      channel.setTestIdAttributeName(testIdAttributeName);
-    });
-    channels.add(channel);
-  }
-
-  void removeChannel(SelectorsImpl channel) {
-    channels.remove(channel);
-  }
-
   private void registerImpl(String name, String script, RegisterOptions options) {
-    channels.forEach(impl -> impl.register(name, script, options));
-    registrations.add(new Registration(name, script, options));
+    JsonObject engine = new JsonObject();
+    engine.addProperty("name", name);
+    engine.addProperty("source", script);
+    if (options.contentScript != null) {
+      engine.addProperty("contentScript", options.contentScript);
+    }
+    for (BrowserContextImpl context : contextsForSelectors) {
+      JsonObject params = new JsonObject();
+      params.add("selectorEngine", engine);
+      context.sendMessageAsync("registerSelectorEngine", params);
+    }
+    selectorEngines.add(engine);
   }
 }
diff --git a/scripts/DRIVER_VERSION b/scripts/DRIVER_VERSION
index a6c040f60..efdb8fa68 100644
--- a/scripts/DRIVER_VERSION
+++ b/scripts/DRIVER_VERSION
@@ -1 +1 @@
-1.53.0-alpha-2025-05-21
+1.53.0-beta-1749131401000

From b59d88c66c96a9b89a05a24e73a00a6d226df3ef Mon Sep 17 00:00:00 2001
From: Simon Knott 
Date: Wed, 11 Jun 2025 09:41:37 +0200
Subject: [PATCH 02/28] remove markAsInternalType

---
 .../java/com/microsoft/playwright/impl/ChannelOwner.java  | 8 --------
 .../java/com/microsoft/playwright/impl/LocalUtils.java    | 1 -
 .../java/com/microsoft/playwright/impl/RequestImpl.java   | 1 -
 .../java/com/microsoft/playwright/impl/ResponseImpl.java  | 1 -
 .../java/com/microsoft/playwright/impl/RouteImpl.java     | 1 -
 .../java/com/microsoft/playwright/impl/TracingImpl.java   | 1 -
 .../com/microsoft/playwright/impl/WebSocketRouteImpl.java | 1 -
 7 files changed, 14 deletions(-)

diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/ChannelOwner.java b/playwright/src/main/java/com/microsoft/playwright/impl/ChannelOwner.java
index b5f6edd37..97641f573 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/ChannelOwner.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/ChannelOwner.java
@@ -35,7 +35,6 @@ class ChannelOwner extends LoggingSupport {
   final String guid;
   final JsonObject initializer;
   private boolean wasCollected;
-  private boolean isInternalType;
 
   protected ChannelOwner(ChannelOwner parent, String type, String guid, JsonObject initializer) {
     this(parent.connection, parent, type, guid, initializer);
@@ -59,10 +58,6 @@ private ChannelOwner(Connection connection, ChannelOwner parent, String type, St
     }
   }
 
-  void markAsInternalType() {
-    isInternalType = true;
-  }
-
   void disposeChannelOwner(boolean wasGarbageCollected) {
     // Clean up from parent and connection.
     if (parent != null) {
@@ -89,9 +84,6 @@  T withWaitLogging(String apiName, Function code) {
 
   @Override
    T withLogging(String apiName, Supplier code) {
-    if (isInternalType) {
-      apiName = null;
-    }
     String previousApiName = connection.setApiName(apiName);
     try {
       return super.withLogging(apiName, code);
diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/LocalUtils.java b/playwright/src/main/java/com/microsoft/playwright/impl/LocalUtils.java
index b4f36c03f..e99b852de 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/LocalUtils.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/LocalUtils.java
@@ -28,7 +28,6 @@
 public class LocalUtils extends ChannelOwner {
   LocalUtils(ChannelOwner parent, String type, String guid, JsonObject initializer) {
     super(parent, type, guid, initializer);
-    markAsInternalType();
   }
 
   JsonArray deviceDescriptors() {
diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/RequestImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/RequestImpl.java
index e2c2a545e..30a495619 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/RequestImpl.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/RequestImpl.java
@@ -53,7 +53,6 @@ static class FallbackOverrides {
 
   RequestImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
     super(parent, type, guid, initializer);
-    markAsInternalType();
 
     if (initializer.has("redirectedFrom")) {
       redirectedFrom = connection.getExistingObject(initializer.getAsJsonObject("redirectedFrom").get("guid").getAsString());
diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/ResponseImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/ResponseImpl.java
index 2523335a5..78368662f 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/ResponseImpl.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/ResponseImpl.java
@@ -40,7 +40,6 @@ public class ResponseImpl extends ChannelOwner implements Response {
 
   ResponseImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
     super(parent, type, guid, initializer);
-    markAsInternalType();
     headers = new RawHeaders(asList(gson().fromJson(initializer.getAsJsonArray("headers"), HttpHeader[].class)));
     request = connection.getExistingObject(initializer.getAsJsonObject("request").get("guid").getAsString());
     request.timing = gson().fromJson(initializer.get("timing"), Timing.class);
diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/RouteImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/RouteImpl.java
index 7a96aac37..1533f7b89 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/RouteImpl.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/RouteImpl.java
@@ -38,7 +38,6 @@ public class RouteImpl extends ChannelOwner implements Route {
 
   public RouteImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
     super(parent, type, guid, initializer);
-    markAsInternalType();
     request = connection.getExistingObject(initializer.getAsJsonObject("request").get("guid").getAsString());
   }
 
diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/TracingImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/TracingImpl.java
index de7575762..9234d89b2 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/TracingImpl.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/TracingImpl.java
@@ -33,7 +33,6 @@ class TracingImpl extends ChannelOwner implements Tracing {
 
   TracingImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
     super(parent, type, guid, initializer);
-    markAsInternalType();
   }
 
   private void stopChunkImpl(Path path) {
diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/WebSocketRouteImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/WebSocketRouteImpl.java
index 3fe7a72db..53479fcf2 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/WebSocketRouteImpl.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/WebSocketRouteImpl.java
@@ -69,7 +69,6 @@ public String url() {
 
   WebSocketRouteImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
     super(parent, type, guid, initializer);
-    markAsInternalType();
   }
 
     @Override

From 1ff30296653f40dd3496f253cea55d9128e3a57b Mon Sep 17 00:00:00 2001
From: Simon Knott 
Date: Wed, 11 Jun 2025 10:18:58 +0200
Subject: [PATCH 03/28] titles

---
 .../playwright/impl/AssertionsBase.java       | 12 +--
 .../playwright/impl/BrowserImpl.java          |  2 +-
 .../playwright/impl/ChannelOwner.java         | 20 ++++-
 .../microsoft/playwright/impl/Connection.java | 19 ++--
 .../impl/LocatorAssertionsImpl.java           | 88 +++++++++----------
 .../playwright/impl/LocatorImpl.java          | 52 +++++------
 .../microsoft/playwright/impl/MouseImpl.java  |  2 +-
 .../playwright/impl/PageAssertionsImpl.java   |  8 +-
 8 files changed, 110 insertions(+), 93 deletions(-)

diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/AssertionsBase.java b/playwright/src/main/java/com/microsoft/playwright/impl/AssertionsBase.java
index ff348a3d8..92ad7ef3c 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/AssertionsBase.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/AssertionsBase.java
@@ -38,19 +38,19 @@ 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 expectedText, Object expected, String message, FrameExpectOptions options) {
+  void expectImpl(String expression, List 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;
     }
@@ -58,7 +58,7 @@ void expectImpl(String expression, FrameExpectOptions expectOptions, Object expe
     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);
diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserImpl.java
index c3abf4017..387f27a0d 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserImpl.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserImpl.java
@@ -229,7 +229,7 @@ private BrowserContextImpl newContextImpl(NewContextOptions options) {
 
   @Override
   public Page newPage(NewPageOptions options) {
-    return withLogging("Browser.newPage", () -> newPageImpl(options));
+    return withTitle("Create Page", () -> newPageImpl(options));
   }
 
   @Override
diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/ChannelOwner.java b/playwright/src/main/java/com/microsoft/playwright/impl/ChannelOwner.java
index 97641f573..47a7aec6d 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/ChannelOwner.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/ChannelOwner.java
@@ -82,13 +82,27 @@  T withWaitLogging(String apiName, Function code) {
     return new WaitForEventLogger<>(this, apiName, code).get();
   }
 
+  @Deprecated
   @Override
    T withLogging(String apiName, Supplier code) {
-    String previousApiName = connection.setApiName(apiName);
+    // this has so many callers, removing it would clutter this PR.
+    // it's a no-op for now, and i'll remove it from the codebase in the next PR.
+    return super.withLogging(apiName, code);
+  }
+
+  void withTitle(String title, Runnable code) {
+    withTitle(title, () -> {
+      code.run();
+      return null;
+    });
+  }
+
+   T withTitle(String title, Supplier code) {
+    String previousTitle = connection.setTitle(title);
     try {
-      return super.withLogging(apiName, code);
+      return code.get();
     } finally {
-      connection.setApiName(previousApiName);
+      connection.setTitle(previousTitle);
     }
   }
 
diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/Connection.java b/playwright/src/main/java/com/microsoft/playwright/impl/Connection.java
index 419119598..6eb178250 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/Connection.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/Connection.java
@@ -19,7 +19,6 @@
 import com.google.gson.JsonArray;
 import com.google.gson.JsonElement;
 import com.google.gson.JsonObject;
-import com.microsoft.playwright.Playwright;
 import com.microsoft.playwright.PlaywrightException;
 import com.microsoft.playwright.TimeoutError;
 
@@ -64,7 +63,8 @@ public class Connection {
   private int lastId = 0;
   private final StackTraceCollector stackTraceCollector;
   private final Map> callbacks = new HashMap<>();
-  private String apiName;
+  private String title;
+  private boolean titleReported = false;
   private static final boolean isLogging;
   static {
     String debug = System.getenv("DEBUG");
@@ -116,9 +116,10 @@ void setIsTracing(boolean tracing) {
     }
   }
 
-  String setApiName(String name) {
-    String previous = apiName;
-    apiName = name;
+  String setTitle(String newTitle) {
+    String previous = title;
+    titleReported = false;
+    title = newTitle;
     return previous;
   }
 
@@ -146,12 +147,12 @@ private WaitableResult internalSendMessage(String guid, String meth
     JsonObject metadata = new JsonObject();
     metadata.addProperty("wallTime", currentTimeMillis());
     JsonArray stack = null;
-    if (apiName == null) {
+    if (titleReported) {
       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;
+      metadata.addProperty("title", title);
+      // All but first message in a custom-titled API call are considered internal and will be hidden from the inspector.
+      titleReported = true;
       if (stackTraceCollector != null) {
         stack = stackTraceCollector.currentStackTrace();
         if (!stack.isEmpty()) {
diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/LocatorAssertionsImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/LocatorAssertionsImpl.java
index ab9448b44..aa138da7f 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/LocatorAssertionsImpl.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/LocatorAssertionsImpl.java
@@ -44,7 +44,7 @@ private LocatorAssertionsImpl(Locator locator, boolean isNot) {
   public void containsClass(String classname, ContainsClassOptions options) {
     ExpectedTextValue expected = new ExpectedTextValue();
     expected.string = classname;
-    expectImpl("to.contain.class", expected, classname, "Locator expected to contain class", convertType(options, FrameExpectOptions.class));
+    expectImpl("to.contain.class", expected, classname, "Locator expected to contain class", convertType(options, FrameExpectOptions.class), "Expect \"containsClass\"");
   }
 
   @Override
@@ -55,7 +55,7 @@ public void containsClass(List classnames, ContainsClassOptions options)
       expected.string = text;
       list.add(expected);
     }
-    expectImpl("to.contain.class.array", list, classnames, "Locator expected to contain classes", convertType(options, FrameExpectOptions.class));
+    expectImpl("to.contain.class.array", list, classnames, "Locator expected to contain classes", convertType(options, FrameExpectOptions.class), "Expect \"containsClass\"");
   }
 
   @Override
@@ -65,7 +65,7 @@ public void containsText(String text, ContainsTextOptions options) {
     expected.ignoreCase = shouldIgnoreCase(options);
     expected.matchSubstring = true;
     expected.normalizeWhiteSpace = true;
-    expectImpl("to.have.text", expected, text, "Locator expected to contain text", convertType(options, FrameExpectOptions.class));
+    expectImpl("to.have.text", expected, text, "Locator expected to contain text", convertType(options, FrameExpectOptions.class), "Expect \"containsText\"");
   }
 
   @Override
@@ -74,7 +74,7 @@ public void containsText(Pattern pattern, ContainsTextOptions options) {
     expected.ignoreCase = shouldIgnoreCase(options);
     expected.matchSubstring = true;
     expected.normalizeWhiteSpace = true;
-    expectImpl("to.have.text", expected, pattern, "Locator expected to contain regex", convertType(options, FrameExpectOptions.class));
+    expectImpl("to.have.text", expected, pattern, "Locator expected to contain regex", convertType(options, FrameExpectOptions.class), "Expect \"containsText\"");
   }
 
   @Override
@@ -88,7 +88,7 @@ public void containsText(String[] strings, ContainsTextOptions options) {
       expected.normalizeWhiteSpace = true;
       list.add(expected);
     }
-    expectImpl("to.contain.text.array", list, strings, "Locator expected to contain text", convertType(options, FrameExpectOptions.class));
+    expectImpl("to.contain.text.array", list, strings, "Locator expected to contain text", convertType(options, FrameExpectOptions.class), "Expect \"containsText\"");
   }
 
   @Override
@@ -101,7 +101,7 @@ public void containsText(Pattern[] patterns, ContainsTextOptions options) {
       expected.normalizeWhiteSpace = true;
       list.add(expected);
     }
-    expectImpl("to.contain.text.array", list, patterns, "Locator expected to contain text", convertType(options, FrameExpectOptions.class));
+    expectImpl("to.contain.text.array", list, patterns, "Locator expected to contain text", convertType(options, FrameExpectOptions.class), "Expect \"containsText\"");
   }
 
   @Override
@@ -110,7 +110,7 @@ public void hasAccessibleDescription(String description, HasAccessibleDescriptio
     expected.string = description;
     expected.ignoreCase = shouldIgnoreCase(options);
     expected.normalizeWhiteSpace = true;
-    expectImpl("to.have.accessible.description", expected, description, "Locator expected to have accessible description", convertType(options, FrameExpectOptions.class));
+    expectImpl("to.have.accessible.description", expected, description, "Locator expected to have accessible description", convertType(options, FrameExpectOptions.class), "Expect \"hasAccessibleDescription\"");
   }
 
   @Override
@@ -118,7 +118,7 @@ public void hasAccessibleDescription(Pattern pattern, HasAccessibleDescriptionOp
     ExpectedTextValue expected = expectedRegex(pattern);
     expected.ignoreCase = shouldIgnoreCase(options);
     expected.normalizeWhiteSpace = true;
-    expectImpl("to.have.accessible.description", expected, pattern, "Locator expected to have accessible description", convertType(options, FrameExpectOptions.class));
+    expectImpl("to.have.accessible.description", expected, pattern, "Locator expected to have accessible description", convertType(options, FrameExpectOptions.class), "Expect \"hasAccessibleDescription\"");
   }
 
   @Override
@@ -127,7 +127,7 @@ public void hasAccessibleErrorMessage(String errorMessage, HasAccessibleErrorMes
     expected.string = errorMessage;
     expected.ignoreCase = shouldIgnoreCase(options);
     expected.normalizeWhiteSpace = true;
-    expectImpl("to.have.accessible.error.message", expected, errorMessage, "Locator expected to have accessible error message", convertType(options, FrameExpectOptions.class));
+    expectImpl("to.have.accessible.error.message", expected, errorMessage, "Locator expected to have accessible error message", convertType(options, FrameExpectOptions.class), "Expect \"hasAccessibleErrorMessage\"");
   }
 
   @Override
@@ -135,7 +135,7 @@ public void hasAccessibleErrorMessage(Pattern pattern, HasAccessibleErrorMessage
     ExpectedTextValue expected = expectedRegex(pattern);
     expected.ignoreCase = shouldIgnoreCase(options);
     expected.normalizeWhiteSpace = true;
-    expectImpl("to.have.accessible.error.message", expected, pattern, "Locator expected to have accessible error message", convertType(options, FrameExpectOptions.class));
+    expectImpl("to.have.accessible.error.message", expected, pattern, "Locator expected to have accessible error message", convertType(options, FrameExpectOptions.class), "Expect \"hasAccessibleErrorMessage\"");
   }
 
   @Override
@@ -144,7 +144,7 @@ public void hasAccessibleName(String name, HasAccessibleNameOptions options) {
     expected.string = name;
     expected.ignoreCase = shouldIgnoreCase(options);
     expected.normalizeWhiteSpace = true;
-    expectImpl("to.have.accessible.name", expected, name, "Locator expected to have accessible name", convertType(options, FrameExpectOptions.class));
+    expectImpl("to.have.accessible.name", expected, name, "Locator expected to have accessible name", convertType(options, FrameExpectOptions.class), "Expect \"hasAccessibleName\"");
   }
 
   @Override
@@ -152,7 +152,7 @@ public void hasAccessibleName(Pattern pattern, HasAccessibleNameOptions options)
     ExpectedTextValue expected = expectedRegex(pattern);
     expected.ignoreCase = shouldIgnoreCase(options);
     expected.normalizeWhiteSpace = true;
-    expectImpl("to.have.accessible.name", expected, pattern, "Locator expected to have accessible name", convertType(options, FrameExpectOptions.class));
+    expectImpl("to.have.accessible.name", expected, pattern, "Locator expected to have accessible name", convertType(options, FrameExpectOptions.class), "Expect \"hasAccessibleName\"");
   }
 
   @Override
@@ -180,20 +180,20 @@ private void hasAttribute(String name, ExpectedTextValue expectedText, Object ex
     if (expectedValue instanceof Pattern) {
       message += " matching regex";
     }
-    expectImpl("to.have.attribute.value", expectedText, expectedValue, message, commonOptions);
+    expectImpl("to.have.attribute.value", expectedText, expectedValue, message, commonOptions, "Expect \"hasAttribute\"");
   }
 
   @Override
   public void hasClass(String text, HasClassOptions options) {
     ExpectedTextValue expected = new ExpectedTextValue();
     expected.string = text;
-    expectImpl("to.have.class", expected, text, "Locator expected to have class", convertType(options, FrameExpectOptions.class));
+    expectImpl("to.have.class", expected, text, "Locator expected to have class", convertType(options, FrameExpectOptions.class), "Expect \"hasClass\"");
   }
 
   @Override
   public void hasClass(Pattern pattern, HasClassOptions options) {
     ExpectedTextValue expected = expectedRegex(pattern);
-    expectImpl("to.have.class", expected, pattern, "Locator expected to have class matching regex", convertType(options, FrameExpectOptions.class));
+    expectImpl("to.have.class", expected, pattern, "Locator expected to have class matching regex", convertType(options, FrameExpectOptions.class), "Expect \"hasClass\"");
   }
 
   @Override
@@ -204,7 +204,7 @@ public void hasClass(String[] strings, HasClassOptions options) {
       expected.string = text;
       list.add(expected);
     }
-    expectImpl("to.have.class.array", list, strings, "Locator expected to have class", convertType(options, FrameExpectOptions.class));
+    expectImpl("to.have.class.array", list, strings, "Locator expected to have class", convertType(options, FrameExpectOptions.class), "Expect \"hasClass\"");
   }
 
   @Override
@@ -214,7 +214,7 @@ public void hasClass(Pattern[] patterns, HasClassOptions options) {
       ExpectedTextValue expected = expectedRegex(pattern);
       list.add(expected);
     }
-    expectImpl("to.have.class.array", list, patterns, "Locator expected to have class matching regex", convertType(options, FrameExpectOptions.class));
+    expectImpl("to.have.class.array", list, patterns, "Locator expected to have class matching regex", convertType(options, FrameExpectOptions.class), "Expect \"hasClass\"");
   }
 
   @Override
@@ -225,7 +225,7 @@ public void hasCount(int count, HasCountOptions options) {
     FrameExpectOptions commonOptions = convertType(options, FrameExpectOptions.class);
     commonOptions.expectedNumber = (double) count;
     List expectedText = null;
-    expectImpl("to.have.count", expectedText, count, "Locator expected to have count", commonOptions);
+    expectImpl("to.have.count", expectedText, count, "Locator expected to have count", commonOptions, "Expect \"hasCount\"");
   }
 
   @Override
@@ -251,20 +251,20 @@ private void hasCSS(String name, ExpectedTextValue expectedText, Object expected
     if (expectedValue instanceof Pattern) {
       message += " matching regex";
     }
-    expectImpl("to.have.css", expectedText, expectedValue, message, commonOptions);
+    expectImpl("to.have.css", expectedText, expectedValue, message, commonOptions, "Expect \"hasCSS\"");
   }
 
   @Override
   public void hasId(String id, HasIdOptions options) {
     ExpectedTextValue expected = new ExpectedTextValue();
     expected.string = id;
-    expectImpl("to.have.id", expected, id, "Locator expected to have ID", convertType(options, FrameExpectOptions.class));
+    expectImpl("to.have.id", expected, id, "Locator expected to have ID", convertType(options, FrameExpectOptions.class), "Expect \"hasId\"");
   }
 
   @Override
   public void hasId(Pattern pattern, HasIdOptions options) {
     ExpectedTextValue expected = expectedRegex(pattern);
-    expectImpl("to.have.id", expected, pattern, "Locator expected to have ID matching regex", convertType(options, FrameExpectOptions.class));
+    expectImpl("to.have.id", expected, pattern, "Locator expected to have ID matching regex", convertType(options, FrameExpectOptions.class), "Expect \"hasId\"");
   }
 
   @Override
@@ -276,14 +276,14 @@ public void hasJSProperty(String name, Object value, HasJSPropertyOptions option
     commonOptions.expressionArg = name;
     commonOptions.expectedValue = serializeArgument(value);
     List list = null;
-    expectImpl("to.have.property", list, value, "Locator expected to have JavaScript property '" + name + "'", commonOptions);
+    expectImpl("to.have.property", list, value, "Locator expected to have JavaScript property '" + name + "'", commonOptions, "Expect \"hasJSProperty\"");
   }
 
   @Override
   public void hasRole(AriaRole role, HasRoleOptions options) {
     ExpectedTextValue expected = new ExpectedTextValue();
     expected.string = role.toString().toLowerCase();
-    expectImpl("to.have.role", expected, expected.string, "Locator expected to have role", convertType(options, FrameExpectOptions.class));
+    expectImpl("to.have.role", expected, expected.string, "Locator expected to have role", convertType(options, FrameExpectOptions.class), "Expect \"hasRole\"");
   }
 
   @Override
@@ -293,7 +293,7 @@ public void hasText(String text, HasTextOptions options) {
     expected.ignoreCase = shouldIgnoreCase(options);
     expected.matchSubstring = false;
     expected.normalizeWhiteSpace = true;
-    expectImpl("to.have.text", expected, text, "Locator expected to have text", convertType(options, FrameExpectOptions.class));
+    expectImpl("to.have.text", expected, text, "Locator expected to have text", convertType(options, FrameExpectOptions.class), "Expect \"hasText\"");
   }
 
   @Override
@@ -303,7 +303,7 @@ public void hasText(Pattern pattern, HasTextOptions options) {
     // Just match substring, same as containsText.
     expected.matchSubstring = true;
     expected.normalizeWhiteSpace = true;
-    expectImpl("to.have.text", expected, pattern, "Locator expected to have text matching regex", convertType(options, FrameExpectOptions.class));
+    expectImpl("to.have.text", expected, pattern, "Locator expected to have text matching regex", convertType(options, FrameExpectOptions.class), "Expect \"hasText\"");
   }
 
   @Override
@@ -317,7 +317,7 @@ public void hasText(String[] strings, HasTextOptions options) {
       expected.normalizeWhiteSpace = true;
       list.add(expected);
     }
-    expectImpl("to.have.text.array", list, strings, "Locator expected to have text", convertType(options, FrameExpectOptions.class));
+    expectImpl("to.have.text.array", list, strings, "Locator expected to have text", convertType(options, FrameExpectOptions.class), "Expect \"hasText\"");
   }
 
   @Override
@@ -330,20 +330,20 @@ public void hasText(Pattern[] patterns, HasTextOptions options) {
       expected.normalizeWhiteSpace = true;
       list.add(expected);
     }
-    expectImpl("to.have.text.array", list, patterns, "Locator expected to have text matching regex", convertType(options, FrameExpectOptions.class));
+    expectImpl("to.have.text.array", list, patterns, "Locator expected to have text matching regex", convertType(options, FrameExpectOptions.class), "Expect \"hasText\"");
   }
 
   @Override
   public void hasValue(String value, HasValueOptions options) {
     ExpectedTextValue expected = new ExpectedTextValue();
     expected.string = value;
-    expectImpl("to.have.value", expected, value, "Locator expected to have value", convertType(options, FrameExpectOptions.class));
+    expectImpl("to.have.value", expected, value, "Locator expected to have value", convertType(options, FrameExpectOptions.class), "Expect \"hasValue\"");
   }
 
   @Override
   public void hasValue(Pattern pattern, HasValueOptions options) {
     ExpectedTextValue expected = expectedRegex(pattern);
-    expectImpl("to.have.value", expected, pattern, "Locator expected to have value matching regex", convertType(options, FrameExpectOptions.class));
+    expectImpl("to.have.value", expected, pattern, "Locator expected to have value matching regex", convertType(options, FrameExpectOptions.class), "Expect \"hasValue\"");
   }
 
   @Override
@@ -354,7 +354,7 @@ public void hasValues(String[] values, HasValuesOptions options) {
       expected.string = text;
       list.add(expected);
     }
-    expectImpl("to.have.values", list, values, "Locator expected to have values", convertType(options, FrameExpectOptions.class));
+    expectImpl("to.have.values", list, values, "Locator expected to have values", convertType(options, FrameExpectOptions.class), "Expect \"hasValues\"");
   }
 
   @Override
@@ -365,7 +365,7 @@ public void hasValues(Pattern[] patterns, HasValuesOptions options) {
       expected.matchSubstring = true;
       list.add(expected);
     }
-    expectImpl("to.have.values", list, patterns, "Locator expected to have values matching regex", convertType(options, FrameExpectOptions.class));
+    expectImpl("to.have.values", list, patterns, "Locator expected to have values matching regex", convertType(options, FrameExpectOptions.class), "Expect \"hasValues\"");
   }
 
   @Override
@@ -375,7 +375,7 @@ public void matchesAriaSnapshot(String expected, MatchesAriaSnapshotOptions snap
     }
     FrameExpectOptions options = convertType(snapshotOptions, FrameExpectOptions.class);
     options.expectedValue = serializeArgument(expected);
-    expectImpl("to.match.aria", options, expected,"Locator expected to match Aria snapshot");
+    expectImpl("to.match.aria", options, expected,"Locator expected to match Aria snapshot", "Expect \"matchesAriaSnapshot\"");
   }
 
   @Override
@@ -403,12 +403,12 @@ public void isChecked(IsCheckedOptions options) {
     String message = "Locator expected to be";
     FrameExpectOptions expectOptions = convertType(options, FrameExpectOptions.class);
     expectOptions.expectedValue = serializeArgument(expectedValue);
-    expectImpl("to.be.checked", expectOptions, expected, message);
+    expectImpl("to.be.checked", expectOptions, expected, message, "Expect \"isChecked\"");
   }
 
   @Override
   public void isDisabled(IsDisabledOptions options) {
-    expectTrue("to.be.disabled", "Locator expected to be disabled", convertType(options, FrameExpectOptions.class));
+    expectTrue("to.be.disabled", "Locator expected to be disabled", convertType(options, FrameExpectOptions.class), "Expect \"isDisabled\"");
   }
 
   @Override
@@ -416,12 +416,12 @@ public void isEditable(IsEditableOptions options) {
     FrameExpectOptions frameOptions = convertType(options, FrameExpectOptions.class);
     boolean editable = options == null || options.editable == null || options.editable == true;
     String message = "Locator expected to be " + (editable ? "editable" : "readonly");
-    expectTrue(editable ? "to.be.editable" : "to.be.readonly", message, frameOptions);
+    expectTrue(editable ? "to.be.editable" : "to.be.readonly", message, frameOptions, "Expect \"isEditable\"");
   }
 
   @Override
   public void isEmpty(IsEmptyOptions options) {
-    expectTrue("to.be.empty", "Locator expected to be empty", convertType(options, FrameExpectOptions.class));
+    expectTrue("to.be.empty", "Locator expected to be empty", convertType(options, FrameExpectOptions.class), "Expect \"isEmpty\"");
   }
 
   @Override
@@ -429,17 +429,17 @@ public void isEnabled(IsEnabledOptions options) {
     FrameExpectOptions frameOptions = convertType(options, FrameExpectOptions.class);
     boolean enabled = options == null || options.enabled == null || options.enabled == true;
     String message = "Locator expected to be " + (enabled ? "enabled" : "disabled");
-    expectTrue(enabled ? "to.be.enabled" : "to.be.disabled", message, frameOptions);
+    expectTrue(enabled ? "to.be.enabled" : "to.be.disabled", message, frameOptions, "Expect \"isEnabled\"");
   }
 
   @Override
   public void isFocused(IsFocusedOptions options) {
-    expectTrue("to.be.focused", "Locator expected to be focused", convertType(options, FrameExpectOptions.class));
+    expectTrue("to.be.focused", "Locator expected to be focused", convertType(options, FrameExpectOptions.class), "Expect \"isFocused\"");
   }
 
   @Override
   public void isHidden(IsHiddenOptions options) {
-    expectTrue("to.be.hidden", "Locator expected to be hidden", convertType(options, FrameExpectOptions.class));
+    expectTrue("to.be.hidden", "Locator expected to be hidden", convertType(options, FrameExpectOptions.class), "Expect \"isHidden\"");
   }
 
   @Override
@@ -448,7 +448,7 @@ public void isInViewport(IsInViewportOptions options) {
     if (options != null && options.ratio != null) {
       expectOptions.expectedNumber = options.ratio;
     }
-    expectTrue("to.be.in.viewport", "Locator expected to be in viewport",  expectOptions);
+    expectTrue("to.be.in.viewport", "Locator expected to be in viewport",  expectOptions, "Expect \"isInViewport\"");
   }
 
   @Override
@@ -456,12 +456,12 @@ public void isVisible(IsVisibleOptions options) {
     FrameExpectOptions frameOptions = convertType(options, FrameExpectOptions.class);
     boolean visible = options == null || options.visible == null || options.visible == true;
     String message = "Locator expected to be " + (visible ? "visible" : "hidden");
-    expectTrue(visible ? "to.be.visible" : "to.be.hidden", message, frameOptions);
+    expectTrue(visible ? "to.be.visible" : "to.be.hidden", message, frameOptions, "Expect \"isVisible\"");
   }
 
-  private void expectTrue(String expression, String message, FrameExpectOptions options) {
+  private void expectTrue(String expression, String message, FrameExpectOptions options, String title) {
     List expectedText = null;
-    expectImpl(expression, expectedText, null, message, options);
+    expectImpl(expression, expectedText, null, message, options, title);
   }
 
   @Override
@@ -474,6 +474,6 @@ public void isAttached(IsAttachedOptions options) {
     FrameExpectOptions frameOptions = convertType(options, FrameExpectOptions.class);
     boolean attached = options == null || options.attached == null || options.attached == true;
     String message = "Locator expected to be " + (attached ? "attached" : "detached");
-    expectTrue(attached ? "to.be.attached" : "to.be.detached", message, frameOptions);
+    expectTrue(attached ? "to.be.attached" : "to.be.detached", message, frameOptions, "Expect \"isAttached\"");
   }
 }
diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/LocatorImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/LocatorImpl.java
index 90f7b77e9..7d0099de8 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/LocatorImpl.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/LocatorImpl.java
@@ -71,23 +71,25 @@ private static String escapeWithQuotes(String text) {
     return gson().toJson(text);
   }
 
-  private  R withElement(BiFunction callback, O options) {
-    ElementHandleOptions handleOptions = convertType(options, ElementHandleOptions.class);
-    // TODO: support deadline based timeout
-//    Double timeout = null;
-//    if (handleOptions != null) {
-//      timeout = handleOptions.timeout;
-//    }
-//    timeout = frame.page.timeoutSettings.timeout(timeout);
-//    long deadline = System.nanoTime() + (long) timeout.doubleValue() * 1_000_000;
-    ElementHandle handle = elementHandle(handleOptions);
-    try {
-      return callback.apply(handle, options);
-    } finally {
-      if (handle != null) {
-        handle.dispose();
+  private  R withElement(BiFunction callback, O options, String title) {
+    return frame.withTitle(title, () -> {
+      ElementHandleOptions handleOptions = convertType(options, ElementHandleOptions.class);
+      // TODO: support deadline based timeout
+      //    Double timeout = null;
+      //    if (handleOptions != null) {
+      //      timeout = handleOptions.timeout;
+      //    }
+      //    timeout = frame.page.timeoutSettings.timeout(timeout);
+      //    long deadline = System.nanoTime() + (long) timeout.doubleValue() * 1_000_000;
+      ElementHandle handle = elementHandle(handleOptions);
+      try {
+        return callback.apply(handle, options);
+      } finally {
+        if (handle != null) {
+          handle.dispose();
+        }
       }
-    }
+    });
   }
 
   @Override
@@ -152,7 +154,7 @@ private void blurImpl(BlurOptions options) {
 
   @Override
   public BoundingBox boundingBox(BoundingBoxOptions options) {
-    return withElement((h, o) -> h.boundingBox(), options);
+    return withElement((h, o) -> h.boundingBox(), options, "Bounding Box");
   }
 
   @Override
@@ -165,7 +167,7 @@ public void check(CheckOptions options) {
 
   @Override
   public void clear(ClearOptions options) {
-    fill("", convertType(options, FillOptions.class));
+    frame.withTitle("Clear", () -> fill("", convertType(options, FillOptions.class)));
   }
 
   @Override
@@ -235,7 +237,7 @@ public FrameLocator contentFrame() {
 
   @Override
   public Object evaluate(String expression, Object arg, EvaluateOptions options) {
-    return withElement((h, o) -> h.evaluate(expression, arg), options);
+    return withElement((h, o) -> h.evaluate(expression, arg), options, "Evaluate");
   }
 
   @Override
@@ -245,7 +247,7 @@ public Object evaluateAll(String expression, Object arg) {
 
   @Override
   public JSHandle evaluateHandle(String expression, Object arg, EvaluateHandleOptions options) {
-    return withElement((h, o) -> h.evaluateHandle(expression, arg), options);
+    return withElement((h, o) -> h.evaluateHandle(expression, arg), options, "Evaluate");
   }
 
   @Override
@@ -490,7 +492,7 @@ public void pressSequentially(String text, PressSequentiallyOptions options) {
 
   @Override
   public byte[] screenshot(ScreenshotOptions options) {
-    return withElement((h, o) -> h.screenshot(o), convertType(options, ElementHandle.ScreenshotOptions.class));
+    return withElement((h, o) -> h.screenshot(o), convertType(options, ElementHandle.ScreenshotOptions.class), "Screenshot");
   }
 
   @Override
@@ -498,7 +500,7 @@ public void scrollIntoViewIfNeeded(ScrollIntoViewIfNeededOptions options) {
     withElement((h, o) -> {
       h.scrollIntoViewIfNeeded(o);
       return null;
-    }, convertType(options, ElementHandle.ScrollIntoViewIfNeededOptions.class));
+    }, convertType(options, ElementHandle.ScrollIntoViewIfNeededOptions.class), "Scroll into view");
   }
 
   @Override
@@ -554,7 +556,7 @@ public void selectText(SelectTextOptions options) {
     withElement((h, o) -> {
       h.selectText(o);
       return null;
-    }, convertType(options, ElementHandle.SelectTextOptions.class));
+    }, convertType(options, ElementHandle.SelectTextOptions.class), "Select text");
   }
 
   @Override
@@ -660,8 +662,8 @@ public int hashCode() {
     return frame.hashCode() ^ selector.hashCode();
   }
 
-  FrameExpectResult expect(String expression, FrameExpectOptions options) {
-    return frame.withLogging("Locator.expect", () -> expectImpl(expression, options));
+  FrameExpectResult expect(String expression, FrameExpectOptions options, String title) {
+    return frame.withTitle(title, () -> expectImpl(expression, options));
   }
 
   JsonObject toProtocol() {
diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/MouseImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/MouseImpl.java
index 283b209c9..7b0b55b98 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/MouseImpl.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/MouseImpl.java
@@ -46,7 +46,7 @@ private void clickImpl(double x, double y, ClickOptions options) {
 
   @Override
   public void dblclick(double x, double y, DblclickOptions options) {
-    page.withLogging("Mouse.dblclick", () -> dblclickImpl(x, y, options));
+    page.withTitle("Double click", () -> dblclickImpl(x, y, options));
   }
 
   private void dblclickImpl(double x, double y, DblclickOptions options) {
diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/PageAssertionsImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/PageAssertionsImpl.java
index b103d4ff6..58778e6cb 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/PageAssertionsImpl.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/PageAssertionsImpl.java
@@ -42,13 +42,13 @@ 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));
+    expectImpl("to.have.title", expected, title, "Page title expected to be", convertType(options, FrameExpectOptions.class), "Expect \"hasTitle\"");
   }
 
   @Override
   public void hasTitle(Pattern pattern, HasTitleOptions options) {
     ExpectedTextValue expected = expectedRegex(pattern);
-    expectImpl("to.have.title", expected, pattern, "Page title expected to match regex", convertType(options, FrameExpectOptions.class));
+    expectImpl("to.have.title", expected, pattern, "Page title expected to match regex", convertType(options, FrameExpectOptions.class), "Expect \"hasTitle\"");
   }
 
   @Override
@@ -59,13 +59,13 @@ public void hasURL(String url, HasURLOptions options) {
     }
     expected.string = url;
     expected.ignoreCase = shouldIgnoreCase(options);
-    expectImpl("to.have.url", expected, url, "Page URL expected to be", convertType(options, FrameExpectOptions.class));
+    expectImpl("to.have.url", expected, url, "Page URL expected to be", convertType(options, FrameExpectOptions.class), "Expect \"hasURL\"");
   }
 
   @Override
   public void hasURL(Pattern pattern, HasURLOptions options) {
     ExpectedTextValue expected = expectedRegex(pattern);
-    expectImpl("to.have.url", expected, pattern, "Page URL expected to match regex", convertType(options, FrameExpectOptions.class));
+    expectImpl("to.have.url", expected, pattern, "Page URL expected to match regex", convertType(options, FrameExpectOptions.class), "Expect \"hasURL\"");
   }
 
   @Override

From b9e57594a3ff88d49a0217b79a8a6682b07a695f Mon Sep 17 00:00:00 2001
From: Simon Knott 
Date: Wed, 11 Jun 2025 10:51:56 +0200
Subject: [PATCH 04/28] more

---
 .../playwright/impl/BrowserContextImpl.java   |  9 ++--
 .../playwright/impl/BrowserImpl.java          | 45 +++++++++++++++----
 .../playwright/impl/BrowserTypeImpl.java      |  2 +
 .../playwright/impl/PlaywrightImpl.java       |  6 ++-
 .../playwright/impl/SharedSelectors.java      |  4 +-
 .../playwright/TestLocatorAssertions.java     |  6 +--
 .../playwright/TestLocatorAssertions2.java    |  2 +-
 7 files changed, 54 insertions(+), 20 deletions(-)

diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java
index b06d127de..ec5952829 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java
@@ -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;
@@ -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 closePromise;
   final Map bindings = new HashMap<>();
   PageImpl ownerPage;
@@ -286,8 +286,8 @@ public List cookies(String url) {
   }
 
   private void closeImpl(CloseOptions options) {
-    if (!closeWasCalled) {
-      closeWasCalled = true;
+    if (!closingOrClosed) {
+      closingOrClosed = true;
       if (options == null) {
         options = new CloseOptions();
       }
@@ -851,6 +851,7 @@ protected void handleEvent(String event, JsonObject params) {
   void didClose() {
     if (browser != null) {
       browser.contexts.remove(this);
+      browser.browserType.playwright.sharedSelectors.contextsForSelectors.remove(this);
     }
     listeners.notify(EventType.CLOSE, this);
   }
diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserImpl.java
index 387f27a0d..046810cce 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserImpl.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserImpl.java
@@ -32,7 +32,6 @@
 import static com.microsoft.playwright.impl.Serialization.addHarUrlFilter;
 import static com.microsoft.playwright.impl.Serialization.gson;
 import static com.microsoft.playwright.impl.Utils.*;
-import static com.microsoft.playwright.impl.Utils.convertType;
 
 class BrowserImpl extends ChannelOwner implements Browser {
   final Set contexts = new HashSet<>();
@@ -41,7 +40,7 @@ class BrowserImpl extends ChannelOwner implements Browser {
   private boolean isConnected = true;
   BrowserTypeImpl browserType;
   BrowserType.LaunchOptions launchOptions;
-  private Path tracePath;
+  private Path tracesDir;
   String closeReason;
 
   enum EventType {
@@ -241,7 +240,7 @@ private void startTracingImpl(Page page, StartTracingOptions options) {
     if (options == null) {
       options = new StartTracingOptions();
     }
-    tracePath = options.path;
+    tracesDir = options.path;
     JsonObject params = gson().toJsonTree(options).getAsJsonObject();
     if (page != null) {
       params.add("page", ((PageImpl) page).toProtocolRef());
@@ -259,14 +258,14 @@ private byte[] stopTracingImpl() {
     ArtifactImpl artifact = connection.getExistingObject(json.getAsJsonObject().getAsJsonObject("artifact").get("guid").getAsString());
     byte[] data = artifact.readAllBytes();
     artifact.delete();
-    if (tracePath != null) {
+    if (tracesDir != null) {
       try {
-        Files.createDirectories(tracePath.getParent());
-        Files.write(tracePath, data);
+        Files.createDirectories(tracesDir.getParent());
+        Files.write(tracesDir, data);
       } catch (IOException e) {
         throw new PlaywrightException("Failed to write trace file", e);
       } finally {
-        tracePath = null;
+        tracesDir = null;
       }
     }
     return data;
@@ -295,8 +294,13 @@ public String version() {
 
   @Override
   void handleEvent(String event, JsonObject parameters) {
-    if ("close".equals(event)) {
-      didClose();
+    switch (event) {
+      case "context":
+        didCreateContext(connection.getExistingObject(parameters.getAsJsonObject("context").get("guid").getAsString()));
+        break;
+      case "close":
+        didClose();
+        break;
     }
   }
 
@@ -307,6 +311,29 @@ public CDPSession newBrowserCDPSession() {
     return connection.getExistingObject(result.getAsJsonObject("session").get("guid").getAsString());
   }
 
+  protected void connectToBrowserType(BrowserTypeImpl browserType, Path tracesDir){
+    // Note: when using connect(), `browserType` is different from `this.parent`.
+    // This is why browser type is not wired up in the constructor, and instead this separate method is called later on.
+    this.browserType = browserType;
+    this.tracesDir = tracesDir;
+
+    for (BrowserContextImpl context : contexts) {
+      context.tracing().setTracesDir(tracesDir);
+      browserType.playwright.sharedSelectors.contextsForSelectors.add(context);
+    }
+  }
+
+  private void didCreateContext(BrowserContextImpl context) {
+    context.browser = this;
+    contexts.add(context);
+    // Note: when connecting to a browser, initial contexts arrive before `_browserType` is set,
+    // and will be configured later in `ConnectToBrowserType`.
+    if (browserType != null) {
+      context.tracing().setTracesDir(tracesDir);
+      browserType.playwright.sharedSelectors.contextsForSelectors.add(context);
+    }
+  }
+
   private void didClose() {
     isConnected = false;
     listeners.notify(EventType.DISCONNECTED, this);
diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserTypeImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserTypeImpl.java
index 6750751a1..7a5215030 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserTypeImpl.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserTypeImpl.java
@@ -35,6 +35,8 @@
 import static com.microsoft.playwright.impl.Utils.convertType;
 
 class BrowserTypeImpl extends ChannelOwner implements BrowserType {
+  protected PlaywrightImpl playwright;
+
   BrowserTypeImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
     super(parent, type, guid, initializer);
   }
diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/PlaywrightImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/PlaywrightImpl.java
index 2f90e6273..2749cbeca 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/PlaywrightImpl.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/PlaywrightImpl.java
@@ -62,7 +62,7 @@ public static PlaywrightImpl createImpl(CreateOptions options, boolean forceNewD
   private final BrowserTypeImpl firefox;
   private final BrowserTypeImpl webkit;
   private final APIRequestImpl apiRequest;
-  private SharedSelectors sharedSelectors;
+  protected SharedSelectors sharedSelectors;
 
   PlaywrightImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
     super(parent, type, guid, initializer);
@@ -70,6 +70,10 @@ public static PlaywrightImpl createImpl(CreateOptions options, boolean forceNewD
     firefox = parent.connection.getExistingObject(initializer.getAsJsonObject("firefox").get("guid").getAsString());
     webkit = parent.connection.getExistingObject(initializer.getAsJsonObject("webkit").get("guid").getAsString());
 
+    chromium.playwright = this;
+    firefox.playwright = this;
+    webkit.playwright = this;
+
     sharedSelectors = new SharedSelectors();
     apiRequest = new APIRequestImpl(this);
   }
diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/SharedSelectors.java b/playwright/src/main/java/com/microsoft/playwright/impl/SharedSelectors.java
index c03a70e75..d30f4d13f 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/SharedSelectors.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/SharedSelectors.java
@@ -29,8 +29,8 @@
 import static java.nio.charset.StandardCharsets.UTF_8;
 
 public class SharedSelectors extends LoggingSupport implements Selectors {
-  private final List contextsForSelectors = new ArrayList<>();
-  private final List selectorEngines = new ArrayList<>(); // FIXME: do we need this? is it ever read?
+  protected final List contextsForSelectors = new ArrayList<>();
+  protected final List selectorEngines = new ArrayList<>(); // FIXME: do we need this? is it ever read?
 
   String testIdAttributeName = "data-testid";
 
diff --git a/playwright/src/test/java/com/microsoft/playwright/TestLocatorAssertions.java b/playwright/src/test/java/com/microsoft/playwright/TestLocatorAssertions.java
index e275483cd..de7fb0897 100644
--- a/playwright/src/test/java/com/microsoft/playwright/TestLocatorAssertions.java
+++ b/playwright/src/test/java/com/microsoft/playwright/TestLocatorAssertions.java
@@ -1070,7 +1070,7 @@ void defaultTimeoutHasTextFail() {
     Locator locator = page.locator("div");
     PlaywrightAssertions.setDefaultAssertionTimeout(1000);
     AssertionFailedError exception = assertThrows(AssertionFailedError.class, () -> assertThat(locator).hasText("foo"));
-    assertTrue(exception.getMessage().contains("Locator.expect with timeout 1000ms"), exception.getMessage());
+    assertTrue(exception.getMessage().contains("Expect \"hasText\" with timeout 1000ms"), exception.getMessage());
     // Restore default.
     PlaywrightAssertions.setDefaultAssertionTimeout(5_000);
   }
@@ -1119,7 +1119,7 @@ void containsClassFail() {
     AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> {
       assertThat(page.locator("div")).containsClass("does-not-exist", new ContainsClassOptions().setTimeout(1000));
     });
-    assertTrue(e.getMessage().contains("Locator.expect with timeout 1000ms"), e.getMessage());
+    assertTrue(e.getMessage().contains("Expect \"containsClass\" with timeout 1000ms"), e.getMessage());
   }
 
   @Test
@@ -1137,6 +1137,6 @@ void containsClassFailWithArray() {
     AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> {
       assertThat(page.locator("div")).containsClass(asList("foo", "bar", "baz"), new ContainsClassOptions().setTimeout(1000));
     });
-    assertTrue(e.getMessage().contains("Locator.expect with timeout 1000ms"), e.getMessage());
+    assertTrue(e.getMessage().contains("Expect \"containsClass\" with timeout 1000ms"), e.getMessage());
   }
 }
diff --git a/playwright/src/test/java/com/microsoft/playwright/TestLocatorAssertions2.java b/playwright/src/test/java/com/microsoft/playwright/TestLocatorAssertions2.java
index 89564c631..f5adc03b1 100644
--- a/playwright/src/test/java/com/microsoft/playwright/TestLocatorAssertions2.java
+++ b/playwright/src/test/java/com/microsoft/playwright/TestLocatorAssertions2.java
@@ -218,6 +218,6 @@ void toBeEditableFailWithIndeterminateTrue() {
     AssertionFailedError e = assertThrows(AssertionFailedError.class, () ->
       assertThat(locator).isChecked(new LocatorAssertions.IsCheckedOptions().setIndeterminate(true).setTimeout(1000)));
     // TODO: should be "assertThat().isChecked() with timeout 1000ms"
-    assertTrue(e.getMessage().contains("Locator.expect with timeout 1000ms"), e.getMessage());
+    assertTrue(e.getMessage().contains("Expect \"isChecked\" with timeout 1000ms"), e.getMessage());
   }
 }

From f2325cc6f3f5f5b1fb141f09c09043e63afb6c45 Mon Sep 17 00:00:00 2001
From: Simon Knott 
Date: Wed, 11 Jun 2025 11:47:08 +0200
Subject: [PATCH 05/28] some more

---
 .../playwright/impl/BrowserContextImpl.java   | 69 ++++++++++++++-----
 .../playwright/impl/BrowserImpl.java          | 46 +------------
 .../playwright/impl/BrowserTypeImpl.java      | 66 +++++-------------
 .../microsoft/playwright/impl/PageImpl.java   |  2 +-
 .../TestDefaultBrowserContext2.java           | 14 ++++
 5 files changed, 87 insertions(+), 110 deletions(-)

diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java
index ec5952829..4e2212425 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java
@@ -98,11 +98,6 @@ 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;
@@ -500,7 +495,7 @@ 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);
@@ -538,24 +533,41 @@ private void routeWebSocketImpl(UrlMatcher matcher, Consumer han
     });
   }
 
-  void recordIntoHar(PageImpl page, Path har, RouteFromHAROptions options) {
+  private HarContentPolicy routeFromHarUpdateContentPolicyToHarContentPolicy(RouteFromHarUpdateContentPolicy contentPolicy) {
+    switch (contentPolicy) {
+      case ATTACH:
+        return HarContentPolicy.ATTACH;
+      case EMBED:
+        return HarContentPolicy.EMBED;
+      default:
+        return null;
+    }
+  }
+
+  void recordIntoHar(PageImpl page, Path har, RouteFromHAROptions options, HarContentPolicy contentPolicy) {
+    if (contentPolicy == null) {
+      contentPolicy = routeFromHarUpdateContentPolicyToHarContentPolicy(options.updateContent);
+    }
+    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 ?
+    JsonObject recordHarArgs = new JsonObject();
+    recordHarArgs.addProperty("zip", har.endsWith(".zip"));  
+    recordHarArgs.addProperty("content", contentPolicy.name().toLowerCase());
+    recordHarArgs.addProperty("mode", options.updateMode == null ?
       HarMode.MINIMAL.name().toLowerCase() :
       options.updateMode.name().toLowerCase());
-    addHarUrlFilter(jsonOptions, options.url);
-    params.add("options", jsonOptions);
+    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
@@ -863,4 +875,29 @@ 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) {
+      return;
+    }
+
+    HarContentPolicy defaultPolicy = options.recordHarPath.endsWith(".zip") ? HarContentPolicy.ATTACH : HarContentPolicy.EMBED;
+    HarContentPolicy contentPolicy = options.recordHarContent == null ? (options.recordHarOmitContent ? HarContentPolicy.OMIT : defaultPolicy) : options.recordHarContent;
+    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);
+  }
 }
diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserImpl.java
index 046810cce..24d271993 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserImpl.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserImpl.java
@@ -20,7 +20,6 @@
 import com.google.gson.JsonElement;
 import com.google.gson.JsonObject;
 import com.microsoft.playwright.*;
-import com.microsoft.playwright.options.HarContentPolicy;
 
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
@@ -140,51 +139,10 @@ private BrowserContextImpl newContextImpl(NewContextOptions options) {
       storageState = new Gson().fromJson(options.storageState, JsonObject.class);
       options.storageState = null;
     }
-    JsonObject recordHar = null;
-    Path recordHarPath = options.recordHarPath;
-    HarContentPolicy harContentPolicy = null;
-    if (options.recordHarPath != null) {
-      recordHar = new JsonObject();
-      recordHar.addProperty("path", options.recordHarPath.toString());
-      if (options.recordHarContent != null) {
-        harContentPolicy = options.recordHarContent;
-      } else if (options.recordHarOmitContent != null && options.recordHarOmitContent) {
-        harContentPolicy = HarContentPolicy.OMIT;
-      }
-      if (harContentPolicy != null) {
-        recordHar.addProperty("content", harContentPolicy.name().toLowerCase());
-      }
-      if (options.recordHarMode != null) {
-        recordHar.addProperty("mode", options.recordHarMode.name().toLowerCase());
-      }
-      addHarUrlFilter(recordHar, options.recordHarUrlFilter);
-      options.recordHarPath = null;
-      options.recordHarMode = null;
-      options.recordHarOmitContent = null;
-      options.recordHarContent = null;
-      options.recordHarUrlFilter = null;
-    } else {
-      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");
-      }
-    }
-
     JsonObject params = gson().toJsonTree(options).getAsJsonObject();
     if (storageState != null) {
       params.add("storageState", storageState);
     }
-    if (recordHar != null) {
-      params.add("recordHar", recordHar);
-    }
     if (options.recordVideoDir != null) {
       JsonObject recordVideo = new JsonObject();
       recordVideo.addProperty("dir", options.recordVideoDir.toAbsolutePath().toString());
@@ -212,13 +170,15 @@ private BrowserContextImpl newContextImpl(NewContextOptions options) {
     if (options.acceptDownloads != null) {
       params.addProperty("acceptDownloads", options.acceptDownloads ? "accept" : "deny");
     }
+    params.add("selectorEngines", gson().toJsonTree(browserType.playwright.sharedSelectors.selectorEngines));
+    params.addProperty("testIdAttributeName", browserType.playwright.sharedSelectors.testIdAttributeName);
     JsonElement result = sendMessage("newContext", params);
     BrowserContextImpl context = connection.getExistingObject(result.getAsJsonObject().getAsJsonObject("context").get("guid").getAsString());
     context.videosDir = options.recordVideoDir;
     if (options.baseURL != null) {
       context.setBaseUrl(options.baseURL);
     }
-    context.setRecordHar(recordHarPath, harContentPolicy);
+    context.initializeHarFromOptions(options);
     if (launchOptions != null) {
       context.tracing().setTracesDir(launchOptions.tracesDir);
     }
diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserTypeImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserTypeImpl.java
index 7a5215030..a41fe33a7 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserTypeImpl.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserTypeImpl.java
@@ -22,14 +22,12 @@
 import com.microsoft.playwright.Browser;
 import com.microsoft.playwright.BrowserType;
 import com.microsoft.playwright.PlaywrightException;
-import com.microsoft.playwright.options.HarContentPolicy;
 
 import java.io.IOException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.function.Consumer;
 
-import static com.microsoft.playwright.impl.Serialization.addHarUrlFilter;
 import static com.microsoft.playwright.impl.Serialization.gson;
 import static com.microsoft.playwright.impl.Utils.addToProtocol;
 import static com.microsoft.playwright.impl.Utils.convertType;
@@ -103,9 +101,10 @@ private Browser connectImpl(String wsEndpoint, ConnectOptions options) {
       }
       throw new PlaywrightException("Malformed endpoint. Did you use launchServer method?");
     }
+    playwright.sharedSelectors = this.playwright.sharedSelectors;
     BrowserImpl browser = connection.getExistingObject(playwright.initializer.getAsJsonObject("preLaunchedBrowser").get("guid").getAsString());
     browser.isConnectedOverWebSocket = true;
-    browser.browserType = this;
+    browser.connectToBrowserType(this, null);
     Consumer connectionCloseListener = t -> browser.notifyRemoteClosed();
     pipe.onClose(connectionCloseListener);
     browser.onDisconnected(b -> {
@@ -138,12 +137,7 @@ private Browser connectOverCDPImpl(String endpointURL, ConnectOverCDPOptions opt
     JsonObject json = sendMessage("connectOverCDP", params).getAsJsonObject();
 
     BrowserImpl browser = connection.getExistingObject(json.getAsJsonObject("browser").get("guid").getAsString());
-    browser.browserType = this;
-    if (json.has("defaultContext")) {
-      String contextId = json.getAsJsonObject("defaultContext").get("guid").getAsString();
-      BrowserContextImpl defaultContext = connection.getExistingObject(contextId);
-      browser.contexts.add(defaultContext);
-    }
+    browser.connectToBrowserType(this, null);
     return browser;
   }
 
@@ -164,43 +158,6 @@ private BrowserContextImpl launchPersistentContextImpl(Path userDataDir, LaunchP
       // Make a copy so that we can nullify some fields below.
       options = convertType(options, LaunchPersistentContextOptions.class);
     }
-    JsonObject recordHar = null;
-    Path recordHarPath = options.recordHarPath;
-    HarContentPolicy harContentPolicy = null;
-    if (options.recordHarPath != null) {
-      recordHar = new JsonObject();
-      recordHar.addProperty("path", options.recordHarPath.toString());
-      if (options.recordHarContent != null) {
-        harContentPolicy = options.recordHarContent;
-      } else if (options.recordHarOmitContent != null && options.recordHarOmitContent) {
-        harContentPolicy = HarContentPolicy.OMIT;
-      }
-      if (harContentPolicy != null) {
-        recordHar.addProperty("content", harContentPolicy.name().toLowerCase());
-      }
-      if (options.recordHarMode != null) {
-        recordHar.addProperty("mode", options.recordHarMode.toString().toLowerCase());
-      }
-      addHarUrlFilter(recordHar, options.recordHarUrlFilter);
-      options.recordHarPath = null;
-      options.recordHarMode = null;
-      options.recordHarOmitContent = null;
-      options.recordHarContent = null;
-      options.recordHarUrlFilter = null;
-    } else {
-      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");
-      }
-    }
     options.timeout = TimeoutSettings.launchTimeout(options.timeout);
 
     JsonObject params = gson().toJsonTree(options).getAsJsonObject();
@@ -209,9 +166,6 @@ private BrowserContextImpl launchPersistentContextImpl(Path userDataDir, LaunchP
       userDataDir = cwd.resolve(userDataDir);
     }
     params.addProperty("userDataDir", userDataDir.toString());
-    if (recordHar != null) {
-      params.add("recordHar", recordHar);
-    }
     if (options.recordVideoDir != null) {
       JsonObject recordVideo = new JsonObject();
       recordVideo.addProperty("dir", options.recordVideoDir.toAbsolutePath().toString());
@@ -239,13 +193,25 @@ private BrowserContextImpl launchPersistentContextImpl(Path userDataDir, LaunchP
     if (options.acceptDownloads != null) {
       params.addProperty("acceptDownloads", options.acceptDownloads ? "accept" : "deny");
     }
+    params.add("selectorEngines", gson().toJsonTree(playwright.sharedSelectors.selectorEngines));
+    params.addProperty("testIdAttributeName", playwright.sharedSelectors.testIdAttributeName);
     JsonObject json = sendMessage("launchPersistentContext", params).getAsJsonObject();
+    BrowserImpl browser = connection.getExistingObject(json.getAsJsonObject("browser").get("guid").getAsString());
+    browser.connectToBrowserType(this, options.tracesDir);
     BrowserContextImpl context = connection.getExistingObject(json.getAsJsonObject("context").get("guid").getAsString());
     context.videosDir = options.recordVideoDir;
     if (options.baseURL != null) {
       context.setBaseUrl(options.baseURL);
     }
-    context.setRecordHar(recordHarPath, harContentPolicy);
+
+    Browser.NewContextOptions harOptions = new Browser.NewContextOptions();
+    harOptions.recordHarContent = options.recordHarContent;
+    harOptions.recordHarMode = options.recordHarMode;
+    harOptions.recordHarOmitContent = options.recordHarOmitContent;
+    harOptions.recordHarPath = options.recordHarPath;
+    harOptions.recordHarUrlFilter = options.recordHarUrlFilter;
+    context.initializeHarFromOptions(harOptions);
+
     context.tracing().setTracesDir(options.tracesDir);
     return context;
   }
diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/PageImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/PageImpl.java
index 8dbf29731..40ebd5ab5 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/PageImpl.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/PageImpl.java
@@ -1127,7 +1127,7 @@ public void routeFromHAR(Path har, RouteFromHAROptions options) {
       options = new RouteFromHAROptions();
     }
     if (options.update != null && options.update) {
-      browserContext.recordIntoHar(this, har, convertType(options, BrowserContext.RouteFromHAROptions.class));
+      browserContext.recordIntoHar(this, har, convertType(options, BrowserContext.RouteFromHAROptions.class), null);
       return;
     }
     UrlMatcher matcher = UrlMatcher.forOneOf(browserContext.baseUrl, options.url, this.connection.localUtils, false);
diff --git a/playwright/src/test/java/com/microsoft/playwright/TestDefaultBrowserContext2.java b/playwright/src/test/java/com/microsoft/playwright/TestDefaultBrowserContext2.java
index b11525dbf..7db9e19f1 100644
--- a/playwright/src/test/java/com/microsoft/playwright/TestDefaultBrowserContext2.java
+++ b/playwright/src/test/java/com/microsoft/playwright/TestDefaultBrowserContext2.java
@@ -309,4 +309,18 @@ void shouldAcceptRelativeUserDataDir(@TempDir Path tmpDir) throws Exception {
     assertTrue(Files.list(userDataDir).count() > 0);
     context.close();
   }
+
+  @Test
+  void shouldExposeBrowser() {
+    Page page = launchPersistent();
+    Browser browser = page.context().browser();
+    assertNotNull(browser);
+    Page page2 = browser.newPage();
+    page2.navigate("data:text/html,Title");
+    assertEquals("Title", page2.title());
+    browser.close();
+    assertEquals(0, page.context().pages().size());
+    // Next line should not throw.
+    page.context().close();
+  }
 }

From d889905f044336a4631c2e82b9e63db6590dea97 Mon Sep 17 00:00:00 2001
From: Simon Knott 
Date: Wed, 11 Jun 2025 12:23:55 +0200
Subject: [PATCH 06/28] more

---
 .../microsoft/playwright/impl/Connection.java |  8 ++-
 .../playwright/TestPageAriaSnapshot.java      |  3 -
 .../com/microsoft/playwright/TestPdf.java     |  7 ---
 .../com/microsoft/playwright/TestTracing.java | 62 +++++++++++--------
 4 files changed, 42 insertions(+), 38 deletions(-)

diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/Connection.java b/playwright/src/main/java/com/microsoft/playwright/impl/Connection.java
index 6eb178250..20da50cd7 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/Connection.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/Connection.java
@@ -150,9 +150,11 @@ private WaitableResult internalSendMessage(String guid, String meth
     if (titleReported) {
       metadata.addProperty("internal", true);
     } else {
-      metadata.addProperty("title", title);
-      // All but first message in a custom-titled API call are considered internal and will be hidden from the inspector.
-      titleReported = true;
+      if (title != null) {
+        metadata.addProperty("title", title);
+        // All but first message in a custom-titled API call are considered internal and will be hidden from the inspector.
+        titleReported = true;
+      }
       if (stackTraceCollector != null) {
         stack = stackTraceCollector.currentStackTrace();
         if (!stack.isEmpty()) {
diff --git a/playwright/src/test/java/com/microsoft/playwright/TestPageAriaSnapshot.java b/playwright/src/test/java/com/microsoft/playwright/TestPageAriaSnapshot.java
index 3f51c7977..527e0d7b7 100644
--- a/playwright/src/test/java/com/microsoft/playwright/TestPageAriaSnapshot.java
+++ b/playwright/src/test/java/com/microsoft/playwright/TestPageAriaSnapshot.java
@@ -1,6 +1,5 @@
 package com.microsoft.playwright;
 
-import com.microsoft.playwright.Locator.AriaSnapshotOptions;
 import com.microsoft.playwright.junit.FixtureTest;
 import com.microsoft.playwright.junit.UsePlaywright;
 import org.junit.jupiter.api.Test;
@@ -13,8 +12,6 @@
 
 import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
 import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.junit.jupiter.api.Assertions.assertTrue;
 
 @FixtureTest
 @UsePlaywright
diff --git a/playwright/src/test/java/com/microsoft/playwright/TestPdf.java b/playwright/src/test/java/com/microsoft/playwright/TestPdf.java
index 0818d17e4..8b22eb23f 100644
--- a/playwright/src/test/java/com/microsoft/playwright/TestPdf.java
+++ b/playwright/src/test/java/com/microsoft/playwright/TestPdf.java
@@ -60,13 +60,6 @@ void shouldBeAbleToGenerateOutline(@TempDir Path tempDir) throws IOException {
     assertTrue(outlineSize > noOutlineSize, "Unexpected sizes: " + outlineSize + " noOutline: " + noOutlineSize);
   }
 
-  @Test
-  @DisabledIf(value="com.microsoft.playwright.TestBase#isChromium", disabledReason="skip")
-  void shouldThrowInNonChromium() {
-    PlaywrightException e = assertThrows(PlaywrightException.class, () -> page.pdf());
-    assertTrue(e.getMessage().contains("PDF generation is only supported for Headless Chromium"), e.getMessage());
-  }
-
 
   @Test
   @DisabledIf(value="com.microsoft.playwright.TestBase#isChromium", disabledReason="skip")
diff --git a/playwright/src/test/java/com/microsoft/playwright/TestTracing.java b/playwright/src/test/java/com/microsoft/playwright/TestTracing.java
index 938ef80cc..877bbe5f0 100644
--- a/playwright/src/test/java/com/microsoft/playwright/TestTracing.java
+++ b/playwright/src/test/java/com/microsoft/playwright/TestTracing.java
@@ -17,6 +17,7 @@
 package com.microsoft.playwright;
 
 import com.google.gson.Gson;
+import com.google.gson.annotations.SerializedName;
 import com.microsoft.playwright.options.AriaRole;
 import com.microsoft.playwright.options.Location;
 import com.microsoft.playwright.options.MouseButton;
@@ -202,8 +203,8 @@ void traceGroupGroupEnd(@TempDir Path tempDir) throws Exception {
     context.tracing().stop(new Tracing.StopOptions().setPath(traceFile1));
 
     List events = parseTraceEvents(traceFile1);
-    List calls = events.stream().filter(e -> e.title != null).map(e -> e.title).collect(Collectors.toList());
-    assertEquals(asList("outer group", "Page.navigate", "inner group 1", "Frame.click", "inner group 2", "Page.isVisible"), calls);
+    List calls = events.stream().filter(e -> e.renderedTitle() != null).map(e -> e.renderedTitle()).collect(Collectors.toList());
+    assertEquals(asList("outer group", "Frame.goto", "inner group 1", "Frame.click", "inner group 2", "Frame.isVisible"), calls);
   }
 
   @Test
@@ -240,30 +241,30 @@ void shouldTraceVariousAPIs(@TempDir Path tempDir) throws Exception {
     context.tracing().stop(new Tracing.StopOptions().setPath(traceFile1));
 
     List events = parseTraceEvents(traceFile1);
-    List calls = events.stream().filter(e -> e.title != null).map(e -> e.title)
+    List calls = events.stream().filter(e -> e.renderedTitle() != null).map(e -> e.renderedTitle())
         .collect(Collectors.toList());
     assertEquals(asList(
-        "Clock.install",
-        "Page.setContent",
+        "BrowserContext.clockInstall",
+        "Frame.setContent",
         "Frame.click",
         "Frame.click",
-        "Keyboard.type",
-        "Keyboard.press",
-        "Keyboard.down",
-        "Keyboard.insertText",
-        "Keyboard.up",
-        "Mouse.move",
-        "Mouse.down",
-        "Mouse.move",
-        "Mouse.wheel",
-        "Mouse.up",
-        "Clock.fastForward",
-        "Clock.fastForward",
-        "Clock.pauseAt",
-        "Clock.runFor",
-        "Clock.setFixedTime",
-        "Clock.setSystemTime",
-        "Clock.resume",
+        "Page.keyboardType",
+        "Page.keyboardPress",
+        "Page.keyboardDown",
+        "Page.keyboardInsertText",
+        "Page.keyboardUp",
+        "Page.mouseMove",
+        "Page.mouseDown",
+        "Page.mouseMove",
+        "Page.mouseWheel",
+        "Page.mouseUp",
+        "BrowserContext.clockFastForward",
+        "BrowserContext.clockFastForward",
+        "BrowserContext.clockPauseAt",
+        "BrowserContext.clockRunFor",
+        "BrowserContext.clockSetFixedTime",
+        "BrowserContext.clockSetSystemTime",
+        "BrowserContext.clockResume",
         "Frame.click"),
         calls);
   }
@@ -284,20 +285,31 @@ public void shouldNotRecordNetworkActions(@TempDir Path tempDir) throws IOExcept
     context.tracing().stop(new Tracing.StopOptions().setPath(traceFile1));
 
     List events = parseTraceEvents(traceFile1);
-    List calls = events.stream().filter(e -> e.title != null).map(e -> e.title)
+    List calls = events.stream().filter(e -> e.renderedTitle() != null).map(e -> e.renderedTitle())
       .collect(Collectors.toList());
-    assertEquals(asList("Page.navigate"), calls);
+    assertEquals(asList("Frame.goto"), calls);
   }
 
   private static class TraceEvent {
     String type;
     String name;
-    String apiName;
     String title;
+    @SerializedName("class")
+    String clazz;
     String method;
     Double startTime;
     Double endTime;
     String callId;
+
+    String renderedTitle() {
+      if (title != null) {
+        return title;
+      }
+      if (clazz != null && method != null) {
+        return clazz + "." + method;
+      }
+      return null;
+    }
   }
 
   private static List parseTraceEvents(Path traceFile) throws IOException {

From d90550b9e94c23a58a97aa86ec84208520e85123 Mon Sep 17 00:00:00 2001
From: Simon Knott 
Date: Wed, 11 Jun 2025 12:38:56 +0200
Subject: [PATCH 07/28] rewrite test

---
 .../microsoft/playwright/TestDefaultBrowserContext2.java | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/playwright/src/test/java/com/microsoft/playwright/TestDefaultBrowserContext2.java b/playwright/src/test/java/com/microsoft/playwright/TestDefaultBrowserContext2.java
index 7db9e19f1..ea60a2372 100644
--- a/playwright/src/test/java/com/microsoft/playwright/TestDefaultBrowserContext2.java
+++ b/playwright/src/test/java/com/microsoft/playwright/TestDefaultBrowserContext2.java
@@ -313,14 +313,15 @@ void shouldAcceptRelativeUserDataDir(@TempDir Path tmpDir) throws Exception {
   @Test
   void shouldExposeBrowser() {
     Page page = launchPersistent();
-    Browser browser = page.context().browser();
-    assertNotNull(browser);
+    BrowserContext context = page.context();
+    Browser browser = context.browser();
+    assertFalse(browser.version().isEmpty());
     Page page2 = browser.newPage();
     page2.navigate("data:text/html,Title");
     assertEquals("Title", page2.title());
     browser.close();
-    assertEquals(0, page.context().pages().size());
+    assertEquals(0, context.pages().size());
     // Next line should not throw.
-    page.context().close();
+    context.close();
   }
 }

From 951073087c600abcd9f148e1460451d1b80a5b79 Mon Sep 17 00:00:00 2001
From: Simon Knott 
Date: Wed, 11 Jun 2025 12:49:05 +0200
Subject: [PATCH 08/28] remove comment

---
 .../java/com/microsoft/playwright/impl/SharedSelectors.java     | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/SharedSelectors.java b/playwright/src/main/java/com/microsoft/playwright/impl/SharedSelectors.java
index d30f4d13f..2767e3e59 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/SharedSelectors.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/SharedSelectors.java
@@ -30,7 +30,7 @@
 
 public class SharedSelectors extends LoggingSupport implements Selectors {
   protected final List contextsForSelectors = new ArrayList<>();
-  protected final List selectorEngines = new ArrayList<>(); // FIXME: do we need this? is it ever read?
+  protected final List selectorEngines = new ArrayList<>();
 
   String testIdAttributeName = "data-testid";
 

From 2252bea755e40ae847e40a9a29bb1ed1a8d49f19 Mon Sep 17 00:00:00 2001
From: Simon Knott 
Date: Wed, 11 Jun 2025 12:54:44 +0200
Subject: [PATCH 09/28] remove unneeded

---
 .../main/java/com/microsoft/playwright/impl/BrowserImpl.java  | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserImpl.java
index 24d271993..d55cda349 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserImpl.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserImpl.java
@@ -179,10 +179,6 @@ private BrowserContextImpl newContextImpl(NewContextOptions options) {
       context.setBaseUrl(options.baseURL);
     }
     context.initializeHarFromOptions(options);
-    if (launchOptions != null) {
-      context.tracing().setTracesDir(launchOptions.tracesDir);
-    }
-    contexts.add(context);
     return context;
   }
 

From 586149e6f7cbb34d261ad9612600dc0578c322bc Mon Sep 17 00:00:00 2001
From: Simon Knott 
Date: Wed, 11 Jun 2025 13:07:29 +0200
Subject: [PATCH 10/28] fix some more tests

---
 .../playwright/impl/BrowserContextImpl.java     | 17 ++++++++++++-----
 .../microsoft/playwright/impl/BrowserImpl.java  |  3 +++
 .../playwright/TestBrowserContextHar.java       |  2 --
 3 files changed, 15 insertions(+), 7 deletions(-)

diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java
index 4e2212425..599e3dfc4 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java
@@ -534,6 +534,10 @@ private void routeWebSocketImpl(UrlMatcher matcher, Consumer han
   }
 
   private HarContentPolicy routeFromHarUpdateContentPolicyToHarContentPolicy(RouteFromHarUpdateContentPolicy contentPolicy) {
+    if (contentPolicy == null) {
+      return null;
+    }
+
     switch (contentPolicy) {
       case ATTACH:
         return HarContentPolicy.ATTACH;
@@ -559,9 +563,7 @@ void recordIntoHar(PageImpl page, Path har, RouteFromHAROptions options, HarCont
     JsonObject recordHarArgs = new JsonObject();
     recordHarArgs.addProperty("zip", har.endsWith(".zip"));  
     recordHarArgs.addProperty("content", contentPolicy.name().toLowerCase());
-    recordHarArgs.addProperty("mode", options.updateMode == null ?
-      HarMode.MINIMAL.name().toLowerCase() :
-      options.updateMode.name().toLowerCase());
+    recordHarArgs.addProperty("mode", (options.updateMode == null ? HarMode.MINIMAL : options.updateMode).name().toLowerCase());
     addHarUrlFilter(recordHarArgs, options.url);
 
     params.add("options", recordHarArgs);
@@ -881,8 +883,13 @@ protected void initializeHarFromOptions(Browser.NewContextOptions options) {
       return;
     }
 
-    HarContentPolicy defaultPolicy = options.recordHarPath.endsWith(".zip") ? HarContentPolicy.ATTACH : HarContentPolicy.EMBED;
-    HarContentPolicy contentPolicy = options.recordHarContent == null ? (options.recordHarOmitContent ? HarContentPolicy.OMIT : defaultPolicy) : options.recordHarContent;
+    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) {
diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserImpl.java
index d55cda349..d9a9848be 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserImpl.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserImpl.java
@@ -125,6 +125,8 @@ private BrowserContextImpl newContextImpl(NewContextOptions options) {
       // Make a copy so that we can nullify some fields below.
       options = convertType(options, NewContextOptions.class);
     }
+    Object recordHarUrlFilter = options.recordHarUrlFilter;
+    options.recordHarUrlFilter = null;
     if (options.storageStatePath != null) {
       try {
         byte[] bytes = Files.readAllBytes(options.storageStatePath);
@@ -178,6 +180,7 @@ private BrowserContextImpl newContextImpl(NewContextOptions options) {
     if (options.baseURL != null) {
       context.setBaseUrl(options.baseURL);
     }
+    options.recordHarUrlFilter = recordHarUrlFilter;
     context.initializeHarFromOptions(options);
     return context;
   }
diff --git a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextHar.java b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextHar.java
index d46aec960..9cc58d13c 100644
--- a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextHar.java
+++ b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextHar.java
@@ -16,7 +16,6 @@
 
 package com.microsoft.playwright;
 
-import com.microsoft.playwright.options.HarContentPolicy;
 import com.microsoft.playwright.options.HarMode;
 import com.microsoft.playwright.options.HarNotFound;
 import com.microsoft.playwright.options.RouteFromHarUpdateContentPolicy;
@@ -40,7 +39,6 @@
 import static com.microsoft.playwright.Utils.extractZip;
 import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
 import static com.microsoft.playwright.options.HarContentPolicy.ATTACH;
-import static com.microsoft.playwright.options.HarContentPolicy.EMBED;
 import static org.junit.jupiter.api.Assertions.*;
 
 public class TestBrowserContextHar extends TestBase {

From 100118da1de7f6735b818060e801f497a3366b6f Mon Sep 17 00:00:00 2001
From: Simon Knott 
Date: Wed, 11 Jun 2025 13:16:49 +0200
Subject: [PATCH 11/28] convertType

---
 .../com/microsoft/playwright/impl/BrowserTypeImpl.java | 10 +---------
 1 file changed, 1 insertion(+), 9 deletions(-)

diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserTypeImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserTypeImpl.java
index a41fe33a7..3e3ae14b4 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserTypeImpl.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserTypeImpl.java
@@ -203,15 +203,7 @@ private BrowserContextImpl launchPersistentContextImpl(Path userDataDir, LaunchP
     if (options.baseURL != null) {
       context.setBaseUrl(options.baseURL);
     }
-
-    Browser.NewContextOptions harOptions = new Browser.NewContextOptions();
-    harOptions.recordHarContent = options.recordHarContent;
-    harOptions.recordHarMode = options.recordHarMode;
-    harOptions.recordHarOmitContent = options.recordHarOmitContent;
-    harOptions.recordHarPath = options.recordHarPath;
-    harOptions.recordHarUrlFilter = options.recordHarUrlFilter;
-    context.initializeHarFromOptions(harOptions);
-
+    context.initializeHarFromOptions(convertType(options, Browser.NewContextOptions.class));
     context.tracing().setTracesDir(options.tracesDir);
     return context;
   }

From 8c334fa63345c7373e64cfb704fdcb0d8007c624 Mon Sep 17 00:00:00 2001
From: Simon Knott 
Date: Wed, 11 Jun 2025 13:20:36 +0200
Subject: [PATCH 12/28] one more fix

---
 .../java/com/microsoft/playwright/impl/BrowserContextImpl.java  | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java
index 599e3dfc4..b1920095f 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java
@@ -561,7 +561,7 @@ void recordIntoHar(PageImpl page, Path har, RouteFromHAROptions options, HarCont
       params.add("page", page.toProtocolRef());
     }
     JsonObject recordHarArgs = new JsonObject();
-    recordHarArgs.addProperty("zip", har.endsWith(".zip"));  
+    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);

From 442bb4120f76f00951fe3e0beb685acbc381c16c Mon Sep 17 00:00:00 2001
From: Simon Knott 
Date: Wed, 11 Jun 2025 14:00:48 +0200
Subject: [PATCH 13/28] fix other tests

---
 .../java/com/microsoft/playwright/impl/BrowserContextImpl.java   | 1 +
 1 file changed, 1 insertion(+)

diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java
index b1920095f..8321166d5 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java
@@ -863,6 +863,7 @@ protected void handleEvent(String event, JsonObject params) {
   }
 
   void didClose() {
+    closingOrClosed = true;
     if (browser != null) {
       browser.contexts.remove(this);
       browser.browserType.playwright.sharedSelectors.contextsForSelectors.remove(this);

From a8afd685f16a916553ecf5554ed8ca13dda1d05f Mon Sep 17 00:00:00 2001
From: Simon Knott 
Date: Wed, 11 Jun 2025 14:11:58 +0200
Subject: [PATCH 14/28] roll to 1.53.0

---
 README.md                                                    | 2 +-
 .../java/com/microsoft/playwright/impl/SharedSelectors.java  | 4 ++--
 .../com/microsoft/playwright/TestBrowserTypeConnect.java     | 2 --
 .../com/microsoft/playwright/TestDefaultBrowserContext2.java | 2 --
 .../microsoft/playwright/TestElementHandleConvenience.java   | 5 -----
 .../java/com/microsoft/playwright/TestSelectorsRegister.java | 2 --
 scripts/DRIVER_VERSION                                       | 2 +-
 7 files changed, 4 insertions(+), 15 deletions(-)

diff --git a/README.md b/README.md
index 3e32fce0b..2f91c43c8 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@ Playwright is a Java library to automate [Chromium](https://www.chromium.org/Hom
 
 |          | Linux | macOS | Windows |
 |   :---   | :---: | :---: | :---:   |
-| Chromium 138.0.7204.4 | :white_check_mark: | :white_check_mark: | :white_check_mark: |
+| Chromium 138.0.7204.15 | :white_check_mark: | :white_check_mark: | :white_check_mark: |
 | WebKit 18.5 | ✅ | ✅ | ✅ |
 | Firefox 139.0 | :white_check_mark: | :white_check_mark: | :white_check_mark: |
 
diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/SharedSelectors.java b/playwright/src/main/java/com/microsoft/playwright/impl/SharedSelectors.java
index 2767e3e59..5bf9cb26d 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/SharedSelectors.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/SharedSelectors.java
@@ -72,13 +72,13 @@ private void registerImpl(String name, String script, RegisterOptions options) {
     JsonObject engine = new JsonObject();
     engine.addProperty("name", name);
     engine.addProperty("source", script);
-    if (options.contentScript != null) {
+    if (options != null && options.contentScript != null) {
       engine.addProperty("contentScript", options.contentScript);
     }
     for (BrowserContextImpl context : contextsForSelectors) {
       JsonObject params = new JsonObject();
       params.add("selectorEngine", engine);
-      context.sendMessageAsync("registerSelectorEngine", params);
+      context.sendMessage("registerSelectorEngine", params);
     }
     selectorEngines.add(engine);
   }
diff --git a/playwright/src/test/java/com/microsoft/playwright/TestBrowserTypeConnect.java b/playwright/src/test/java/com/microsoft/playwright/TestBrowserTypeConnect.java
index 19705c0ac..618a36604 100644
--- a/playwright/src/test/java/com/microsoft/playwright/TestBrowserTypeConnect.java
+++ b/playwright/src/test/java/com/microsoft/playwright/TestBrowserTypeConnect.java
@@ -20,7 +20,6 @@
 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.Assumptions;
 import org.junit.jupiter.api.BeforeAll;
-import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.io.TempDir;
 
@@ -297,7 +296,6 @@ void shouldEmitCloseEventsOnPagesAndContexts() throws InterruptedException {
     assertEquals(Arrays.asList("page", "context"), events);
   }
 
-  @Disabled("Temporarily skipping until the roll that contains https://github.com/microsoft/playwright/pull/36227")
   @Test
   void shouldRespectSelectors() {
     String mycss = "{\n" +
diff --git a/playwright/src/test/java/com/microsoft/playwright/TestDefaultBrowserContext2.java b/playwright/src/test/java/com/microsoft/playwright/TestDefaultBrowserContext2.java
index ea60a2372..85acbe08a 100644
--- a/playwright/src/test/java/com/microsoft/playwright/TestDefaultBrowserContext2.java
+++ b/playwright/src/test/java/com/microsoft/playwright/TestDefaultBrowserContext2.java
@@ -20,7 +20,6 @@
 import com.microsoft.playwright.options.Geolocation;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Assumptions;
-import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.condition.DisabledIf;
 import org.junit.jupiter.api.io.TempDir;
@@ -227,7 +226,6 @@ void coverageShouldBeMissing() {
     // TODO:
   }
 
-  @Disabled("Temporarily skipping until the roll that contains https://github.com/microsoft/playwright/pull/36227")
   @Test
   void shouldRespectSelectors() {
     Page page = launchPersistent();
diff --git a/playwright/src/test/java/com/microsoft/playwright/TestElementHandleConvenience.java b/playwright/src/test/java/com/microsoft/playwright/TestElementHandleConvenience.java
index 343374cf2..90322262f 100644
--- a/playwright/src/test/java/com/microsoft/playwright/TestElementHandleConvenience.java
+++ b/playwright/src/test/java/com/microsoft/playwright/TestElementHandleConvenience.java
@@ -16,7 +16,6 @@
 
 package com.microsoft.playwright;
 
-import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
 
 import java.util.Collections;
@@ -113,7 +112,6 @@ void textContentShouldWork() {
     assertEquals("Text,\nmore text", page.textContent("#inner"));
   }
 
-  @Disabled("Temporarily skipping until the roll that contains https://github.com/microsoft/playwright/pull/36227")
   @Test
   void textContentShouldBeAtomic() {
     String createDummySelector = "{\n" +
@@ -137,7 +135,6 @@ void textContentShouldBeAtomic() {
     assertEquals("modified", page.evaluate("() => document.querySelector('div').textContent"));
   }
 
-  @Disabled("Temporarily skipping until the roll that contains https://github.com/microsoft/playwright/pull/36227")
   @Test
   void innerTextShouldBeAtomic() {
     String createDummySelector = "{\n" +
@@ -161,7 +158,6 @@ void innerTextShouldBeAtomic() {
     assertEquals("modified", page.evaluate("() => document.querySelector('div').innerText"));
   }
 
-  @Disabled("Temporarily skipping until the roll that contains https://github.com/microsoft/playwright/pull/36227")
   @Test
   void innerHTMLShouldBeAtomic() {
     String createDummySelector = "{\n" +
@@ -185,7 +181,6 @@ void innerHTMLShouldBeAtomic() {
     assertEquals("modified", page.evaluate("() => document.querySelector('div').innerHTML"));
   }
 
-  @Disabled("Temporarily skipping until the roll that contains https://github.com/microsoft/playwright/pull/36227")
   @Test
   void getAttributeShouldBeAtomic() {
     String createDummySelector = "{\n" +
diff --git a/playwright/src/test/java/com/microsoft/playwright/TestSelectorsRegister.java b/playwright/src/test/java/com/microsoft/playwright/TestSelectorsRegister.java
index 9d65b42a3..4d50891ce 100644
--- a/playwright/src/test/java/com/microsoft/playwright/TestSelectorsRegister.java
+++ b/playwright/src/test/java/com/microsoft/playwright/TestSelectorsRegister.java
@@ -16,14 +16,12 @@
 
 package com.microsoft.playwright;
 
-import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
 
 import java.nio.file.Paths;
 
 import static org.junit.jupiter.api.Assertions.*;
 
-@Disabled("Temporarily skipping until the roll that contains https://github.com/microsoft/playwright/pull/36227")
 public class TestSelectorsRegister extends TestBase {
   @Test
   void shouldWork() {
diff --git a/scripts/DRIVER_VERSION b/scripts/DRIVER_VERSION
index efdb8fa68..3f4830156 100644
--- a/scripts/DRIVER_VERSION
+++ b/scripts/DRIVER_VERSION
@@ -1 +1 @@
-1.53.0-beta-1749131401000
+1.53.0

From 5f0e4ef432c4ae108758dadae58435272a60b5f4 Mon Sep 17 00:00:00 2001
From: Simon Knott 
Date: Wed, 11 Jun 2025 15:37:25 +0200
Subject: [PATCH 15/28] tracePath

---
 .../microsoft/playwright/impl/BrowserImpl.java  | 17 ++++++++---------
 1 file changed, 8 insertions(+), 9 deletions(-)

diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserImpl.java
index d9a9848be..c7cc9b8c1 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserImpl.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserImpl.java
@@ -28,7 +28,6 @@
 import java.util.*;
 import java.util.function.Consumer;
 
-import static com.microsoft.playwright.impl.Serialization.addHarUrlFilter;
 import static com.microsoft.playwright.impl.Serialization.gson;
 import static com.microsoft.playwright.impl.Utils.*;
 
@@ -39,7 +38,7 @@ class BrowserImpl extends ChannelOwner implements Browser {
   private boolean isConnected = true;
   BrowserTypeImpl browserType;
   BrowserType.LaunchOptions launchOptions;
-  private Path tracesDir;
+  private Path tracePath;
   String closeReason;
 
   enum EventType {
@@ -199,7 +198,7 @@ private void startTracingImpl(Page page, StartTracingOptions options) {
     if (options == null) {
       options = new StartTracingOptions();
     }
-    tracesDir = options.path;
+    tracePath = options.path;
     JsonObject params = gson().toJsonTree(options).getAsJsonObject();
     if (page != null) {
       params.add("page", ((PageImpl) page).toProtocolRef());
@@ -217,14 +216,14 @@ private byte[] stopTracingImpl() {
     ArtifactImpl artifact = connection.getExistingObject(json.getAsJsonObject().getAsJsonObject("artifact").get("guid").getAsString());
     byte[] data = artifact.readAllBytes();
     artifact.delete();
-    if (tracesDir != null) {
+    if (tracePath != null) {
       try {
-        Files.createDirectories(tracesDir.getParent());
-        Files.write(tracesDir, data);
+        Files.createDirectories(tracePath.getParent());
+        Files.write(tracePath, data);
       } catch (IOException e) {
         throw new PlaywrightException("Failed to write trace file", e);
       } finally {
-        tracesDir = null;
+        tracePath = null;
       }
     }
     return data;
@@ -274,7 +273,7 @@ protected void connectToBrowserType(BrowserTypeImpl browserType, Path tracesDir)
     // Note: when using connect(), `browserType` is different from `this.parent`.
     // This is why browser type is not wired up in the constructor, and instead this separate method is called later on.
     this.browserType = browserType;
-    this.tracesDir = tracesDir;
+    this.tracePath = tracesDir;
 
     for (BrowserContextImpl context : contexts) {
       context.tracing().setTracesDir(tracesDir);
@@ -288,7 +287,7 @@ private void didCreateContext(BrowserContextImpl context) {
     // Note: when connecting to a browser, initial contexts arrive before `_browserType` is set,
     // and will be configured later in `ConnectToBrowserType`.
     if (browserType != null) {
-      context.tracing().setTracesDir(tracesDir);
+      context.tracing().setTracesDir(tracePath);
       browserType.playwright.sharedSelectors.contextsForSelectors.add(context);
     }
   }

From ef6691d3a7e799a07701629c12ed6888203ae89d Mon Sep 17 00:00:00 2001
From: Simon Knott 
Date: Wed, 11 Jun 2025 15:42:18 +0200
Subject: [PATCH 16/28] copy har options

---
 .../com/microsoft/playwright/impl/BrowserImpl.java    | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)

diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserImpl.java
index c7cc9b8c1..dff9971a7 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserImpl.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserImpl.java
@@ -124,8 +124,14 @@ private BrowserContextImpl newContextImpl(NewContextOptions options) {
       // Make a copy so that we can nullify some fields below.
       options = convertType(options, NewContextOptions.class);
     }
-    Object recordHarUrlFilter = options.recordHarUrlFilter;
+
+    NewContextOptions harOptions = Utils.clone(options);
+    options.recordHarContent = null;
+    options.recordHarMode = null;
+    options.recordHarPath = null;
+    options.recordHarOmitContent = null;
     options.recordHarUrlFilter = null;
+    
     if (options.storageStatePath != null) {
       try {
         byte[] bytes = Files.readAllBytes(options.storageStatePath);
@@ -179,8 +185,7 @@ private BrowserContextImpl newContextImpl(NewContextOptions options) {
     if (options.baseURL != null) {
       context.setBaseUrl(options.baseURL);
     }
-    options.recordHarUrlFilter = recordHarUrlFilter;
-    context.initializeHarFromOptions(options);
+    context.initializeHarFromOptions(harOptions);
     return context;
   }
 

From 68e144d4aa1acd1cdc45f3dc0fd0951d0eedaa67 Mon Sep 17 00:00:00 2001
From: Simon Knott 
Date: Wed, 11 Jun 2025 15:44:32 +0200
Subject: [PATCH 17/28] remove har options

---
 .../com/microsoft/playwright/impl/BrowserTypeImpl.java | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserTypeImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserTypeImpl.java
index 3e3ae14b4..dde828fd7 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserTypeImpl.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserTypeImpl.java
@@ -158,6 +158,14 @@ private BrowserContextImpl launchPersistentContextImpl(Path userDataDir, LaunchP
       // Make a copy so that we can nullify some fields below.
       options = convertType(options, LaunchPersistentContextOptions.class);
     }
+
+    Browser.NewContextOptions harOptions = convertType(options, Browser.NewContextOptions.class);
+    options.recordHarContent = null;
+    options.recordHarMode = null;
+    options.recordHarPath = null;
+    options.recordHarOmitContent = null;
+    options.recordHarUrlFilter = null;
+
     options.timeout = TimeoutSettings.launchTimeout(options.timeout);
 
     JsonObject params = gson().toJsonTree(options).getAsJsonObject();
@@ -203,7 +211,7 @@ private BrowserContextImpl launchPersistentContextImpl(Path userDataDir, LaunchP
     if (options.baseURL != null) {
       context.setBaseUrl(options.baseURL);
     }
-    context.initializeHarFromOptions(convertType(options, Browser.NewContextOptions.class));
+    context.initializeHarFromOptions(harOptions);
     context.tracing().setTracesDir(options.tracesDir);
     return context;
   }

From 9048a0f6b6b8f394cf8161fbd3e5f859b704815b Mon Sep 17 00:00:00 2001
From: Simon Knott 
Date: Wed, 11 Jun 2025 15:53:44 +0200
Subject: [PATCH 18/28] read baseUrl and videoDir from initializer

---
 .../playwright/impl/BrowserContextImpl.java   | 23 +++++++++++--------
 .../playwright/impl/BrowserImpl.java          |  4 ----
 .../playwright/impl/BrowserTypeImpl.java      |  4 ----
 3 files changed, 14 insertions(+), 17 deletions(-)

diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java
index 8321166d5..4891ddb0f 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java
@@ -102,7 +102,20 @@ enum EventType {
     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); 
+
+    String url = initializer.getAsJsonObject("options").get("baseUrl").getAsString();
+    if (url != null) {
+      try {
+        this.baseUrl = new URL(url);
+      } catch (MalformedURLException e) {
+      }
+    }
+  
+    JsonObject recordVideo = initializer.getAsJsonObject("options").getAsJsonObject("recordVideo");
+    if (recordVideo != null) {
+      this.videosDir = Path.of(recordVideo.get("dir").getAsString());
+    }
   }
 
   void setRecordHar(Path path, HarContentPolicy policy) {
@@ -111,14 +124,6 @@ void setRecordHar(Path path, HarContentPolicy policy) {
     }
   }
 
-  void setBaseUrl(String spec) {
-    try {
-      this.baseUrl = new URL(spec);
-    } catch (MalformedURLException e) {
-      this.baseUrl = null;
-    }
-  }
-
   String effectiveCloseReason() {
     if (closeReason != null) {
       return closeReason;
diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserImpl.java
index dff9971a7..29d2b51e5 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserImpl.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserImpl.java
@@ -181,10 +181,6 @@ private BrowserContextImpl newContextImpl(NewContextOptions options) {
     params.addProperty("testIdAttributeName", browserType.playwright.sharedSelectors.testIdAttributeName);
     JsonElement result = sendMessage("newContext", params);
     BrowserContextImpl context = connection.getExistingObject(result.getAsJsonObject().getAsJsonObject("context").get("guid").getAsString());
-    context.videosDir = options.recordVideoDir;
-    if (options.baseURL != null) {
-      context.setBaseUrl(options.baseURL);
-    }
     context.initializeHarFromOptions(harOptions);
     return context;
   }
diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserTypeImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserTypeImpl.java
index dde828fd7..946507ac2 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserTypeImpl.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserTypeImpl.java
@@ -207,10 +207,6 @@ private BrowserContextImpl launchPersistentContextImpl(Path userDataDir, LaunchP
     BrowserImpl browser = connection.getExistingObject(json.getAsJsonObject("browser").get("guid").getAsString());
     browser.connectToBrowserType(this, options.tracesDir);
     BrowserContextImpl context = connection.getExistingObject(json.getAsJsonObject("context").get("guid").getAsString());
-    context.videosDir = options.recordVideoDir;
-    if (options.baseURL != null) {
-      context.setBaseUrl(options.baseURL);
-    }
     context.initializeHarFromOptions(harOptions);
     context.tracing().setTracesDir(options.tracesDir);
     return context;

From 4ad0e657c00005999f6419d07a61acb981afff18 Mon Sep 17 00:00:00 2001
From: Simon Knott 
Date: Wed, 11 Jun 2025 15:55:45 +0200
Subject: [PATCH 19/28] assert

---
 .../impl/LocatorAssertionsImpl.java           | 85 +++++++++----------
 .../playwright/impl/LocatorImpl.java          |  4 -
 .../playwright/impl/PageAssertionsImpl.java   |  9 +-
 .../playwright/TestLocatorAssertions.java     |  6 +-
 .../playwright/TestLocatorAssertions2.java    |  2 +-
 .../playwright/TestPlaywrightCreate.java      |  2 -
 6 files changed, 50 insertions(+), 58 deletions(-)

diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/LocatorAssertionsImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/LocatorAssertionsImpl.java
index aa138da7f..616366b06 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/LocatorAssertionsImpl.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/LocatorAssertionsImpl.java
@@ -20,7 +20,6 @@
 import com.microsoft.playwright.assertions.LocatorAssertions;
 import com.microsoft.playwright.options.AriaRole;
 
-import java.lang.reflect.Field;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -44,7 +43,7 @@ private LocatorAssertionsImpl(Locator locator, boolean isNot) {
   public void containsClass(String classname, ContainsClassOptions options) {
     ExpectedTextValue expected = new ExpectedTextValue();
     expected.string = classname;
-    expectImpl("to.contain.class", expected, classname, "Locator expected to contain class", convertType(options, FrameExpectOptions.class), "Expect \"containsClass\"");
+    expectImpl("to.contain.class", expected, classname, "Locator expected to contain class", convertType(options, FrameExpectOptions.class), "Assert \"containsClass\"");
   }
 
   @Override
@@ -55,7 +54,7 @@ public void containsClass(List classnames, ContainsClassOptions options)
       expected.string = text;
       list.add(expected);
     }
-    expectImpl("to.contain.class.array", list, classnames, "Locator expected to contain classes", convertType(options, FrameExpectOptions.class), "Expect \"containsClass\"");
+    expectImpl("to.contain.class.array", list, classnames, "Locator expected to contain classes", convertType(options, FrameExpectOptions.class), "Assert \"containsClass\"");
   }
 
   @Override
@@ -65,7 +64,7 @@ public void containsText(String text, ContainsTextOptions options) {
     expected.ignoreCase = shouldIgnoreCase(options);
     expected.matchSubstring = true;
     expected.normalizeWhiteSpace = true;
-    expectImpl("to.have.text", expected, text, "Locator expected to contain text", convertType(options, FrameExpectOptions.class), "Expect \"containsText\"");
+    expectImpl("to.have.text", expected, text, "Locator expected to contain text", convertType(options, FrameExpectOptions.class), "Assert \"containsText\"");
   }
 
   @Override
@@ -74,7 +73,7 @@ public void containsText(Pattern pattern, ContainsTextOptions options) {
     expected.ignoreCase = shouldIgnoreCase(options);
     expected.matchSubstring = true;
     expected.normalizeWhiteSpace = true;
-    expectImpl("to.have.text", expected, pattern, "Locator expected to contain regex", convertType(options, FrameExpectOptions.class), "Expect \"containsText\"");
+    expectImpl("to.have.text", expected, pattern, "Locator expected to contain regex", convertType(options, FrameExpectOptions.class), "Assert \"containsText\"");
   }
 
   @Override
@@ -88,7 +87,7 @@ public void containsText(String[] strings, ContainsTextOptions options) {
       expected.normalizeWhiteSpace = true;
       list.add(expected);
     }
-    expectImpl("to.contain.text.array", list, strings, "Locator expected to contain text", convertType(options, FrameExpectOptions.class), "Expect \"containsText\"");
+    expectImpl("to.contain.text.array", list, strings, "Locator expected to contain text", convertType(options, FrameExpectOptions.class), "Assert \"containsText\"");
   }
 
   @Override
@@ -101,7 +100,7 @@ public void containsText(Pattern[] patterns, ContainsTextOptions options) {
       expected.normalizeWhiteSpace = true;
       list.add(expected);
     }
-    expectImpl("to.contain.text.array", list, patterns, "Locator expected to contain text", convertType(options, FrameExpectOptions.class), "Expect \"containsText\"");
+    expectImpl("to.contain.text.array", list, patterns, "Locator expected to contain text", convertType(options, FrameExpectOptions.class), "Assert \"containsText\"");
   }
 
   @Override
@@ -110,7 +109,7 @@ public void hasAccessibleDescription(String description, HasAccessibleDescriptio
     expected.string = description;
     expected.ignoreCase = shouldIgnoreCase(options);
     expected.normalizeWhiteSpace = true;
-    expectImpl("to.have.accessible.description", expected, description, "Locator expected to have accessible description", convertType(options, FrameExpectOptions.class), "Expect \"hasAccessibleDescription\"");
+    expectImpl("to.have.accessible.description", expected, description, "Locator expected to have accessible description", convertType(options, FrameExpectOptions.class), "Assert \"hasAccessibleDescription\"");
   }
 
   @Override
@@ -118,7 +117,7 @@ public void hasAccessibleDescription(Pattern pattern, HasAccessibleDescriptionOp
     ExpectedTextValue expected = expectedRegex(pattern);
     expected.ignoreCase = shouldIgnoreCase(options);
     expected.normalizeWhiteSpace = true;
-    expectImpl("to.have.accessible.description", expected, pattern, "Locator expected to have accessible description", convertType(options, FrameExpectOptions.class), "Expect \"hasAccessibleDescription\"");
+    expectImpl("to.have.accessible.description", expected, pattern, "Locator expected to have accessible description", convertType(options, FrameExpectOptions.class), "Assert \"hasAccessibleDescription\"");
   }
 
   @Override
@@ -127,7 +126,7 @@ public void hasAccessibleErrorMessage(String errorMessage, HasAccessibleErrorMes
     expected.string = errorMessage;
     expected.ignoreCase = shouldIgnoreCase(options);
     expected.normalizeWhiteSpace = true;
-    expectImpl("to.have.accessible.error.message", expected, errorMessage, "Locator expected to have accessible error message", convertType(options, FrameExpectOptions.class), "Expect \"hasAccessibleErrorMessage\"");
+    expectImpl("to.have.accessible.error.message", expected, errorMessage, "Locator expected to have accessible error message", convertType(options, FrameExpectOptions.class), "Assert \"hasAccessibleErrorMessage\"");
   }
 
   @Override
@@ -135,7 +134,7 @@ public void hasAccessibleErrorMessage(Pattern pattern, HasAccessibleErrorMessage
     ExpectedTextValue expected = expectedRegex(pattern);
     expected.ignoreCase = shouldIgnoreCase(options);
     expected.normalizeWhiteSpace = true;
-    expectImpl("to.have.accessible.error.message", expected, pattern, "Locator expected to have accessible error message", convertType(options, FrameExpectOptions.class), "Expect \"hasAccessibleErrorMessage\"");
+    expectImpl("to.have.accessible.error.message", expected, pattern, "Locator expected to have accessible error message", convertType(options, FrameExpectOptions.class), "Assert \"hasAccessibleErrorMessage\"");
   }
 
   @Override
@@ -144,7 +143,7 @@ public void hasAccessibleName(String name, HasAccessibleNameOptions options) {
     expected.string = name;
     expected.ignoreCase = shouldIgnoreCase(options);
     expected.normalizeWhiteSpace = true;
-    expectImpl("to.have.accessible.name", expected, name, "Locator expected to have accessible name", convertType(options, FrameExpectOptions.class), "Expect \"hasAccessibleName\"");
+    expectImpl("to.have.accessible.name", expected, name, "Locator expected to have accessible name", convertType(options, FrameExpectOptions.class), "Assert \"hasAccessibleName\"");
   }
 
   @Override
@@ -152,7 +151,7 @@ public void hasAccessibleName(Pattern pattern, HasAccessibleNameOptions options)
     ExpectedTextValue expected = expectedRegex(pattern);
     expected.ignoreCase = shouldIgnoreCase(options);
     expected.normalizeWhiteSpace = true;
-    expectImpl("to.have.accessible.name", expected, pattern, "Locator expected to have accessible name", convertType(options, FrameExpectOptions.class), "Expect \"hasAccessibleName\"");
+    expectImpl("to.have.accessible.name", expected, pattern, "Locator expected to have accessible name", convertType(options, FrameExpectOptions.class), "Assert \"hasAccessibleName\"");
   }
 
   @Override
@@ -180,20 +179,20 @@ private void hasAttribute(String name, ExpectedTextValue expectedText, Object ex
     if (expectedValue instanceof Pattern) {
       message += " matching regex";
     }
-    expectImpl("to.have.attribute.value", expectedText, expectedValue, message, commonOptions, "Expect \"hasAttribute\"");
+    expectImpl("to.have.attribute.value", expectedText, expectedValue, message, commonOptions, "Assert \"hasAttribute\"");
   }
 
   @Override
   public void hasClass(String text, HasClassOptions options) {
     ExpectedTextValue expected = new ExpectedTextValue();
     expected.string = text;
-    expectImpl("to.have.class", expected, text, "Locator expected to have class", convertType(options, FrameExpectOptions.class), "Expect \"hasClass\"");
+    expectImpl("to.have.class", expected, text, "Locator expected to have class", convertType(options, FrameExpectOptions.class), "Assert \"hasClass\"");
   }
 
   @Override
   public void hasClass(Pattern pattern, HasClassOptions options) {
     ExpectedTextValue expected = expectedRegex(pattern);
-    expectImpl("to.have.class", expected, pattern, "Locator expected to have class matching regex", convertType(options, FrameExpectOptions.class), "Expect \"hasClass\"");
+    expectImpl("to.have.class", expected, pattern, "Locator expected to have class matching regex", convertType(options, FrameExpectOptions.class), "Assert \"hasClass\"");
   }
 
   @Override
@@ -204,7 +203,7 @@ public void hasClass(String[] strings, HasClassOptions options) {
       expected.string = text;
       list.add(expected);
     }
-    expectImpl("to.have.class.array", list, strings, "Locator expected to have class", convertType(options, FrameExpectOptions.class), "Expect \"hasClass\"");
+    expectImpl("to.have.class.array", list, strings, "Locator expected to have class", convertType(options, FrameExpectOptions.class), "Assert \"hasClass\"");
   }
 
   @Override
@@ -214,7 +213,7 @@ public void hasClass(Pattern[] patterns, HasClassOptions options) {
       ExpectedTextValue expected = expectedRegex(pattern);
       list.add(expected);
     }
-    expectImpl("to.have.class.array", list, patterns, "Locator expected to have class matching regex", convertType(options, FrameExpectOptions.class), "Expect \"hasClass\"");
+    expectImpl("to.have.class.array", list, patterns, "Locator expected to have class matching regex", convertType(options, FrameExpectOptions.class), "Assert \"hasClass\"");
   }
 
   @Override
@@ -225,7 +224,7 @@ public void hasCount(int count, HasCountOptions options) {
     FrameExpectOptions commonOptions = convertType(options, FrameExpectOptions.class);
     commonOptions.expectedNumber = (double) count;
     List expectedText = null;
-    expectImpl("to.have.count", expectedText, count, "Locator expected to have count", commonOptions, "Expect \"hasCount\"");
+    expectImpl("to.have.count", expectedText, count, "Locator expected to have count", commonOptions, "Assert \"hasCount\"");
   }
 
   @Override
@@ -251,20 +250,20 @@ private void hasCSS(String name, ExpectedTextValue expectedText, Object expected
     if (expectedValue instanceof Pattern) {
       message += " matching regex";
     }
-    expectImpl("to.have.css", expectedText, expectedValue, message, commonOptions, "Expect \"hasCSS\"");
+    expectImpl("to.have.css", expectedText, expectedValue, message, commonOptions, "Assert \"hasCSS\"");
   }
 
   @Override
   public void hasId(String id, HasIdOptions options) {
     ExpectedTextValue expected = new ExpectedTextValue();
     expected.string = id;
-    expectImpl("to.have.id", expected, id, "Locator expected to have ID", convertType(options, FrameExpectOptions.class), "Expect \"hasId\"");
+    expectImpl("to.have.id", expected, id, "Locator expected to have ID", convertType(options, FrameExpectOptions.class), "Assert \"hasId\"");
   }
 
   @Override
   public void hasId(Pattern pattern, HasIdOptions options) {
     ExpectedTextValue expected = expectedRegex(pattern);
-    expectImpl("to.have.id", expected, pattern, "Locator expected to have ID matching regex", convertType(options, FrameExpectOptions.class), "Expect \"hasId\"");
+    expectImpl("to.have.id", expected, pattern, "Locator expected to have ID matching regex", convertType(options, FrameExpectOptions.class), "Assert \"hasId\"");
   }
 
   @Override
@@ -276,14 +275,14 @@ public void hasJSProperty(String name, Object value, HasJSPropertyOptions option
     commonOptions.expressionArg = name;
     commonOptions.expectedValue = serializeArgument(value);
     List list = null;
-    expectImpl("to.have.property", list, value, "Locator expected to have JavaScript property '" + name + "'", commonOptions, "Expect \"hasJSProperty\"");
+    expectImpl("to.have.property", list, value, "Locator expected to have JavaScript property '" + name + "'", commonOptions, "Assert \"hasJSProperty\"");
   }
 
   @Override
   public void hasRole(AriaRole role, HasRoleOptions options) {
     ExpectedTextValue expected = new ExpectedTextValue();
     expected.string = role.toString().toLowerCase();
-    expectImpl("to.have.role", expected, expected.string, "Locator expected to have role", convertType(options, FrameExpectOptions.class), "Expect \"hasRole\"");
+    expectImpl("to.have.role", expected, expected.string, "Locator expected to have role", convertType(options, FrameExpectOptions.class), "Assert \"hasRole\"");
   }
 
   @Override
@@ -293,7 +292,7 @@ public void hasText(String text, HasTextOptions options) {
     expected.ignoreCase = shouldIgnoreCase(options);
     expected.matchSubstring = false;
     expected.normalizeWhiteSpace = true;
-    expectImpl("to.have.text", expected, text, "Locator expected to have text", convertType(options, FrameExpectOptions.class), "Expect \"hasText\"");
+    expectImpl("to.have.text", expected, text, "Locator expected to have text", convertType(options, FrameExpectOptions.class), "Assert \"hasText\"");
   }
 
   @Override
@@ -303,7 +302,7 @@ public void hasText(Pattern pattern, HasTextOptions options) {
     // Just match substring, same as containsText.
     expected.matchSubstring = true;
     expected.normalizeWhiteSpace = true;
-    expectImpl("to.have.text", expected, pattern, "Locator expected to have text matching regex", convertType(options, FrameExpectOptions.class), "Expect \"hasText\"");
+    expectImpl("to.have.text", expected, pattern, "Locator expected to have text matching regex", convertType(options, FrameExpectOptions.class), "Assert \"hasText\"");
   }
 
   @Override
@@ -317,7 +316,7 @@ public void hasText(String[] strings, HasTextOptions options) {
       expected.normalizeWhiteSpace = true;
       list.add(expected);
     }
-    expectImpl("to.have.text.array", list, strings, "Locator expected to have text", convertType(options, FrameExpectOptions.class), "Expect \"hasText\"");
+    expectImpl("to.have.text.array", list, strings, "Locator expected to have text", convertType(options, FrameExpectOptions.class), "Assert \"hasText\"");
   }
 
   @Override
@@ -330,20 +329,20 @@ public void hasText(Pattern[] patterns, HasTextOptions options) {
       expected.normalizeWhiteSpace = true;
       list.add(expected);
     }
-    expectImpl("to.have.text.array", list, patterns, "Locator expected to have text matching regex", convertType(options, FrameExpectOptions.class), "Expect \"hasText\"");
+    expectImpl("to.have.text.array", list, patterns, "Locator expected to have text matching regex", convertType(options, FrameExpectOptions.class), "Assert \"hasText\"");
   }
 
   @Override
   public void hasValue(String value, HasValueOptions options) {
     ExpectedTextValue expected = new ExpectedTextValue();
     expected.string = value;
-    expectImpl("to.have.value", expected, value, "Locator expected to have value", convertType(options, FrameExpectOptions.class), "Expect \"hasValue\"");
+    expectImpl("to.have.value", expected, value, "Locator expected to have value", convertType(options, FrameExpectOptions.class), "Assert \"hasValue\"");
   }
 
   @Override
   public void hasValue(Pattern pattern, HasValueOptions options) {
     ExpectedTextValue expected = expectedRegex(pattern);
-    expectImpl("to.have.value", expected, pattern, "Locator expected to have value matching regex", convertType(options, FrameExpectOptions.class), "Expect \"hasValue\"");
+    expectImpl("to.have.value", expected, pattern, "Locator expected to have value matching regex", convertType(options, FrameExpectOptions.class), "Assert \"hasValue\"");
   }
 
   @Override
@@ -354,7 +353,7 @@ public void hasValues(String[] values, HasValuesOptions options) {
       expected.string = text;
       list.add(expected);
     }
-    expectImpl("to.have.values", list, values, "Locator expected to have values", convertType(options, FrameExpectOptions.class), "Expect \"hasValues\"");
+    expectImpl("to.have.values", list, values, "Locator expected to have values", convertType(options, FrameExpectOptions.class), "Assert \"hasValues\"");
   }
 
   @Override
@@ -365,7 +364,7 @@ public void hasValues(Pattern[] patterns, HasValuesOptions options) {
       expected.matchSubstring = true;
       list.add(expected);
     }
-    expectImpl("to.have.values", list, patterns, "Locator expected to have values matching regex", convertType(options, FrameExpectOptions.class), "Expect \"hasValues\"");
+    expectImpl("to.have.values", list, patterns, "Locator expected to have values matching regex", convertType(options, FrameExpectOptions.class), "Assert \"hasValues\"");
   }
 
   @Override
@@ -375,7 +374,7 @@ public void matchesAriaSnapshot(String expected, MatchesAriaSnapshotOptions snap
     }
     FrameExpectOptions options = convertType(snapshotOptions, FrameExpectOptions.class);
     options.expectedValue = serializeArgument(expected);
-    expectImpl("to.match.aria", options, expected,"Locator expected to match Aria snapshot", "Expect \"matchesAriaSnapshot\"");
+    expectImpl("to.match.aria", options, expected,"Locator expected to match Aria snapshot", "Assert \"matchesAriaSnapshot\"");
   }
 
   @Override
@@ -403,12 +402,12 @@ public void isChecked(IsCheckedOptions options) {
     String message = "Locator expected to be";
     FrameExpectOptions expectOptions = convertType(options, FrameExpectOptions.class);
     expectOptions.expectedValue = serializeArgument(expectedValue);
-    expectImpl("to.be.checked", expectOptions, expected, message, "Expect \"isChecked\"");
+    expectImpl("to.be.checked", expectOptions, expected, message, "Assert \"isChecked\"");
   }
 
   @Override
   public void isDisabled(IsDisabledOptions options) {
-    expectTrue("to.be.disabled", "Locator expected to be disabled", convertType(options, FrameExpectOptions.class), "Expect \"isDisabled\"");
+    expectTrue("to.be.disabled", "Locator expected to be disabled", convertType(options, FrameExpectOptions.class), "Assert \"isDisabled\"");
   }
 
   @Override
@@ -416,12 +415,12 @@ public void isEditable(IsEditableOptions options) {
     FrameExpectOptions frameOptions = convertType(options, FrameExpectOptions.class);
     boolean editable = options == null || options.editable == null || options.editable == true;
     String message = "Locator expected to be " + (editable ? "editable" : "readonly");
-    expectTrue(editable ? "to.be.editable" : "to.be.readonly", message, frameOptions, "Expect \"isEditable\"");
+    expectTrue(editable ? "to.be.editable" : "to.be.readonly", message, frameOptions, "Assert \"isEditable\"");
   }
 
   @Override
   public void isEmpty(IsEmptyOptions options) {
-    expectTrue("to.be.empty", "Locator expected to be empty", convertType(options, FrameExpectOptions.class), "Expect \"isEmpty\"");
+    expectTrue("to.be.empty", "Locator expected to be empty", convertType(options, FrameExpectOptions.class), "Assert \"isEmpty\"");
   }
 
   @Override
@@ -429,17 +428,17 @@ public void isEnabled(IsEnabledOptions options) {
     FrameExpectOptions frameOptions = convertType(options, FrameExpectOptions.class);
     boolean enabled = options == null || options.enabled == null || options.enabled == true;
     String message = "Locator expected to be " + (enabled ? "enabled" : "disabled");
-    expectTrue(enabled ? "to.be.enabled" : "to.be.disabled", message, frameOptions, "Expect \"isEnabled\"");
+    expectTrue(enabled ? "to.be.enabled" : "to.be.disabled", message, frameOptions, "Assert \"isEnabled\"");
   }
 
   @Override
   public void isFocused(IsFocusedOptions options) {
-    expectTrue("to.be.focused", "Locator expected to be focused", convertType(options, FrameExpectOptions.class), "Expect \"isFocused\"");
+    expectTrue("to.be.focused", "Locator expected to be focused", convertType(options, FrameExpectOptions.class), "Assert \"isFocused\"");
   }
 
   @Override
   public void isHidden(IsHiddenOptions options) {
-    expectTrue("to.be.hidden", "Locator expected to be hidden", convertType(options, FrameExpectOptions.class), "Expect \"isHidden\"");
+    expectTrue("to.be.hidden", "Locator expected to be hidden", convertType(options, FrameExpectOptions.class), "Assert \"isHidden\"");
   }
 
   @Override
@@ -448,7 +447,7 @@ public void isInViewport(IsInViewportOptions options) {
     if (options != null && options.ratio != null) {
       expectOptions.expectedNumber = options.ratio;
     }
-    expectTrue("to.be.in.viewport", "Locator expected to be in viewport",  expectOptions, "Expect \"isInViewport\"");
+    expectTrue("to.be.in.viewport", "Locator expected to be in viewport",  expectOptions, "Assert \"isInViewport\"");
   }
 
   @Override
@@ -456,7 +455,7 @@ public void isVisible(IsVisibleOptions options) {
     FrameExpectOptions frameOptions = convertType(options, FrameExpectOptions.class);
     boolean visible = options == null || options.visible == null || options.visible == true;
     String message = "Locator expected to be " + (visible ? "visible" : "hidden");
-    expectTrue(visible ? "to.be.visible" : "to.be.hidden", message, frameOptions, "Expect \"isVisible\"");
+    expectTrue(visible ? "to.be.visible" : "to.be.hidden", message, frameOptions, "Assert \"isVisible\"");
   }
 
   private void expectTrue(String expression, String message, FrameExpectOptions options, String title) {
@@ -474,6 +473,6 @@ public void isAttached(IsAttachedOptions options) {
     FrameExpectOptions frameOptions = convertType(options, FrameExpectOptions.class);
     boolean attached = options == null || options.attached == null || options.attached == true;
     String message = "Locator expected to be " + (attached ? "attached" : "detached");
-    expectTrue(attached ? "to.be.attached" : "to.be.detached", message, frameOptions, "Expect \"isAttached\"");
+    expectTrue(attached ? "to.be.attached" : "to.be.detached", message, frameOptions, "Assert \"isAttached\"");
   }
 }
diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/LocatorImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/LocatorImpl.java
index 7d0099de8..33bddbdb5 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/LocatorImpl.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/LocatorImpl.java
@@ -67,10 +67,6 @@ private LocatorImpl(FrameImpl frame, String selector, LocatorOptions options, Bo
     this.selector = selector;
   }
 
-  private static String escapeWithQuotes(String text) {
-    return gson().toJson(text);
-  }
-
   private  R withElement(BiFunction callback, O options, String title) {
     return frame.withTitle(title, () -> {
       ElementHandleOptions handleOptions = convertType(options, ElementHandleOptions.class);
diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/PageAssertionsImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/PageAssertionsImpl.java
index 58778e6cb..d9040ec7e 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/PageAssertionsImpl.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/PageAssertionsImpl.java
@@ -21,7 +21,6 @@
 
 import java.util.regex.Pattern;
 
-import static com.microsoft.playwright.impl.LocatorAssertionsImpl.shouldIgnoreCase;
 import static com.microsoft.playwright.impl.UrlMatcher.resolveUrl;
 import static com.microsoft.playwright.impl.Utils.convertType;
 
@@ -42,13 +41,13 @@ 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), "Expect \"hasTitle\"");
+    expectImpl("to.have.title", expected, title, "Page title expected to be", convertType(options, FrameExpectOptions.class), "Assert \"hasTitle\"");
   }
 
   @Override
   public void hasTitle(Pattern pattern, HasTitleOptions options) {
     ExpectedTextValue expected = expectedRegex(pattern);
-    expectImpl("to.have.title", expected, pattern, "Page title expected to match regex", convertType(options, FrameExpectOptions.class), "Expect \"hasTitle\"");
+    expectImpl("to.have.title", expected, pattern, "Page title expected to match regex", convertType(options, FrameExpectOptions.class), "Assert \"hasTitle\"");
   }
 
   @Override
@@ -59,13 +58,13 @@ public void hasURL(String url, HasURLOptions options) {
     }
     expected.string = url;
     expected.ignoreCase = shouldIgnoreCase(options);
-    expectImpl("to.have.url", expected, url, "Page URL expected to be", convertType(options, FrameExpectOptions.class), "Expect \"hasURL\"");
+    expectImpl("to.have.url", expected, url, "Page URL expected to be", convertType(options, FrameExpectOptions.class), "Assert \"hasURL\"");
   }
 
   @Override
   public void hasURL(Pattern pattern, HasURLOptions options) {
     ExpectedTextValue expected = expectedRegex(pattern);
-    expectImpl("to.have.url", expected, pattern, "Page URL expected to match regex", convertType(options, FrameExpectOptions.class), "Expect \"hasURL\"");
+    expectImpl("to.have.url", expected, pattern, "Page URL expected to match regex", convertType(options, FrameExpectOptions.class), "Assert \"hasURL\"");
   }
 
   @Override
diff --git a/playwright/src/test/java/com/microsoft/playwright/TestLocatorAssertions.java b/playwright/src/test/java/com/microsoft/playwright/TestLocatorAssertions.java
index de7fb0897..dd048c9a3 100644
--- a/playwright/src/test/java/com/microsoft/playwright/TestLocatorAssertions.java
+++ b/playwright/src/test/java/com/microsoft/playwright/TestLocatorAssertions.java
@@ -1070,7 +1070,7 @@ void defaultTimeoutHasTextFail() {
     Locator locator = page.locator("div");
     PlaywrightAssertions.setDefaultAssertionTimeout(1000);
     AssertionFailedError exception = assertThrows(AssertionFailedError.class, () -> assertThat(locator).hasText("foo"));
-    assertTrue(exception.getMessage().contains("Expect \"hasText\" with timeout 1000ms"), exception.getMessage());
+    assertTrue(exception.getMessage().contains("Assert \"hasText\" with timeout 1000ms"), exception.getMessage());
     // Restore default.
     PlaywrightAssertions.setDefaultAssertionTimeout(5_000);
   }
@@ -1119,7 +1119,7 @@ void containsClassFail() {
     AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> {
       assertThat(page.locator("div")).containsClass("does-not-exist", new ContainsClassOptions().setTimeout(1000));
     });
-    assertTrue(e.getMessage().contains("Expect \"containsClass\" with timeout 1000ms"), e.getMessage());
+    assertTrue(e.getMessage().contains("Assert \"containsClass\" with timeout 1000ms"), e.getMessage());
   }
 
   @Test
@@ -1137,6 +1137,6 @@ void containsClassFailWithArray() {
     AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> {
       assertThat(page.locator("div")).containsClass(asList("foo", "bar", "baz"), new ContainsClassOptions().setTimeout(1000));
     });
-    assertTrue(e.getMessage().contains("Expect \"containsClass\" with timeout 1000ms"), e.getMessage());
+    assertTrue(e.getMessage().contains("Assert \"containsClass\" with timeout 1000ms"), e.getMessage());
   }
 }
diff --git a/playwright/src/test/java/com/microsoft/playwright/TestLocatorAssertions2.java b/playwright/src/test/java/com/microsoft/playwright/TestLocatorAssertions2.java
index f5adc03b1..e24b4c7b8 100644
--- a/playwright/src/test/java/com/microsoft/playwright/TestLocatorAssertions2.java
+++ b/playwright/src/test/java/com/microsoft/playwright/TestLocatorAssertions2.java
@@ -218,6 +218,6 @@ void toBeEditableFailWithIndeterminateTrue() {
     AssertionFailedError e = assertThrows(AssertionFailedError.class, () ->
       assertThat(locator).isChecked(new LocatorAssertions.IsCheckedOptions().setIndeterminate(true).setTimeout(1000)));
     // TODO: should be "assertThat().isChecked() with timeout 1000ms"
-    assertTrue(e.getMessage().contains("Expect \"isChecked\" with timeout 1000ms"), e.getMessage());
+    assertTrue(e.getMessage().contains("Assert \"isChecked\" with timeout 1000ms"), e.getMessage());
   }
 }
diff --git a/playwright/src/test/java/com/microsoft/playwright/TestPlaywrightCreate.java b/playwright/src/test/java/com/microsoft/playwright/TestPlaywrightCreate.java
index eb809e8b9..0085fa860 100644
--- a/playwright/src/test/java/com/microsoft/playwright/TestPlaywrightCreate.java
+++ b/playwright/src/test/java/com/microsoft/playwright/TestPlaywrightCreate.java
@@ -17,12 +17,10 @@
 package com.microsoft.playwright;
 
 import com.microsoft.playwright.impl.PlaywrightImpl;
-import com.microsoft.playwright.impl.driver.Driver;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.io.TempDir;
 
 import java.io.IOException;
-import java.lang.reflect.Field;
 import java.nio.file.DirectoryStream;
 import java.nio.file.Files;
 import java.nio.file.Path;

From be0e846d25b51fd0d81bd32fa80e13a9fcff6143 Mon Sep 17 00:00:00 2001
From: Simon Knott 
Date: Wed, 11 Jun 2025 15:57:46 +0200
Subject: [PATCH 20/28] rename sharedSelectors to selectorsimpl

---
 .../com/microsoft/playwright/impl/BrowserContextImpl.java | 2 +-
 .../java/com/microsoft/playwright/impl/BrowserImpl.java   | 8 ++++----
 .../com/microsoft/playwright/impl/BrowserTypeImpl.java    | 6 +++---
 .../java/com/microsoft/playwright/impl/LocatorUtils.java  | 3 +--
 .../com/microsoft/playwright/impl/PlaywrightImpl.java     | 6 +++---
 .../impl/{SharedSelectors.java => SelectorsImpl.java}     | 2 +-
 6 files changed, 13 insertions(+), 14 deletions(-)
 rename playwright/src/main/java/com/microsoft/playwright/impl/{SharedSelectors.java => SelectorsImpl.java} (97%)

diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java
index 4891ddb0f..35d2ade58 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java
@@ -871,7 +871,7 @@ void didClose() {
     closingOrClosed = true;
     if (browser != null) {
       browser.contexts.remove(this);
-      browser.browserType.playwright.sharedSelectors.contextsForSelectors.remove(this);
+      browser.browserType.playwright.selectors.contextsForSelectors.remove(this);
     }
     listeners.notify(EventType.CLOSE, this);
   }
diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserImpl.java
index 29d2b51e5..45e25587e 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserImpl.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserImpl.java
@@ -177,8 +177,8 @@ private BrowserContextImpl newContextImpl(NewContextOptions options) {
     if (options.acceptDownloads != null) {
       params.addProperty("acceptDownloads", options.acceptDownloads ? "accept" : "deny");
     }
-    params.add("selectorEngines", gson().toJsonTree(browserType.playwright.sharedSelectors.selectorEngines));
-    params.addProperty("testIdAttributeName", browserType.playwright.sharedSelectors.testIdAttributeName);
+    params.add("selectorEngines", gson().toJsonTree(browserType.playwright.selectors.selectorEngines));
+    params.addProperty("testIdAttributeName", browserType.playwright.selectors.testIdAttributeName);
     JsonElement result = sendMessage("newContext", params);
     BrowserContextImpl context = connection.getExistingObject(result.getAsJsonObject().getAsJsonObject("context").get("guid").getAsString());
     context.initializeHarFromOptions(harOptions);
@@ -278,7 +278,7 @@ protected void connectToBrowserType(BrowserTypeImpl browserType, Path tracesDir)
 
     for (BrowserContextImpl context : contexts) {
       context.tracing().setTracesDir(tracesDir);
-      browserType.playwright.sharedSelectors.contextsForSelectors.add(context);
+      browserType.playwright.selectors.contextsForSelectors.add(context);
     }
   }
 
@@ -289,7 +289,7 @@ private void didCreateContext(BrowserContextImpl context) {
     // and will be configured later in `ConnectToBrowserType`.
     if (browserType != null) {
       context.tracing().setTracesDir(tracePath);
-      browserType.playwright.sharedSelectors.contextsForSelectors.add(context);
+      browserType.playwright.selectors.contextsForSelectors.add(context);
     }
   }
 
diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserTypeImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserTypeImpl.java
index 946507ac2..f8e4cd5d6 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserTypeImpl.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserTypeImpl.java
@@ -101,7 +101,7 @@ private Browser connectImpl(String wsEndpoint, ConnectOptions options) {
       }
       throw new PlaywrightException("Malformed endpoint. Did you use launchServer method?");
     }
-    playwright.sharedSelectors = this.playwright.sharedSelectors;
+    playwright.selectors = this.playwright.selectors;
     BrowserImpl browser = connection.getExistingObject(playwright.initializer.getAsJsonObject("preLaunchedBrowser").get("guid").getAsString());
     browser.isConnectedOverWebSocket = true;
     browser.connectToBrowserType(this, null);
@@ -201,8 +201,8 @@ private BrowserContextImpl launchPersistentContextImpl(Path userDataDir, LaunchP
     if (options.acceptDownloads != null) {
       params.addProperty("acceptDownloads", options.acceptDownloads ? "accept" : "deny");
     }
-    params.add("selectorEngines", gson().toJsonTree(playwright.sharedSelectors.selectorEngines));
-    params.addProperty("testIdAttributeName", playwright.sharedSelectors.testIdAttributeName);
+    params.add("selectorEngines", gson().toJsonTree(playwright.selectors.selectorEngines));
+    params.addProperty("testIdAttributeName", playwright.selectors.testIdAttributeName);
     JsonObject json = sendMessage("launchPersistentContext", params).getAsJsonObject();
     BrowserImpl browser = connection.getExistingObject(json.getAsJsonObject("browser").get("guid").getAsString());
     browser.connectToBrowserType(this, options.tracesDir);
diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/LocatorUtils.java b/playwright/src/main/java/com/microsoft/playwright/impl/LocatorUtils.java
index 08e45028c..cbfb2c7f7 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/LocatorUtils.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/LocatorUtils.java
@@ -44,8 +44,7 @@ static String describeSelector(String description) {
   }
 
   static String getByTestIdSelector(Object testId, PlaywrightImpl playwright) {
-    String testIdAttributeName = ((SharedSelectors) playwright.selectors()).testIdAttributeName;
-    return getByAttributeTextSelector(testIdAttributeName, testId, true);
+    return getByAttributeTextSelector(playwright.selectors.testIdAttributeName, testId, true);
   }
 
   static String getByAltTextSelector(Object text, Locator.GetByAltTextOptions options) {
diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/PlaywrightImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/PlaywrightImpl.java
index 2749cbeca..9cf96db39 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/PlaywrightImpl.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/PlaywrightImpl.java
@@ -62,7 +62,7 @@ public static PlaywrightImpl createImpl(CreateOptions options, boolean forceNewD
   private final BrowserTypeImpl firefox;
   private final BrowserTypeImpl webkit;
   private final APIRequestImpl apiRequest;
-  protected SharedSelectors sharedSelectors;
+  protected SelectorsImpl selectors;
 
   PlaywrightImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
     super(parent, type, guid, initializer);
@@ -74,7 +74,7 @@ public static PlaywrightImpl createImpl(CreateOptions options, boolean forceNewD
     firefox.playwright = this;
     webkit.playwright = this;
 
-    sharedSelectors = new SharedSelectors();
+    selectors = new SelectorsImpl();
     apiRequest = new APIRequestImpl(this);
   }
 
@@ -108,7 +108,7 @@ public BrowserTypeImpl webkit() {
 
   @Override
   public Selectors selectors() {
-    return sharedSelectors;
+    return selectors;
   }
 
   @Override
diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/SharedSelectors.java b/playwright/src/main/java/com/microsoft/playwright/impl/SelectorsImpl.java
similarity index 97%
rename from playwright/src/main/java/com/microsoft/playwright/impl/SharedSelectors.java
rename to playwright/src/main/java/com/microsoft/playwright/impl/SelectorsImpl.java
index 5bf9cb26d..a150afe3a 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/SharedSelectors.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/SelectorsImpl.java
@@ -28,7 +28,7 @@
 
 import static java.nio.charset.StandardCharsets.UTF_8;
 
-public class SharedSelectors extends LoggingSupport implements Selectors {
+public class SelectorsImpl extends LoggingSupport implements Selectors {
   protected final List contextsForSelectors = new ArrayList<>();
   protected final List selectorEngines = new ArrayList<>();
 

From 108f5c67d9c187f0c0988e49d7b464e7ad26ba05 Mon Sep 17 00:00:00 2001
From: Simon Knott 
Date: Wed, 11 Jun 2025 16:01:07 +0200
Subject: [PATCH 21/28] use Paths.get

---
 .../java/com/microsoft/playwright/impl/BrowserContextImpl.java  | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java
index 35d2ade58..91e3bfb2b 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java
@@ -114,7 +114,7 @@ enum EventType {
   
     JsonObject recordVideo = initializer.getAsJsonObject("options").getAsJsonObject("recordVideo");
     if (recordVideo != null) {
-      this.videosDir = Path.of(recordVideo.get("dir").getAsString());
+      this.videosDir = Paths.get(recordVideo.get("dir").getAsString());
     }
   }
 

From 36d36e5df3dd8dc0f9d2eef454d32b737246bf5f Mon Sep 17 00:00:00 2001
From: Simon Knott 
Date: Wed, 11 Jun 2025 16:10:21 +0200
Subject: [PATCH 22/28] fix json reading

---
 .../com/microsoft/playwright/impl/BrowserContextImpl.java     | 4 ++--
 .../java/com/microsoft/playwright/TestPageEmulateMedia.java   | 1 -
 2 files changed, 2 insertions(+), 3 deletions(-)

diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java
index 91e3bfb2b..7b5dfce1e 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java
@@ -104,10 +104,10 @@ enum EventType {
     clock = new ClockImpl(this);
     closePromise = new WaitableEvent<>(listeners, EventType.CLOSE); 
 
-    String url = initializer.getAsJsonObject("options").get("baseUrl").getAsString();
+    JsonElement url = initializer.getAsJsonObject("options").get("baseUrl");
     if (url != null) {
       try {
-        this.baseUrl = new URL(url);
+        this.baseUrl = new URL(url.getAsString());
       } catch (MalformedURLException e) {
       }
     }
diff --git a/playwright/src/test/java/com/microsoft/playwright/TestPageEmulateMedia.java b/playwright/src/test/java/com/microsoft/playwright/TestPageEmulateMedia.java
index 28facfd14..0d988b7fa 100644
--- a/playwright/src/test/java/com/microsoft/playwright/TestPageEmulateMedia.java
+++ b/playwright/src/test/java/com/microsoft/playwright/TestPageEmulateMedia.java
@@ -29,7 +29,6 @@
 import static com.microsoft.playwright.options.Media.PRINT;
 import static com.microsoft.playwright.Utils.attachFrame;
 import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertTrue;
 
 public class TestPageEmulateMedia extends TestBase {
   @Test

From 1291ab6e3394554bb0ffde8157a40c630b6be075 Mon Sep 17 00:00:00 2001
From: Simon Knott 
Date: Wed, 11 Jun 2025 16:21:51 +0200
Subject: [PATCH 23/28] good old casing typo

---
 .../java/com/microsoft/playwright/impl/BrowserContextImpl.java  | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java
index 7b5dfce1e..2fe3b1ff4 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java
@@ -104,7 +104,7 @@ enum EventType {
     clock = new ClockImpl(this);
     closePromise = new WaitableEvent<>(listeners, EventType.CLOSE); 
 
-    JsonElement url = initializer.getAsJsonObject("options").get("baseUrl");
+    JsonElement url = initializer.getAsJsonObject("options").get("baseURL");
     if (url != null) {
       try {
         this.baseUrl = new URL(url.getAsString());

From 741d262d2173c2527d8b9885a9cd68fd7993d8f7 Mon Sep 17 00:00:00 2001
From: Simon Knott 
Date: Thu, 12 Jun 2025 09:49:54 +0200
Subject: [PATCH 24/28] read lazily from initializer

---
 .../playwright/impl/BrowserContextImpl.java   | 28 +++++++++++--------
 1 file changed, 16 insertions(+), 12 deletions(-)

diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java
index 2fe3b1ff4..427115438 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java
@@ -69,8 +69,6 @@ private static final Map eventSubscriptions() {
   }
   private final ListenerCollection listeners = new ListenerCollection<>(eventSubscriptions(), this);
   final TimeoutSettings timeoutSettings = new TimeoutSettings();
-  Path videosDir;
-  URL baseUrl;
   final Map harRecorders = new HashMap<>();
 
   static class HarRecorder {
@@ -103,19 +101,25 @@ enum EventType {
     request.timeoutSettings = timeoutSettings;
     clock = new ClockImpl(this);
     closePromise = new WaitableEvent<>(listeners, EventType.CLOSE); 
+  }
 
+  Path videosDir() {
+    JsonObject recordVideo = initializer.getAsJsonObject("options").getAsJsonObject("recordVideo");
+    if (recordVideo == null) {
+      return null;
+    }
+    return Paths.get(recordVideo.get("dir").getAsString());
+  }
+
+  URL baseUrl() {
     JsonElement url = initializer.getAsJsonObject("options").get("baseURL");
     if (url != null) {
       try {
-        this.baseUrl = new URL(url.getAsString());
+        return new URL(url.getAsString());
       } catch (MalformedURLException e) {
       }
     }
-  
-    JsonObject recordVideo = initializer.getAsJsonObject("options").getAsJsonObject("recordVideo");
-    if (recordVideo != null) {
-      this.videosDir = Paths.get(recordVideo.get("dir").getAsString());
-    }
+    return null;
   }
 
   void setRecordHar(Path path, HarContentPolicy policy) {
@@ -481,7 +485,7 @@ public APIRequestContextImpl request() {
 
   @Override
   public void route(String url, Consumer 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
@@ -503,7 +507,7 @@ public void routeFromHAR(Path har, RouteFromHAROptions 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);
@@ -518,7 +522,7 @@ private void route(UrlMatcher matcher, Consumer handler, RouteOptions opt
 
   @Override
   public void routeWebSocket(String url, Consumer handler) {
-    routeWebSocketImpl(UrlMatcher.forGlob(baseUrl, url, this.connection.localUtils, true), handler);
+    routeWebSocketImpl(UrlMatcher.forGlob(baseUrl(), url, this.connection.localUtils, true), handler);
   }
 
   @Override
@@ -658,7 +662,7 @@ public void unrouteAll() {
 
   @Override
   public void unroute(String url, Consumer handler) {
-    unroute(UrlMatcher.forGlob(this.baseUrl, url, this.connection.localUtils, false), handler);
+    unroute(UrlMatcher.forGlob(this.baseUrl(), url, this.connection.localUtils, false), handler);
   }
 
   @Override

From 27cd744b15c720b13197325ff0d0408fa82c3bb8 Mon Sep 17 00:00:00 2001
From: Simon Knott 
Date: Thu, 12 Jun 2025 09:51:15 +0200
Subject: [PATCH 25/28] convert enums

---
 .../playwright/impl/BrowserContextImpl.java     | 17 +----------------
 1 file changed, 1 insertion(+), 16 deletions(-)

diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java
index 427115438..98454ac11 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java
@@ -542,24 +542,9 @@ private void routeWebSocketImpl(UrlMatcher matcher, Consumer han
     });
   }
 
-  private HarContentPolicy routeFromHarUpdateContentPolicyToHarContentPolicy(RouteFromHarUpdateContentPolicy contentPolicy) {
-    if (contentPolicy == null) {
-      return null;
-    }
-
-    switch (contentPolicy) {
-      case ATTACH:
-        return HarContentPolicy.ATTACH;
-      case EMBED:
-        return HarContentPolicy.EMBED;
-      default:
-        return null;
-    }
-  }
-
   void recordIntoHar(PageImpl page, Path har, RouteFromHAROptions options, HarContentPolicy contentPolicy) {
     if (contentPolicy == null) {
-      contentPolicy = routeFromHarUpdateContentPolicyToHarContentPolicy(options.updateContent);
+      contentPolicy = Utils.convertType(options.updateContent, HarContentPolicy.class);;
     }
     if (contentPolicy == null) {
       contentPolicy = HarContentPolicy.ATTACH;

From 94489520c8ef05ad4c10ed5bb755a4de31bd7633 Mon Sep 17 00:00:00 2001
From: Simon Knott 
Date: Thu, 12 Jun 2025 09:55:05 +0200
Subject: [PATCH 26/28] add checks back in

---
 .../playwright/impl/BrowserContextImpl.java          | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java
index 98454ac11..7c698a0f0 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java
@@ -875,6 +875,18 @@ WritableStream createTempFile(String name, long lastModifiedMs) {
 
   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;
     }
 

From ff6368bf3ff40a7149391ac6a0e85685d42771c9 Mon Sep 17 00:00:00 2001
From: Simon Knott 
Date: Thu, 12 Jun 2025 10:30:17 +0200
Subject: [PATCH 27/28] remove unused

---
 .../com/microsoft/playwright/impl/BrowserContextImpl.java | 8 +-------
 1 file changed, 1 insertion(+), 7 deletions(-)

diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java
index 7c698a0f0..360763c5c 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java
@@ -121,13 +121,7 @@ URL baseUrl() {
     }
     return null;
   }
-
-  void setRecordHar(Path path, HarContentPolicy policy) {
-    if (path != null) {
-      harRecorders.put("", new HarRecorder(path, policy));
-    }
-  }
-
+  
   String effectiveCloseReason() {
     if (closeReason != null) {
       return closeReason;

From 9f941f9c0915cfdda906772485a1da73b156b039 Mon Sep 17 00:00:00 2001
From: Simon Knott 
Date: Thu, 12 Jun 2025 10:31:35 +0200
Subject: [PATCH 28/28] fix compilation

---
 .../microsoft/playwright/impl/FrameImpl.java   |  4 ++--
 .../playwright/impl/PageAssertionsImpl.java    |  4 ++--
 .../microsoft/playwright/impl/PageImpl.java    | 18 +++++++++---------
 3 files changed, 13 insertions(+), 13 deletions(-)

diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/FrameImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/FrameImpl.java
index d31a0949a..e798fda7c 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/FrameImpl.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/FrameImpl.java
@@ -1061,7 +1061,7 @@ private Response waitForNavigationImpl(Logger logger, Runnable code, WaitForNavi
 
     List> waitables = new ArrayList<>();
     if (matcher == null) {
-      matcher = UrlMatcher.forOneOf(page.context().baseUrl, options.url, this.connection.localUtils, false);
+      matcher = UrlMatcher.forOneOf(page.context().baseUrl(), options.url, this.connection.localUtils, false);
     }
     logger.log("waiting for navigation " + matcher);
     waitables.add(new WaitForNavigationHelper(matcher, options.waitUntil, logger));
@@ -1109,7 +1109,7 @@ void waitForTimeoutImpl(double timeout) {
 
   @Override
   public void waitForURL(String url, WaitForURLOptions options) {
-    waitForURL(UrlMatcher.forGlob(page.context().baseUrl, url, this.connection.localUtils, false), options);
+    waitForURL(UrlMatcher.forGlob(page.context().baseUrl(), url, this.connection.localUtils, false), options);
   }
 
   @Override
diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/PageAssertionsImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/PageAssertionsImpl.java
index d9040ec7e..e4c113834 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/PageAssertionsImpl.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/PageAssertionsImpl.java
@@ -53,8 +53,8 @@ public void hasTitle(Pattern pattern, HasTitleOptions options) {
   @Override
   public void hasURL(String url, HasURLOptions options) {
     ExpectedTextValue expected = new ExpectedTextValue();
-    if (actualPage.context().baseUrl != null) {
-      url = resolveUrl(actualPage.context().baseUrl, url);
+    if (actualPage.context().baseUrl() != null) {
+      url = resolveUrl(actualPage.context().baseUrl(), url);
     }
     expected.string = url;
     expected.ignoreCase = shouldIgnoreCase(options);
diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/PageImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/PageImpl.java
index 40ebd5ab5..e132f9591 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/PageImpl.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/PageImpl.java
@@ -785,7 +785,7 @@ public Frame frame(String name) {
 
   @Override
   public Frame frameByUrl(String glob) {
-    return frameFor(UrlMatcher.forGlob(browserContext.baseUrl, glob, this.connection.localUtils, false));
+    return frameFor(UrlMatcher.forGlob(browserContext.baseUrl(), glob, this.connection.localUtils, false));
   }
 
   @Override
@@ -1108,7 +1108,7 @@ private Response reloadImpl(ReloadOptions options) {
 
   @Override
   public void route(String url, Consumer handler, RouteOptions options) {
-    route(UrlMatcher.forGlob(browserContext.baseUrl, url, this.connection.localUtils, false), handler, options);
+    route(UrlMatcher.forGlob(browserContext.baseUrl(), url, this.connection.localUtils, false), handler, options);
   }
 
   @Override
@@ -1130,7 +1130,7 @@ public void routeFromHAR(Path har, RouteFromHAROptions options) {
       browserContext.recordIntoHar(this, har, convertType(options, BrowserContext.RouteFromHAROptions.class), null);
       return;
     }
-    UrlMatcher matcher = UrlMatcher.forOneOf(browserContext.baseUrl, options.url, this.connection.localUtils, false);
+    UrlMatcher matcher = UrlMatcher.forOneOf(browserContext.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);
@@ -1145,7 +1145,7 @@ private void route(UrlMatcher matcher, Consumer handler, RouteOptions opt
 
     @Override
   public void routeWebSocket(String url, Consumer handler) {
-    routeWebSocketImpl(UrlMatcher.forGlob(browserContext.baseUrl, url, this.connection.localUtils, true), handler);
+    routeWebSocketImpl(UrlMatcher.forGlob(browserContext.baseUrl(), url, this.connection.localUtils, true), handler);
   }
 
   @Override
@@ -1363,7 +1363,7 @@ public void unrouteAll() {
 
   @Override
   public void unroute(String url, Consumer handler) {
-    unroute(UrlMatcher.forGlob(browserContext.baseUrl, url, this.connection.localUtils, false), handler);
+    unroute(UrlMatcher.forGlob(browserContext.baseUrl(), url, this.connection.localUtils, false), handler);
   }
 
   @Override
@@ -1409,7 +1409,7 @@ public VideoImpl video() {
     // Note: we are creating Video object lazily, because we do not know
     // BrowserContextOptions when constructing the page - it is assigned
     // too late during launchPersistentContext.
-    if (browserContext.videosDir == null) {
+    if (browserContext.videosDir() == null) {
       return null;
     }
     return forceVideo();
@@ -1506,7 +1506,7 @@ public T get() {
 
   @Override
   public Request waitForRequest(String urlGlob, WaitForRequestOptions options, Runnable code) {
-    return waitForRequest(UrlMatcher.forGlob(browserContext.baseUrl, urlGlob, this.connection.localUtils, false), null, options, code);
+    return waitForRequest(UrlMatcher.forGlob(browserContext.baseUrl(), urlGlob, this.connection.localUtils, false), null, options, code);
   }
 
   @Override
@@ -1551,7 +1551,7 @@ private Request waitForRequestFinishedImpl(WaitForRequestFinishedOptions options
 
   @Override
   public Response waitForResponse(String urlGlob, WaitForResponseOptions options, Runnable code) {
-    return waitForResponse(UrlMatcher.forGlob(browserContext.baseUrl, urlGlob, this.connection.localUtils, false), null, options, code);
+    return waitForResponse(UrlMatcher.forGlob(browserContext.baseUrl(), urlGlob, this.connection.localUtils, false), null, options, code);
   }
 
   @Override
@@ -1604,7 +1604,7 @@ public void waitForTimeout(double timeout) {
 
   @Override
   public void waitForURL(String url, WaitForURLOptions options) {
-    waitForURL(UrlMatcher.forGlob(browserContext.baseUrl, url, this.connection.localUtils, false), options);
+    waitForURL(UrlMatcher.forGlob(browserContext.baseUrl(), url, this.connection.localUtils, false), options);
   }
 
   @Override