forked from microsoft/playwright-python
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy path_helper.py
232 lines (175 loc) · 6.1 KB
/
_helper.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# 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 fnmatch
import math
import re
import sys
import time
import traceback
from types import TracebackType
from typing import (
TYPE_CHECKING,
Any,
Callable,
Dict,
List,
Optional,
Pattern,
Union,
cast,
)
from playwright._impl._api_types import Error, TimeoutError
if sys.version_info >= (3, 8): # pragma: no cover
from typing import Literal, TypedDict
else: # pragma: no cover
from typing_extensions import Literal, TypedDict
if TYPE_CHECKING: # pragma: no cover
from playwright._impl._network import Request, Response, Route
URLMatch = Union[str, Pattern, Callable[[str], bool]]
URLMatchRequest = Union[str, Pattern, Callable[["Request"], bool]]
URLMatchResponse = Union[str, Pattern, Callable[["Response"], bool]]
RouteHandler = Union[Callable[["Route"], Any], Callable[["Route", "Request"], Any]]
ColorScheme = Literal["dark", "light", "no-preference"]
DocumentLoadState = Literal["domcontentloaded", "load", "networkidle"]
KeyboardModifier = Literal["Alt", "Control", "Meta", "Shift"]
MouseButton = Literal["left", "middle", "right"]
class ErrorPayload(TypedDict, total=False):
message: str
name: str
stack: str
value: Any
class Header(TypedDict):
name: str
value: str
class ContinueParameters(TypedDict, total=False):
url: Optional[str]
method: Optional[str]
headers: Optional[List[Header]]
postData: Optional[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 Document(TypedDict):
request: Optional[Any]
class FrameNavigatedEvent(TypedDict):
url: str
name: str
newDocument: Optional[Document]
error: Optional[str]
Env = Dict[str, Union[str, float, bool]]
class URLMatcher:
def __init__(self, match: URLMatch) -> None:
self._callback: Optional[Callable[[str], bool]] = None
self._regex_obj: Optional[Pattern] = None
if isinstance(match, str):
regex = fnmatch.translate(match)
self._regex_obj = re.compile(regex)
elif isinstance(match, Pattern):
self._regex_obj = match
else:
self._callback = match
self.match = match
def matches(self, url: str) -> bool:
if self._callback:
return self._callback(url)
if self._regex_obj:
return cast(bool, self._regex_obj.search(url))
return False
class TimeoutSettings:
def __init__(self, parent: Optional["TimeoutSettings"]) -> None:
self._parent = parent
self._timeout = 30000.0
self._navigation_timeout = 30000.0
def set_timeout(self, timeout: float) -> None:
self._timeout = timeout
def timeout(self) -> float:
if self._timeout is not None:
return self._timeout
if self._parent:
return self._parent.timeout()
return 30000
def set_navigation_timeout(self, navigation_timeout: float) -> None:
self._navigation_timeout = navigation_timeout
def navigation_timeout(self) -> float:
if self._navigation_timeout is not None:
return self._navigation_timeout
if self._parent:
return self._parent.navigation_timeout()
return 30000
def serialize_error(ex: Exception, tb: Optional[TracebackType]) -> ErrorPayload:
return dict(message=str(ex), name="Error", stack="".join(traceback.format_tb(tb)))
def parse_error(error: ErrorPayload) -> Error:
base_error_class = Error
if error.get("name") == "TimeoutError":
base_error_class = TimeoutError
return base_error_class(
cast(str, patch_error_message(error.get("message"))), error["stack"]
)
def patch_error_message(message: Optional[str]) -> Optional[str]:
if not message:
return None
match = re.match(r"(\w+)(: expected .*)", message)
if match:
message = to_snake_case(match.group(1)) + match.group(2)
message = message.replace(
"Pass { acceptDownloads: true }", "Pass { accept_downloads: True }"
)
return message
def is_function_body(expression: str) -> bool:
expression = expression.strip()
return (
expression.startswith("function")
or expression.startswith("async ")
or "=>" in expression
)
def locals_to_params(args: Dict) -> Dict:
copy = {}
for key in args:
if key == "self":
continue
if args[key] is not None:
copy[key] = args[key]
return copy
def monotonic_time() -> int:
return math.floor(time.monotonic() * 1000)
class RouteHandlerEntry:
def __init__(self, matcher: URLMatcher, handler: RouteHandler):
self.matcher = matcher
self.handler = handler
def is_safe_close_error(error: Exception) -> bool:
message = str(error)
return message.endswith("Browser has been closed") or message.endswith(
"Target page, context or browser has been closed"
)
def not_installed_error(message: str) -> Exception:
return Exception(
f"""
================================================================================
{message}
Please complete Playwright installation via running
"python -m playwright install"
================================================================================
"""
)
to_snake_case_regex = re.compile("((?<=[a-z0-9])[A-Z]|(?!^)[A-Z](?=[a-z]))")
def to_snake_case(name: str) -> str:
return to_snake_case_regex.sub(r"_\1", name).lower()