Skip to content

Commit c475963

Browse files
committed
Core roll changes
1 parent a78cea6 commit c475963

21 files changed

+190
-62
lines changed

playwright/_impl/_assertions.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
AriaRole,
2121
ExpectedTextValue,
2222
FrameExpectOptions,
23+
FrameExpectResult,
2324
)
2425
from playwright._impl._connection import format_call_log
2526
from playwright._impl._errors import Error
@@ -45,6 +46,13 @@ def __init__(
4546
self._is_not = is_not
4647
self._custom_message = message
4748

49+
async def _call_expect(
50+
self, expression: str, expect_options: FrameExpectOptions, title: Optional[str]
51+
) -> FrameExpectResult:
52+
raise NotImplementedError(
53+
"_call_expect must be implemented in a derived class."
54+
)
55+
4856
async def _expect_impl(
4957
self,
5058
expression: str,
@@ -61,7 +69,7 @@ async def _expect_impl(
6169
message = message.replace("expected to", "expected not to")
6270
if "useInnerText" in expect_options and expect_options["useInnerText"] is None:
6371
del expect_options["useInnerText"]
64-
result = await self._actual_locator._expect(expression, expect_options, title)
72+
result = await self._call_expect(expression, expect_options, title)
6573
if result["matches"] == self._is_not:
6674
actual = result.get("received")
6775
if self._custom_message:
@@ -88,6 +96,14 @@ def __init__(
8896
super().__init__(page.locator(":root"), timeout, is_not, message)
8997
self._actual_page = page
9098

99+
async def _call_expect(
100+
self, expression: str, expect_options: FrameExpectOptions, title: Optional[str]
101+
) -> FrameExpectResult:
102+
__tracebackhide__ = True
103+
return await self._actual_page.main_frame._expect(
104+
None, expression, expect_options, title
105+
)
106+
91107
@property
92108
def _not(self) -> "PageAssertions":
93109
return PageAssertions(
@@ -122,7 +138,7 @@ async def to_have_url(
122138
ignoreCase: bool = None,
123139
) -> None:
124140
__tracebackhide__ = True
125-
base_url = self._actual_page.context._options.get("baseURL")
141+
base_url = self._actual_page.context.base_url
126142
if isinstance(urlOrRegExp, str) and base_url:
127143
urlOrRegExp = urljoin(base_url, urlOrRegExp)
128144
expected_text = to_expected_text_values([urlOrRegExp], ignoreCase=ignoreCase)
@@ -155,6 +171,12 @@ def __init__(
155171
super().__init__(locator, timeout, is_not, message)
156172
self._actual_locator = locator
157173

174+
async def _call_expect(
175+
self, expression: str, expect_options: FrameExpectOptions, title: Optional[str]
176+
) -> FrameExpectResult:
177+
__tracebackhide__ = True
178+
return await self._actual_locator._expect(expression, expect_options, title)
179+
158180
@property
159181
def _not(self) -> "LocatorAssertions":
160182
return LocatorAssertions(

playwright/_impl/_browser_context.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ def __init__(
119119
self._options: Dict[str, Any] = initializer["options"]
120120
self._background_pages: Set[Page] = set()
121121
self._service_workers: Set[Worker] = set()
122+
self._base_url: Optional[str] = self._options.get("baseURL")
122123
self._tracing = cast(Tracing, from_channel(initializer["tracing"]))
123124
self._har_recorders: Dict[str, HarRecordingMetadata] = {}
124125
self._request: APIRequestContext = from_channel(initializer["requestContext"])
@@ -302,6 +303,14 @@ def pages(self) -> List[Page]:
302303
def browser(self) -> Optional["Browser"]:
303304
return self._browser
304305

306+
@property
307+
def base_url(self) -> Optional[str]:
308+
return self._base_url
309+
310+
@property
311+
def videos_dir(self) -> Optional[str]:
312+
return self._options.get("recordVideo")
313+
305314
async def _initialize_har_from_options(
306315
self,
307316
record_har_path: Optional[Union[Path, str]],
@@ -426,7 +435,7 @@ async def route(
426435
self._routes.insert(
427436
0,
428437
RouteHandler(
429-
self._options.get("baseURL"),
438+
self._base_url,
430439
url,
431440
handler,
432441
True if self._dispatcher_fiber else False,
@@ -454,17 +463,16 @@ async def _unroute_internal(
454463
behavior: Literal["default", "ignoreErrors", "wait"] = None,
455464
) -> None:
456465
self._routes = remaining
466+
if behavior is not None and behavior != "default":
467+
await asyncio.gather(*map(lambda router: router.stop(behavior), removed)) # type: ignore
457468
await self._update_interception_patterns()
458-
if behavior is None or behavior == "default":
459-
return
460-
await asyncio.gather(*map(lambda router: router.stop(behavior), removed)) # type: ignore
461469

462470
async def route_web_socket(
463471
self, url: URLMatch, handler: WebSocketRouteHandlerCallback
464472
) -> None:
465473
self._web_socket_routes.insert(
466474
0,
467-
WebSocketRouteHandler(self._options.get("baseURL"), url, handler),
475+
WebSocketRouteHandler(self._base_url, url, handler),
468476
)
469477
await self._update_web_socket_interception_patterns()
470478

playwright/_impl/_frame.py

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,13 @@
3030

3131
from pyee import EventEmitter
3232

33-
from playwright._impl._api_structures import AriaRole, FilePayload, Position
33+
from playwright._impl._api_structures import (
34+
AriaRole,
35+
FilePayload,
36+
FrameExpectOptions,
37+
FrameExpectResult,
38+
Position,
39+
)
3440
from playwright._impl._connection import (
3541
ChannelOwner,
3642
from_channel,
@@ -56,6 +62,7 @@
5662
Serializable,
5763
add_source_url_to_script,
5864
parse_result,
65+
parse_value,
5966
serialize_argument,
6067
)
6168
from playwright._impl._locator import (
@@ -170,6 +177,29 @@ def _setup_navigation_waiter(self, wait_name: str, timeout: float = None) -> Wai
170177
waiter.reject_on_timeout(timeout, f"Timeout {timeout}ms exceeded.")
171178
return waiter
172179

180+
async def _expect(
181+
self,
182+
selector: Optional[str],
183+
expression: str,
184+
options: FrameExpectOptions,
185+
title: str = None,
186+
) -> FrameExpectResult:
187+
if "expectedValue" in options:
188+
options["expectedValue"] = serialize_argument(options["expectedValue"])
189+
result = await self._channel.send_return_as_dict(
190+
"expect",
191+
self._timeout,
192+
{
193+
"selector": selector,
194+
"expression": expression,
195+
**options,
196+
},
197+
title=title,
198+
)
199+
if result.get("received"):
200+
result["received"] = parse_value(result["received"])
201+
return result
202+
173203
def expect_navigation(
174204
self,
175205
url: URLMatch = None,
@@ -194,7 +224,7 @@ def predicate(event: Any) -> bool:
194224
return True
195225
waiter.log(f' navigated to "{event["url"]}"')
196226
return url_matches(
197-
cast("Page", self._page)._browser_context._options.get("baseURL"),
227+
cast("Page", self._page)._browser_context.base_url,
198228
event["url"],
199229
url,
200230
)
@@ -227,9 +257,7 @@ async def wait_for_url(
227257
timeout: float = None,
228258
) -> None:
229259
assert self._page
230-
if url_matches(
231-
self._page._browser_context._options.get("baseURL"), self.url, url
232-
):
260+
if url_matches(self._page._browser_context.base_url, self.url, url):
233261
await self._wait_for_load_state_impl(state=waitUntil, timeout=timeout)
234262
return
235263
async with self.expect_navigation(
@@ -801,7 +829,7 @@ async def uncheck(
801829
await self._channel.send("uncheck", self._timeout, locals_to_params(locals()))
802830

803831
async def wait_for_timeout(self, timeout: float) -> None:
804-
await self._channel.send("waitForTimeout", None, locals_to_params(locals()))
832+
await self._channel.send("waitForTimeout", None, {"waitTimeout": timeout})
805833

806834
async def wait_for_function(
807835
self,

playwright/_impl/_helper.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,16 @@ def map_token(original: str, replacement: str) -> str:
189189

190190
# Escaped `\\?` behaves the same as `?` in our glob patterns.
191191
match = match.replace(r"\\?", "?")
192+
# Special case about: URLs as they are not relative to base_url
193+
if (
194+
match.startswith("about:")
195+
or match.startswith("data:")
196+
or match.startswith("chrome:")
197+
or match.startswith("edge:")
198+
or match.startswith("file:")
199+
):
200+
# about: and data: URLs are not relative to base_url, so we return them as is.
201+
return match
192202
# Glob symbols may be escaped in the URL and some of them such as ? affect resolution,
193203
# so we replace them with safe components first.
194204
processed_parts = []

playwright/_impl/_locator.py

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
monotonic_time,
4848
to_impl,
4949
)
50-
from playwright._impl._js_handle import Serializable, parse_value, serialize_argument
50+
from playwright._impl._js_handle import Serializable
5151
from playwright._impl._str_utils import (
5252
escape_for_attribute_selector,
5353
escape_for_text_selector,
@@ -722,21 +722,7 @@ async def _expect(
722722
options: FrameExpectOptions,
723723
title: str = None,
724724
) -> FrameExpectResult:
725-
if "expectedValue" in options:
726-
options["expectedValue"] = serialize_argument(options["expectedValue"])
727-
result = await self._frame._channel.send_return_as_dict(
728-
"expect",
729-
self._frame._timeout,
730-
{
731-
"selector": self._selector,
732-
"expression": expression,
733-
**options,
734-
},
735-
title=title,
736-
)
737-
if result.get("received"):
738-
result["received"] = parse_value(result["received"])
739-
return result
725+
return await self._frame._expect(self._selector, expression, options, title)
740726

741727
async def highlight(self) -> None:
742728
await self._frame._highlight(self._selector)

playwright/_impl/_network.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -733,10 +733,13 @@ async def _after_handle(self) -> None:
733733
if self._connected:
734734
return
735735
# Ensure that websocket is "open" and can send messages without an actual server connection.
736-
await self._channel.send(
737-
"ensureOpened",
738-
None,
739-
)
736+
try:
737+
await self._channel.send(
738+
"ensureOpened",
739+
None,
740+
)
741+
except Exception:
742+
pass
740743

741744

742745
class WebSocketRouteHandler:

playwright/_impl/_page.py

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -388,9 +388,7 @@ def frame(self, name: str = None, url: URLMatch = None) -> Optional[Frame]:
388388
for frame in self._frames:
389389
if name and frame.name == name:
390390
return frame
391-
if url and url_matches(
392-
self._browser_context._options.get("baseURL"), frame.url, url
393-
):
391+
if url and url_matches(self._browser_context.base_url, frame.url, url):
394392
return frame
395393

396394
return None
@@ -682,7 +680,7 @@ async def route(
682680
self._routes.insert(
683681
0,
684682
RouteHandler(
685-
self._browser_context._options.get("baseURL"),
683+
self._browser_context.base_url,
686684
url,
687685
handler,
688686
True if self._dispatcher_fiber else False,
@@ -710,24 +708,21 @@ async def _unroute_internal(
710708
behavior: Literal["default", "ignoreErrors", "wait"] = None,
711709
) -> None:
712710
self._routes = remaining
713-
await self._update_interception_patterns()
714-
if behavior is None or behavior == "default":
715-
return
716-
await asyncio.gather(
717-
*map(
718-
lambda route: route.stop(behavior), # type: ignore
719-
removed,
711+
if behavior is not None and behavior != "default":
712+
await asyncio.gather(
713+
*map(
714+
lambda route: route.stop(behavior), # type: ignore
715+
removed,
716+
)
720717
)
721-
)
718+
await self._update_interception_patterns()
722719

723720
async def route_web_socket(
724721
self, url: URLMatch, handler: WebSocketRouteHandlerCallback
725722
) -> None:
726723
self._web_socket_routes.insert(
727724
0,
728-
WebSocketRouteHandler(
729-
self._browser_context._options.get("baseURL"), url, handler
730-
),
725+
WebSocketRouteHandler(self._browser_context.base_url, url, handler),
731726
)
732727
await self._update_web_socket_interception_patterns()
733728

@@ -1186,7 +1181,7 @@ def video(
11861181
# Note: we are creating Video object lazily, because we do not know
11871182
# BrowserContextOptions when constructing the page - it is assigned
11881183
# too late during launchPersistentContext.
1189-
if not self._browser_context._options.get("recordVideo"):
1184+
if not self._browser_context.videos_dir:
11901185
return None
11911186
return self._force_video()
11921187

@@ -1273,7 +1268,7 @@ def expect_request(
12731268
def my_predicate(request: Request) -> bool:
12741269
if not callable(urlOrPredicate):
12751270
return url_matches(
1276-
self._browser_context._options.get("baseURL"),
1271+
self._browser_context.base_url,
12771272
request.url,
12781273
urlOrPredicate,
12791274
)
@@ -1305,7 +1300,7 @@ def expect_response(
13051300
def my_predicate(request: Response) -> bool:
13061301
if not callable(urlOrPredicate):
13071302
return url_matches(
1308-
self._browser_context._options.get("baseURL"),
1303+
self._browser_context.base_url,
13091304
request.url,
13101305
urlOrPredicate,
13111306
)

playwright/_impl/_selectors.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ async def register(
3737
path: Union[str, Path] = None,
3838
contentScript: bool = None,
3939
) -> None:
40+
if any(engine for engine in self._selector_engines if engine["name"] == name):
41+
raise Error(
42+
f'Selectors.register: "{name}" selector engine has been already registered'
43+
)
4044
if not script and not path:
4145
raise Error("Either source or path should be specified")
4246
if path:

tests/async/conftest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ def action_titles(self) -> Locator:
153153

154154
@property
155155
def stack_frames(self) -> Locator:
156-
return self.page.get_by_test_id("stack-trace-list").locator(".list-view-entry")
156+
return self.page.get_by_role("list", name="Stack Trace").get_by_role("listitem")
157157

158158
async def select_action(self, title: str, ordinal: int = 0) -> None:
159159
await self.page.locator(".action-title", has_text=title).nth(ordinal).click()

tests/async/test_browsercontext.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,9 +118,9 @@ async def test_page_event_should_propagate_default_viewport_to_the_page(
118118

119119

120120
async def test_page_event_should_respect_device_scale_factor(browser: Browser) -> None:
121-
context = await browser.new_context(device_scale_factor=3)
121+
context = await browser.new_context(device_scale_factor=3.5)
122122
page = await context.new_page()
123-
assert await page.evaluate("window.devicePixelRatio") == 3
123+
assert await page.evaluate("window.devicePixelRatio") == 3.5
124124
await context.close()
125125

126126

0 commit comments

Comments
 (0)