Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 65 additions & 1 deletion playwright/async_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.


import pathlib
import sys
import typing
Expand Down Expand Up @@ -65,6 +64,8 @@
from playwright.page import Worker as WorkerImpl
from playwright.playwright import Playwright as PlaywrightImpl
from playwright.selectors import Selectors as SelectorsImpl
from playwright.stream import Stream as StreamImpl
from playwright.stream import StreamIO as StreamIOImpl

NoneType = type(None)

Expand Down Expand Up @@ -2761,6 +2762,7 @@ async def register(
Name that is used in selectors as a prefix, e.g. `{name: 'foo'}` enables `foo=myselectorbody` selectors. May only contain `[a-zA-Z0-9_]` characters.
source : Optional[str]
Script that evaluates to a selector engine instance.
path : Optional[str]
contentScript : Optional[bool]
Whether to run this selector engine in isolated JavaScript environment. This environment has access to the same DOM, but not any JavaScript objects from the frame's scripts. Defaults to `false`. Note that running as a content script is not guaranteed when this engine is used together with other registered engines.
"""
Expand Down Expand Up @@ -2882,6 +2884,49 @@ async def dismiss(self) -> NoneType:
mapping.register(DialogImpl, Dialog)


class StreamIO(AsyncBase):
def __init__(self, obj: StreamIOImpl):
super().__init__(obj)

async def read(self, size: int = None) -> typing.Union[bytes, NoneType]:
"""StreamIO.read

Reads from the download bytes

Parameters
----------
size : Optional[int]
Size in bytes how much should be read.

Returns
-------
Optional[bytes]
"""
return mapping.from_maybe_impl(await self._impl_obj.read(size=size))


mapping.register(StreamIOImpl, StreamIO)


class Stream(AsyncBase):
def __init__(self, obj: StreamImpl):
super().__init__(obj)

async def stream(self) -> "StreamIO":
"""Stream.stream

Returns the file stream

Returns
-------
StreamIO
"""
return mapping.from_impl(await self._impl_obj.stream())


mapping.register(StreamImpl, Stream)


class Download(AsyncBase):
def __init__(self, obj: DownloadImpl):
super().__init__(obj)
Expand Down Expand Up @@ -2951,6 +2996,17 @@ async def saveAs(self, path: typing.Union[pathlib.Path, str]) -> NoneType:
"""
return mapping.from_maybe_impl(await self._impl_obj.saveAs(path=path))

async def createReadStream(self) -> typing.Union["StreamIO", NoneType]:
"""Download.createReadStream

Returns readable stream for current download or `null` if download failed.

Returns
-------
Optional[StreamIO]
"""
return mapping.from_impl_nullable(await self._impl_obj.createReadStream())


mapping.register(DownloadImpl, Download)

Expand Down Expand Up @@ -3708,6 +3764,7 @@ async def waitForRequest(
----------
url : Union[str, Pattern, typing.Callable[[str], bool], NoneType]
Request URL string, regex or predicate receiving Request object.
predicate : Optional[typing.Callable[[playwright.network.Request], bool]]
timeout : Optional[int]
Maximum wait time in milliseconds, defaults to 30 seconds, pass `0` to disable the timeout. The default value can be changed by using the page.setDefaultTimeout(timeout) method.

Expand Down Expand Up @@ -3737,6 +3794,7 @@ async def waitForResponse(
----------
url : Union[str, Pattern, typing.Callable[[str], bool], NoneType]
Request URL string, regex or predicate receiving Response object.
predicate : Optional[typing.Callable[[playwright.network.Response], bool]]
timeout : Optional[int]
Maximum wait time in milliseconds, defaults to 30 seconds, pass `0` to disable the timeout. The default value can be changed by using the browserContext.setDefaultTimeout(timeout) or page.setDefaultTimeout(timeout) methods.

Expand Down Expand Up @@ -3768,6 +3826,8 @@ async def waitForEvent(
----------
event : str
Event name, same one would pass into `page.on(event)`.
predicate : Optional[typing.Callable[[typing.Any], bool]]
timeout : Optional[int]

Returns
-------
Expand Down Expand Up @@ -3907,6 +3967,7 @@ async def addInitScript(self, source: str = None, path: str = None) -> NoneType:
----------
source : Optional[str]
Script to be evaluated in the page.
path : Optional[str]
"""
return mapping.from_maybe_impl(
await self._impl_obj.addInitScript(source=source, path=path)
Expand Down Expand Up @@ -5033,6 +5094,7 @@ async def addInitScript(self, source: str = None, path: str = None) -> NoneType:
----------
source : Optional[str]
Script to be evaluated in all pages in the browser context.
path : Optional[str]
"""
return mapping.from_maybe_impl(
await self._impl_obj.addInitScript(source=source, path=path)
Expand Down Expand Up @@ -5149,6 +5211,8 @@ async def waitForEvent(
----------
event : str
Event name, same one would pass into `browserContext.on(event)`.
predicate : Optional[typing.Callable[[typing.Any], bool]]
timeout : Optional[int]

Returns
-------
Expand Down
2 changes: 1 addition & 1 deletion playwright/browser_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ async def waitForEvent(
timeout = self._timeout_settings.timeout()
wait_helper = WaitHelper(self._loop)
wait_helper.reject_on_timeout(
timeout, f'Timeout while waiting for event "${event}"'
timeout, f'Timeout while waiting for event "{event}"'
)
if event != BrowserContext.Events.Close:
wait_helper.reject_on_event(
Expand Down
10 changes: 9 additions & 1 deletion playwright/download.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import io
from pathlib import Path
from typing import Dict, Optional, Union

from playwright.connection import ChannelOwner
from playwright.connection import ChannelOwner, from_channel
from playwright.stream import StreamIO


class Download(ChannelOwner):
Expand Down Expand Up @@ -44,3 +46,9 @@ async def path(self) -> Optional[str]:
async def saveAs(self, path: Union[Path, str]) -> None:
path = str(Path(path))
return await self._channel.send("saveAs", dict(path=path))

async def createReadStream(self) -> Optional[StreamIO]:
stream = await self._channel.send("stream")
if not stream:
return None
return await from_channel(stream).stream()
3 changes: 3 additions & 0 deletions playwright/object_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from playwright.page import BindingCall, Page, Worker
from playwright.playwright import Playwright
from playwright.selectors import Selectors
from playwright.stream import Stream


class DummyObject(ChannelOwner):
Expand Down Expand Up @@ -88,4 +89,6 @@ def create_remote_object(
return Worker(parent, type, guid, initializer)
if type == "Selectors":
return Selectors(parent, type, guid, initializer)
if type == "Stream":
return Stream(parent, type, guid, initializer)
return DummyObject(parent, type, guid, initializer)
2 changes: 1 addition & 1 deletion playwright/page.py
Original file line number Diff line number Diff line change
Expand Up @@ -461,7 +461,7 @@ async def waitForEvent(
timeout = self._timeout_settings.timeout()
wait_helper = WaitHelper(self._loop)
wait_helper.reject_on_timeout(
timeout, f'Timeout while waiting for event "${event}"'
timeout, f'Timeout while waiting for event "{event}"'
)
if event != Page.Events.Crash:
wait_helper.reject_on_event(self, Page.Events.Crash, Error("Page crashed"))
Expand Down
44 changes: 44 additions & 0 deletions playwright/stream.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Copyright (c) Microsoft Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# 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 base64
import io
from typing import Dict, Optional

from playwright.connection import ChannelOwner


class StreamIO(io.RawIOBase):
def __init__(self, channel: ChannelOwner, loop) -> None:
self._channel = channel
self._loop = loop


async def read(self, size: int = None) -> Optional[bytes]: # type: ignore
if not size:
size = 16384
result = await self._channel.send("read", dict(size=size))
return base64.b64decode(result)


class Stream(ChannelOwner):
def __init__(
self, parent: ChannelOwner, type: str, guid: str, initializer: Dict
) -> None:
super().__init__(parent, type, guid, initializer)
self._loop = parent._loop

async def stream(self,) -> StreamIO:
return StreamIO(self._channel, self._loop)
66 changes: 65 additions & 1 deletion playwright/sync_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.


import pathlib
import sys
import typing
Expand Down Expand Up @@ -64,6 +63,8 @@
from playwright.page import Worker as WorkerImpl
from playwright.playwright import Playwright as PlaywrightImpl
from playwright.selectors import Selectors as SelectorsImpl
from playwright.stream import Stream as StreamImpl
from playwright.stream import StreamIO as StreamIOImpl
from playwright.sync_base import EventContextManager, SyncBase, mapping

NoneType = type(None)
Expand Down Expand Up @@ -2885,6 +2886,7 @@ def register(
Name that is used in selectors as a prefix, e.g. `{name: 'foo'}` enables `foo=myselectorbody` selectors. May only contain `[a-zA-Z0-9_]` characters.
source : Optional[str]
Script that evaluates to a selector engine instance.
path : Optional[str]
contentScript : Optional[bool]
Whether to run this selector engine in isolated JavaScript environment. This environment has access to the same DOM, but not any JavaScript objects from the frame's scripts. Defaults to `false`. Note that running as a content script is not guaranteed when this engine is used together with other registered engines.
"""
Expand Down Expand Up @@ -3008,6 +3010,49 @@ def dismiss(self) -> NoneType:
mapping.register(DialogImpl, Dialog)


class StreamIO(SyncBase):
def __init__(self, obj: StreamIOImpl):
super().__init__(obj)

def read(self, size: int = None) -> typing.Union[bytes, NoneType]:
"""StreamIO.read

Reads from the download bytes

Parameters
----------
size : Optional[int]
Size in bytes how much should be read.

Returns
-------
Optional[bytes]
"""
return mapping.from_maybe_impl(self._sync(self._impl_obj.read(size=size)))


mapping.register(StreamIOImpl, StreamIO)


class Stream(SyncBase):
def __init__(self, obj: StreamImpl):
super().__init__(obj)

def stream(self) -> "StreamIO":
"""Stream.stream

Returns the file stream

Returns
-------
StreamIO
"""
return mapping.from_impl(self._sync(self._impl_obj.stream()))


mapping.register(StreamImpl, Stream)


class Download(SyncBase):
def __init__(self, obj: DownloadImpl):
super().__init__(obj)
Expand Down Expand Up @@ -3077,6 +3122,17 @@ def saveAs(self, path: typing.Union[pathlib.Path, str]) -> NoneType:
"""
return mapping.from_maybe_impl(self._sync(self._impl_obj.saveAs(path=path)))

def createReadStream(self) -> typing.Union["StreamIO", NoneType]:
"""Download.createReadStream

Returns readable stream for current download or `null` if download failed.

Returns
-------
Optional[StreamIO]
"""
return mapping.from_impl_nullable(self._sync(self._impl_obj.createReadStream()))


mapping.register(DownloadImpl, Download)

Expand Down Expand Up @@ -3862,6 +3918,7 @@ def waitForRequest(
----------
url : Union[str, Pattern, typing.Callable[[str], bool], NoneType]
Request URL string, regex or predicate receiving Request object.
predicate : Optional[typing.Callable[[playwright.network.Request], bool]]
timeout : Optional[int]
Maximum wait time in milliseconds, defaults to 30 seconds, pass `0` to disable the timeout. The default value can be changed by using the page.setDefaultTimeout(timeout) method.

Expand Down Expand Up @@ -3893,6 +3950,7 @@ def waitForResponse(
----------
url : Union[str, Pattern, typing.Callable[[str], bool], NoneType]
Request URL string, regex or predicate receiving Response object.
predicate : Optional[typing.Callable[[playwright.network.Response], bool]]
timeout : Optional[int]
Maximum wait time in milliseconds, defaults to 30 seconds, pass `0` to disable the timeout. The default value can be changed by using the browserContext.setDefaultTimeout(timeout) or page.setDefaultTimeout(timeout) methods.

Expand Down Expand Up @@ -3926,6 +3984,8 @@ def waitForEvent(
----------
event : str
Event name, same one would pass into `page.on(event)`.
predicate : Optional[typing.Callable[[typing.Any], bool]]
timeout : Optional[int]

Returns
-------
Expand Down Expand Up @@ -4071,6 +4131,7 @@ def addInitScript(self, source: str = None, path: str = None) -> NoneType:
----------
source : Optional[str]
Script to be evaluated in the page.
path : Optional[str]
"""
return mapping.from_maybe_impl(
self._sync(self._impl_obj.addInitScript(source=source, path=path))
Expand Down Expand Up @@ -5247,6 +5308,7 @@ def addInitScript(self, source: str = None, path: str = None) -> NoneType:
----------
source : Optional[str]
Script to be evaluated in all pages in the browser context.
path : Optional[str]
"""
return mapping.from_maybe_impl(
self._sync(self._impl_obj.addInitScript(source=source, path=path))
Expand Down Expand Up @@ -5371,6 +5433,8 @@ def waitForEvent(
----------
event : str
Event name, same one would pass into `browserContext.on(event)`.
predicate : Optional[typing.Callable[[typing.Any], bool]]
timeout : Optional[int]

Returns
-------
Expand Down
Loading