diff --git a/README.md b/README.md index c4dcdc856..2f91c43c8 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.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: | ## 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/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/BrowserContextImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java
index b06d127de..360763c5c 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;
@@ -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 {
@@ -98,32 +96,32 @@ enum EventType {
 
   BrowserContextImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
     super(parent, type, guid, initializer);
-    if (parent instanceof BrowserImpl) {
-      browser = (BrowserImpl) parent;
-    } else {
-      browser = null;
-    }
     tracing = connection.getExistingObject(initializer.getAsJsonObject("tracing").get("guid").getAsString());
     request = connection.getExistingObject(initializer.getAsJsonObject("requestContext").get("guid").getAsString());
     request.timeoutSettings = timeoutSettings;
     clock = new ClockImpl(this);
-    closePromise = new WaitableEvent<>(listeners, EventType.CLOSE);
+    closePromise = new WaitableEvent<>(listeners, EventType.CLOSE); 
   }
 
-  void setRecordHar(Path path, HarContentPolicy policy) {
-    if (path != null) {
-      harRecorders.put("", new HarRecorder(path, policy));
+  Path videosDir() {
+    JsonObject recordVideo = initializer.getAsJsonObject("options").getAsJsonObject("recordVideo");
+    if (recordVideo == null) {
+      return null;
     }
+    return Paths.get(recordVideo.get("dir").getAsString());
   }
 
-  void setBaseUrl(String spec) {
-    try {
-      this.baseUrl = new URL(spec);
-    } catch (MalformedURLException e) {
-      this.baseUrl = null;
+  URL baseUrl() {
+    JsonElement url = initializer.getAsJsonObject("options").get("baseURL");
+    if (url != null) {
+      try {
+        return new URL(url.getAsString());
+      } catch (MalformedURLException e) {
+      }
     }
+    return null;
   }
-
+  
   String effectiveCloseReason() {
     if (closeReason != null) {
       return closeReason;
@@ -286,8 +284,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();
       }
@@ -481,7 +479,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
@@ -500,10 +498,10 @@ public void routeFromHAR(Path har, RouteFromHAROptions options) {
       options = new RouteFromHAROptions();
     }
     if (options.update != null && options.update) {
-      recordIntoHar(null, har, options);
+      recordIntoHar(null, har, options, null);
       return;
     }
-    UrlMatcher matcher = UrlMatcher.forOneOf(baseUrl, options.url, this.connection.localUtils, false);
+    UrlMatcher matcher = UrlMatcher.forOneOf(baseUrl(), options.url, this.connection.localUtils, false);
     HARRouter harRouter = new HARRouter(connection.localUtils, har, options.notFound);
     onClose(context -> harRouter.dispose());
     route(matcher, route -> harRouter.handle(route), null);
@@ -518,7 +516,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
@@ -538,24 +536,28 @@ private void routeWebSocketImpl(UrlMatcher matcher, Consumer han
     });
   }
 
-  void recordIntoHar(PageImpl page, Path har, RouteFromHAROptions options) {
+  void recordIntoHar(PageImpl page, Path har, RouteFromHAROptions options, HarContentPolicy contentPolicy) {
+    if (contentPolicy == null) {
+      contentPolicy = Utils.convertType(options.updateContent, HarContentPolicy.class);;
+    }
+    if (contentPolicy == null) {
+      contentPolicy = HarContentPolicy.ATTACH;
+    }
+
     JsonObject params = new JsonObject();
     if (page != null) {
       params.add("page", page.toProtocolRef());
     }
-    JsonObject jsonOptions = new JsonObject();
-    jsonOptions.addProperty("path", har.toAbsolutePath().toString());
-    jsonOptions.addProperty("content", options.updateContent == null ?
-      HarContentPolicy.ATTACH.name().toLowerCase() :
-      options.updateContent.name().toLowerCase());
-    jsonOptions.addProperty("mode", options.updateMode == null ?
-      HarMode.MINIMAL.name().toLowerCase() :
-      options.updateMode.name().toLowerCase());
-    addHarUrlFilter(jsonOptions, options.url);
-    params.add("options", jsonOptions);
+    JsonObject recordHarArgs = new JsonObject();
+    recordHarArgs.addProperty("zip", har.toString().endsWith(".zip"));  
+    recordHarArgs.addProperty("content", contentPolicy.name().toLowerCase());
+    recordHarArgs.addProperty("mode", (options.updateMode == null ? HarMode.MINIMAL : options.updateMode).name().toLowerCase());
+    addHarUrlFilter(recordHarArgs, options.url);
+
+    params.add("options", recordHarArgs);
     JsonObject json = sendMessage("harStart", params).getAsJsonObject();
     String harId = json.get("harId").getAsString();
-    harRecorders.put(harId, new HarRecorder(har, HarContentPolicy.ATTACH));
+    harRecorders.put(harId, new HarRecorder(har, contentPolicy));
   }
 
   @Override
@@ -639,7 +641,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
@@ -849,8 +851,10 @@ protected void handleEvent(String event, JsonObject params) {
   }
 
   void didClose() {
+    closingOrClosed = true;
     if (browser != null) {
       browser.contexts.remove(this);
+      browser.browserType.playwright.selectors.contextsForSelectors.remove(this);
     }
     listeners.notify(EventType.CLOSE, this);
   }
@@ -862,4 +866,46 @@ WritableStream createTempFile(String name, long lastModifiedMs) {
     JsonObject json = sendMessage("createTempFile", params).getAsJsonObject();
     return connection.getExistingObject(json.getAsJsonObject("writableStream").get("guid").getAsString());
   }
+
+  protected void initializeHarFromOptions(Browser.NewContextOptions options) {
+    if (options.recordHarPath == null) {
+      if (options.recordHarOmitContent != null) {
+        throw new PlaywrightException("recordHarOmitContent is set but recordHarPath is null");
+      }
+      if (options.recordHarUrlFilter != null) {
+        throw new PlaywrightException("recordHarUrlFilter is set but recordHarPath is null");
+      }
+      if (options.recordHarMode != null) {
+        throw new PlaywrightException("recordHarMode is set but recordHarPath is null");
+      }
+      if (options.recordHarContent != null) {
+        throw new PlaywrightException("recordHarContent is set but recordHarPath is null");
+      }
+      return;
+    }
+
+    HarContentPolicy contentPolicy = options.recordHarContent;
+    if (contentPolicy == null && options.recordHarOmitContent != null && options.recordHarOmitContent == true) {
+      contentPolicy = HarContentPolicy.OMIT;
+    }
+    if (contentPolicy == null) {
+      contentPolicy = options.recordHarPath.endsWith(".zip") ? HarContentPolicy.ATTACH : HarContentPolicy.EMBED;
+    }
+    RouteFromHAROptions routeFromHAROptions = new RouteFromHAROptions();
+
+    if (options.recordHarUrlFilter instanceof String) {
+      routeFromHAROptions.setUrl((String) options.recordHarUrlFilter);
+    } else if (options.recordHarUrlFilter instanceof Pattern) {
+      routeFromHAROptions.setUrl((Pattern) options.recordHarUrlFilter);
+    }
+
+    if (options.recordHarMode != null) {
+      routeFromHAROptions.updateMode = options.recordHarMode;
+    } else {
+      routeFromHAROptions.updateMode = HarMode.FULL;
+    }
+    routeFromHAROptions.url = options.recordHarUrlFilter;
+
+    recordIntoHar(null, options.recordHarPath, routeFromHAROptions, contentPolicy);
+  }
 }
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..45e25587e 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;
@@ -29,10 +28,8 @@
 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.*;
-import static com.microsoft.playwright.impl.Utils.convertType;
 
 class BrowserImpl extends ChannelOwner implements Browser {
   final Set contexts = new HashSet<>();
@@ -127,6 +124,14 @@ private BrowserContextImpl newContextImpl(NewContextOptions options) {
       // Make a copy so that we can nullify some fields below.
       options = convertType(options, NewContextOptions.class);
     }
+
+    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);
@@ -141,51 +146,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());
@@ -213,23 +177,17 @@ private BrowserContextImpl newContextImpl(NewContextOptions options) {
     if (options.acceptDownloads != null) {
       params.addProperty("acceptDownloads", options.acceptDownloads ? "accept" : "deny");
     }
+    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.videosDir = options.recordVideoDir;
-    if (options.baseURL != null) {
-      context.setBaseUrl(options.baseURL);
-    }
-    context.setRecordHar(recordHarPath, harContentPolicy);
-    if (launchOptions != null) {
-      context.tracing().setTracesDir(launchOptions.tracesDir);
-    }
-    contexts.add(context);
+    context.initializeHarFromOptions(harOptions);
     return context;
   }
 
   @Override
   public Page newPage(NewPageOptions options) {
-    return withLogging("Browser.newPage", () -> newPageImpl(options));
+    return withTitle("Create Page", () -> newPageImpl(options));
   }
 
   @Override
@@ -295,8 +253,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 +270,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.tracePath = tracesDir;
+
+    for (BrowserContextImpl context : contexts) {
+      context.tracing().setTracesDir(tracesDir);
+      browserType.playwright.selectors.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(tracePath);
+      browserType.playwright.selectors.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 a00e42d00..f8e4cd5d6 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserTypeImpl.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserTypeImpl.java
@@ -22,19 +22,19 @@
 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;
 
 class BrowserTypeImpl extends ChannelOwner implements BrowserType {
+  protected PlaywrightImpl playwright;
+
   BrowserTypeImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
     super(parent, type, guid, initializer);
   }
@@ -101,14 +101,13 @@ private Browser connectImpl(String wsEndpoint, ConnectOptions options) {
       }
       throw new PlaywrightException("Malformed endpoint. Did you use launchServer method?");
     }
-    playwright.initSharedSelectors(this.connection.getExistingObject("Playwright"));
+    playwright.selectors = this.playwright.selectors;
     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 -> {
-      playwright.unregisterSelectors();
       pipe.offClose(connectionCloseListener);
       try {
         connection.close();
@@ -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,14 @@ 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");
-      }
-    }
+
+    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();
@@ -209,9 +174,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 +201,13 @@ private BrowserContextImpl launchPersistentContextImpl(Path userDataDir, LaunchP
     if (options.acceptDownloads != null) {
       params.addProperty("acceptDownloads", options.acceptDownloads ? "accept" : "deny");
     }
+    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);
     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);
+    context.initializeHarFromOptions(harOptions);
     context.tracing().setTracesDir(options.tracesDir);
     return context;
   }
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..47a7aec6d 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) {
@@ -87,16 +82,27 @@  T withWaitLogging(String apiName, Function code) {
     return new WaitForEventLogger<>(this, apiName, code).get();
   }
 
+  @Deprecated
   @Override
    T withLogging(String apiName, Supplier code) {
-    if (isInternalType) {
-      apiName = null;
-    }
-    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 f0d3ca5f0..20da50cd7 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,14 @@ 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;
+      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()) {
@@ -373,9 +376,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/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/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/LocatorAssertionsImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/LocatorAssertionsImpl.java
index ab9448b44..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));
+    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));
+    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));
+    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));
+    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));
+    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));
+    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));
+    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));
+    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));
+    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));
+    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));
+    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));
+    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);
+    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));
+    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));
+    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));
+    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));
+    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);
+    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);
+    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));
+    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));
+    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);
+    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));
+    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));
+    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));
+    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));
+    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));
+    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));
+    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));
+    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));
+    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));
+    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");
+    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);
+    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));
+    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);
+    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));
+    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);
+    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));
+    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));
+    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);
+    expectTrue("to.be.in.viewport", "Locator expected to be in viewport",  expectOptions, "Assert \"isInViewport\"");
   }
 
   @Override
@@ -456,12 +455,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, "Assert \"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 +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);
+    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 90f7b77e9..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,27 +67,25 @@ 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) {
-    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 +150,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 +163,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 +233,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 +243,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 +488,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 +496,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 +552,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 +658,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/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/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..e4c113834 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,30 +41,30 @@ 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), "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));
+    expectImpl("to.have.title", expected, pattern, "Page title expected to match regex", convertType(options, FrameExpectOptions.class), "Assert \"hasTitle\"");
   }
 
   @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);
-    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), "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));
+    expectImpl("to.have.url", expected, pattern, "Page URL expected to match regex", convertType(options, FrameExpectOptions.class), "Assert \"hasURL\"");
   }
 
   @Override
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..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
@@ -1127,10 +1127,10 @@ 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);
+    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
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..9cf96db39 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,9 +61,8 @@ 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;
+  protected SelectorsImpl selectors;
 
   PlaywrightImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
     super(parent, type, guid, initializer);
@@ -72,22 +70,12 @@ 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());
-    apiRequest = new APIRequestImpl(this);
-  }
-
-  void initSharedSelectors(PlaywrightImpl parent) {
-    assert sharedSelectors == null;
-    if (parent == null) {
-      sharedSelectors = new SharedSelectors();
-    } else {
-      sharedSelectors = parent.sharedSelectors;
-    }
-    sharedSelectors.addChannel(selectors);
-  }
+    chromium.playwright = this;
+    firefox.playwright = this;
+    webkit.playwright = this;
 
-  void unregisterSelectors() {
-    sharedSelectors.removeChannel(selectors);
+    selectors = new SelectorsImpl();
+    apiRequest = new APIRequestImpl(this);
   }
 
   public LocalUtils localUtils() {
@@ -120,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/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/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/SelectorsImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/SelectorsImpl.java
index 77977bba8..a150afe3a 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/SelectorsImpl.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/SelectorsImpl.java
@@ -17,28 +17,69 @@
 package com.microsoft.playwright.impl;
 
 import com.google.gson.JsonObject;
+import com.microsoft.playwright.PlaywrightException;
 import com.microsoft.playwright.Selectors;
 
-import static com.microsoft.playwright.impl.Serialization.gson;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
 
-class SelectorsImpl extends ChannelOwner {
-  SelectorsImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
-    super(parent, type, guid, initializer);
-  }
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+public class SelectorsImpl extends LoggingSupport implements Selectors {
+  protected final List contextsForSelectors = new ArrayList<>();
+  protected final List selectorEngines = new ArrayList<>();
+
+  String testIdAttributeName = "data-testid";
 
-  void register(String name, String script, Selectors.RegisterOptions options) {
-    if (options == null) {
-      options = new Selectors.RegisterOptions();
+  @Override
+  public void setTestIdAttribute(String attributeName) {
+    if (attributeName == null) {
+      throw new PlaywrightException("Test id attribute cannot be null");
     }
-    JsonObject params = gson().toJsonTree(options).getAsJsonObject();
-    params.addProperty("name", name);
-    params.addProperty("source", script);
-    sendMessage("register", params);
+    testIdAttributeName = attributeName;
+    for (BrowserContextImpl context : contextsForSelectors) {
+      try {
+        JsonObject params = new JsonObject();
+        params.addProperty("testIdAttributeName", attributeName);
+        context.sendMessageAsync("setTestIdAttributeName", params);
+      } catch (PlaywrightException e) {  
+      }
+    }
+  }
+
+  @Override
+  public void register(String name, String script, RegisterOptions options) {
+    withLogging("Selectors.register", () -> registerImpl(name, script, options));
+  }
+
+  @Override
+  public void register(String name, Path path, RegisterOptions options) {
+    withLogging("Selectors.register", () -> {
+      byte[] buffer;
+      try {
+        buffer = Files.readAllBytes(path);
+      } catch (IOException e) {
+        throw new PlaywrightException("Failed to read selector from file: " + path, e);
+      }
+      registerImpl(name, new String(buffer, UTF_8), options);
+    });
   }
 
-  void setTestIdAttributeName(String name) {
-    JsonObject params = new JsonObject();
-    params.addProperty("testIdAttributeName", name);
-    sendMessageAsync("setTestIdAttributeName", params);
+  private void registerImpl(String name, String script, RegisterOptions options) {
+    JsonObject engine = new JsonObject();
+    engine.addProperty("name", name);
+    engine.addProperty("source", script);
+    if (options != null && options.contentScript != null) {
+      engine.addProperty("contentScript", options.contentScript);
+    }
+    for (BrowserContextImpl context : contextsForSelectors) {
+      JsonObject params = new JsonObject();
+      params.add("selectorEngine", engine);
+      context.sendMessage("registerSelectorEngine", params);
+    }
+    selectorEngines.add(engine);
   }
 }
diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/SharedSelectors.java b/playwright/src/main/java/com/microsoft/playwright/impl/SharedSelectors.java
deleted file mode 100644
index b654ed251..000000000
--- a/playwright/src/main/java/com/microsoft/playwright/impl/SharedSelectors.java
+++ /dev/null
@@ -1,95 +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.microsoft.playwright.PlaywrightException;
-import com.microsoft.playwright.Selectors;
-
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.List;
-
-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<>();
-
-  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 register(String name, String script, RegisterOptions options) {
-    withLogging("Selectors.register", () -> registerImpl(name, script, options));
-  }
-
-  @Override
-  public void register(String name, Path path, RegisterOptions options) {
-    withLogging("Selectors.register", () -> {
-      byte[] buffer;
-      try {
-        buffer = Files.readAllBytes(path);
-      } catch (IOException e) {
-        throw new PlaywrightException("Failed to read selector from file: " + path, e);
-      }
-      registerImpl(name, new String(buffer, UTF_8), 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));
-  }
-}
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
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 {
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 b11525dbf..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();
@@ -309,4 +307,19 @@ void shouldAcceptRelativeUserDataDir(@TempDir Path tmpDir) throws Exception {
     assertTrue(Files.list(userDataDir).count() > 0);
     context.close();
   }
+
+  @Test
+  void shouldExposeBrowser() {
+    Page page = launchPersistent();
+    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, context.pages().size());
+    // Next line should not throw.
+    context.close();
+  }
 }
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/TestLocatorAssertions.java b/playwright/src/test/java/com/microsoft/playwright/TestLocatorAssertions.java
index e275483cd..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("Locator.expect 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("Locator.expect 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("Locator.expect 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 89564c631..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("Locator.expect 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/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/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
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/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;
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/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 {
diff --git a/scripts/DRIVER_VERSION b/scripts/DRIVER_VERSION
index a6c040f60..3f4830156 100644
--- a/scripts/DRIVER_VERSION
+++ b/scripts/DRIVER_VERSION
@@ -1 +1 @@
-1.53.0-alpha-2025-05-21
+1.53.0