Skip to content

Commit 4104f6e

Browse files
committed
chore(v2): polish after the review of the port
- An error that the eager part of an API call raises (a validation error, a bad wait_until) is raised as it is, without the API name in front; marked as deliberate. - remove_all_listeners(behavior="wait") takes the pending handlers at the call, as upstream. - The instrumentation iterates its live list, as the JS array. - The buffer mode of the validator rejects primitives. - The DEBUG matcher is the one of the debug npm package: `*`, `-name`, names split on spaces and commas. - The test server ignores finish() after the client went away, as res.end() does; BT-034 suppresses Node's certificate warning. - test-v2.sh runs the tests of v2.
1 parent 9eb7307 commit 4104f6e

10 files changed

Lines changed: 72 additions & 15 deletions

File tree

‎playwright/_impl_v2/api_classes.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
"""Python only: the v2 class of each class in the generated API.
1616
1717
playwright/_impl/_api_classes.py takes these when PW_IMPL=v2 is set. The expect()
18-
matchers are not part of the client; they stay in playwright/_impl/_assertions.py.
18+
matchers are not part of the JS client; the Python ones are in assertions.py.
1919
"""
2020

2121
from playwright._impl_v2.assertions import (

‎playwright/_impl_v2/channel_owner.py‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,9 @@ def func(api_zone: ApiZone) -> Awaitable[Any]:
151151
)
152152

153153
# Python: a channel call never raises at the call; the awaitable raises, as a
154-
# rejected promise does.
154+
# rejected promise does. A validation error is raised as it is, without the
155+
# API name in front of it (upstream adds it): the message names the Python
156+
# option, which is enough.
155157
try:
156158
result = owner._wrap_api_call(func, ApiCallOptions(internal=is_internal))
157159
except Exception as e:
@@ -334,6 +336,9 @@ def _wrap_api_call(
334336
internal=options.internal if options else False,
335337
)
336338
zone = current_zone().with_("apiZone", api_zone)
339+
# Python: func runs now, up to its first await. An error that it raises here
340+
# is raised as it is, without the API name and the logging below; upstream
341+
# has it as a rejected promise, which the catch below decorates.
337342
awaitable = zone.run(lambda: func(api_zone))
338343

339344
async def finish() -> R:

‎playwright/_impl_v2/client_instrumentation.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,13 @@ def remove_all_listeners(self) -> None:
5353
self._listeners.clear()
5454

5555
def _on(self, name: str, *params: Any) -> None:
56-
for listener in list(self._listeners):
56+
for listener in self._listeners:
5757
method = getattr(listener, name, None)
5858
if method:
5959
method(*params)
6060

6161
async def _run(self, name: str, *params: Any) -> None:
62-
for listener in list(self._listeners):
62+
for listener in self._listeners:
6363
method = getattr(listener, name, None)
6464
if method:
6565
await method(*params)

‎playwright/_impl_v2/event_emitter.py‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,8 +247,12 @@ def remove_all_listeners(
247247
errors: List[BaseException] = []
248248
self._rejection_handler = lambda error: errors.append(error)
249249

250+
# Python: the pending handlers are taken now, as _waitFor() takes them
251+
# before its first await.
252+
pending = self._wait_for(type)
253+
250254
async def wait() -> None:
251-
await self._wait_for(type)
255+
await pending
252256
if errors:
253257
raise errors[0]
254258

‎playwright/_impl_v2/protocol/validator_primitives.py‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,10 @@ def t_binary(arg: Any, path: str, context: ValidatorContext) -> Any:
149149
raise ValidationError(f"{path}: expected Buffer, got {_typeof(arg)}")
150150
return base64.b64encode(arg).decode()
151151
if context.binary == "buffer":
152-
if not isinstance(arg, (bytes, bytearray)) and not isinstance(arg, object):
152+
# Python: `arg instanceof Object` is false for a primitive.
153+
if not isinstance(arg, (bytes, bytearray)) and isinstance(
154+
arg, (str, int, float, type(None))
155+
):
153156
raise ValidationError(f"{path}: expected Buffer, got {_typeof(arg)}")
154157
return arg
155158
raise ValidationError(f'Unsupported binary behavior "{context.binary}"')

‎playwright/_impl_v2/utils/debug_logger.py‎

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,28 +19,44 @@
1919
"""
2020

2121
import os
22+
import re
2223
import sys
23-
from typing import Set
24+
from typing import List, Pattern
2425

2526
LogName = str
2627

2728

2829
class DebugLogger:
2930
def __init__(self) -> None:
30-
self._enabled: Set[str] = set()
31-
for name in (os.environ.get("DEBUG") or "").split(","):
32-
name = name.strip()
33-
if name == "pw:*":
34-
self._enabled.update({"api", "channel"})
35-
elif name.startswith("pw:"):
36-
self._enabled.add(name[3:])
31+
# Python: the names of the `debug` npm package, which debugLogger.ts uses:
32+
# DEBUG is a list of names, split on commas and spaces; `*` in a name
33+
# matches anything; a name that starts with `-` turns its matches off.
34+
self._names: List[Pattern[str]] = []
35+
self._skips: List[Pattern[str]] = []
36+
for name in re.split(r"[\s,]+", (os.environ.get("DEBUG") or "").strip()):
37+
if not name:
38+
continue
39+
if name.startswith("-"):
40+
self._skips.append(_template(name[1:]))
41+
else:
42+
self._names.append(_template(name))
3743

3844
def log(self, name: LogName, message: object) -> None:
3945
if self.is_enabled(name):
4046
print(f"pw:{name} {message}", file=sys.stderr)
4147

4248
def is_enabled(self, name: LogName) -> bool:
43-
return name in self._enabled
49+
full_name = "pw:" + name
50+
if any(skip.match(full_name) for skip in self._skips):
51+
return False
52+
return any(enabled.match(full_name) for enabled in self._names)
53+
54+
55+
def _template(name: str) -> Pattern[str]:
56+
# matchesTemplate() of the `debug` package: `*` matches any text.
57+
return re.compile(
58+
"^" + ".*".join(re.escape(part) for part in name.split("*")) + "$"
59+
)
4460

4561

4662
debug_logger = DebugLogger()

‎test-v2.sh‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#!/usr/bin/env bash
2+
# Runs the tests of playwright/_impl_v2 (PW_IMPL=v2) under xvfb, with one worker for
3+
# every two cores: all of tests/async (the older tests, the ports of the upstream
4+
# tests and the Python-only tests of v2). On a terminal, the progress is one status
5+
# line (scripts/pytest_line_reporter.py), and the summary lists only failures and
6+
# errors, without the warnings.
7+
# Usage: ./test-v2.sh [chromium|firefox|webkit] [extra pytest args]
8+
set -u
9+
cd "$(dirname "$0")"
10+
browser=chromium
11+
case "${1:-}" in chromium|firefox|webkit) browser=$1; shift ;; esac
12+
workers=$(( $(nproc) / 2 ))
13+
(( workers < 1 )) && workers=1
14+
python=python
15+
[ -x env/bin/python ] && python=env/bin/python
16+
export PYTHONPATH="scripts${PYTHONPATH:+:$PYTHONPATH}"
17+
PW_IMPL=v2 exec xvfb-run -a "$python" -m pytest tests/async --browser "$browser" -n "$workers" --timeout 120 --timeout-method thread -p pytest_line_reporter -rfE --disable-warnings "$@"

‎tests/async/library/test_browsertype_connect.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,8 @@ async def test_BT_034_should_connect_over_wss(
239239

240240
# https://stackoverflow.com/a/21961005/552185
241241
monkeypatch.setenv("NODE_TLS_REJECT_UNAUTHORIZED", "0")
242+
# suppressCertificateWarning()
243+
monkeypatch.setenv("NODE_NO_WARNINGS", "1")
242244

243245
https_server.once_web_socket_connection(
244246
lambda ws: forward_web_socket(ws, remote_server.ws_endpoint)

‎tests/server.py‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,14 @@ def __init__(self, *args: Any, **kwargs: Any) -> None:
100100
kwargs["parsePOSTFormSubmission"] = False
101101
super().__init__(*args, **kwargs)
102102

103+
def finish(self) -> None:
104+
# Like res.end() in Node.js, which does nothing after the client went away;
105+
# Twisted raises "Request.finish called on a request after its connection
106+
# was lost".
107+
if self._disconnected or self.channel is None or not self.channel.transport:
108+
return
109+
super().finish()
110+
103111
def _parse_form(self) -> None:
104112
"""Adds the fields of a POSTed form to self.args, ignoring a malformed body."""
105113
if self.method != b"POST" or not self.post_body or self.args is None:

‎tests/sync/library/test_browsertype_connect.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,8 @@ def test_BT_034_should_connect_over_wss(
256256

257257
# https://stackoverflow.com/a/21961005/552185
258258
monkeypatch.setenv("NODE_TLS_REJECT_UNAUTHORIZED", "0")
259+
# suppressCertificateWarning()
260+
monkeypatch.setenv("NODE_NO_WARNINGS", "1")
259261

260262
https_server.once_web_socket_connection(
261263
lambda ws: forward_web_socket(ws, remote_server.ws_endpoint)

0 commit comments

Comments
 (0)