From f515d9f318e1130ad54b18c45bb61bd6ffae46b2 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Wed, 10 Nov 2021 09:09:04 -0800 Subject: [PATCH] feat: frame locators, roll driver (#695) --- README.md | 2 +- .../assertions/LocatorAssertions.java | 80 +++--- .../playwright/assertions/PageAssertions.java | 26 +- .../playwright/TestLocatorAssertions.java | 2 +- .../playwright/TestLocatorFrame.java | 254 ++++++++++++++++++ .../java/com/microsoft/playwright/Frame.java | 13 + .../microsoft/playwright/FrameLocator.java | 71 +++++ .../com/microsoft/playwright/Locator.java | 15 +- .../java/com/microsoft/playwright/Page.java | 13 + .../microsoft/playwright/impl/FrameImpl.java | 5 + .../playwright/impl/FrameLocatorImpl.java | 54 ++++ .../playwright/impl/LocatorImpl.java | 10 +- .../microsoft/playwright/impl/PageImpl.java | 5 + scripts/CLI_VERSION | 2 +- 14 files changed, 504 insertions(+), 48 deletions(-) create mode 100644 assertions/src/test/java/com/microsoft/playwright/TestLocatorFrame.java create mode 100644 playwright/src/main/java/com/microsoft/playwright/FrameLocator.java create mode 100644 playwright/src/main/java/com/microsoft/playwright/impl/FrameLocatorImpl.java diff --git a/README.md b/README.md index edcf38a8..1741e04a 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Playwright is a Java library to automate [Chromium](https://www.chromium.org/Hom | | Linux | macOS | Windows | | :--- | :---: | :---: | :---: | -| Chromium 97.0.4688.0 | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| Chromium 98.0.4695.0 | :white_check_mark: | :white_check_mark: | :white_check_mark: | | WebKit 15.4 | ✅ | ✅ | ✅ | | Firefox 94.0.1 | :white_check_mark: | :white_check_mark: | :white_check_mark: | diff --git a/assertions/src/main/java/com/microsoft/playwright/assertions/LocatorAssertions.java b/assertions/src/main/java/com/microsoft/playwright/assertions/LocatorAssertions.java index 12a60647..4a1e2066 100644 --- a/assertions/src/main/java/com/microsoft/playwright/assertions/LocatorAssertions.java +++ b/assertions/src/main/java/com/microsoft/playwright/assertions/LocatorAssertions.java @@ -23,7 +23,21 @@ import com.microsoft.playwright.Locator; /** * The {@code LocatorAssertions} class provides assertion methods that can be used to make assertions about the {@code Locator} state * in the tests. A new instance of {@code LocatorAssertions} is created by calling {@link PlaywrightAssertions#assertThat - * PlaywrightAssertions.assertThat()}. + * PlaywrightAssertions.assertThat()}: + *
{@code
+ * ...
+ * import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
+ *
+ * public class TestLocator {
+ *   ...
+ *   @Test
+ *   void statusBecomesSubmitted() {
+ *     ...
+ *     page.click("#submit-button");
+ *     assertThat(page.locator(".status")).hasText("Submitted");
+ *   }
+ * }
+ * }
*/ public interface LocatorAssertions { class ContainsTextOptions { @@ -295,7 +309,7 @@ public interface LocatorAssertions { * *

Note that if array is passed as an expected value, entire lists can be asserted: *

{@code
-   * assertThat(page.locator("list > .list-item")).containsText(Arrays.asList("Text 1", "Text 4", "Text 5"));
+   * assertThat(page.locator("list > .list-item")).containsText(new String[] {"Text 1", "Text 4", "Text 5"});
    * }
* * @param expected Expected substring or RegExp or a list of those. @@ -312,7 +326,7 @@ public interface LocatorAssertions { * *

Note that if array is passed as an expected value, entire lists can be asserted: *

{@code
-   * assertThat(page.locator("list > .list-item")).containsText(Arrays.asList("Text 1", "Text 4", "Text 5"));
+   * assertThat(page.locator("list > .list-item")).containsText(new String[] {"Text 1", "Text 4", "Text 5"});
    * }
* * @param expected Expected substring or RegExp or a list of those. @@ -327,7 +341,7 @@ public interface LocatorAssertions { * *

Note that if array is passed as an expected value, entire lists can be asserted: *

{@code
-   * assertThat(page.locator("list > .list-item")).containsText(Arrays.asList("Text 1", "Text 4", "Text 5"));
+   * assertThat(page.locator("list > .list-item")).containsText(new String[] {"Text 1", "Text 4", "Text 5"});
    * }
* * @param expected Expected substring or RegExp or a list of those. @@ -344,7 +358,7 @@ public interface LocatorAssertions { * *

Note that if array is passed as an expected value, entire lists can be asserted: *

{@code
-   * assertThat(page.locator("list > .list-item")).containsText(Arrays.asList("Text 1", "Text 4", "Text 5"));
+   * assertThat(page.locator("list > .list-item")).containsText(new String[] {"Text 1", "Text 4", "Text 5"});
    * }
* * @param expected Expected substring or RegExp or a list of those. @@ -359,7 +373,7 @@ public interface LocatorAssertions { * *

Note that if array is passed as an expected value, entire lists can be asserted: *

{@code
-   * assertThat(page.locator("list > .list-item")).containsText(Arrays.asList("Text 1", "Text 4", "Text 5"));
+   * assertThat(page.locator("list > .list-item")).containsText(new String[] {"Text 1", "Text 4", "Text 5"});
    * }
* * @param expected Expected substring or RegExp or a list of those. @@ -376,7 +390,7 @@ public interface LocatorAssertions { * *

Note that if array is passed as an expected value, entire lists can be asserted: *

{@code
-   * assertThat(page.locator("list > .list-item")).containsText(Arrays.asList("Text 1", "Text 4", "Text 5"));
+   * assertThat(page.locator("list > .list-item")).containsText(new String[] {"Text 1", "Text 4", "Text 5"});
    * }
* * @param expected Expected substring or RegExp or a list of those. @@ -391,7 +405,7 @@ public interface LocatorAssertions { * *

Note that if array is passed as an expected value, entire lists can be asserted: *

{@code
-   * assertThat(page.locator("list > .list-item")).containsText(Arrays.asList("Text 1", "Text 4", "Text 5"));
+   * assertThat(page.locator("list > .list-item")).containsText(new String[] {"Text 1", "Text 4", "Text 5"});
    * }
* * @param expected Expected substring or RegExp or a list of those. @@ -408,7 +422,7 @@ public interface LocatorAssertions { * *

Note that if array is passed as an expected value, entire lists can be asserted: *

{@code
-   * assertThat(page.locator("list > .list-item")).containsText(Arrays.asList("Text 1", "Text 4", "Text 5"));
+   * assertThat(page.locator("list > .list-item")).containsText(new String[] {"Text 1", "Text 4", "Text 5"});
    * }
* * @param expected Expected substring or RegExp or a list of those. @@ -466,7 +480,7 @@ public interface LocatorAssertions { * *

Note that if array is passed as an expected value, entire lists can be asserted: *

{@code
-   * assertThat(page.locator("list > .component")).hasClass(Arrays.asList("component", "component selected", "component"));
+   * assertThat(page.locator("list > .component")).hasClass(new String[] {"component", "component selected", "component"});
    * }
* * @param expected Expected class or RegExp or a list of those. @@ -482,7 +496,7 @@ public interface LocatorAssertions { * *

Note that if array is passed as an expected value, entire lists can be asserted: *

{@code
-   * assertThat(page.locator("list > .component")).hasClass(Arrays.asList("component", "component selected", "component"));
+   * assertThat(page.locator("list > .component")).hasClass(new String[] {"component", "component selected", "component"});
    * }
* * @param expected Expected class or RegExp or a list of those. @@ -496,7 +510,7 @@ public interface LocatorAssertions { * *

Note that if array is passed as an expected value, entire lists can be asserted: *

{@code
-   * assertThat(page.locator("list > .component")).hasClass(Arrays.asList("component", "component selected", "component"));
+   * assertThat(page.locator("list > .component")).hasClass(new String[] {"component", "component selected", "component"});
    * }
* * @param expected Expected class or RegExp or a list of those. @@ -512,7 +526,7 @@ public interface LocatorAssertions { * *

Note that if array is passed as an expected value, entire lists can be asserted: *

{@code
-   * assertThat(page.locator("list > .component")).hasClass(Arrays.asList("component", "component selected", "component"));
+   * assertThat(page.locator("list > .component")).hasClass(new String[] {"component", "component selected", "component"});
    * }
* * @param expected Expected class or RegExp or a list of those. @@ -526,7 +540,7 @@ public interface LocatorAssertions { * *

Note that if array is passed as an expected value, entire lists can be asserted: *

{@code
-   * assertThat(page.locator("list > .component")).hasClass(Arrays.asList("component", "component selected", "component"));
+   * assertThat(page.locator("list > .component")).hasClass(new String[] {"component", "component selected", "component"});
    * }
* * @param expected Expected class or RegExp or a list of those. @@ -542,7 +556,7 @@ public interface LocatorAssertions { * *

Note that if array is passed as an expected value, entire lists can be asserted: *

{@code
-   * assertThat(page.locator("list > .component")).hasClass(Arrays.asList("component", "component selected", "component"));
+   * assertThat(page.locator("list > .component")).hasClass(new String[] {"component", "component selected", "component"});
    * }
* * @param expected Expected class or RegExp or a list of those. @@ -556,7 +570,7 @@ public interface LocatorAssertions { * *

Note that if array is passed as an expected value, entire lists can be asserted: *

{@code
-   * assertThat(page.locator("list > .component")).hasClass(Arrays.asList("component", "component selected", "component"));
+   * assertThat(page.locator("list > .component")).hasClass(new String[] {"component", "component selected", "component"});
    * }
* * @param expected Expected class or RegExp or a list of those. @@ -572,7 +586,7 @@ public interface LocatorAssertions { * *

Note that if array is passed as an expected value, entire lists can be asserted: *

{@code
-   * assertThat(page.locator("list > .component")).hasClass(Arrays.asList("component", "component selected", "component"));
+   * assertThat(page.locator("list > .component")).hasClass(new String[] {"component", "component selected", "component"});
    * }
* * @param expected Expected class or RegExp or a list of those. @@ -689,13 +703,13 @@ public interface LocatorAssertions { /** * Ensures the {@code Locator} points to an element with the given text. You can use regular expressions for the value as well. *
{@code
-   * assertThat(page.locator(".title")).hasText(Pattern.compile("Welcome, Test User"));
+   * assertThat(page.locator(".title")).hasText("Welcome, Test User");
    * assertThat(page.locator(".title")).hasText(Pattern.compile("Welcome, .*"));
    * }
* *

Note that if array is passed as an expected value, entire lists can be asserted: *

{@code
-   * assertThat(page.locator("list > .component")).hasText(Arrays.asList("Text 1", "Text 2", "Text 3"));
+   * assertThat(page.locator("list > .component")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
    * }
* * @param expected Expected substring or RegExp or a list of those. @@ -706,13 +720,13 @@ public interface LocatorAssertions { /** * Ensures the {@code Locator} points to an element with the given text. You can use regular expressions for the value as well. *
{@code
-   * assertThat(page.locator(".title")).hasText(Pattern.compile("Welcome, Test User"));
+   * assertThat(page.locator(".title")).hasText("Welcome, Test User");
    * assertThat(page.locator(".title")).hasText(Pattern.compile("Welcome, .*"));
    * }
* *

Note that if array is passed as an expected value, entire lists can be asserted: *

{@code
-   * assertThat(page.locator("list > .component")).hasText(Arrays.asList("Text 1", "Text 2", "Text 3"));
+   * assertThat(page.locator("list > .component")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
    * }
* * @param expected Expected substring or RegExp or a list of those. @@ -721,13 +735,13 @@ public interface LocatorAssertions { /** * Ensures the {@code Locator} points to an element with the given text. You can use regular expressions for the value as well. *
{@code
-   * assertThat(page.locator(".title")).hasText(Pattern.compile("Welcome, Test User"));
+   * assertThat(page.locator(".title")).hasText("Welcome, Test User");
    * assertThat(page.locator(".title")).hasText(Pattern.compile("Welcome, .*"));
    * }
* *

Note that if array is passed as an expected value, entire lists can be asserted: *

{@code
-   * assertThat(page.locator("list > .component")).hasText(Arrays.asList("Text 1", "Text 2", "Text 3"));
+   * assertThat(page.locator("list > .component")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
    * }
* * @param expected Expected substring or RegExp or a list of those. @@ -738,13 +752,13 @@ public interface LocatorAssertions { /** * Ensures the {@code Locator} points to an element with the given text. You can use regular expressions for the value as well. *
{@code
-   * assertThat(page.locator(".title")).hasText(Pattern.compile("Welcome, Test User"));
+   * assertThat(page.locator(".title")).hasText("Welcome, Test User");
    * assertThat(page.locator(".title")).hasText(Pattern.compile("Welcome, .*"));
    * }
* *

Note that if array is passed as an expected value, entire lists can be asserted: *

{@code
-   * assertThat(page.locator("list > .component")).hasText(Arrays.asList("Text 1", "Text 2", "Text 3"));
+   * assertThat(page.locator("list > .component")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
    * }
* * @param expected Expected substring or RegExp or a list of those. @@ -753,13 +767,13 @@ public interface LocatorAssertions { /** * Ensures the {@code Locator} points to an element with the given text. You can use regular expressions for the value as well. *
{@code
-   * assertThat(page.locator(".title")).hasText(Pattern.compile("Welcome, Test User"));
+   * assertThat(page.locator(".title")).hasText("Welcome, Test User");
    * assertThat(page.locator(".title")).hasText(Pattern.compile("Welcome, .*"));
    * }
* *

Note that if array is passed as an expected value, entire lists can be asserted: *

{@code
-   * assertThat(page.locator("list > .component")).hasText(Arrays.asList("Text 1", "Text 2", "Text 3"));
+   * assertThat(page.locator("list > .component")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
    * }
* * @param expected Expected substring or RegExp or a list of those. @@ -770,13 +784,13 @@ public interface LocatorAssertions { /** * Ensures the {@code Locator} points to an element with the given text. You can use regular expressions for the value as well. *
{@code
-   * assertThat(page.locator(".title")).hasText(Pattern.compile("Welcome, Test User"));
+   * assertThat(page.locator(".title")).hasText("Welcome, Test User");
    * assertThat(page.locator(".title")).hasText(Pattern.compile("Welcome, .*"));
    * }
* *

Note that if array is passed as an expected value, entire lists can be asserted: *

{@code
-   * assertThat(page.locator("list > .component")).hasText(Arrays.asList("Text 1", "Text 2", "Text 3"));
+   * assertThat(page.locator("list > .component")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
    * }
* * @param expected Expected substring or RegExp or a list of those. @@ -785,13 +799,13 @@ public interface LocatorAssertions { /** * Ensures the {@code Locator} points to an element with the given text. You can use regular expressions for the value as well. *
{@code
-   * assertThat(page.locator(".title")).hasText(Pattern.compile("Welcome, Test User"));
+   * assertThat(page.locator(".title")).hasText("Welcome, Test User");
    * assertThat(page.locator(".title")).hasText(Pattern.compile("Welcome, .*"));
    * }
* *

Note that if array is passed as an expected value, entire lists can be asserted: *

{@code
-   * assertThat(page.locator("list > .component")).hasText(Arrays.asList("Text 1", "Text 2", "Text 3"));
+   * assertThat(page.locator("list > .component")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
    * }
* * @param expected Expected substring or RegExp or a list of those. @@ -802,13 +816,13 @@ public interface LocatorAssertions { /** * Ensures the {@code Locator} points to an element with the given text. You can use regular expressions for the value as well. *
{@code
-   * assertThat(page.locator(".title")).hasText(Pattern.compile("Welcome, Test User"));
+   * assertThat(page.locator(".title")).hasText("Welcome, Test User");
    * assertThat(page.locator(".title")).hasText(Pattern.compile("Welcome, .*"));
    * }
* *

Note that if array is passed as an expected value, entire lists can be asserted: *

{@code
-   * assertThat(page.locator("list > .component")).hasText(Arrays.asList("Text 1", "Text 2", "Text 3"));
+   * assertThat(page.locator("list > .component")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
    * }
* * @param expected Expected substring or RegExp or a list of those. diff --git a/assertions/src/main/java/com/microsoft/playwright/assertions/PageAssertions.java b/assertions/src/main/java/com/microsoft/playwright/assertions/PageAssertions.java index 009112b2..cda9d25a 100644 --- a/assertions/src/main/java/com/microsoft/playwright/assertions/PageAssertions.java +++ b/assertions/src/main/java/com/microsoft/playwright/assertions/PageAssertions.java @@ -23,7 +23,21 @@ import com.microsoft.playwright.Page; /** * The {@code PageAssertions} class provides assertion methods that can be used to make assertions about the {@code Page} state in the * tests. A new instance of {@code LocatorAssertions} is created by calling {@link PlaywrightAssertions#assertThat - * PlaywrightAssertions.assertThat()}. + * PlaywrightAssertions.assertThat()}: + *
{@code
+ * ...
+ * import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
+ *
+ * public class TestPage {
+ *   ...
+ *   @Test
+ *   void navigatesToLoginPage() {
+ *     ...
+ *     page.click("#login");
+ *     assertThat(page).hasURL(Pattern.compile(".*\/login"));
+ *   }
+ * }
+ * }
*/ public interface PageAssertions { class HasTitleOptions { @@ -97,7 +111,7 @@ public interface PageAssertions { /** * Ensures the page is navigated to the given URL. *
{@code
-   * assertThat(page).hasURL('.com');
+   * assertThat(page).hasURL(".com");
    * }
* * @param urlOrRegExp Expected substring or RegExp. @@ -108,7 +122,7 @@ public interface PageAssertions { /** * Ensures the page is navigated to the given URL. *
{@code
-   * assertThat(page).hasURL('.com');
+   * assertThat(page).hasURL(".com");
    * }
* * @param urlOrRegExp Expected substring or RegExp. @@ -117,7 +131,7 @@ public interface PageAssertions { /** * Ensures the page is navigated to the given URL. *
{@code
-   * assertThat(page).hasURL('.com');
+   * assertThat(page).hasURL(".com");
    * }
* * @param urlOrRegExp Expected substring or RegExp. @@ -128,7 +142,7 @@ public interface PageAssertions { /** * Ensures the page is navigated to the given URL. *
{@code
-   * assertThat(page).hasURL('.com');
+   * assertThat(page).hasURL(".com");
    * }
* * @param urlOrRegExp Expected substring or RegExp. @@ -138,7 +152,7 @@ public interface PageAssertions { * Makes the assertion check for the opposite condition. For example, this code tests that the page URL doesn't contain * {@code "error"}: *
{@code
-   * assertThat(page).not().hasURL('error');
+   * assertThat(page).not().hasURL("error");
    * }
*/ PageAssertions not(); diff --git a/assertions/src/test/java/com/microsoft/playwright/TestLocatorAssertions.java b/assertions/src/test/java/com/microsoft/playwright/TestLocatorAssertions.java index 9de2720d..978c9b54 100644 --- a/assertions/src/test/java/com/microsoft/playwright/TestLocatorAssertions.java +++ b/assertions/src/test/java/com/microsoft/playwright/TestLocatorAssertions.java @@ -160,7 +160,7 @@ public class TestLocatorAssertions extends TestBase { fail("did not throw"); } catch (AssertionFailedError e) { assertEquals("[]", e.getExpected().getStringRepresentation()); - assertEquals("[]", e.getActual().getStringRepresentation()); + assertEquals("null", e.getActual().getStringRepresentation()); assertTrue(e.getMessage().contains("Locator expected not to have text"), e.getMessage()); } } diff --git a/assertions/src/test/java/com/microsoft/playwright/TestLocatorFrame.java b/assertions/src/test/java/com/microsoft/playwright/TestLocatorFrame.java new file mode 100644 index 00000000..04eb70c4 --- /dev/null +++ b/assertions/src/test/java/com/microsoft/playwright/TestLocatorFrame.java @@ -0,0 +1,254 @@ +/* + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.microsoft.playwright; + +import org.junit.jupiter.api.Test; + +import java.net.MalformedURLException; +import java.net.URL; + +import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat; +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"))); + page.route("**/iframe.html", route -> { + route.fulfill(new Route.FulfillOptions().setBody("\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + " 1\n" + + " 2\n" + + " ").setContentType("text/html")); + }); + page.route("**/iframe-2.html", route -> { + route.fulfill(new Route.FulfillOptions().setBody("").setContentType("text/html")); + }); + } + + private static void routeAmbiguous(Page page) { + page.route("**/empty.html", route -> { + route.fulfill(new Route.FulfillOptions() + .setBody("\n" + + "\n" + + "") + .setContentType("text/html")); + }); + page.route("**/iframe-*", route -> { + try { + String path = new URL(route.request().url()).getPath().substring(1); + route.fulfill(new Route.FulfillOptions() + .setBody("") + .setContentType("text/html")); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + }); + } + + @Test + void shouldWorkForIframe() { + routeIframe(page); + page.navigate(server.EMPTY_PAGE); + Locator button = page.frameLocator("iframe").locator("button"); + button.waitFor(); + assertEquals("Hello iframe", button.innerText()); + assertThat(button).hasText("Hello iframe"); + button.click(); + } + + @Test + void shouldWorkForNestedIframe() { + routeIframe(page); + page.navigate(server.EMPTY_PAGE); + Locator button = page.frameLocator("iframe").frameLocator("iframe").locator("button"); + button.waitFor(); + assertEquals("Hello nested iframe", button.innerText()); + assertThat(button).hasText("Hello nested iframe"); + button.click(); + } + + @Test + void shouldWorkForAnd() { + routeIframe(page); + page.navigate(server.EMPTY_PAGE); + Locator locator = page.frameLocator("iframe").locator("button"); + assertThat(locator).hasText("Hello iframe"); + assertEquals("Hello iframe", locator.innerText()); + Locator spans = page.frameLocator("iframe").locator("span"); + assertThat(spans).hasCount(2); + } + + @Test + void shouldWaitForFrame() { + page.navigate(server.EMPTY_PAGE); + try { + page.frameLocator("iframe").locator("span").click(new Locator.ClickOptions().setTimeout(300)); + fail("did not throw"); + } catch (PlaywrightException e) { + assertTrue(e.getMessage().contains("waiting for frame \"iframe\""), e.getMessage()); + } + } + + @Test + void shouldWaitForFrame2() { + routeIframe(page); + page.evaluate("url => setTimeout(() => location.href = url, 300)", server.EMPTY_PAGE); + page.frameLocator("iframe").locator("button").click(); + } + + void shouldWaitForFrameToGo() { + } + + @Test + void shouldNotWaitForFrame() { + page.navigate(server.EMPTY_PAGE); + assertThat(page.frameLocator("iframe").locator("span")).isHidden(); + } + + @Test + void shouldNotWaitForFrame2() { + page.navigate(server.EMPTY_PAGE); + assertThat(page.frameLocator("iframe").locator("span")).not().isVisible(); + } + + @Test + void shouldNotWaitForFrame3() { + page.navigate(server.EMPTY_PAGE); + assertThat(page.frameLocator("iframe").locator("span")).hasCount(0); + } + + @Test + void shouldClickInLazyIframe() { + page.route("**/iframe.html", route -> { + route.fulfill(new Route.FulfillOptions().setBody("").setContentType("text/html")); + }); + // empty pge + page.navigate(server.EMPTY_PAGE); + + // add blank iframe + page.evaluate("setTimeout(() => {\n" + + " const iframe = document.createElement('iframe');\n" + + " document.body.appendChild(iframe);\n" + + " // navigate iframe\n" + + " setTimeout(() => iframe.src = 'iframe.html', 500);\n" + + " }, 500);"); + // Click in iframe + Locator button = page.frameLocator("iframe").locator("button"); + button.click(); + assertThat(button).hasText("Hello iframe"); + assertEquals("Hello iframe", button.innerText()); + } + + @Test + void waitForShouldSurviveFrameReattach() { + routeIframe(page); + page.navigate(server.EMPTY_PAGE); + Locator button = page.frameLocator("iframe").locator("button:has-text('Hello nested iframe')"); + page.evaluate("setTimeout(() => {\n" + + " document.querySelector('iframe').remove();\n" + + " setTimeout(() => {\n" + + " const iframe = document.createElement('iframe');\n" + + " iframe.src = 'iframe-2.html';\n" + + " document.body.appendChild(iframe);\n" + + " }, 500);\n" + + " }, 500);"); + button.waitFor(); + } + + @Test + void clickShouldSurviveFrameReattach() { + routeIframe(page); + page.navigate(server.EMPTY_PAGE); + Locator button = page.frameLocator("iframe").locator("button:has-text('Hello nested iframe')"); + + page.evaluate("setTimeout(() => {\n" + + " document.querySelector('iframe').remove();\n" + + " setTimeout(() => {\n" + + " const iframe = document.createElement('iframe');\n" + + " iframe.src = 'iframe-2.html';\n" + + " document.body.appendChild(iframe);\n" + + " }, 500);\n" + + " }, 500);"); + button.click(); + } + + @Test + void clickShouldSurviveIframeNavigation() { + routeIframe(page); + page.navigate(server.EMPTY_PAGE); + Locator button = page.frameLocator("iframe").locator("button:has-text('Hello nested iframe')"); + page.evaluate("setTimeout(() => {\n" + + " document.querySelector('iframe').src = 'iframe-2.html';\n" + + " }, 500);"); + button.click(); + } + + @Test + void shouldNonWorkForNonFrame() { + routeIframe(page); + page.setContent("
"); + Locator button = page.frameLocator("div").locator("button"); + try { + button.waitFor(); + fail("did not throw"); + } catch (PlaywrightException e) { + assertTrue(e.getMessage().contains("
"), e.getMessage()); + assertTrue(e.getMessage().contains("