Skip to content

Commit 2ce6f3f

Browse files
authored
tests: added input spec tests (microsoft#28)
a bunch of drive-by fixes
1 parent 89fd978 commit 2ce6f3f

18 files changed

+334
-59
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ on:
77
jobs:
88
build:
99
strategy:
10+
fail-fast: false
1011
matrix:
1112
os: [ubuntu-latest, windows-latest, macos-latest]
1213
python-version: [3.7, 3.8]

playwright/__init__.py

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

1515
from playwright.main import playwright_object
16+
import playwright.helper as helper
1617

1718
chromium = playwright_object.chromium
1819
firefox = playwright_object.firefox
1920
webkit = playwright_object.webkit
2021
devices = playwright_object.devices
2122
browser_types = playwright_object.browser_types
23+
TimeoutError = helper.TimeoutError
2224

2325
__all__ = [
2426
'browser_types',
2527
'chromium',
2628
'firefox',
2729
'webkit',
28-
'devices'
30+
'devices',
31+
'TimeoutError'
2932
]

playwright/browser_context.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,14 +56,14 @@ def _on_page(self, page: Page) -> None:
5656

5757
def _on_route(self, route: Route, request: Request) -> None:
5858
for handler_entry in self._routes:
59-
if handler_entry.matcher.matches(request.url()):
59+
if handler_entry.matcher.matches(request.url):
6060
handler_entry.handler(route, request)
6161
return
6262
asyncio.ensure_future(route.continue_())
6363

6464
def _on_binding(self, binding_call: BindingCall) -> None:
6565
func = self._bindings.get(binding_call._initializer['name'])
66-
if func == None:
66+
if func is None:
6767
return
6868
binding_call.call(func)
6969

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

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

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

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

playwright/connection.py

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

1515
import asyncio
16-
from playwright.helper import parse_error
16+
from playwright.helper import parse_error, ParsedMessagePayload
1717
from playwright.transport import Transport
1818
from pyee import BaseEventEmitter
1919
from typing import Any, Awaitable, Dict, List, Optional
@@ -22,12 +22,12 @@
2222
class Channel(BaseEventEmitter):
2323
def __init__(self, scope: 'ConnectionScope', guid: str) -> None:
2424
super().__init__()
25-
self._scope = scope
25+
self._scope: ConnectionScope = scope
2626
self._guid = guid
27-
self._object = None
27+
self._object: Optional[ChannelOwner] = None
2828

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

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

124-
def _dispatch(self, msg: Dict):
125-
guid = msg.get('guid')
124+
def _dispatch(self, msg: ParsedMessagePayload):
126125

127-
if msg.get('id'):
128-
callback = self._callbacks.pop(msg.get('id'))
129-
if msg.get('error'):
130-
callback.set_exception(parse_error(msg.get('error')))
126+
id = msg.get('id')
127+
if id:
128+
callback = self._callbacks.pop(id)
129+
error = msg.get('error')
130+
if error:
131+
callback.set_exception(parse_error(error))
131132
else:
132133
result = self._replace_guids_with_channels(msg.get('result'))
133134
callback.set_result(result)
134135
return
135136

137+
guid = msg['guid']
136138
method = msg.get('method')
137-
params = msg.get('params')
139+
params = msg['params']
138140
if method == '__create__':
139141
scope = self._scopes[guid]
140142
scope.create_remote_object(params['type'], params['guid'], params['initializer'])
@@ -144,7 +146,7 @@ def _dispatch(self, msg: Dict):
144146
object._channel.emit(method, self._replace_guids_with_channels(params))
145147

146148
def _replace_channels_with_guids(self, payload: Any) -> Any:
147-
if payload == None:
149+
if payload is None:
148150
return payload
149151
if isinstance(payload, list):
150152
return list(map(lambda p: self._replace_channels_with_guids(p), payload))
@@ -158,13 +160,13 @@ def _replace_channels_with_guids(self, payload: Any) -> Any:
158160
return payload
159161

160162
def _replace_guids_with_channels(self, payload: Any) -> Any:
161-
if payload == None:
163+
if payload is None:
162164
return payload
163165
if isinstance(payload, list):
164166
return list(map(lambda p: self._replace_guids_with_channels(p), payload))
165167
if isinstance(payload, dict):
166168
if payload.get('guid') in self._objects:
167-
return self._objects[payload.get('guid')]._channel
169+
return self._objects[payload['guid']]._channel
168170
result = dict()
169171
for key in payload:
170172
result[key] = self._replace_guids_with_channels(payload[key])

playwright/console_message.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ class ConsoleMessage(ChannelOwner):
2222
def __init__(self, scope: ConnectionScope, guid: str, initializer: Dict) -> None:
2323
super().__init__(scope, guid, initializer)
2424

25+
def __str__(self) -> str:
26+
return self.text
27+
2528
@property
2629
def type(self) -> str:
2730
return self._initializer['type']

playwright/frame.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from playwright.helper import ConsoleMessageLocation, FilePayload, SelectOption, is_function_body, locals_to_params
1919
from playwright.js_handle import JSHandle, parse_result, serialize_argument
2020
from playwright.network import Request, Response, Route
21+
from playwright.serializers import normalize_file_payloads
2122
from typing import Any, Awaitable, Dict, List, Optional, Union, TYPE_CHECKING
2223

2324
if TYPE_CHECKING:
@@ -203,10 +204,12 @@ async def selectOption(self,
203204

204205
async def setInputFiles(self,
205206
selector: str,
206-
files: Union[str, FilePayload, List[str], List[FilePayload]],
207+
files:Union[str, FilePayload, List[Union[str, FilePayload]]],
207208
timeout: int = None,
208209
noWaitAfter: bool = None) -> None:
209-
await self._channel.send('setInputFiles', locals_to_params(locals()))
210+
params = locals_to_params(locals())
211+
params['files'] = normalize_file_payloads(files)
212+
await self._channel.send('setInputFiles', params)
210213

211214
async def type(self,
212215
selector: str,

playwright/helper.py

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
import fnmatch
1818
import re
1919

20-
from typing import Any, Callable, Dict, List, Optional, Tuple, Union, TYPE_CHECKING
20+
from typing import Any, Callable, Dict, List, Optional, Tuple, Union, TYPE_CHECKING, Pattern, cast
2121

2222
import sys
2323

@@ -37,7 +37,7 @@
3737
class FilePayload(TypedDict):
3838
name: str
3939
mimeType: str
40-
buffer: bytes
40+
buffer: Union[bytes, str]
4141
class FrameMatch(TypedDict):
4242
url: URLMatch
4343
name: str
@@ -49,16 +49,35 @@ class ConsoleMessageLocation(TypedDict):
4949
url: Optional[str]
5050
lineNumber: Optional[int]
5151
columnNumber: Optional[int]
52-
class ErrorPayload(TypedDict):
52+
class ErrorPayload(TypedDict, total=False):
5353
message: str
5454
name: str
5555
stack: str
5656
value: Any
5757

58+
class ContinueParameters(TypedDict, total=False):
59+
method: str
60+
headers: Dict[str, str]
61+
postData: str
62+
63+
class ParsedMessageParams(TypedDict):
64+
type: str
65+
guid: str
66+
initializer: Dict
67+
68+
class ParsedMessagePayload(TypedDict, total=False):
69+
id: int
70+
guid: str
71+
method: str
72+
params: ParsedMessageParams
73+
result: Any
74+
error: ErrorPayload
75+
76+
5877
class URLMatcher:
5978
def __init__(self, match: URLMatch):
60-
self._callback = None
61-
self._regex_obj = None
79+
self._callback: Optional[Callable[[str], bool]] = None
80+
self._regex_obj: Optional[Pattern] = None
6281
if isinstance(match, str):
6382
regex = '(?:http://|https://)' + fnmatch.translate(match)
6483
self._regex_obj = re.compile(regex)
@@ -69,7 +88,9 @@ def __init__(self, match: URLMatch):
6988
def matches(self, url: str) -> bool:
7089
if self._callback:
7190
return self._callback(url)
72-
return self._regex_obj.match(url)
91+
if self._regex_obj:
92+
return cast(bool, self._regex_obj.match(url))
93+
return False
7394

7495

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

82-
class Error(BaseException):
103+
class Error(Exception):
83104
def __init__(self, message: str, stack: str = None) -> None:
84105
self.message = message
85106
self.stack = stack
86107

87-
def serialize_error(ex: BaseException) -> ErrorPayload:
108+
class TimeoutError(Error):
109+
pass
110+
111+
def serialize_error(ex: Exception) -> ErrorPayload:
88112
return dict(message=str(ex))
89113

90114
def parse_error(error: ErrorPayload):
91-
return Error(error['message'], error['stack'])
115+
base_error_class = Error
116+
if error.get("name") == "TimeoutError":
117+
base_error_class = TimeoutError
118+
return base_error_class(error['message'], error['stack'])
92119

93120
def is_function_body(expression: str) -> bool:
94121
expression = expression.strip()

playwright/js_handle.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ def __init__(self, scope: ConnectionScope, guid: str, initializer: Dict) -> None
2929
self._preview = self._initializer['preview']
3030
self._channel.on('previewUpdated', lambda preview: self._on_preview_updated(preview))
3131

32+
def __str__(self) -> str:
33+
return self._preview
34+
3235
def _on_preview_updated(self, preview: str) -> None:
3336
self._preview = preview
3437

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

63-
def toString(self) -> str:
64-
return self._preview
65-
6666

6767
def is_primitive_value(value: Any):
6868
return isinstance(value, bool) or isinstance(value, int) or isinstance(value, float) or isinstance(value, str)
@@ -74,7 +74,7 @@ def serialize_value(value: Any, handles: List[JSHandle], depth: int) -> Any:
7474
return dict(h=h)
7575
if depth > 100:
7676
raise Error('Maximum argument depth exceeded')
77-
if value == None:
77+
if value is None:
7878
return dict(v='undefined')
7979
if isinstance(value, float):
8080
if value == float('inf'):
@@ -97,19 +97,19 @@ def serialize_value(value: Any, handles: List[JSHandle], depth: int) -> Any:
9797
return dict(a=result)
9898

9999
if isinstance(value, dict):
100-
result = dict()
100+
result: Dict[str, Any] = dict() # type: ignore
101101
for name in value:
102102
result[name] = serialize_value(value[name], handles, depth + 1)
103103
return dict(o=result)
104104
return dict(v='undefined')
105105

106106
def serialize_argument(arg: Any) -> Any:
107-
guids = list()
107+
guids: List[JSHandle] = list()
108108
value = serialize_value(arg, guids, 0)
109109
return dict(value=value, guids=guids)
110110

111111
def parse_value(value: Any) -> Any:
112-
if value == None:
112+
if value is None:
113113
return None
114114
if isinstance(value, dict):
115115
if 'v' in value:

playwright/network.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
import base64
1616
import json
1717
from playwright.connection import Channel, ChannelOwner, ConnectionScope, from_nullable_channel, from_channel
18-
from playwright.helper import Error
18+
from playwright.helper import Error, ContinueParameters
1919
from typing import Awaitable, Dict, List, Optional, Union, TYPE_CHECKING
2020

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

101101
async def continue_(self, method: str = None, headers: Dict[str,str] = None, postData: Union[str, bytes] = None) -> None:
102-
overrides = dict()
102+
overrides: ContinueParameters = dict()
103103
if method:
104104
overrides['method'] = method
105105
if headers:
106106
overrides['headers'] = headers
107107
if isinstance(postData, str):
108-
overrides['postData'] = base64.b64encode(bytes(postData, 'utf-8'))
108+
overrides['postData'] = base64.b64encode(bytes(postData, 'utf-8')).decode()
109109
elif isinstance(postData, bytes):
110-
overrides['postData'] = base64.b64encode(postData)
110+
overrides['postData'] = base64.b64encode(postData).decode()
111111
await self._channel.send('continue', overrides)
112112

113113

0 commit comments

Comments
 (0)