1
0
mirror of synced 2026-07-07 00:50:01 +00:00

feat: frame locators, roll driver (#695)

This commit is contained in:
Yury Semikhatsky
2021-11-10 09:09:04 -08:00
committed by GitHub
parent 2f706012a7
commit f515d9f318
14 changed files with 504 additions and 48 deletions
+1 -1
View File
@@ -11,7 +11,7 @@ Playwright is a Java library to automate [Chromium](https://www.chromium.org/Hom
| | Linux | macOS | Windows |
| :--- | :---: | :---: | :---: |
| Chromium <!-- GEN:chromium-version -->97.0.4688.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Chromium <!-- GEN:chromium-version -->98.0.4695.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| WebKit <!-- GEN:webkit-version -->15.4<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Firefox <!-- GEN:firefox-version -->94.0.1<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
@@ -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()}:
* <pre>{@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");
* }
* }
* }</pre>
*/
public interface LocatorAssertions {
class ContainsTextOptions {
@@ -295,7 +309,7 @@ public interface LocatorAssertions {
*
* <p> Note that if array is passed as an expected value, entire lists can be asserted:
* <pre>{@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"});
* }</pre>
*
* @param expected Expected substring or RegExp or a list of those.
@@ -312,7 +326,7 @@ public interface LocatorAssertions {
*
* <p> Note that if array is passed as an expected value, entire lists can be asserted:
* <pre>{@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"});
* }</pre>
*
* @param expected Expected substring or RegExp or a list of those.
@@ -327,7 +341,7 @@ public interface LocatorAssertions {
*
* <p> Note that if array is passed as an expected value, entire lists can be asserted:
* <pre>{@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"});
* }</pre>
*
* @param expected Expected substring or RegExp or a list of those.
@@ -344,7 +358,7 @@ public interface LocatorAssertions {
*
* <p> Note that if array is passed as an expected value, entire lists can be asserted:
* <pre>{@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"});
* }</pre>
*
* @param expected Expected substring or RegExp or a list of those.
@@ -359,7 +373,7 @@ public interface LocatorAssertions {
*
* <p> Note that if array is passed as an expected value, entire lists can be asserted:
* <pre>{@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"});
* }</pre>
*
* @param expected Expected substring or RegExp or a list of those.
@@ -376,7 +390,7 @@ public interface LocatorAssertions {
*
* <p> Note that if array is passed as an expected value, entire lists can be asserted:
* <pre>{@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"});
* }</pre>
*
* @param expected Expected substring or RegExp or a list of those.
@@ -391,7 +405,7 @@ public interface LocatorAssertions {
*
* <p> Note that if array is passed as an expected value, entire lists can be asserted:
* <pre>{@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"});
* }</pre>
*
* @param expected Expected substring or RegExp or a list of those.
@@ -408,7 +422,7 @@ public interface LocatorAssertions {
*
* <p> Note that if array is passed as an expected value, entire lists can be asserted:
* <pre>{@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"});
* }</pre>
*
* @param expected Expected substring or RegExp or a list of those.
@@ -466,7 +480,7 @@ public interface LocatorAssertions {
*
* <p> Note that if array is passed as an expected value, entire lists can be asserted:
* <pre>{@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"});
* }</pre>
*
* @param expected Expected class or RegExp or a list of those.
@@ -482,7 +496,7 @@ public interface LocatorAssertions {
*
* <p> Note that if array is passed as an expected value, entire lists can be asserted:
* <pre>{@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"});
* }</pre>
*
* @param expected Expected class or RegExp or a list of those.
@@ -496,7 +510,7 @@ public interface LocatorAssertions {
*
* <p> Note that if array is passed as an expected value, entire lists can be asserted:
* <pre>{@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"});
* }</pre>
*
* @param expected Expected class or RegExp or a list of those.
@@ -512,7 +526,7 @@ public interface LocatorAssertions {
*
* <p> Note that if array is passed as an expected value, entire lists can be asserted:
* <pre>{@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"});
* }</pre>
*
* @param expected Expected class or RegExp or a list of those.
@@ -526,7 +540,7 @@ public interface LocatorAssertions {
*
* <p> Note that if array is passed as an expected value, entire lists can be asserted:
* <pre>{@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"});
* }</pre>
*
* @param expected Expected class or RegExp or a list of those.
@@ -542,7 +556,7 @@ public interface LocatorAssertions {
*
* <p> Note that if array is passed as an expected value, entire lists can be asserted:
* <pre>{@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"});
* }</pre>
*
* @param expected Expected class or RegExp or a list of those.
@@ -556,7 +570,7 @@ public interface LocatorAssertions {
*
* <p> Note that if array is passed as an expected value, entire lists can be asserted:
* <pre>{@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"});
* }</pre>
*
* @param expected Expected class or RegExp or a list of those.
@@ -572,7 +586,7 @@ public interface LocatorAssertions {
*
* <p> Note that if array is passed as an expected value, entire lists can be asserted:
* <pre>{@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"});
* }</pre>
*
* @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.
* <pre>{@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, .*"));
* }</pre>
*
* <p> Note that if array is passed as an expected value, entire lists can be asserted:
* <pre>{@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"});
* }</pre>
*
* @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.
* <pre>{@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, .*"));
* }</pre>
*
* <p> Note that if array is passed as an expected value, entire lists can be asserted:
* <pre>{@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"});
* }</pre>
*
* @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.
* <pre>{@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, .*"));
* }</pre>
*
* <p> Note that if array is passed as an expected value, entire lists can be asserted:
* <pre>{@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"});
* }</pre>
*
* @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.
* <pre>{@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, .*"));
* }</pre>
*
* <p> Note that if array is passed as an expected value, entire lists can be asserted:
* <pre>{@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"});
* }</pre>
*
* @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.
* <pre>{@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, .*"));
* }</pre>
*
* <p> Note that if array is passed as an expected value, entire lists can be asserted:
* <pre>{@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"});
* }</pre>
*
* @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.
* <pre>{@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, .*"));
* }</pre>
*
* <p> Note that if array is passed as an expected value, entire lists can be asserted:
* <pre>{@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"});
* }</pre>
*
* @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.
* <pre>{@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, .*"));
* }</pre>
*
* <p> Note that if array is passed as an expected value, entire lists can be asserted:
* <pre>{@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"});
* }</pre>
*
* @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.
* <pre>{@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, .*"));
* }</pre>
*
* <p> Note that if array is passed as an expected value, entire lists can be asserted:
* <pre>{@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"});
* }</pre>
*
* @param expected Expected substring or RegExp or a list of those.
@@ -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()}:
* <pre>{@code
* ...
* import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
*
* public class TestPage {
* ...
* @Test
* void navigatesToLoginPage() {
* ...
* page.click("#login");
* assertThat(page).hasURL(Pattern.compile(".*\/login"));
* }
* }
* }</pre>
*/
public interface PageAssertions {
class HasTitleOptions {
@@ -97,7 +111,7 @@ public interface PageAssertions {
/**
* Ensures the page is navigated to the given URL.
* <pre>{@code
* assertThat(page).hasURL('.com');
* assertThat(page).hasURL(".com");
* }</pre>
*
* @param urlOrRegExp Expected substring or RegExp.
@@ -108,7 +122,7 @@ public interface PageAssertions {
/**
* Ensures the page is navigated to the given URL.
* <pre>{@code
* assertThat(page).hasURL('.com');
* assertThat(page).hasURL(".com");
* }</pre>
*
* @param urlOrRegExp Expected substring or RegExp.
@@ -117,7 +131,7 @@ public interface PageAssertions {
/**
* Ensures the page is navigated to the given URL.
* <pre>{@code
* assertThat(page).hasURL('.com');
* assertThat(page).hasURL(".com");
* }</pre>
*
* @param urlOrRegExp Expected substring or RegExp.
@@ -128,7 +142,7 @@ public interface PageAssertions {
/**
* Ensures the page is navigated to the given URL.
* <pre>{@code
* assertThat(page).hasURL('.com');
* assertThat(page).hasURL(".com");
* }</pre>
*
* @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"}:
* <pre>{@code
* assertThat(page).not().hasURL('error');
* assertThat(page).not().hasURL("error");
* }</pre>
*/
PageAssertions not();
@@ -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());
}
}
@@ -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("<iframe src='iframe.html'></iframe>").setContentType("text/html")));
page.route("**/iframe.html", route -> {
route.fulfill(new Route.FulfillOptions().setBody("<html>\n" +
" <div>\n" +
" <button>Hello iframe</button>\n" +
" <iframe src='iframe-2.html'></iframe>\n" +
" </div>\n" +
" <span>1</span>\n" +
" <span>2</span>\n" +
" </html>").setContentType("text/html"));
});
page.route("**/iframe-2.html", route -> {
route.fulfill(new Route.FulfillOptions().setBody("<html><button>Hello nested iframe</button></html>").setContentType("text/html"));
});
}
private static void routeAmbiguous(Page page) {
page.route("**/empty.html", route -> {
route.fulfill(new Route.FulfillOptions()
.setBody("<iframe src='iframe-1.html'></iframe>\n" +
"<iframe src='iframe-2.html'></iframe>\n" +
"<iframe src='iframe-3.html'></iframe>")
.setContentType("text/html"));
});
page.route("**/iframe-*", route -> {
try {
String path = new URL(route.request().url()).getPath().substring(1);
route.fulfill(new Route.FulfillOptions()
.setBody("<html><button>Hello from " + path + "</button></html>")
.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("<html><button>Hello iframe</button></html>").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("<div></div>");
Locator button = page.frameLocator("div").locator("button");
try {
button.waitFor();
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("<div></div>"), e.getMessage());
assertTrue(e.getMessage().contains("<iframe> was expected"), e.getMessage());
}
}
@Test
void locatorFrameLocatorShouldWorkForIframe() {
routeIframe(page);
page.navigate(server.EMPTY_PAGE);
Locator button = page.locator("body").frameLocator("iframe").locator("button");
button.waitFor();
assertThat(button).hasText("Hello iframe");
assertEquals("Hello iframe", button.innerText());
button.click();
}
@Test
void locatorFrameLocatorShouldThrowOnAmbiguity() {
routeAmbiguous(page);
page.navigate(server.EMPTY_PAGE);
Locator button = page.locator("body").frameLocator("iframe").locator("button");
try {
button.waitFor();
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Error: strict mode violation: \"body >> iframe\" resolved to 3 elements"), e.getMessage());
}
}
@Test
void locatorFrameLocatorShouldNotThrowOnFirstLastNth() {
routeAmbiguous(page);
page.navigate(server.EMPTY_PAGE);
Locator button1 = page.locator("body").frameLocator("iframe").first().locator("button");
assertThat(button1).hasText("Hello from iframe-1.html");
Locator button2 = page.locator("body").frameLocator("iframe").nth(1).locator("button");
assertThat(button2).hasText("Hello from iframe-2.html");
Locator button3 = page.locator("body").frameLocator("iframe").last().locator("button");
assertThat(button3).hasText("Hello from iframe-3.html");
}
}
@@ -2653,6 +2653,19 @@ public interface Frame {
* }</pre>
*/
ElementHandle frameElement();
/**
* When working with iframes, you can create a frame locator that will enter the iframe and allow selecting elements in
* that iframe. Following snippet locates element with text "Submit" in the iframe with id {@code my-frame}, like {@code <iframe
* id="my-frame">}:
* <pre>{@code
* Locator locator = frame.frameLocator("#my-iframe").locator("text=Submit");
* locator.click();
* }</pre>
*
* @param selector A selector to use when resolving DOM element. See <a href="https://playwright.dev/java/docs/selectors/">working with
* selectors</a> for more details.
*/
FrameLocator frameLocator(String selector);
/**
* Returns element attribute value.
*
@@ -0,0 +1,71 @@
/*
* 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 java.util.*;
/**
* FrameLocator represents a view to the {@code iframe} on the page. It captures the logic sufficient to retrieve the {@code iframe}
* and locate elements in that iframe. FrameLocator can be created with either {@link Page#frameLocator
* Page.frameLocator()} or {@link Locator#frameLocator Locator.frameLocator()} method.
* <pre>{@code
* Locator locator = page.frameLocator("#my-frame").locator("text=Submit");
* locator.click();
* }</pre>
*
* <p> **Strictness**
*
* <p> Frame locators are strict. This means that all operations on frame locators will throw if more than one element matches
* given selector.
* <pre>{@code
* // Throws if there are several frames in DOM:
* page.frame_locator(".result-frame").locator("button").click();
*
* // Works because we explicitly tell locator to pick the first frame:
* page.frame_locator(".result-frame").first().locator("button").click();
* }</pre>
*/
public interface FrameLocator {
/**
* Returns locator to the first matching frame.
*/
FrameLocator first();
/**
* When working with iframes, you can create a frame locator that will enter the iframe and allow selecting elements in
* that iframe.
*
* @param selector A selector to use when resolving DOM element. See <a href="https://playwright.dev/java/docs/selectors/">working with
* selectors</a> for more details.
*/
FrameLocator frameLocator(String selector);
/**
* Returns locator to the last matching frame.
*/
FrameLocator last();
/**
* The method finds an element matching the specified selector in the FrameLocator's subtree.
*
* @param selector A selector to use when resolving DOM element. See <a href="https://playwright.dev/java/docs/selectors/">working with
* selectors</a> for more details.
*/
Locator locator(String selector);
/**
* Returns locator to the n-th matching frame.
*/
FrameLocator nth(int index);
}
@@ -1898,6 +1898,18 @@ public interface Locator {
* Calls <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus">focus</a> on the element.
*/
void focus(FocusOptions options);
/**
* When working with iframes, you can create a frame locator that will enter the iframe and allow selecting elements in
* that iframe:
* <pre>{@code
* Locator locator = page.frameLocator("iframe").locator("text=Submit");
* locator.click();
* }</pre>
*
* @param selector A selector to use when resolving DOM element. See <a href="https://playwright.dev/java/docs/selectors/">working with
* selectors</a> for more details.
*/
FrameLocator frameLocator(String selector);
/**
* Returns element attribute value.
*
@@ -2045,8 +2057,7 @@ public interface Locator {
*/
Locator last();
/**
* The method finds an element matching the specified selector in the {@code Locator}'s subtree. See <a
* href="https://playwright.dev/java/docs/selectors/">Working with selectors</a> for more details.
* The method finds an element matching the specified selector in the {@code Locator}'s subtree.
*
* @param selector A selector to use when resolving DOM element. See <a href="https://playwright.dev/java/docs/selectors/">working with
* selectors</a> for more details.
@@ -3993,6 +3993,19 @@ public interface Page extends AutoCloseable {
* @param url A glob pattern, regex pattern or predicate receiving frame's {@code url} as a [URL] object.
*/
Frame frameByUrl(Predicate<String> url);
/**
* When working with iframes, you can create a frame locator that will enter the iframe and allow selecting elements in
* that iframe. Following snippet locates element with text "Submit" in the iframe with id {@code my-frame}, like {@code <iframe
* id="my-frame">}:
* <pre>{@code
* Locator locator = page.frameLocator("#my-iframe").locator("text=Submit");
* locator.click();
* }</pre>
*
* @param selector A selector to use when resolving DOM element. See <a href="https://playwright.dev/java/docs/selectors/">working with
* selectors</a> for more details.
*/
FrameLocator frameLocator(String selector);
/**
* An array of all frames attached to the page.
*/
@@ -358,6 +358,11 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
@Override
public FrameLocator frameLocator(String selector) {
return new FrameLocatorImpl(this, selector);
}
ElementHandle frameElementImpl() {
JsonObject json = sendMessage("frameElement").getAsJsonObject();
return connection.getExistingObject(json.getAsJsonObject("element").get("guid").getAsString());
@@ -0,0 +1,54 @@
/*
* 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.FrameLocator;
class FrameLocatorImpl implements FrameLocator {
private final FrameImpl frame;
private final String frameSelector;
FrameLocatorImpl(FrameImpl frame, String selector) {
this.frame = frame;
this.frameSelector = selector;
}
@Override
public FrameLocator first() {
return new FrameLocatorImpl(frame, frameSelector + " >> nth=0");
}
@Override
public FrameLocatorImpl frameLocator(String selector) {
return new FrameLocatorImpl(frame, frameSelector + " >> control=enter-frame >> " + selector);
}
@Override
public FrameLocator last() {
return new FrameLocatorImpl(frame, frameSelector + " >> nth=-1");
}
@Override
public LocatorImpl locator(String selector) {
return new LocatorImpl(frame, frameSelector + " >> control=enter-frame >> " + selector);
}
@Override
public FrameLocator nth(int index) {
return new FrameLocatorImpl(frame, frameSelector + " >> nth=" + index);
}
}
@@ -2,10 +2,7 @@ package com.microsoft.playwright.impl;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.ElementHandle;
import com.microsoft.playwright.Frame;
import com.microsoft.playwright.JSHandle;
import com.microsoft.playwright.Locator;
import com.microsoft.playwright.*;
import com.microsoft.playwright.options.BoundingBox;
import com.microsoft.playwright.options.FilePayload;
import com.microsoft.playwright.options.SelectOption;
@@ -151,6 +148,11 @@ class LocatorImpl implements Locator {
frame.focus(selector, convertViaJson(options, Frame.FocusOptions.class).setStrict(true));
}
@Override
public FrameLocatorImpl frameLocator(String selector) {
return new FrameLocatorImpl(frame, this.selector + " >> " + selector);
}
@Override
public String getAttribute(String name, GetAttributeOptions options) {
if (options == null) {
@@ -737,6 +737,11 @@ public class PageImpl extends ChannelOwner implements Page {
return frameFor(new UrlMatcher(predicate));
}
@Override
public FrameLocator frameLocator(String selector) {
return mainFrame.frameLocator(selector);
}
private Frame frameFor(UrlMatcher matcher) {
for (Frame frame : frames) {
if (matcher.test(frame.url())) {
+1 -1
View File
@@ -1 +1 @@
1.17.0-next-1636157051000
1.17.0-next-1636496060000