Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 20 additions & 5 deletions playwright/_impl/_locator.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ def locator(
return Locator(self._frame, f"{self._selector} >> {selector}")

def frame_locator(self, selector: str) -> "FrameLocator":
return FrameLocator(self._frame, selector)
return FrameLocator(self._frame, self._selector + " >> " + selector)

async def element_handle(
self,
Expand Down Expand Up @@ -477,14 +477,29 @@ async def set_checked(


class FrameLocator:
def __init__(self, frame: "Frame", selector: str) -> None:
def __init__(self, frame: "Frame", frame_selector: str) -> None:
self._frame = frame
self._loop = frame._loop
self._dispatcher_fiber = frame._connection._dispatcher_fiber
self._selector = f"{selector} >> control=enter-frame"
self._frame_selector = frame_selector

def locator(self, selector: str) -> Locator:
return Locator(self._frame, f"{self._selector} >> {selector}")
return Locator(
self._frame, f"{self._frame_selector} >> control=enter-frame >> {selector}"
)

def frame_locator(self, selector: str) -> "FrameLocator":
return FrameLocator(self._frame, f"{self._selector} >> {selector}")
return FrameLocator(
self._frame, f"{self._frame_selector} >> control=enter-frame >> {selector}"
)

@property
def first(self) -> "FrameLocator":
return FrameLocator(self._frame, f"{self._frame_selector} >> nth=0")

@property
def last(self) -> "FrameLocator":
return FrameLocator(self._frame, f"{self._frame_selector} >> nth=-1")

def nth(self, index: int) -> "FrameLocator":
return FrameLocator(self._frame, f"{self._frame_selector} >> nth={index}")
54 changes: 47 additions & 7 deletions playwright/async_api/_generated.py
Original file line number Diff line number Diff line change
Expand Up @@ -5221,6 +5221,30 @@ async def set_checked(


class FrameLocator(AsyncBase):
@property
def first(self) -> "FrameLocator":
"""FrameLocator.first

Returns locator to the first matching frame.

Returns
-------
FrameLocator
"""
return mapping.from_impl(self._impl_obj.first)

@property
def last(self) -> "FrameLocator":
"""FrameLocator.last

Returns locator to the last matching frame.

Returns
-------
FrameLocator
"""
return mapping.from_impl(self._impl_obj.last)

def locator(self, selector: str) -> "Locator":
"""FrameLocator.locator

Expand Down Expand Up @@ -5256,6 +5280,22 @@ def frame_locator(self, selector: str) -> "FrameLocator":

return mapping.from_impl(self._impl_obj.frame_locator(selector=selector))

def nth(self, index: int) -> "FrameLocator":
"""FrameLocator.nth

Returns locator to the n-th matching frame.

Parameters
----------
index : int

Returns
-------
FrameLocator
"""

return mapping.from_impl(self._impl_obj.nth(index=index))


mapping.register(FrameLocatorImpl, FrameLocator)

Expand Down Expand Up @@ -13792,7 +13832,7 @@ async def delete(
url : str
Target URL.
params : Union[Dict[str, Union[bool, float, str]], NoneType]
Query parameters to be send with the URL.
Query parameters to be sent with the URL.
headers : Union[Dict[str, str], NoneType]
Allows to set HTTP headers.
data : Union[Any, bytes, str, NoneType]
Expand Down Expand Up @@ -13860,7 +13900,7 @@ async def head(
url : str
Target URL.
params : Union[Dict[str, Union[bool, float, str]], NoneType]
Query parameters to be send with the URL.
Query parameters to be sent with the URL.
headers : Union[Dict[str, str], NoneType]
Allows to set HTTP headers.
timeout : Union[float, NoneType]
Expand Down Expand Up @@ -13912,7 +13952,7 @@ async def get(
url : str
Target URL.
params : Union[Dict[str, Union[bool, float, str]], NoneType]
Query parameters to be send with the URL.
Query parameters to be sent with the URL.
headers : Union[Dict[str, str], NoneType]
Allows to set HTTP headers.
timeout : Union[float, NoneType]
Expand Down Expand Up @@ -13969,7 +14009,7 @@ async def patch(
url : str
Target URL.
params : Union[Dict[str, Union[bool, float, str]], NoneType]
Query parameters to be send with the URL.
Query parameters to be sent with the URL.
headers : Union[Dict[str, str], NoneType]
Allows to set HTTP headers.
data : Union[Any, bytes, str, NoneType]
Expand Down Expand Up @@ -14042,7 +14082,7 @@ async def put(
url : str
Target URL.
params : Union[Dict[str, Union[bool, float, str]], NoneType]
Query parameters to be send with the URL.
Query parameters to be sent with the URL.
headers : Union[Dict[str, str], NoneType]
Allows to set HTTP headers.
data : Union[Any, bytes, str, NoneType]
Expand Down Expand Up @@ -14115,7 +14155,7 @@ async def post(
url : str
Target URL.
params : Union[Dict[str, Union[bool, float, str]], NoneType]
Query parameters to be send with the URL.
Query parameters to be sent with the URL.
headers : Union[Dict[str, str], NoneType]
Allows to set HTTP headers.
data : Union[Any, bytes, str, NoneType]
Expand Down Expand Up @@ -14188,7 +14228,7 @@ async def fetch(
url_or_request : Union[Request, str]
Target URL or Request to get all parameters from.
params : Union[Dict[str, Union[bool, float, str]], NoneType]
Query parameters to be send with the URL.
Query parameters to be sent with the URL.
method : Union[str, NoneType]
If set changes the fetch method (e.g. [PUT](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PUT) or
[POST](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST)). If not specified, GET method is used.
Expand Down
54 changes: 47 additions & 7 deletions playwright/sync_api/_generated.py
Original file line number Diff line number Diff line change
Expand Up @@ -5162,6 +5162,30 @@ def set_checked(


class FrameLocator(SyncBase):
@property
def first(self) -> "FrameLocator":
"""FrameLocator.first

Returns locator to the first matching frame.

Returns
-------
FrameLocator
"""
return mapping.from_impl(self._impl_obj.first)

@property
def last(self) -> "FrameLocator":
"""FrameLocator.last

Returns locator to the last matching frame.

Returns
-------
FrameLocator
"""
return mapping.from_impl(self._impl_obj.last)

def locator(self, selector: str) -> "Locator":
"""FrameLocator.locator

Expand Down Expand Up @@ -5197,6 +5221,22 @@ def frame_locator(self, selector: str) -> "FrameLocator":

return mapping.from_impl(self._impl_obj.frame_locator(selector=selector))

def nth(self, index: int) -> "FrameLocator":
"""FrameLocator.nth

Returns locator to the n-th matching frame.

Parameters
----------
index : int

Returns
-------
FrameLocator
"""

return mapping.from_impl(self._impl_obj.nth(index=index))


mapping.register(FrameLocatorImpl, FrameLocator)

Expand Down Expand Up @@ -13515,7 +13555,7 @@ def delete(
url : str
Target URL.
params : Union[Dict[str, Union[bool, float, str]], NoneType]
Query parameters to be send with the URL.
Query parameters to be sent with the URL.
headers : Union[Dict[str, str], NoneType]
Allows to set HTTP headers.
data : Union[Any, bytes, str, NoneType]
Expand Down Expand Up @@ -13583,7 +13623,7 @@ def head(
url : str
Target URL.
params : Union[Dict[str, Union[bool, float, str]], NoneType]
Query parameters to be send with the URL.
Query parameters to be sent with the URL.
headers : Union[Dict[str, str], NoneType]
Allows to set HTTP headers.
timeout : Union[float, NoneType]
Expand Down Expand Up @@ -13635,7 +13675,7 @@ def get(
url : str
Target URL.
params : Union[Dict[str, Union[bool, float, str]], NoneType]
Query parameters to be send with the URL.
Query parameters to be sent with the URL.
headers : Union[Dict[str, str], NoneType]
Allows to set HTTP headers.
timeout : Union[float, NoneType]
Expand Down Expand Up @@ -13692,7 +13732,7 @@ def patch(
url : str
Target URL.
params : Union[Dict[str, Union[bool, float, str]], NoneType]
Query parameters to be send with the URL.
Query parameters to be sent with the URL.
headers : Union[Dict[str, str], NoneType]
Allows to set HTTP headers.
data : Union[Any, bytes, str, NoneType]
Expand Down Expand Up @@ -13765,7 +13805,7 @@ def put(
url : str
Target URL.
params : Union[Dict[str, Union[bool, float, str]], NoneType]
Query parameters to be send with the URL.
Query parameters to be sent with the URL.
headers : Union[Dict[str, str], NoneType]
Allows to set HTTP headers.
data : Union[Any, bytes, str, NoneType]
Expand Down Expand Up @@ -13838,7 +13878,7 @@ def post(
url : str
Target URL.
params : Union[Dict[str, Union[bool, float, str]], NoneType]
Query parameters to be send with the URL.
Query parameters to be sent with the URL.
headers : Union[Dict[str, str], NoneType]
Allows to set HTTP headers.
data : Union[Any, bytes, str, NoneType]
Expand Down Expand Up @@ -13911,7 +13951,7 @@ def fetch(
url_or_request : Union[Request, str]
Target URL or Request to get all parameters from.
params : Union[Dict[str, Union[bool, float, str]], NoneType]
Query parameters to be send with the URL.
Query parameters to be sent with the URL.
method : Union[str, NoneType]
If set changes the fetch method (e.g. [PUT](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PUT) or
[POST](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST)). If not specified, GET method is used.
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
InWheel = None
from wheel.bdist_wheel import bdist_wheel as BDistWheelCommand

driver_version = "1.17.0-next-1636463074000"
driver_version = "1.18.0-next-1636539685000"


def extractall(zip: zipfile.ZipFile, path: str) -> None:
Expand Down
48 changes: 48 additions & 0 deletions tests/async/test_locators.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
# limitations under the License.

import os
from urllib.parse import urlparse

import pytest

Expand Down Expand Up @@ -537,3 +538,50 @@ async def test_locators_frame_should_work_with_locator_frame_locator(
await button.wait_for()
assert await button.inner_text() == "Hello iframe"
await button.click()


async def route_ambiguous(page: Page) -> None:
await page.route(
"**/empty.html",
lambda route: route.fulfill(
body="""
<iframe src="iframe-1.html"></iframe>
<iframe src="iframe-2.html"></iframe>
<iframe src="iframe-3.html"></iframe>
""",
content_type="text/html",
),
)
await page.route(
"**/iframe-*",
lambda route: route.fulfill(
body=f"<html><button>Hello from {urlparse(route.request.url).path[1:]}</button></html>",
content_type="text/html",
),
)


async def test_locator_frame_locator_should_throw_on_ambiguity(
page: Page, server: Server
) -> None:
await route_ambiguous(page)
await page.goto(server.EMPTY_PAGE)
button = page.locator("body").frame_locator("iframe").locator("button")
with pytest.raises(
Error,
match='.*strict mode violation: "body >> iframe" resolved to 3 elements.*',
):
await button.wait_for()


async def test_locator_frame_locator_should_not_throw_on_first_last_nth(
page: Page, server: Server
) -> None:
await route_ambiguous(page)
await page.goto(server.EMPTY_PAGE)
button1 = page.locator("body").frame_locator("iframe").first.locator("button")
assert await button1.text_content() == "Hello from iframe-1.html"
button2 = page.locator("body").frame_locator("iframe").nth(1).locator("button")
assert await button2.text_content() == "Hello from iframe-2.html"
button3 = page.locator("body").frame_locator("iframe").last.locator("button")
assert await button3.text_content() == "Hello from iframe-3.html"
Loading