Skip to content
Merged
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
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ on:
jobs:
build:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
python-version: [3.7, 3.8]
Expand Down
5 changes: 4 additions & 1 deletion playwright/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,20 @@
# limitations under the License.

from playwright.main import playwright_object
import playwright.helper as helper

chromium = playwright_object.chromium
firefox = playwright_object.firefox
webkit = playwright_object.webkit
devices = playwright_object.devices
browser_types = playwright_object.browser_types
TimeoutError = helper.TimeoutError

__all__ = [
'browser_types',
'chromium',
'firefox',
'webkit',
'devices'
'devices',
'TimeoutError'
]
8 changes: 4 additions & 4 deletions playwright/browser_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,14 @@ def _on_page(self, page: Page) -> None:

def _on_route(self, route: Route, request: Request) -> None:
for handler_entry in self._routes:
if handler_entry.matcher.matches(request.url()):
if handler_entry.matcher.matches(request.url):
handler_entry.handler(route, request)
return
asyncio.ensure_future(route.continue_())

def _on_binding(self, binding_call: BindingCall) -> None:
func = self._bindings.get(binding_call._initializer['name'])
if func == None:
if func is None:
return
binding_call.call(func)

Expand All @@ -84,7 +84,7 @@ async def newPage(self) -> Page:
return from_channel(await self._channel.send('newPage'))

async def cookies(self, urls: Union[str, List[str]]) -> List[Cookie]:
if urls == None:
if urls is None:
urls = list()
return await self._channel.send('cookies', dict(urls=urls))

Expand Down Expand Up @@ -135,7 +135,7 @@ async def route(self, match: URLMatch, handler: RouteHandler) -> None:
await self._channel.send('setNetworkInterceptionEnabled', dict(enabled=True))

async def unroute(self, match: URLMatch, handler: Optional[RouteHandler]) -> None:
self._routes = filter(lambda r: r.matcher.match != match or (handler and r.handler != handler), self._routes)
self._routes = list(filter(lambda r: r.matcher.match != match or (handler and r.handler != handler), self._routes))
if len(self._routes) == 0:
await self._channel.send('setNetworkInterceptionEnabled', dict(enabled=False))

Expand Down
30 changes: 16 additions & 14 deletions playwright/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
# limitations under the License.

import asyncio
from playwright.helper import parse_error
from playwright.helper import parse_error, ParsedMessagePayload
from playwright.transport import Transport
from pyee import BaseEventEmitter
from typing import Any, Awaitable, Dict, List, Optional
Expand All @@ -22,12 +22,12 @@
class Channel(BaseEventEmitter):
def __init__(self, scope: 'ConnectionScope', guid: str) -> None:
super().__init__()
self._scope = scope
self._scope: ConnectionScope = scope
self._guid = guid
self._object = None
self._object: Optional[ChannelOwner] = None

async def send(self, method: str, params: dict = None) -> Any:
if params == None:
if params is None:
params = dict()
return await self._scope.send_message_to_server(self._guid, method, params)

Expand Down Expand Up @@ -121,20 +121,22 @@ async def _send_message_to_server(self, guid: str, method: str, params: Dict) ->
self._callbacks[id] = callback
return await callback

def _dispatch(self, msg: Dict):
guid = msg.get('guid')
def _dispatch(self, msg: ParsedMessagePayload):

if msg.get('id'):
callback = self._callbacks.pop(msg.get('id'))
if msg.get('error'):
callback.set_exception(parse_error(msg.get('error')))
id = msg.get('id')
if id:
callback = self._callbacks.pop(id)
error = msg.get('error')
if error:
callback.set_exception(parse_error(error))
else:
result = self._replace_guids_with_channels(msg.get('result'))
callback.set_result(result)
return

guid = msg['guid']
method = msg.get('method')
params = msg.get('params')
params = msg['params']
if method == '__create__':
scope = self._scopes[guid]
scope.create_remote_object(params['type'], params['guid'], params['initializer'])
Expand All @@ -144,7 +146,7 @@ def _dispatch(self, msg: Dict):
object._channel.emit(method, self._replace_guids_with_channels(params))

def _replace_channels_with_guids(self, payload: Any) -> Any:
if payload == None:
if payload is None:
return payload
if isinstance(payload, list):
return list(map(lambda p: self._replace_channels_with_guids(p), payload))
Expand All @@ -158,13 +160,13 @@ def _replace_channels_with_guids(self, payload: Any) -> Any:
return payload

def _replace_guids_with_channels(self, payload: Any) -> Any:
if payload == None:
if payload is None:
return payload
if isinstance(payload, list):
return list(map(lambda p: self._replace_guids_with_channels(p), payload))
if isinstance(payload, dict):
if payload.get('guid') in self._objects:
return self._objects[payload.get('guid')]._channel
return self._objects[payload['guid']]._channel
result = dict()
for key in payload:
result[key] = self._replace_guids_with_channels(payload[key])
Expand Down
3 changes: 3 additions & 0 deletions playwright/console_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ class ConsoleMessage(ChannelOwner):
def __init__(self, scope: ConnectionScope, guid: str, initializer: Dict) -> None:
super().__init__(scope, guid, initializer)

def __str__(self) -> str:
return self.text

@property
def type(self) -> str:
return self._initializer['type']
Expand Down
7 changes: 5 additions & 2 deletions playwright/frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from playwright.helper import ConsoleMessageLocation, FilePayload, SelectOption, is_function_body, locals_to_params
from playwright.js_handle import JSHandle, parse_result, serialize_argument
from playwright.network import Request, Response, Route
from playwright.serializers import normalize_file_payloads
from typing import Any, Awaitable, Dict, List, Optional, Union, TYPE_CHECKING

if TYPE_CHECKING:
Expand Down Expand Up @@ -203,10 +204,12 @@ async def selectOption(self,

async def setInputFiles(self,
selector: str,
files: Union[str, FilePayload, List[str], List[FilePayload]],
files:Union[str, FilePayload, List[Union[str, FilePayload]]],
timeout: int = None,
noWaitAfter: bool = None) -> None:
await self._channel.send('setInputFiles', locals_to_params(locals()))
params = locals_to_params(locals())
params['files'] = normalize_file_payloads(files)
await self._channel.send('setInputFiles', params)

async def type(self,
selector: str,
Expand Down
45 changes: 36 additions & 9 deletions playwright/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import fnmatch
import re

from typing import Any, Callable, Dict, List, Optional, Tuple, Union, TYPE_CHECKING
from typing import Any, Callable, Dict, List, Optional, Tuple, Union, TYPE_CHECKING, Pattern, cast

import sys

Expand All @@ -37,7 +37,7 @@
class FilePayload(TypedDict):
name: str
mimeType: str
buffer: bytes
buffer: Union[bytes, str]
class FrameMatch(TypedDict):
url: URLMatch
name: str
Expand All @@ -49,16 +49,35 @@ class ConsoleMessageLocation(TypedDict):
url: Optional[str]
lineNumber: Optional[int]
columnNumber: Optional[int]
class ErrorPayload(TypedDict):
class ErrorPayload(TypedDict, total=False):
message: str
name: str
stack: str
value: Any

class ContinueParameters(TypedDict, total=False):
method: str
headers: Dict[str, str]
postData: str

class ParsedMessageParams(TypedDict):
type: str
guid: str
initializer: Dict

class ParsedMessagePayload(TypedDict, total=False):
id: int
guid: str
method: str
params: ParsedMessageParams
result: Any
error: ErrorPayload


class URLMatcher:
def __init__(self, match: URLMatch):
self._callback = None
self._regex_obj = None
self._callback: Optional[Callable[[str], bool]] = None
self._regex_obj: Optional[Pattern] = None
if isinstance(match, str):
regex = '(?:http://|https://)' + fnmatch.translate(match)
self._regex_obj = re.compile(regex)
Expand All @@ -69,7 +88,9 @@ def __init__(self, match: URLMatch):
def matches(self, url: str) -> bool:
if self._callback:
return self._callback(url)
return self._regex_obj.match(url)
if self._regex_obj:
return cast(bool, self._regex_obj.match(url))
return False


class TimeoutSettings:
Expand All @@ -79,16 +100,22 @@ def __init__(self, parent: Optional['TimeoutSettings']) -> None:
def set_default_timeout(self, timeout):
self.timeout = timeout

class Error(BaseException):
class Error(Exception):
def __init__(self, message: str, stack: str = None) -> None:
self.message = message
self.stack = stack

def serialize_error(ex: BaseException) -> ErrorPayload:
class TimeoutError(Error):
pass

def serialize_error(ex: Exception) -> ErrorPayload:
return dict(message=str(ex))

def parse_error(error: ErrorPayload):
return Error(error['message'], error['stack'])
base_error_class = Error
if error.get("name") == "TimeoutError":
base_error_class = TimeoutError
return base_error_class(error['message'], error['stack'])

def is_function_body(expression: str) -> bool:
expression = expression.strip()
Expand Down
14 changes: 7 additions & 7 deletions playwright/js_handle.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ def __init__(self, scope: ConnectionScope, guid: str, initializer: Dict) -> None
self._preview = self._initializer['preview']
self._channel.on('previewUpdated', lambda preview: self._on_preview_updated(preview))

def __str__(self) -> str:
return self._preview

def _on_preview_updated(self, preview: str) -> None:
self._preview = preview

Expand Down Expand Up @@ -60,9 +63,6 @@ async def dispose(self) -> None:
async def jsonValue(self) -> Any:
return parse_result(await self._channel.send('jsonValue'))

def toString(self) -> str:
return self._preview


def is_primitive_value(value: Any):
return isinstance(value, bool) or isinstance(value, int) or isinstance(value, float) or isinstance(value, str)
Expand All @@ -74,7 +74,7 @@ def serialize_value(value: Any, handles: List[JSHandle], depth: int) -> Any:
return dict(h=h)
if depth > 100:
raise Error('Maximum argument depth exceeded')
if value == None:
if value is None:
return dict(v='undefined')
if isinstance(value, float):
if value == float('inf'):
Expand All @@ -97,19 +97,19 @@ def serialize_value(value: Any, handles: List[JSHandle], depth: int) -> Any:
return dict(a=result)

if isinstance(value, dict):
result = dict()
result: Dict[str, Any] = dict() # type: ignore
for name in value:
result[name] = serialize_value(value[name], handles, depth + 1)
return dict(o=result)
return dict(v='undefined')

def serialize_argument(arg: Any) -> Any:
guids = list()
guids: List[JSHandle] = list()
value = serialize_value(arg, guids, 0)
return dict(value=value, guids=guids)

def parse_value(value: Any) -> Any:
if value == None:
if value is None:
return None
if isinstance(value, dict):
if 'v' in value:
Expand Down
8 changes: 4 additions & 4 deletions playwright/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import base64
import json
from playwright.connection import Channel, ChannelOwner, ConnectionScope, from_nullable_channel, from_channel
from playwright.helper import Error
from playwright.helper import Error, ContinueParameters
from typing import Awaitable, Dict, List, Optional, Union, TYPE_CHECKING

if TYPE_CHECKING:
Expand Down Expand Up @@ -99,15 +99,15 @@ async def fulfill(self, status: int = 200, headers: Dict[str,str] = dict(), body
await self._channel.send('fulfill', response)

async def continue_(self, method: str = None, headers: Dict[str,str] = None, postData: Union[str, bytes] = None) -> None:
overrides = dict()
overrides: ContinueParameters = dict()
if method:
overrides['method'] = method
if headers:
overrides['headers'] = headers
if isinstance(postData, str):
overrides['postData'] = base64.b64encode(bytes(postData, 'utf-8'))
overrides['postData'] = base64.b64encode(bytes(postData, 'utf-8')).decode()
elif isinstance(postData, bytes):
overrides['postData'] = base64.b64encode(postData)
overrides['postData'] = base64.b64encode(postData).decode()
await self._channel.send('continue', overrides)


Expand Down
Loading