diff --git a/playwright/_impl/_browser.py b/playwright/_impl/_browser.py index 2066b3b38..331b446d5 100644 --- a/playwright/_impl/_browser.py +++ b/playwright/_impl/_browser.py @@ -31,6 +31,7 @@ from playwright._impl._helper import ( ColorScheme, ReducedMotion, + async_readfile, is_safe_close_error, locals_to_params, ) @@ -106,7 +107,7 @@ async def new_context( storageState: Union[StorageState, str, Path] = None, ) -> BrowserContext: params = locals_to_params(locals()) - normalize_context_params(self._connection._is_sync, params) + await normalize_context_params(self._connection._is_sync, params) channel = await self._channel.send("newContext", params) context = from_channel(channel) @@ -190,7 +191,7 @@ async def stop_tracing(self) -> bytes: return base64.b64decode(encoded_binary) -def normalize_context_params(is_sync: bool, params: Dict) -> None: +async def normalize_context_params(is_sync: bool, params: Dict) -> None: params["sdkLanguage"] = "python" if is_sync else "python-async" if params.get("noViewport"): del params["noViewport"] @@ -214,5 +215,6 @@ def normalize_context_params(is_sync: bool, params: Dict) -> None: if "storageState" in params: storageState = params["storageState"] if not isinstance(storageState, dict): - with open(storageState, "r") as f: - params["storageState"] = json.load(f) + params["storageState"] = json.loads( + (await async_readfile(storageState)).decode() + ) diff --git a/playwright/_impl/_browser_context.py b/playwright/_impl/_browser_context.py index 2d443988c..a68ccf232 100644 --- a/playwright/_impl/_browser_context.py +++ b/playwright/_impl/_browser_context.py @@ -34,6 +34,8 @@ TimeoutSettings, URLMatch, URLMatcher, + async_readfile, + async_writefile, is_safe_close_error, locals_to_params, ) @@ -214,8 +216,7 @@ async def add_init_script( self, script: str = None, path: Union[str, Path] = None ) -> None: if path: - with open(path, "r") as file: - script = file.read() + script = (await async_readfile(path)).decode() if not isinstance(script, str): raise Error("Either path or script parameter must be specified") await self._channel.send("addInitScript", dict(source=script)) @@ -298,8 +299,7 @@ async def _pause(self) -> None: async def storage_state(self, path: Union[str, Path] = None) -> StorageState: result = await self._channel.send_return_as_dict("storageState") if path: - with open(path, "w") as f: - json.dump(result, f) + await async_writefile(path, json.dumps(result)) return result async def wait_for_event( diff --git a/playwright/_impl/_browser_type.py b/playwright/_impl/_browser_type.py index 5418f79a7..07e0ad724 100644 --- a/playwright/_impl/_browser_type.py +++ b/playwright/_impl/_browser_type.py @@ -135,7 +135,7 @@ async def launch_persistent_context( ) -> BrowserContext: userDataDir = str(Path(userDataDir)) params = locals_to_params(locals()) - normalize_context_params(self._connection._is_sync, params) + await normalize_context_params(self._connection._is_sync, params) normalize_launch_params(params) try: context = from_channel( diff --git a/playwright/_impl/_element_handle.py b/playwright/_impl/_element_handle.py index e7f86e69e..3dc7c5909 100644 --- a/playwright/_impl/_element_handle.py +++ b/playwright/_impl/_element_handle.py @@ -23,6 +23,7 @@ from playwright._impl._helper import ( KeyboardModifier, MouseButton, + async_writefile, locals_to_params, make_dirs_for_file, ) @@ -179,7 +180,7 @@ async def set_input_files( noWaitAfter: bool = None, ) -> None: params = locals_to_params(locals()) - params["files"] = normalize_file_payloads(files) + params["files"] = await normalize_file_payloads(files) await self._channel.send("setInputFiles", params) async def focus(self) -> None: @@ -241,8 +242,7 @@ async def screenshot( decoded_binary = base64.b64decode(encoded_binary) if path: make_dirs_for_file(path) - with open(path, "wb") as fd: - fd.write(decoded_binary) + await async_writefile(path, decoded_binary) return decoded_binary async def query_selector(self, selector: str) -> Optional["ElementHandle"]: diff --git a/playwright/_impl/_file_chooser.py b/playwright/_impl/_file_chooser.py index 67f0fc0ec..7add73e07 100644 --- a/playwright/_impl/_file_chooser.py +++ b/playwright/_impl/_file_chooser.py @@ -18,6 +18,7 @@ from typing import TYPE_CHECKING, List, Union from playwright._impl._api_structures import FilePayload +from playwright._impl._helper import async_readfile if TYPE_CHECKING: # pragma: no cover from playwright._impl._element_handle import ElementHandle @@ -57,20 +58,19 @@ async def set_files( await self._element_handle.set_input_files(files, timeout, noWaitAfter) -def normalize_file_payloads( +async def normalize_file_payloads( files: Union[str, Path, FilePayload, List[Union[str, Path]], List[FilePayload]] ) -> List: file_list = files if isinstance(files, list) else [files] file_payloads: List = [] for item in file_list: - if isinstance(item, str) or isinstance(item, Path): - with open(item, mode="rb") as fd: - file_payloads.append( - { - "name": os.path.basename(item), - "buffer": base64.b64encode(fd.read()).decode(), - } - ) + if isinstance(item, (str, Path)): + file_payloads.append( + { + "name": os.path.basename(item), + "buffer": base64.b64encode(await async_readfile(item)).decode(), + } + ) else: file_payloads.append( { diff --git a/playwright/_impl/_frame.py b/playwright/_impl/_frame.py index c33bd1e97..21ded4d2e 100644 --- a/playwright/_impl/_frame.py +++ b/playwright/_impl/_frame.py @@ -36,6 +36,7 @@ MouseButton, URLMatch, URLMatcher, + async_readfile, locals_to_params, monotonic_time, ) @@ -360,9 +361,12 @@ async def add_script_tag( ) -> ElementHandle: params = locals_to_params(locals()) if path: - with open(path, "r") as file: - params["content"] = file.read() + "\n//# sourceURL=" + str(Path(path)) - del params["path"] + params["content"] = ( + (await async_readfile(path)).decode() + + "\n//# sourceURL=" + + str(Path(path)) + ) + del params["path"] return from_channel(await self._channel.send("addScriptTag", params)) async def add_style_tag( @@ -370,11 +374,13 @@ async def add_style_tag( ) -> ElementHandle: params = locals_to_params(locals()) if path: - with open(path, "r") as file: - params["content"] = ( - file.read() + "\n/*# sourceURL=" + str(Path(path)) + "*/" - ) - del params["path"] + params["content"] = ( + (await async_readfile(path)).decode() + + "\n/*# sourceURL=" + + str(Path(path)) + + "*/" + ) + del params["path"] return from_channel(await self._channel.send("addStyleTag", params)) async def click( @@ -479,7 +485,7 @@ async def set_input_files( noWaitAfter: bool = None, ) -> None: params = locals_to_params(locals()) - params["files"] = normalize_file_payloads(files) + params["files"] = await normalize_file_payloads(files) await self._channel.send("setInputFiles", params) async def type( diff --git a/playwright/_impl/_helper.py b/playwright/_impl/_helper.py index e3917a546..0485e0411 100644 --- a/playwright/_impl/_helper.py +++ b/playwright/_impl/_helper.py @@ -11,7 +11,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - +import asyncio import fnmatch import math import os @@ -231,3 +231,21 @@ def make_dirs_for_file(path: Union[Path, str]) -> None: if not os.path.isabs(path): path = Path.cwd() / path os.makedirs(os.path.dirname(path), exist_ok=True) + + +async def async_writefile(file: Union[str, Path], data: Union[str, bytes]) -> None: + def inner() -> None: + with open(file, "w" if isinstance(data, str) else "wb") as fh: + fh.write(data) + + loop = asyncio.get_running_loop() + await loop.run_in_executor(None, inner) + + +async def async_readfile(file: Union[str, Path]) -> bytes: + def inner() -> bytes: + with open(file, "rb") as fh: + return fh.read() + + loop = asyncio.get_running_loop() + return await loop.run_in_executor(None, inner) diff --git a/playwright/_impl/_page.py b/playwright/_impl/_page.py index 826b894f0..c139aad4f 100644 --- a/playwright/_impl/_page.py +++ b/playwright/_impl/_page.py @@ -54,6 +54,8 @@ URLMatcher, URLMatchRequest, URLMatchResponse, + async_readfile, + async_writefile, is_safe_close_error, locals_to_params, make_dirs_for_file, @@ -498,8 +500,7 @@ async def add_init_script( self, script: str = None, path: Union[str, Path] = None ) -> None: if path: - with open(path, "r") as file: - script = file.read() + script = (await async_readfile(path)).decode() if not isinstance(script, str): raise Error("Either path or script parameter must be specified") await self._channel.send("addInitScript", dict(source=script)) @@ -542,8 +543,7 @@ async def screenshot( decoded_binary = base64.b64decode(encoded_binary) if path: make_dirs_for_file(path) - with open(path, "wb") as fd: - fd.write(decoded_binary) + await async_writefile(path, decoded_binary) return decoded_binary async def title(self) -> str: @@ -741,8 +741,7 @@ async def pdf( decoded_binary = base64.b64decode(encoded_binary) if path: make_dirs_for_file(path) - with open(path, "wb") as fd: - fd.write(decoded_binary) + await async_writefile(path, decoded_binary) return decoded_binary @property diff --git a/playwright/_impl/_selectors.py b/playwright/_impl/_selectors.py index 00d3a8af4..3f67f2055 100644 --- a/playwright/_impl/_selectors.py +++ b/playwright/_impl/_selectors.py @@ -17,6 +17,7 @@ from playwright._impl._api_types import Error from playwright._impl._connection import ChannelOwner +from playwright._impl._helper import async_readfile class Selectors(ChannelOwner): @@ -35,8 +36,7 @@ async def register( if not script and not path: raise Error("Either source or path should be specified") if path: - with open(path, "r") as file: - script = file.read() + script = (await async_readfile(path)).decode() params: Dict = dict(name=name, source=script) if contentScript: params["contentScript"] = True diff --git a/playwright/_impl/_stream.py b/playwright/_impl/_stream.py index b2663e330..2ed352192 100644 --- a/playwright/_impl/_stream.py +++ b/playwright/_impl/_stream.py @@ -26,9 +26,12 @@ def __init__( super().__init__(parent, type, guid, initializer) async def save_as(self, path: Union[str, Path]) -> None: - with open(path, mode="wb") as file: - while True: - binary = await self._channel.send("read") - if not binary: - break - file.write(base64.b64decode(binary)) + file = await self._loop.run_in_executor(None, lambda: open(path, "wb")) + while True: + binary = await self._channel.send("read") + if not binary: + break + await self._loop.run_in_executor( + None, lambda: file.write(base64.b64decode(binary)) + ) + await self._loop.run_in_executor(None, lambda: file.close())