Skip to content

Commit 0c26cbf

Browse files
authored
feat(roll): roll Playwright 1.15.0-next-1631655106000 (#905)
1 parent d12962b commit 0c26cbf

File tree

13 files changed

+387
-136
lines changed

13 files changed

+387
-136
lines changed

CONTRIBUTING.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,13 @@ pre-commit run --all-files
4949

5050
For more details look at the [CI configuration](./blob/master/.github/workflows/ci.yml).
5151

52+
Collect coverage
53+
54+
```sh
55+
pytest --browser chromium --cov-report html --cov=playwright
56+
open htmlcov/index.html
57+
```
58+
5259
### Regenerating APIs
5360

5461
```bash

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ Playwright is a Python library to automate [Chromium](https://www.chromium.org/H
44

55
| | Linux | macOS | Windows |
66
| :--- | :---: | :---: | :---: |
7-
| Chromium <!-- GEN:chromium-version -->95.0.4636.0<!-- GEN:stop --> ||||
7+
| Chromium <!-- GEN:chromium-version -->96.0.4641.0<!-- GEN:stop --> ||||
88
| WebKit <!-- GEN:webkit-version -->15.0<!-- GEN:stop --> ||||
9-
| Firefox <!-- GEN:firefox-version -->91.0<!-- GEN:stop --> ||||
9+
| Firefox <!-- GEN:firefox-version -->92.0<!-- GEN:stop --> ||||
1010

1111
## Documentation
1212

playwright/_impl/_api_structures.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
# limitations under the License.
1414

1515
import sys
16-
from typing import List, Optional, Union
16+
from typing import Dict, List, Optional, Union
1717

1818
if sys.version_info >= (3, 8): # pragma: no cover
1919
from typing import Literal, TypedDict
@@ -139,3 +139,12 @@ class SecurityDetails(TypedDict):
139139
subjectName: Optional[str]
140140
validFrom: Optional[float]
141141
validTo: Optional[float]
142+
143+
144+
class NameValue(TypedDict):
145+
name: str
146+
value: str
147+
148+
149+
HeadersArray = List[NameValue]
150+
Headers = Dict[str, str]

playwright/_impl/_browser_type.py

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@
3838
ForcedColors,
3939
ReducedMotion,
4040
locals_to_params,
41-
not_installed_error,
4241
)
4342
from playwright._impl._transport import WebSocketTransport
4443
from playwright._impl._wait_helper import throw_on_timeout
@@ -86,12 +85,7 @@ async def launch(
8685
) -> Browser:
8786
params = locals_to_params(locals())
8887
normalize_launch_params(params)
89-
try:
90-
return from_channel(await self._channel.send("launch", params))
91-
except Exception as e:
92-
if "npx playwright install" in str(e):
93-
raise not_installed_error(f'"{self.name}" browser was not found.')
94-
raise e
88+
return from_channel(await self._channel.send("launch", params))
9589

9690
async def launch_persistent_context(
9791
self,
@@ -144,16 +138,11 @@ async def launch_persistent_context(
144138
params = locals_to_params(locals())
145139
await normalize_context_params(self._connection._is_sync, params)
146140
normalize_launch_params(params)
147-
try:
148-
context = from_channel(
149-
await self._channel.send("launchPersistentContext", params)
150-
)
151-
context._options = params
152-
return context
153-
except Exception as e:
154-
if "npx playwright install" in str(e):
155-
raise not_installed_error(f'"{self.name}" browser was not found.')
156-
raise e
141+
context = from_channel(
142+
await self._channel.send("launchPersistentContext", params)
143+
)
144+
context._options = params
145+
return context
157146

158147
async def connect_over_cdp(
159148
self,

playwright/_impl/_helper.py

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
)
3737
from urllib.parse import urljoin
3838

39+
from playwright._impl._api_structures import NameValue
3940
from playwright._impl._api_types import Error, TimeoutError
4041

4142
if sys.version_info >= (3, 8): # pragma: no cover
@@ -69,15 +70,10 @@ class ErrorPayload(TypedDict, total=False):
6970
value: Any
7071

7172

72-
class Header(TypedDict):
73-
name: str
74-
value: str
75-
76-
7773
class ContinueParameters(TypedDict, total=False):
7874
url: Optional[str]
7975
method: Optional[str]
80-
headers: Optional[List[Header]]
76+
headers: Optional[List[NameValue]]
8177
postData: Optional[str]
8278

8379

@@ -234,20 +230,6 @@ def is_safe_close_error(error: Exception) -> bool:
234230
)
235231

236232

237-
def not_installed_error(message: str) -> Exception:
238-
return Error(
239-
f"""
240-
================================================================================
241-
{message}
242-
Please complete Playwright installation via running
243-
244-
"python -m playwright install"
245-
246-
================================================================================
247-
"""
248-
)
249-
250-
251233
to_snake_case_regex = re.compile("((?<=[a-z0-9])[A-Z]|(?!^)[A-Z](?=[a-z]))")
252234

253235

playwright/_impl/_input.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ async def dblclick(
8080
) -> None:
8181
await self.click(x, y, delay=delay, button=button, clickCount=2)
8282

83+
async def wheel(self, deltaX: float, deltaY: float) -> None:
84+
await self._channel.send("mouseWheel", locals_to_params(locals()))
85+
8386

8487
class Touchscreen:
8588
def __init__(self, channel: Channel) -> None:

playwright/_impl/_network.py

Lines changed: 64 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,15 @@
1616
import base64
1717
import json
1818
import mimetypes
19+
from collections import defaultdict
1920
from pathlib import Path
2021
from types import SimpleNamespace
2122
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Union, cast
2223
from urllib import parse
2324

2425
from playwright._impl._api_structures import (
26+
Headers,
27+
HeadersArray,
2528
RemoteAddr,
2629
RequestSizes,
2730
ResourceTiming,
@@ -34,7 +37,7 @@
3437
from_nullable_channel,
3538
)
3639
from playwright._impl._event_context_manager import EventContextManagerImpl
37-
from playwright._impl._helper import ContinueParameters, Header, locals_to_params
40+
from playwright._impl._helper import ContinueParameters, locals_to_params
3841
from playwright._impl._wait_helper import WaitHelper
3942

4043
if TYPE_CHECKING: # pragma: no cover
@@ -64,8 +67,8 @@ def __init__(
6467
"responseStart": -1,
6568
"responseEnd": -1,
6669
}
67-
self._headers: List[Header] = self._initializer["headers"]
68-
self._all_headers_future: Optional[asyncio.Future[List[Header]]] = None
70+
self._provisional_headers = RawHeaders(self._initializer["headers"])
71+
self._all_headers_future: Optional[asyncio.Future[RawHeaders]] = None
6972

7073
def __repr__(self) -> str:
7174
return f"<Request url={self.url!r} method={self.method!r}>"
@@ -115,10 +118,6 @@ def post_data_buffer(self) -> Optional[bytes]:
115118
return None
116119
return base64.b64decode(b64_content)
117120

118-
@property
119-
def headers(self) -> Dict[str, str]:
120-
return headers_array_to_object(self._headers, True)
121-
122121
async def response(self) -> Optional["Response"]:
123122
return from_nullable_channel(await self._channel.send("response"))
124123

@@ -145,25 +144,27 @@ def failure(self) -> Optional[str]:
145144
def timing(self) -> ResourceTiming:
146145
return self._timing
147146

148-
async def all_headers(self) -> Dict[str, str]:
149-
return headers_array_to_object(await self._get_headers_if_needed(), True)
147+
@property
148+
def headers(self) -> Headers:
149+
return self._provisional_headers.headers()
150150

151-
async def headers_array(self) -> List[List[str]]:
152-
return list(
153-
map(
154-
lambda header: [header["name"], header["value"]],
155-
await self._get_headers_if_needed(),
156-
)
157-
)
151+
async def all_headers(self) -> Headers:
152+
return (await self._actual_headers()).headers()
153+
154+
async def headers_array(self) -> HeadersArray:
155+
return (await self._actual_headers()).headers_array()
156+
157+
async def header_value(self, name: str) -> Optional[str]:
158+
return (await self._actual_headers()).get(name)
158159

159-
async def _get_headers_if_needed(self) -> List[Header]:
160+
async def _actual_headers(self) -> "RawHeaders":
160161
if not self._all_headers_future:
161162
self._all_headers_future = asyncio.Future()
162163
response = await self.response()
163164
if not response:
164-
return self._headers
165+
return self._provisional_headers
165166
headers = await response._channel.send("rawRequestHeaders")
166-
self._all_headers_future.set_result(headers)
167+
self._all_headers_future.set_result(RawHeaders(headers))
167168
return await self._all_headers_future
168169

169170

@@ -256,10 +257,10 @@ def __init__(
256257
self._request._timing["connectEnd"] = timing["connectEnd"]
257258
self._request._timing["requestStart"] = timing["requestStart"]
258259
self._request._timing["responseStart"] = timing["responseStart"]
259-
self._headers = headers_array_to_object(
260-
cast(List[Header], self._initializer["headers"]), True
260+
self._provisional_headers = RawHeaders(
261+
cast(HeadersArray, self._initializer["headers"])
261262
)
262-
self._raw_headers_future: Optional[asyncio.Future[List[Header]]] = None
263+
self._raw_headers_future: Optional[asyncio.Future[RawHeaders]] = None
263264
self._finished_future: asyncio.Future[bool] = asyncio.Future()
264265

265266
def __repr__(self) -> str:
@@ -284,25 +285,26 @@ def status_text(self) -> str:
284285
return self._initializer["statusText"]
285286

286287
@property
287-
def headers(self) -> Dict[str, str]:
288-
return self._headers.copy()
288+
def headers(self) -> Headers:
289+
return self._provisional_headers.headers()
289290

290-
async def all_headers(self) -> Dict[str, str]:
291-
return headers_array_to_object(await self._get_headers_if_needed(), True)
291+
async def all_headers(self) -> Headers:
292+
return (await self._actual_headers()).headers()
292293

293-
async def headers_array(self) -> List[List[str]]:
294-
return list(
295-
map(
296-
lambda header: [header["name"], header["value"]],
297-
await self._get_headers_if_needed(),
298-
)
299-
)
294+
async def headers_array(self) -> HeadersArray:
295+
return (await self._actual_headers()).headers_array()
296+
297+
async def header_value(self, name: str) -> Optional[str]:
298+
return (await self._actual_headers()).get(name)
299+
300+
async def header_values(self, name: str) -> List[str]:
301+
return (await self._actual_headers()).get_all(name)
300302

301-
async def _get_headers_if_needed(self) -> List[Header]:
303+
async def _actual_headers(self) -> "RawHeaders":
302304
if not self._raw_headers_future:
303305
self._raw_headers_future = asyncio.Future()
304-
headers = cast(List[Header], await self._channel.send("rawResponseHeaders"))
305-
self._raw_headers_future.set_result(headers)
306+
headers = cast(HeadersArray, await self._channel.send("rawResponseHeaders"))
307+
self._raw_headers_future.set_result(RawHeaders(headers))
306308
return await self._raw_headers_future
307309

308310
async def server_addr(self) -> Optional[RemoteAddr]:
@@ -420,12 +422,32 @@ def _on_close(self) -> None:
420422
self.emit(WebSocket.Events.Close, self)
421423

422424

423-
def serialize_headers(headers: Dict[str, str]) -> List[Header]:
425+
def serialize_headers(headers: Dict[str, str]) -> HeadersArray:
424426
return [{"name": name, "value": value} for name, value in headers.items()]
425427

426428

427-
def headers_array_to_object(headers: List[Header], lower_case: bool) -> Dict[str, str]:
428-
return {
429-
(header["name"].lower() if lower_case else header["name"]): header["value"]
430-
for header in headers
431-
}
429+
class RawHeaders:
430+
def __init__(self, headers: HeadersArray) -> None:
431+
self._headers_array = headers
432+
self._headers_map: Dict[str, Dict[str, bool]] = defaultdict(dict)
433+
for header in headers:
434+
self._headers_map[header["name"].lower()][header["value"]] = True
435+
436+
def get(self, name: str) -> Optional[str]:
437+
values = self.get_all(name)
438+
if not values:
439+
return None
440+
separator = "\n" if name.lower() == "set-cookie" else ", "
441+
return separator.join(values)
442+
443+
def get_all(self, name: str) -> List[str]:
444+
return list(self._headers_map[name.lower()].keys())
445+
446+
def headers(self) -> Dict[str, str]:
447+
result = {}
448+
for name in self._headers_map.keys():
449+
result[name] = cast(str, self.get(name))
450+
return result
451+
452+
def headers_array(self) -> HeadersArray:
453+
return self._headers_array

0 commit comments

Comments
 (0)