Skip to content

Commit f515d9f

Browse files
authored
feat: frame locators, roll driver (#695)
1 parent 2f70601 commit f515d9f

File tree

14 files changed

+504
-48
lines changed

14 files changed

+504
-48
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ Playwright is a Java library to automate [Chromium](https://www.chromium.org/Hom
1111

1212
| | Linux | macOS | Windows |
1313
| :--- | :---: | :---: | :---: |
14-
| Chromium <!-- GEN:chromium-version -->97.0.4688.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
14+
| Chromium <!-- GEN:chromium-version -->98.0.4695.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
1515
| WebKit <!-- GEN:webkit-version -->15.4<!-- GEN:stop --> ||||
1616
| Firefox <!-- GEN:firefox-version -->94.0.1<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
1717

assertions/src/main/java/com/microsoft/playwright/assertions/LocatorAssertions.java

Lines changed: 47 additions & 33 deletions
Large diffs are not rendered by default.

assertions/src/main/java/com/microsoft/playwright/assertions/PageAssertions.java

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,21 @@
2323
/**
2424
* The {@code PageAssertions} class provides assertion methods that can be used to make assertions about the {@code Page} state in the
2525
* tests. A new instance of {@code LocatorAssertions} is created by calling {@link PlaywrightAssertions#assertThat
26-
* PlaywrightAssertions.assertThat()}.
26+
* PlaywrightAssertions.assertThat()}:
27+
* <pre>{@code
28+
* ...
29+
* import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
30+
*
31+
* public class TestPage {
32+
* ...
33+
* @Test
34+
* void navigatesToLoginPage() {
35+
* ...
36+
* page.click("#login");
37+
* assertThat(page).hasURL(Pattern.compile(".*\/login"));
38+
* }
39+
* }
40+
* }</pre>
2741
*/
2842
public interface PageAssertions {
2943
class HasTitleOptions {
@@ -97,7 +111,7 @@ default void hasTitle(Pattern titleOrRegExp) {
97111
/**
98112
* Ensures the page is navigated to the given URL.
99113
* <pre>{@code
100-
* assertThat(page).hasURL('.com');
114+
* assertThat(page).hasURL(".com");
101115
* }</pre>
102116
*
103117
* @param urlOrRegExp Expected substring or RegExp.
@@ -108,7 +122,7 @@ default void hasURL(String urlOrRegExp) {
108122
/**
109123
* Ensures the page is navigated to the given URL.
110124
* <pre>{@code
111-
* assertThat(page).hasURL('.com');
125+
* assertThat(page).hasURL(".com");
112126
* }</pre>
113127
*
114128
* @param urlOrRegExp Expected substring or RegExp.
@@ -117,7 +131,7 @@ default void hasURL(String urlOrRegExp) {
117131
/**
118132
* Ensures the page is navigated to the given URL.
119133
* <pre>{@code
120-
* assertThat(page).hasURL('.com');
134+
* assertThat(page).hasURL(".com");
121135
* }</pre>
122136
*
123137
* @param urlOrRegExp Expected substring or RegExp.
@@ -128,7 +142,7 @@ default void hasURL(Pattern urlOrRegExp) {
128142
/**
129143
* Ensures the page is navigated to the given URL.
130144
* <pre>{@code
131-
* assertThat(page).hasURL('.com');
145+
* assertThat(page).hasURL(".com");
132146
* }</pre>
133147
*
134148
* @param urlOrRegExp Expected substring or RegExp.
@@ -138,7 +152,7 @@ default void hasURL(Pattern urlOrRegExp) {
138152
* Makes the assertion check for the opposite condition. For example, this code tests that the page URL doesn't contain
139153
* {@code "error"}:
140154
* <pre>{@code
141-
* assertThat(page).not().hasURL('error');
155+
* assertThat(page).not().hasURL("error");
142156
* }</pre>
143157
*/
144158
PageAssertions not();

assertions/src/test/java/com/microsoft/playwright/TestLocatorAssertions.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ void hasTextWTextArrayFailOnNotEmpty() {
160160
fail("did not throw");
161161
} catch (AssertionFailedError e) {
162162
assertEquals("[]", e.getExpected().getStringRepresentation());
163-
assertEquals("[]", e.getActual().getStringRepresentation());
163+
assertEquals("null", e.getActual().getStringRepresentation());
164164
assertTrue(e.getMessage().contains("Locator expected not to have text"), e.getMessage());
165165
}
166166
}
Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
/*
2+
* Copyright (c) Microsoft Corporation.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.microsoft.playwright;
18+
19+
import org.junit.jupiter.api.Test;
20+
21+
import java.net.MalformedURLException;
22+
import java.net.URL;
23+
24+
import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
25+
import static org.junit.jupiter.api.Assertions.*;
26+
27+
public class TestLocatorFrame extends TestBase {
28+
private static void routeIframe(Page page) {
29+
page.route("**/empty.html", route -> route.fulfill(new Route.FulfillOptions()
30+
.setBody("<iframe src='iframe.html'></iframe>").setContentType("text/html")));
31+
page.route("**/iframe.html", route -> {
32+
route.fulfill(new Route.FulfillOptions().setBody("<html>\n" +
33+
" <div>\n" +
34+
" <button>Hello iframe</button>\n" +
35+
" <iframe src='iframe-2.html'></iframe>\n" +
36+
" </div>\n" +
37+
" <span>1</span>\n" +
38+
" <span>2</span>\n" +
39+
" </html>").setContentType("text/html"));
40+
});
41+
page.route("**/iframe-2.html", route -> {
42+
route.fulfill(new Route.FulfillOptions().setBody("<html><button>Hello nested iframe</button></html>").setContentType("text/html"));
43+
});
44+
}
45+
46+
private static void routeAmbiguous(Page page) {
47+
page.route("**/empty.html", route -> {
48+
route.fulfill(new Route.FulfillOptions()
49+
.setBody("<iframe src='iframe-1.html'></iframe>\n" +
50+
"<iframe src='iframe-2.html'></iframe>\n" +
51+
"<iframe src='iframe-3.html'></iframe>")
52+
.setContentType("text/html"));
53+
});
54+
page.route("**/iframe-*", route -> {
55+
try {
56+
String path = new URL(route.request().url()).getPath().substring(1);
57+
route.fulfill(new Route.FulfillOptions()
58+
.setBody("<html><button>Hello from " + path + "</button></html>")
59+
.setContentType("text/html"));
60+
} catch (MalformedURLException e) {
61+
throw new RuntimeException(e);
62+
}
63+
});
64+
}
65+
66+
@Test
67+
void shouldWorkForIframe() {
68+
routeIframe(page);
69+
page.navigate(server.EMPTY_PAGE);
70+
Locator button = page.frameLocator("iframe").locator("button");
71+
button.waitFor();
72+
assertEquals("Hello iframe", button.innerText());
73+
assertThat(button).hasText("Hello iframe");
74+
button.click();
75+
}
76+
77+
@Test
78+
void shouldWorkForNestedIframe() {
79+
routeIframe(page);
80+
page.navigate(server.EMPTY_PAGE);
81+
Locator button = page.frameLocator("iframe").frameLocator("iframe").locator("button");
82+
button.waitFor();
83+
assertEquals("Hello nested iframe", button.innerText());
84+
assertThat(button).hasText("Hello nested iframe");
85+
button.click();
86+
}
87+
88+
@Test
89+
void shouldWorkForAnd() {
90+
routeIframe(page);
91+
page.navigate(server.EMPTY_PAGE);
92+
Locator locator = page.frameLocator("iframe").locator("button");
93+
assertThat(locator).hasText("Hello iframe");
94+
assertEquals("Hello iframe", locator.innerText());
95+
Locator spans = page.frameLocator("iframe").locator("span");
96+
assertThat(spans).hasCount(2);
97+
}
98+
99+
@Test
100+
void shouldWaitForFrame() {
101+
page.navigate(server.EMPTY_PAGE);
102+
try {
103+
page.frameLocator("iframe").locator("span").click(new Locator.ClickOptions().setTimeout(300));
104+
fail("did not throw");
105+
} catch (PlaywrightException e) {
106+
assertTrue(e.getMessage().contains("waiting for frame \"iframe\""), e.getMessage());
107+
}
108+
}
109+
110+
@Test
111+
void shouldWaitForFrame2() {
112+
routeIframe(page);
113+
page.evaluate("url => setTimeout(() => location.href = url, 300)", server.EMPTY_PAGE);
114+
page.frameLocator("iframe").locator("button").click();
115+
}
116+
117+
void shouldWaitForFrameToGo() {
118+
}
119+
120+
@Test
121+
void shouldNotWaitForFrame() {
122+
page.navigate(server.EMPTY_PAGE);
123+
assertThat(page.frameLocator("iframe").locator("span")).isHidden();
124+
}
125+
126+
@Test
127+
void shouldNotWaitForFrame2() {
128+
page.navigate(server.EMPTY_PAGE);
129+
assertThat(page.frameLocator("iframe").locator("span")).not().isVisible();
130+
}
131+
132+
@Test
133+
void shouldNotWaitForFrame3() {
134+
page.navigate(server.EMPTY_PAGE);
135+
assertThat(page.frameLocator("iframe").locator("span")).hasCount(0);
136+
}
137+
138+
@Test
139+
void shouldClickInLazyIframe() {
140+
page.route("**/iframe.html", route -> {
141+
route.fulfill(new Route.FulfillOptions().setBody("<html><button>Hello iframe</button></html>").setContentType("text/html"));
142+
});
143+
// empty pge
144+
page.navigate(server.EMPTY_PAGE);
145+
146+
// add blank iframe
147+
page.evaluate("setTimeout(() => {\n" +
148+
" const iframe = document.createElement('iframe');\n" +
149+
" document.body.appendChild(iframe);\n" +
150+
" // navigate iframe\n" +
151+
" setTimeout(() => iframe.src = 'iframe.html', 500);\n" +
152+
" }, 500);");
153+
// Click in iframe
154+
Locator button = page.frameLocator("iframe").locator("button");
155+
button.click();
156+
assertThat(button).hasText("Hello iframe");
157+
assertEquals("Hello iframe", button.innerText());
158+
}
159+
160+
@Test
161+
void waitForShouldSurviveFrameReattach() {
162+
routeIframe(page);
163+
page.navigate(server.EMPTY_PAGE);
164+
Locator button = page.frameLocator("iframe").locator("button:has-text('Hello nested iframe')");
165+
page.evaluate("setTimeout(() => {\n" +
166+
" document.querySelector('iframe').remove();\n" +
167+
" setTimeout(() => {\n" +
168+
" const iframe = document.createElement('iframe');\n" +
169+
" iframe.src = 'iframe-2.html';\n" +
170+
" document.body.appendChild(iframe);\n" +
171+
" }, 500);\n" +
172+
" }, 500);");
173+
button.waitFor();
174+
}
175+
176+
@Test
177+
void clickShouldSurviveFrameReattach() {
178+
routeIframe(page);
179+
page.navigate(server.EMPTY_PAGE);
180+
Locator button = page.frameLocator("iframe").locator("button:has-text('Hello nested iframe')");
181+
182+
page.evaluate("setTimeout(() => {\n" +
183+
" document.querySelector('iframe').remove();\n" +
184+
" setTimeout(() => {\n" +
185+
" const iframe = document.createElement('iframe');\n" +
186+
" iframe.src = 'iframe-2.html';\n" +
187+
" document.body.appendChild(iframe);\n" +
188+
" }, 500);\n" +
189+
" }, 500);");
190+
button.click();
191+
}
192+
193+
@Test
194+
void clickShouldSurviveIframeNavigation() {
195+
routeIframe(page);
196+
page.navigate(server.EMPTY_PAGE);
197+
Locator button = page.frameLocator("iframe").locator("button:has-text('Hello nested iframe')");
198+
page.evaluate("setTimeout(() => {\n" +
199+
" document.querySelector('iframe').src = 'iframe-2.html';\n" +
200+
" }, 500);");
201+
button.click();
202+
}
203+
204+
@Test
205+
void shouldNonWorkForNonFrame() {
206+
routeIframe(page);
207+
page.setContent("<div></div>");
208+
Locator button = page.frameLocator("div").locator("button");
209+
try {
210+
button.waitFor();
211+
fail("did not throw");
212+
} catch (PlaywrightException e) {
213+
assertTrue(e.getMessage().contains("<div></div>"), e.getMessage());
214+
assertTrue(e.getMessage().contains("<iframe> was expected"), e.getMessage());
215+
}
216+
}
217+
218+
@Test
219+
void locatorFrameLocatorShouldWorkForIframe() {
220+
routeIframe(page);
221+
page.navigate(server.EMPTY_PAGE);
222+
Locator button = page.locator("body").frameLocator("iframe").locator("button");
223+
button.waitFor();
224+
assertThat(button).hasText("Hello iframe");
225+
assertEquals("Hello iframe", button.innerText());
226+
button.click();
227+
}
228+
229+
@Test
230+
void locatorFrameLocatorShouldThrowOnAmbiguity() {
231+
routeAmbiguous(page);
232+
page.navigate(server.EMPTY_PAGE);
233+
Locator button = page.locator("body").frameLocator("iframe").locator("button");
234+
try {
235+
button.waitFor();
236+
fail("did not throw");
237+
} catch (PlaywrightException e) {
238+
assertTrue(e.getMessage().contains("Error: strict mode violation: \"body >> iframe\" resolved to 3 elements"), e.getMessage());
239+
}
240+
}
241+
242+
@Test
243+
void locatorFrameLocatorShouldNotThrowOnFirstLastNth() {
244+
routeAmbiguous(page);
245+
page.navigate(server.EMPTY_PAGE);
246+
Locator button1 = page.locator("body").frameLocator("iframe").first().locator("button");
247+
assertThat(button1).hasText("Hello from iframe-1.html");
248+
Locator button2 = page.locator("body").frameLocator("iframe").nth(1).locator("button");
249+
assertThat(button2).hasText("Hello from iframe-2.html");
250+
Locator button3 = page.locator("body").frameLocator("iframe").last().locator("button");
251+
assertThat(button3).hasText("Hello from iframe-3.html");
252+
}
253+
254+
}

playwright/src/main/java/com/microsoft/playwright/Frame.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2653,6 +2653,19 @@ default void focus(String selector) {
26532653
* }</pre>
26542654
*/
26552655
ElementHandle frameElement();
2656+
/**
2657+
* When working with iframes, you can create a frame locator that will enter the iframe and allow selecting elements in
2658+
* that iframe. Following snippet locates element with text "Submit" in the iframe with id {@code my-frame}, like {@code <iframe
2659+
* id="my-frame">}:
2660+
* <pre>{@code
2661+
* Locator locator = frame.frameLocator("#my-iframe").locator("text=Submit");
2662+
* locator.click();
2663+
* }</pre>
2664+
*
2665+
* @param selector A selector to use when resolving DOM element. See <a href="https://playwright.dev/java/docs/selectors/">working with
2666+
* selectors</a> for more details.
2667+
*/
2668+
FrameLocator frameLocator(String selector);
26562669
/**
26572670
* Returns element attribute value.
26582671
*

0 commit comments

Comments
 (0)