diff --git a/README.md b/README.md index 7f9627e7..480cf67d 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,9 @@ Playwright is a Java library to automate [Chromium](https://www.chromium.org/Hom | | Linux | macOS | Windows | | :--- | :---: | :---: | :---: | -| Chromium 123.0.6312.4 | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| Chromium 124.0.6367.8 | :white_check_mark: | :white_check_mark: | :white_check_mark: | | WebKit 17.4 | ✅ | ✅ | ✅ | -| Firefox 123.0 | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| Firefox 124.0 | :white_check_mark: | :white_check_mark: | :white_check_mark: | Headless execution is supported for all the browsers on all platforms. Check out [system requirements](https://playwright.dev/java/docs/intro#system-requirements) for details. diff --git a/playwright/src/main/java/com/microsoft/playwright/BrowserContext.java b/playwright/src/main/java/com/microsoft/playwright/BrowserContext.java index a65762ee..3541a932 100644 --- a/playwright/src/main/java/com/microsoft/playwright/BrowserContext.java +++ b/playwright/src/main/java/com/microsoft/playwright/BrowserContext.java @@ -186,6 +186,63 @@ public interface BrowserContext extends AutoCloseable { */ void offResponse(Consumer handler); + class ClearCookiesOptions { + /** + * Only removes cookies with the given domain. + */ + public Object domain; + /** + * Only removes cookies with the given name. + */ + public Object name; + /** + * Only removes cookies with the given path. + */ + public Object path; + + /** + * Only removes cookies with the given domain. + */ + public ClearCookiesOptions setDomain(String domain) { + this.domain = domain; + return this; + } + /** + * Only removes cookies with the given domain. + */ + public ClearCookiesOptions setDomain(Pattern domain) { + this.domain = domain; + return this; + } + /** + * Only removes cookies with the given name. + */ + public ClearCookiesOptions setName(String name) { + this.name = name; + return this; + } + /** + * Only removes cookies with the given name. + */ + public ClearCookiesOptions setName(Pattern name) { + this.name = name; + return this; + } + /** + * Only removes cookies with the given path. + */ + public ClearCookiesOptions setPath(String path) { + this.path = path; + return this; + } + /** + * Only removes cookies with the given path. + */ + public ClearCookiesOptions setPath(Pattern path) { + this.path = path; + return this; + } + } class CloseOptions { /** * The reason to be reported to the operations interrupted by the context closure. @@ -492,11 +549,41 @@ public interface BrowserContext extends AutoCloseable { */ Browser browser(); /** - * Clears context cookies. + * Removes cookies from context. Accepts optional filter. + * + *

**Usage** + *

{@code
+   * context.clearCookies();
+   * context.clearCookies(new BrowserContext.ClearCookiesOptions().setName("session-id"));
+   * context.clearCookies(new BrowserContext.ClearCookiesOptions().setDomain("my-origin.com"));
+   * context.clearCookies(new BrowserContext.ClearCookiesOptions().setPath("/api/v1"));
+   * context.clearCookies(new BrowserContext.ClearCookiesOptions()
+   *                          .setName("session-id")
+   *                          .setDomain("my-origin.com"));
+   * }
* * @since v1.8 */ - void clearCookies(); + default void clearCookies() { + clearCookies(null); + } + /** + * Removes cookies from context. Accepts optional filter. + * + *

**Usage** + *

{@code
+   * context.clearCookies();
+   * context.clearCookies(new BrowserContext.ClearCookiesOptions().setName("session-id"));
+   * context.clearCookies(new BrowserContext.ClearCookiesOptions().setDomain("my-origin.com"));
+   * context.clearCookies(new BrowserContext.ClearCookiesOptions().setPath("/api/v1"));
+   * context.clearCookies(new BrowserContext.ClearCookiesOptions()
+   *                          .setName("session-id")
+   *                          .setDomain("my-origin.com"));
+   * }
+ * + * @since v1.8 + */ + void clearCookies(ClearCookiesOptions options); /** * Clears all permission overrides for the browser context. * diff --git a/playwright/src/main/java/com/microsoft/playwright/BrowserType.java b/playwright/src/main/java/com/microsoft/playwright/BrowserType.java index b7703540..3914a061 100644 --- a/playwright/src/main/java/com/microsoft/playwright/BrowserType.java +++ b/playwright/src/main/java/com/microsoft/playwright/BrowserType.java @@ -182,8 +182,7 @@ public interface BrowserType { */ public Boolean chromiumSandbox; /** - * **Chromium-only** Whether to auto-open a Developer Tools panel for each tab. If this option is {@code true}, the {@code - * headless} option will be set {@code false}. + * @deprecated Use debugging tools instead. */ public Boolean devtools; /** @@ -291,8 +290,7 @@ public interface BrowserType { return this; } /** - * **Chromium-only** Whether to auto-open a Developer Tools panel for each tab. If this option is {@code true}, the {@code - * headless} option will be set {@code false}. + * @deprecated Use debugging tools instead. */ public LaunchOptions setDevtools(boolean devtools) { this.devtools = devtools; @@ -468,8 +466,7 @@ public interface BrowserType { */ public Double deviceScaleFactor; /** - * **Chromium-only** Whether to auto-open a Developer Tools panel for each tab. If this option is {@code true}, the {@code - * headless} option will be set {@code false}. + * @deprecated Use debugging tools instead. */ public Boolean devtools; /** @@ -760,8 +757,7 @@ public interface BrowserType { return this; } /** - * **Chromium-only** Whether to auto-open a Developer Tools panel for each tab. If this option is {@code true}, the {@code - * headless} option will be set {@code false}. + * @deprecated Use debugging tools instead. */ public LaunchPersistentContextOptions setDevtools(boolean devtools) { this.devtools = devtools; diff --git a/playwright/src/main/java/com/microsoft/playwright/FrameLocator.java b/playwright/src/main/java/com/microsoft/playwright/FrameLocator.java index d3b14279..e23e0848 100644 --- a/playwright/src/main/java/com/microsoft/playwright/FrameLocator.java +++ b/playwright/src/main/java/com/microsoft/playwright/FrameLocator.java @@ -42,11 +42,13 @@ import java.util.regex.Pattern; * *

**Converting Locator to FrameLocator** * - *

If you have a {@code Locator} object pointing to an {@code iframe} it can be converted to {@code FrameLocator} using {@code :scope} CSS selector: - *

{@code
- * Locator frameLocator = locator.frameLocator(':scope');
- * }
+ *

If you have a {@code Locator} object pointing to an {@code iframe} it can be converted to {@code FrameLocator} using + * {@link Locator#contentFrame Locator.contentFrame()}. + * + *

**Converting FrameLocator to Locator** + * + *

If you have a {@code FrameLocator} object it can be converted to {@code Locator} pointing to the same {@code iframe} + * using {@link FrameLocator#owner FrameLocator.owner()}. */ public interface FrameLocator { class GetByAltTextOptions { @@ -1003,5 +1005,24 @@ public interface FrameLocator { * @since v1.17 */ FrameLocator nth(int index); + /** + * Returns a {@code Locator} object pointing to the same {@code iframe} as this frame locator. + * + *

Useful when you have a {@code FrameLocator} object obtained somewhere, and later on would like to interact with the + * {@code iframe} element. + * + *

For a reverse operation, use {@link Locator#contentFrame Locator.contentFrame()}. + * + *

**Usage** + *

{@code
+   * FrameLocator frameLocator = page.frameLocator("iframe[name=\"embedded\"]");
+   * // ...
+   * Locator locator = frameLocator.owner();
+   * assertThat(locator).isVisible();
+   * }
+ * + * @since v1.43 + */ + Locator owner(); } diff --git a/playwright/src/main/java/com/microsoft/playwright/Locator.java b/playwright/src/main/java/com/microsoft/playwright/Locator.java index ba19bc0d..fd4322cf 100644 --- a/playwright/src/main/java/com/microsoft/playwright/Locator.java +++ b/playwright/src/main/java/com/microsoft/playwright/Locator.java @@ -2704,6 +2704,25 @@ public interface Locator { * @since v1.14 */ List elementHandles(); + /** + * Returns a {@code FrameLocator} object pointing to the same {@code iframe} as this locator. + * + *

Useful when you have a {@code Locator} object obtained somewhere, and later on would like to interact with the content + * inside the frame. + * + *

For a reverse operation, use {@link FrameLocator#owner FrameLocator.owner()}. + * + *

**Usage** + *

{@code
+   * Locator locator = page.locator("iframe[name=\"embedded\"]");
+   * // ...
+   * FrameLocator frameLocator = locator.contentFrame();
+   * frameLocator.getByRole(AriaRole.BUTTON).click();
+   * }
+ * + * @since v1.43 + */ + FrameLocator contentFrame(); /** * Execute JavaScript code in the page, taking the matching element as an argument. * diff --git a/playwright/src/main/java/com/microsoft/playwright/Page.java b/playwright/src/main/java/com/microsoft/playwright/Page.java index cff4770a..f99ecc12 100644 --- a/playwright/src/main/java/com/microsoft/playwright/Page.java +++ b/playwright/src/main/java/com/microsoft/playwright/Page.java @@ -5918,9 +5918,11 @@ public interface Page extends AutoCloseable { */ List querySelectorAll(String selector); /** - * When testing a web page, sometimes unexpected overlays like a coookie consent dialog appear and block actions you want - * to automate, e.g. clicking a button. These overlays don't always show up in the same way or at the same time, making - * them tricky to handle in automated tests. + * NOTE: This method is experimental and its behavior may change in the upcoming releases. + * + *

When testing a web page, sometimes unexpected overlays like a "Sign up" dialog appear and block actions you want to + * automate, e.g. clicking a button. These overlays don't always show up in the same way or at the same time, making them + * tricky to handle in automated tests. * *

This method lets you set up a special function, called a handler, that activates when it detects that overlay is * visible. The handler's job is to remove the overlay, allowing your test to continue as if the overlay wasn't there. @@ -5932,7 +5934,8 @@ public interface Page extends AutoCloseable { *

  • Playwright checks for the overlay every time before executing or retrying an action that requires an actionability check, or before performing an auto-waiting * assertion check. When overlay is visible, Playwright calls the handler first, and then proceeds with the - * action/assertion.
  • + * action/assertion. Note that the handler is only called when you perform an action/assertion - if the overlay becomes + * visible but you don't perform any actions, the handler will not be triggered. *
  • The execution time of the handler counts towards the timeout of the action/assertion that executed the handler. If your * handler takes too long, it might cause timeouts.
  • *
  • You can register multiple handlers. However, only a single handler will be running at a time. Make sure the actions @@ -5951,11 +5954,11 @@ public interface Page extends AutoCloseable { * *

    **Usage** * - *

    An example that closes a cookie consent dialog when it appears: + *

    An example that closes a "Sign up to the newsletter" dialog when it appears: *

    {@code
        * // Setup the handler.
    -   * page.addLocatorHandler(page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Accept all cookies")), () => {
    -   *   page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Reject all cookies")).click();
    +   * page.addLocatorHandler(page.getByText("Sign up to the newsletter"), () => {
    +   *   page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("No thanks")).click();
        * });
        *
        * // Write the test as usual.
    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 9108ea74..d6b92888 100644
    --- a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java
    +++ b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java
    @@ -37,6 +37,7 @@ import java.util.regex.Pattern;
     import static com.microsoft.playwright.impl.Serialization.addHarUrlFilter;
     import static com.microsoft.playwright.impl.Serialization.gson;
     import static com.microsoft.playwright.impl.Utils.isSafeCloseError;
    +import static com.microsoft.playwright.impl.Utils.toJsRegexFlags;
     import static java.nio.charset.StandardCharsets.UTF_8;
     import static java.nio.file.Files.readAllBytes;
     import static java.util.Arrays.asList;
    @@ -335,8 +336,29 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
       }
     
       @Override
    -  public void clearCookies() {
    -    withLogging("BrowserContext.clearCookies", () -> sendMessage("clearCookies"));
    +  public void clearCookies(ClearCookiesOptions options) {
    +    withLogging("BrowserContext.clearCookies", () -> clearCookiesImpl(options));
    +  }
    +
    +  private void clearCookiesImpl(ClearCookiesOptions options) {
    +    if (options == null) {
    +      options = new ClearCookiesOptions();
    +    }
    +    JsonObject params = new JsonObject();
    +    setStringOrRegex(params, "name", options.name);
    +    setStringOrRegex(params, "domain", options.domain);
    +    setStringOrRegex(params, "path", options.path);
    +    sendMessage("clearCookies", params);
    +  }
    +
    +  private static void setStringOrRegex(JsonObject params, String name, Object value) {
    +    if (value instanceof String) {
    +      params.addProperty(name, (String) value);
    +    } else if (value instanceof Pattern) {
    +      Pattern pattern = (Pattern) value;
    +      params.addProperty(name + "RegexSource", pattern.pattern());
    +      params.addProperty(name + "RegexFlags", toJsRegexFlags(pattern));
    +    }
       }
     
       @Override
    diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/FrameLocatorImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/FrameLocatorImpl.java
    index d0e52908..64870413 100644
    --- a/playwright/src/main/java/com/microsoft/playwright/impl/FrameLocatorImpl.java
    +++ b/playwright/src/main/java/com/microsoft/playwright/impl/FrameLocatorImpl.java
    @@ -133,4 +133,9 @@ class FrameLocatorImpl implements FrameLocator {
       public FrameLocator nth(int index) {
         return new FrameLocatorImpl(frame, frameSelector + " >> nth=" + index);
       }
    +
    +  @Override
    +  public Locator owner() {
    +    return new LocatorImpl(frame, frameSelector);
    +  }
     }
    diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/JSHandleImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/JSHandleImpl.java
    index 9f640274..4adc8170 100644
    --- a/playwright/src/main/java/com/microsoft/playwright/impl/JSHandleImpl.java
    +++ b/playwright/src/main/java/com/microsoft/playwright/impl/JSHandleImpl.java
    @@ -41,7 +41,12 @@ public class JSHandleImpl extends ChannelOwner implements JSHandle {
     
       @Override
       public void dispose() {
    -    withLogging("JSHandle.dispose", () -> sendMessage("dispose"));
    +    withLogging("JSHandle.dispose", () -> {
    +      try {
    +        sendMessage("dispose");
    +      } catch (TargetClosedError e) {
    +      }
    +    });
       }
     
       @Override
    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 65b713cb..f999d8fb 100644
    --- a/playwright/src/main/java/com/microsoft/playwright/impl/LocatorImpl.java
    +++ b/playwright/src/main/java/com/microsoft/playwright/impl/LocatorImpl.java
    @@ -39,6 +39,10 @@ class LocatorImpl implements Locator {
       final FrameImpl frame;
       final String selector;
     
    +  LocatorImpl(FrameImpl frame, String frameSelector) {
    +    this(frame, frameSelector, null);
    +  }
    +
       public LocatorImpl(FrameImpl frame, String selector, LocatorOptions options) {
         this.frame = frame;
         if (options != null) {
    @@ -203,6 +207,11 @@ class LocatorImpl implements Locator {
         return frame.querySelectorAll(selector);
       }
     
    +  @Override
    +  public FrameLocator contentFrame() {
    +    return new FrameLocatorImpl(frame, selector);
    +  }
    +
       @Override
       public Object evaluate(String expression, Object arg, EvaluateOptions options) {
         return withElement((h, o) -> h.evaluate(expression, arg), options);
    diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/TargetClosedError.java b/playwright/src/main/java/com/microsoft/playwright/impl/TargetClosedError.java
    index eb62cb4b..002851ba 100644
    --- a/playwright/src/main/java/com/microsoft/playwright/impl/TargetClosedError.java
    +++ b/playwright/src/main/java/com/microsoft/playwright/impl/TargetClosedError.java
    @@ -24,6 +24,10 @@ public class TargetClosedError extends PlaywrightException {
       }
     
       public TargetClosedError(String message) {
    -    super(message != null ? message : "Target page, context or browser has been closed");
    +    this(message, null);
    +  }
    +
    +  public TargetClosedError(String message, Throwable cause) {
    +    super(message != null ? message : "Target page, context or browser has been closed", cause);
       }
     }
    diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/WaitableResult.java b/playwright/src/main/java/com/microsoft/playwright/impl/WaitableResult.java
    index 8ff7fc9a..831874db 100644
    --- a/playwright/src/main/java/com/microsoft/playwright/impl/WaitableResult.java
    +++ b/playwright/src/main/java/com/microsoft/playwright/impl/WaitableResult.java
    @@ -50,6 +50,8 @@ class WaitableResult implements Waitable {
         if (exception != null) {
           if (exception instanceof TimeoutError) {
             throw new TimeoutError(exception.getMessage(), exception);
    +      } if (exception instanceof TargetClosedError) {
    +        throw new TargetClosedError(exception.getMessage(), exception);
           }
           throw new PlaywrightException(exception.getMessage(), exception);
         }
    diff --git a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextClearCookies.java b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextClearCookies.java
    index 2f7bb17d..7d8d2453 100644
    --- a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextClearCookies.java
    +++ b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextClearCookies.java
    @@ -16,16 +16,25 @@
     
     package com.microsoft.playwright;
     
    +import com.microsoft.playwright.junit.FixtureTest;
    +import com.microsoft.playwright.junit.UsePlaywright;
     import com.microsoft.playwright.options.Cookie;
     import org.junit.jupiter.api.Test;
     
    +import java.net.MalformedURLException;
    +import java.net.URL;
    +import java.util.Arrays;
    +import java.util.regex.Pattern;
    +
     import static java.util.Arrays.asList;
     import static java.util.Collections.emptyList;
     import static org.junit.jupiter.api.Assertions.assertEquals;
     
    -public class TestBrowserContextClearCookies extends TestBase {
    +@FixtureTest
    +@UsePlaywright(TestOptionsFactories.BasicOptionsFactory.class)
    +public class TestBrowserContextClearCookies {
       @Test
    -  void shouldClearCookies() {
    +  void shouldClearCookies(Page page, BrowserContext context, Server server) {
         page.navigate(server.EMPTY_PAGE);
         context.addCookies(asList(
           new Cookie("cookie1", "1").setUrl(server.EMPTY_PAGE)));
    @@ -37,7 +46,7 @@ public class TestBrowserContextClearCookies extends TestBase {
       }
     
       @Test
    -  void shouldIsolateCookiesWhenClearing() {
    +  void shouldIsolateCookiesWhenClearing(BrowserContext context, Browser browser, Server server) {
         BrowserContext anotherContext = browser.newContext();
         context.addCookies(asList(
           new Cookie("page1cookie", "page1value").setUrl(server.EMPTY_PAGE)));
    @@ -56,4 +65,77 @@ public class TestBrowserContextClearCookies extends TestBase {
         assertEquals(0, (anotherContext.cookies()).size());
         anotherContext.close();
       }
    +
    +  @Test
    +  void shouldRemoveCookiesByName(Page page, BrowserContext context, Server server) throws MalformedURLException {
    +    context.addCookies(Arrays.asList(
    +      new Cookie("cookie1", "1").setDomain(new URL(server.PREFIX).getHost()).setPath("/"),
    +      new Cookie("cookie2", "2").setDomain(new URL(server.PREFIX).getHost()).setPath("/")
    +    ));
    +
    +    page.navigate(server.PREFIX);
    +    assertEquals("cookie1=1; cookie2=2", page.evaluate("document.cookie"));
    +    context.clearCookies(new BrowserContext.ClearCookiesOptions().setName("cookie1"));
    +    assertEquals("cookie2=2", page.evaluate("document.cookie"));
    +  }
    +
    +  @Test
    +  public void shouldRemoveCookiesByNameRegex(Page page, BrowserContext context, Server server) throws MalformedURLException {
    +    context.addCookies(Arrays.asList(
    +      new Cookie("cookie1", "1").setDomain(new URL(server.PREFIX).getHost()).setPath("/"),
    +      new Cookie("cookie2", "2").setDomain(new URL(server.PREFIX).getHost()).setPath("/")
    +    ));
    +
    +    page.navigate(server.PREFIX);
    +    assertEquals("cookie1=1; cookie2=2", page.evaluate("document.cookie"));
    +    context.clearCookies(new BrowserContext.ClearCookiesOptions().setName(Pattern.compile("coo.*1")));
    +    assertEquals("cookie2=2", page.evaluate("document.cookie"));
    +  }
    +
    +  @Test
    +  public void shouldRemoveCookiesByDomain(Page page, BrowserContext context, Server server) throws MalformedURLException {
    +    context.addCookies(Arrays.asList(
    +      new Cookie("cookie1", "1").setDomain(new URL(server.PREFIX).getHost()).setPath("/"),
    +      new Cookie("cookie2", "2").setDomain(new URL(server.CROSS_PROCESS_PREFIX).getHost()).setPath("/")
    +    ));
    +    page.navigate(server.PREFIX);
    +    assertEquals("cookie1=1", page.evaluate("document.cookie"));
    +    page.navigate(server.CROSS_PROCESS_PREFIX);
    +    assertEquals("cookie2=2", page.evaluate("document.cookie"));
    +    context.clearCookies(new BrowserContext.ClearCookiesOptions().setDomain(new URL(server.CROSS_PROCESS_PREFIX).getHost()));
    +    assertEquals("", page.evaluate("document.cookie"));
    +    page.navigate(server.PREFIX);
    +    assertEquals("cookie1=1", page.evaluate("document.cookie"));
    +  }
    +
    +  @Test
    +  public void shouldRemoveCookiesByPath(Page page, BrowserContext context, Server server) throws MalformedURLException {
    +    context.addCookies(Arrays.asList(
    +      new Cookie("cookie1", "1").setDomain(new URL(server.PREFIX).getHost()).setPath("/api/v1"),
    +      new Cookie("cookie2", "2").setDomain(new URL(server.PREFIX).getHost()).setPath("/api/v2"),
    +      new Cookie("cookie3", "3").setDomain(new URL(server.PREFIX).getHost()).setPath("/")
    +    ));
    +    page.navigate(server.PREFIX + "/api/v1");
    +    assertEquals("cookie1=1; cookie3=3", page.evaluate("document.cookie"));
    +    context.clearCookies(new BrowserContext.ClearCookiesOptions().setPath("/api/v1"));
    +    assertEquals("cookie3=3", page.evaluate("document.cookie"));
    +    page.navigate(server.PREFIX + "/api/v2");
    +    assertEquals("cookie2=2; cookie3=3", page.evaluate("document.cookie"));
    +    page.navigate(server.PREFIX + "/");
    +    assertEquals("cookie3=3", page.evaluate("document.cookie"));
    +  }
    +
    +  @Test
    +  public void shouldRemoveCookiesByNameAndDomain(Page page, BrowserContext context, Server server) throws MalformedURLException {
    +    context.addCookies(Arrays.asList(
    +      new Cookie("cookie1", "1").setDomain(new URL(server.PREFIX).getHost()).setPath("/"),
    +      new Cookie("cookie1", "1").setDomain(new URL(server.CROSS_PROCESS_PREFIX).getHost()).setPath("/")
    +    ));
    +    page.navigate(server.PREFIX);
    +    assertEquals("cookie1=1", page.evaluate("document.cookie"));
    +    context.clearCookies(new BrowserContext.ClearCookiesOptions().setName("cookie1").setDomain(new URL(server.PREFIX).getHost()));
    +    assertEquals("", page.evaluate("document.cookie"));
    +    page.navigate(server.CROSS_PROCESS_PREFIX);
    +    assertEquals("cookie1=1", page.evaluate("document.cookie"));
    +  }
     }
    diff --git a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextStorageState.java b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextStorageState.java
    index 0d423330..2c897c42 100644
    --- a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextStorageState.java
    +++ b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextStorageState.java
    @@ -46,17 +46,17 @@ public class TestBrowserContextStorageState extends TestBase {
         assertJsonEquals("{" +
           "cookies:[]," +
           "origins:[{\n" +
    -      "  origin: 'https://www.example.com',\n" +
    -      "  localStorage: [{\n" +
    -      "    name: 'name1',\n" +
    -      "    value: 'value1'\n" +
    -      "  }]\n" +
    -      "}, {\n" +
           "  origin: 'https://www.domain.com',\n" +
           "  localStorage: [{\n" +
           "    name: 'name2',\n" +
           "    value: 'value2'\n" +
           "  }]\n" +
    +      "}, {\n" +
    +      "  origin: 'https://www.example.com',\n" +
    +      "  localStorage: [{\n" +
    +      "    name: 'name1',\n" +
    +      "    value: 'value1'\n" +
    +      "  }]\n" +
           "}]}", new Gson().fromJson(storageState, JsonObject.class));
       }
     
    diff --git a/playwright/src/test/java/com/microsoft/playwright/TestElementHandleMisc.java b/playwright/src/test/java/com/microsoft/playwright/TestElementHandleMisc.java
    index c64a63ce..695da49c 100644
    --- a/playwright/src/test/java/com/microsoft/playwright/TestElementHandleMisc.java
    +++ b/playwright/src/test/java/com/microsoft/playwright/TestElementHandleMisc.java
    @@ -20,6 +20,7 @@ import org.junit.jupiter.api.Test;
     
     import static java.util.Arrays.asList;
     import static org.junit.jupiter.api.Assertions.assertEquals;
    +import static org.junit.jupiter.api.Assertions.assertNotNull;
     
     public class TestElementHandleMisc extends TestBase {
       @Test
    @@ -108,4 +109,12 @@ public class TestElementHandleMisc extends TestBase {
         assertEquals(asList("blue"), page.evaluate("() => window['result'].onChange"));
       }
     
    +  @Test
    +  void shouldAllowDisposingTwice() {
    +    page.setContent("
    39
    "); + ElementHandle element = page.querySelector("section"); + assertNotNull(element); + element.dispose(); + element.dispose(); + } } diff --git a/playwright/src/test/java/com/microsoft/playwright/TestLocatorFrame.java b/playwright/src/test/java/com/microsoft/playwright/TestLocatorFrame.java index 81d056c8..ac8aab13 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestLocatorFrame.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestLocatorFrame.java @@ -28,7 +28,7 @@ import static org.junit.jupiter.api.Assertions.*; public class TestLocatorFrame extends TestBase { private static void routeIframe(Page page) { page.route("**/empty.html", route -> route.fulfill(new Route.FulfillOptions() - .setBody("").setContentType("text/html"))); + .setBody("").setContentType("text/html"))); page.route("**/iframe.html", route -> { route.fulfill(new Route.FulfillOptions().setBody("\n" + "
    \n" + @@ -263,4 +263,25 @@ public class TestLocatorFrame extends TestBase { assertThat(input4).hasValue(""); } + @Test + void locatorContentFrameShouldWork() { + routeIframe(page); + page.navigate(server.EMPTY_PAGE); + Locator locator = page.locator("iframe"); + FrameLocator frameLocator = locator.contentFrame(); + Locator button = frameLocator.locator("button"); + assertEquals("Hello iframe", button.innerText()); + assertThat(button).hasText("Hello iframe"); + button.click(); + } + + @Test + void frameLocatorOwnerShouldWork() { + routeIframe(page); + page.navigate(server.EMPTY_PAGE); + FrameLocator frameLocator = page.frameLocator("iframe"); + Locator locator = frameLocator.owner(); + assertThat(locator).isVisible(); + assertEquals("frame1", locator.getAttribute("name")); + } } diff --git a/playwright/src/test/java/com/microsoft/playwright/TestPageNetworkRequest.java b/playwright/src/test/java/com/microsoft/playwright/TestPageNetworkRequest.java index 2c0c95fe..a8c611f7 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestPageNetworkRequest.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestPageNetworkRequest.java @@ -134,4 +134,31 @@ public class TestPageNetworkRequest extends TestBase { assertTrue(request[0].isNavigationRequest()); assertTrue(error[0].getMessage().contains("Frame for this navigation request is not available"), error[0].getMessage()); } + + @Test + void shouldParseDataIfContentTypeIsApplicationFormUrlEncoded() { + page.navigate(server.EMPTY_PAGE); + server.setRoute("/post", exchange -> exchange.close()); + Request[] request = {null}; + page.onRequest(r -> request[0] = r); + page.setContent("
    "); + page.click("input[type=submit]"); + assertNotNull(request[0]); + assertEquals("foo=bar&baz=123", request[0].postData()); + } + + @Test + void shouldParseDataIfContentTypeIsApplicationFormUrlEncodedCharsetUTF8() { + page.navigate(server.EMPTY_PAGE); + Request request = page.waitForRequest("**/post", () -> { + page.evaluate("() => fetch('./post', {\n" + + " method: 'POST',\n" + + " headers: {\n" + + " 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8',\n" + + " },\n" + + " body: 'foo=bar&baz=123'\n" + + "})"); + }); + assertEquals("foo=bar&baz=123", request.postData()); + } } diff --git a/scripts/CLI_VERSION b/scripts/CLI_VERSION index e640847f..59c51def 100644 --- a/scripts/CLI_VERSION +++ b/scripts/CLI_VERSION @@ -1 +1 @@ -1.42.1 +1.43.0-beta-1711493485000 diff --git a/scripts/generate_api.sh b/scripts/generate_api.sh index f922529e..cc8c8c6c 100755 --- a/scripts/generate_api.sh +++ b/scripts/generate_api.sh @@ -9,13 +9,13 @@ cd "$(dirname "$0")/.." PLAYWRIGHT_CLI="unknown" case $(uname) in Darwin) - PLAYWRIGHT_CLI=./driver-bundle/src/main/resources/driver/mac/playwright.sh + PLAYWRIGHT_CLI=./driver-bundle/src/main/resources/driver/mac/package/cli.js ;; Linux) - PLAYWRIGHT_CLI=./driver-bundle/src/main/resources/driver/linux/playwright.sh + PLAYWRIGHT_CLI=./driver-bundle/src/main/resources/driver/linux/package/cli.js ;; MINGW64*) - PLAYWRIGHT_CLI=./driver-bundle/src/main/resources/driver/win32_x64/playwright.cmd + PLAYWRIGHT_CLI=./driver-bundle/src/main/resources/driver/win32_x64/package/cli.js ;; *) echo "Unknown platform '$(uname)'" @@ -25,7 +25,7 @@ esac echo "Updating api.json from $($PLAYWRIGHT_CLI --version)" -$PLAYWRIGHT_CLI print-api-json > ./tools/api-generator/src/main/resources/api.json +node $PLAYWRIGHT_CLI print-api-json > ./tools/api-generator/src/main/resources/api.json mvn compile -f ./tools/api-generator --no-transfer-progress