Skip to content

Commit 0c70108

Browse files
authored
feat(roll): roll to 1617212285000 aka video (microsoft#609)
1 parent ea66054 commit 0c70108

18 files changed

+250
-65
lines changed

README.md

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

77
| | Linux | macOS | Windows |
88
| :--- | :---: | :---: | :---: |
9-
| Chromium <!-- GEN:chromium-version -->90.0.4430.0<!-- GEN:stop --> ||||
9+
| Chromium <!-- GEN:chromium-version -->91.0.4455.0<!-- GEN:stop --> ||||
1010
| WebKit <!-- GEN:webkit-version -->14.2<!-- GEN:stop --> ||||
1111
| Firefox <!-- GEN:firefox-version -->87.0b10<!-- GEN:stop --> ||||
1212

playwright/_impl/_artifact.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Copyright (c) Microsoft Corporation.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import pathlib
16+
from pathlib import Path
17+
from typing import Dict, Optional, Union, cast
18+
19+
from playwright._impl._connection import ChannelOwner, from_channel
20+
from playwright._impl._helper import Error, make_dirs_for_file, patch_error_message
21+
from playwright._impl._stream import Stream
22+
23+
24+
class Artifact(ChannelOwner):
25+
def __init__(
26+
self, parent: ChannelOwner, type: str, guid: str, initializer: Dict
27+
) -> None:
28+
super().__init__(parent, type, guid, initializer)
29+
self._is_remote = False
30+
self.absolute_path = initializer["absolutePath"]
31+
32+
async def path_after_finished(self) -> Optional[pathlib.Path]:
33+
if self._is_remote:
34+
raise Error(
35+
"Path is not available when using browser_type.connect(). Use save_as() to save a local copy."
36+
)
37+
return pathlib.Path(await self._channel.send("pathAfterFinished"))
38+
39+
async def save_as(self, path: Union[str, Path]) -> None:
40+
stream = cast(Stream, from_channel(await self._channel.send("saveAsStream")))
41+
make_dirs_for_file(path)
42+
await stream.save_as(path)
43+
44+
async def failure(self) -> Optional[str]:
45+
return patch_error_message(await self._channel.send("failure"))
46+
47+
async def delete(self) -> None:
48+
await self._channel.send("delete")

playwright/_impl/_browser.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ def __init__(
4747
self._browser_type = parent
4848
self._is_connected = True
4949
self._is_closed_or_closing = False
50+
self._is_remote = False
5051

5152
self._contexts: List[BrowserContext] = []
5253
self._channel.on("close", lambda _: self._on_close())

playwright/_impl/_browser_context.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ def __repr__(self) -> str:
7979
return f"<BrowserContext browser={self.browser}>"
8080

8181
def _on_page(self, page: Page) -> None:
82+
print("ON PAGE ARRIVED")
8283
page._set_browser_context(self)
8384
self._pages.append(page)
8485
self.emit(BrowserContext.Events.Page, page)

playwright/_impl/_download.py

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,38 +14,43 @@
1414

1515
import pathlib
1616
from pathlib import Path
17-
from typing import Dict, Optional, Union
17+
from typing import TYPE_CHECKING, Optional, Union
1818

19-
from playwright._impl._connection import ChannelOwner
20-
from playwright._impl._helper import patch_error_message
19+
from playwright._impl._artifact import Artifact
2120

21+
if TYPE_CHECKING: # pragma: no cover
22+
from playwright._impl._page import Page
2223

23-
class Download(ChannelOwner):
24+
25+
class Download:
2426
def __init__(
25-
self, parent: ChannelOwner, type: str, guid: str, initializer: Dict
27+
self, page: "Page", url: str, suggested_filename: str, artifact: Artifact
2628
) -> None:
27-
super().__init__(parent, type, guid, initializer)
29+
self._loop = page._loop
30+
self._dispatcher_fiber = page._dispatcher_fiber
31+
self._url = url
32+
self._suggested_filename = suggested_filename
33+
self._artifact = artifact
2834

2935
def __repr__(self) -> str:
3036
return f"<Download url={self.url!r} suggested_filename={self.suggested_filename!r}>"
3137

3238
@property
3339
def url(self) -> str:
34-
return self._initializer["url"]
40+
return self._url
3541

3642
@property
3743
def suggested_filename(self) -> str:
38-
return self._initializer["suggestedFilename"]
44+
return self._suggested_filename
3945

4046
async def delete(self) -> None:
41-
await self._channel.send("delete")
47+
await self._artifact.delete()
4248

4349
async def failure(self) -> Optional[str]:
44-
return patch_error_message(await self._channel.send("failure"))
50+
return await self._artifact.failure()
4551

4652
async def path(self) -> Optional[pathlib.Path]:
47-
return pathlib.Path(await self._channel.send("path"))
53+
return await self._artifact.path_after_finished()
4854

4955
async def save_as(self, path: Union[str, Path]) -> None:
50-
path = str(Path(path))
51-
return await self._channel.send("saveAs", dict(path=path))
56+
await self._artifact.save_as(path)

playwright/_impl/_element_handle.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,12 @@
2020
from playwright._impl._api_structures import FilePayload, FloatRect, Position
2121
from playwright._impl._connection import ChannelOwner, from_nullable_channel
2222
from playwright._impl._file_chooser import normalize_file_payloads
23-
from playwright._impl._helper import KeyboardModifier, MouseButton, locals_to_params
23+
from playwright._impl._helper import (
24+
KeyboardModifier,
25+
MouseButton,
26+
locals_to_params,
27+
make_dirs_for_file,
28+
)
2429
from playwright._impl._js_handle import (
2530
JSHandle,
2631
Serializable,
@@ -221,6 +226,7 @@ async def screenshot(
221226
encoded_binary = await self._channel.send("screenshot", params)
222227
decoded_binary = base64.b64decode(encoded_binary)
223228
if path:
229+
make_dirs_for_file(path)
224230
with open(path, "wb") as fd:
225231
fd.write(decoded_binary)
226232
return decoded_binary

playwright/_impl/_helper.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,12 @@
1414

1515
import fnmatch
1616
import math
17+
import os
1718
import re
1819
import sys
1920
import time
2021
import traceback
22+
from pathlib import Path
2123
from types import TracebackType
2224
from typing import (
2325
TYPE_CHECKING,
@@ -232,3 +234,9 @@ def not_installed_error(message: str) -> Exception:
232234

233235
def to_snake_case(name: str) -> str:
234236
return to_snake_case_regex.sub(r"_\1", name).lower()
237+
238+
239+
def make_dirs_for_file(path: Union[Path, str]) -> None:
240+
if not os.path.isabs(path):
241+
path = Path.cwd() / path
242+
os.makedirs(os.path.dirname(path), exist_ok=True)

playwright/_impl/_object_factory.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
from typing import Any, Dict, cast
1616

17+
from playwright._impl._artifact import Artifact
1718
from playwright._impl._browser import Browser
1819
from playwright._impl._browser_context import BrowserContext
1920
from playwright._impl._browser_type import BrowserType
@@ -22,14 +23,14 @@
2223
from playwright._impl._connection import ChannelOwner
2324
from playwright._impl._console_message import ConsoleMessage
2425
from playwright._impl._dialog import Dialog
25-
from playwright._impl._download import Download
2626
from playwright._impl._element_handle import ElementHandle
2727
from playwright._impl._frame import Frame
2828
from playwright._impl._js_handle import JSHandle
2929
from playwright._impl._network import Request, Response, Route, WebSocket
3030
from playwright._impl._page import BindingCall, Page, Worker
3131
from playwright._impl._playwright import Playwright
3232
from playwright._impl._selectors import Selectors
33+
from playwright._impl._stream import Stream
3334

3435

3536
class DummyObject(ChannelOwner):
@@ -42,6 +43,8 @@ def __init__(
4243
def create_remote_object(
4344
parent: ChannelOwner, type: str, guid: str, initializer: Dict
4445
) -> Any:
46+
if type == "Artifact":
47+
return Artifact(parent, type, guid, initializer)
4548
if type == "BindingCall":
4649
return BindingCall(parent, type, guid, initializer)
4750
if type == "Browser":
@@ -63,8 +66,6 @@ def create_remote_object(
6366
return ConsoleMessage(parent, type, guid, initializer)
6467
if type == "Dialog":
6568
return Dialog(parent, type, guid, initializer)
66-
if type == "Download":
67-
return Download(parent, type, guid, initializer)
6869
if type == "ElementHandle":
6970
return ElementHandle(parent, type, guid, initializer)
7071
if type == "Frame":
@@ -81,6 +82,8 @@ def create_remote_object(
8182
return Response(parent, type, guid, initializer)
8283
if type == "Route":
8384
return Route(parent, type, guid, initializer)
85+
if type == "Stream":
86+
return Stream(parent, type, guid, initializer)
8487
if type == "WebSocket":
8588
return WebSocket(parent, type, guid, initializer)
8689
if type == "Worker":

playwright/_impl/_page.py

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
URLMatchResponse,
5555
is_safe_close_error,
5656
locals_to_params,
57+
make_dirs_for_file,
5758
parse_error,
5859
serialize_error,
5960
)
@@ -142,12 +143,7 @@ def __init__(
142143
self._channel.on(
143144
"domcontentloaded", lambda _: self.emit(Page.Events.DOMContentLoaded)
144145
)
145-
self._channel.on(
146-
"download",
147-
lambda params: self.emit(
148-
Page.Events.Download, from_channel(params["download"])
149-
),
150-
)
146+
self._channel.on("download", lambda params: self._on_download(params))
151147
self._channel.on(
152148
"fileChooser",
153149
lambda params: self.emit(
@@ -208,12 +204,7 @@ def __init__(
208204
from_channel(params["route"]), from_channel(params["request"])
209205
),
210206
)
211-
self._channel.on(
212-
"video",
213-
lambda params: cast(Video, self.video)._set_relative_path(
214-
params["relativePath"]
215-
),
216-
)
207+
self._channel.on("video", lambda params: self._on_video(params))
217208
self._channel.on(
218209
"webSocket",
219210
lambda params: self.emit(
@@ -294,6 +285,18 @@ def _on_dialog(self, params: Any) -> None:
294285
else:
295286
asyncio.create_task(dialog.dismiss())
296287

288+
def _on_download(self, params: Any) -> None:
289+
url = params["url"]
290+
suggested_filename = params["suggestedFilename"]
291+
artifact = from_channel(params["artifact"])
292+
self.emit(
293+
Page.Events.Download, Download(self, url, suggested_filename, artifact)
294+
)
295+
296+
def _on_video(self, params: Any) -> None:
297+
artifact = from_channel(params["artifact"])
298+
cast(Video, self.video)._artifact_ready(artifact)
299+
297300
def _add_event_handler(self, event: str, k: Any, v: Any) -> None:
298301
if event == Page.Events.FileChooser and len(self.listeners(event)) == 0:
299302
self._channel.send_no_reply(
@@ -575,6 +578,7 @@ async def screenshot(
575578
encoded_binary = await self._channel.send("screenshot", params)
576579
decoded_binary = base64.b64decode(encoded_binary)
577580
if path:
581+
make_dirs_for_file(path)
578582
with open(path, "wb") as fd:
579583
fd.write(decoded_binary)
580584
return decoded_binary
@@ -765,6 +769,7 @@ async def pdf(
765769
encoded_binary = await self._channel.send("pdf", params)
766770
decoded_binary = base64.b64decode(encoded_binary)
767771
if path:
772+
make_dirs_for_file(path)
768773
with open(path, "wb") as fd:
769774
fd.write(decoded_binary)
770775
return decoded_binary
@@ -773,13 +778,10 @@ async def pdf(
773778
def video(
774779
self,
775780
) -> Optional[Video]:
776-
context_options = self._browser_context._options
777-
if "recordVideo" not in context_options:
781+
if "recordVideo" not in self._browser_context._options:
778782
return None
779783
if not self._video:
780784
self._video = Video(self)
781-
if "videoRelativePath" in self._initializer:
782-
self._video._set_relative_path(self._initializer["videoRelativePath"])
783785
return self._video
784786

785787
def expect_event(

playwright/_impl/_stream.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Copyright (c) Microsoft Corporation.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import base64
16+
from pathlib import Path
17+
from typing import Dict
18+
19+
from playwright._impl._connection import ChannelOwner
20+
21+
22+
class Stream(ChannelOwner):
23+
def __init__(
24+
self, parent: ChannelOwner, type: str, guid: str, initializer: Dict
25+
) -> None:
26+
super().__init__(parent, type, guid, initializer)
27+
28+
async def save_as(self, path: Path) -> None:
29+
with open(path, mode="wb") as file:
30+
while True:
31+
binary = await self._channel.send("read")
32+
if not binary:
33+
break
34+
file.write(base64.b64decode(binary))

0 commit comments

Comments
 (0)