This repository was archived by the owner on Jan 3, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 268
/
Copy pathinspect.py
318 lines (246 loc) · 10.7 KB
/
inspect.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
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
import inspect
import time
from typing import Iterator, List, Optional, Union
from selenium.common.exceptions import TimeoutException
from seleniumwire import har
from seleniumwire.request import Request
class InspectRequestsMixin:
"""Mixin class that provides functions to inspect and modify browser requests."""
@property
def requests(self) -> List[Request]:
"""Retrieves the requests made between the browser and server.
Captured requests can be cleared with 'del', e.g:
del firefox.requests
Returns:
A list of Request instances representing the requests made
between the browser and server.
"""
return self.backend.storage.load_requests()
@requests.deleter
def requests(self):
self.backend.storage.clear_requests()
def iter_requests(self) -> Iterator[Request]:
"""Return an iterator of requests.
Returns: An iterator.
"""
yield from self.backend.storage.iter_requests()
@property
def last_request(self) -> Optional[Request]:
"""Retrieve the last request made between the browser and server.
Note that this is more efficient than running requests[-1]
Returns:
A Request instance representing the last request made, or
None if no requests have been made.
"""
return self.backend.storage.load_last_request()
def wait_for_request(self, pat: str, timeout: Union[int, float] = 10) -> Request:
"""Wait up to the timeout period for a request matching the specified
pattern to be seen.
The pat attribute can be can be a simple substring or a regex that will
be searched in the full request URL. If a request is not seen before the
timeout then a TimeoutException is raised. Only requests with corresponding
responses are considered.
Given that pat can be a regex, ensure that any special characters
(e.g. question marks) are escaped.
Args:
pat: The pat of the request to look for. A regex can be supplied.
timeout: The maximum time to wait in seconds. Default 10s.
Returns:
The request.
Raises:
TimeoutException if a request is not seen within the timeout
period.
"""
start = time.time()
while time.time() - start < timeout:
request = self.backend.storage.find(pat)
if request is None:
time.sleep(1 / 5)
else:
return request
raise TimeoutException('Timed out after {}s waiting for request matching {}'.format(timeout, pat))
@property
def har(self) -> str:
"""Get a HAR archive of HTTP transactions that have taken place.
Note that the enable_har option needs to be set before HAR
data will be captured.
Returns: A JSON string of HAR data.
"""
return har.generate_har(self.backend.storage.load_har_entries())
@property
def header_overrides(self):
"""The header overrides for outgoing browser requests.
DEPRECATED. Use request_interceptor and response_interceptor.
The value of the headers can be a dictionary or list of sublists,
with each sublist having two elements - a URL pattern and headers.
Where a header in the dictionary exists in the request, the dictionary
value will overwrite the one in the request. Where a header in the dictionary
does not exist in the request, it will be added to the request as a
new header. To filter out a header from the request, set that header
in the dictionary to None. Header names are case insensitive.
For response headers, prefix the header name with 'response:'.
For example:
header_overrides = {
'User-Agent': 'Firefox',
'response:Cache-Control': 'none'
}
header_overrides = [
('.*somewhere.com.*', {'User-Agent': 'Firefox', 'response:Cache-Control': 'none'}),
('*.somewhere-else.com.*', {'User-Agent': 'Chrome'})
]
"""
return self.backend.modifier.headers
@header_overrides.setter
def header_overrides(self, headers):
if isinstance(headers, list):
for _, h in headers:
self._validate_headers(h)
else:
self._validate_headers(headers)
self.backend.modifier.headers = headers
def _validate_headers(self, headers):
for v in headers.values():
if v is not None:
assert isinstance(v, str), 'Header values must be strings'
@header_overrides.deleter
def header_overrides(self):
del self.backend.modifier.headers
@property
def param_overrides(self):
"""The parameter overrides for outgoing browser requests.
DEPRECATED. Use request_interceptor.
For POST requests, the parameters are assumed to be encoded in the
request body.
The value of the params can be a dictionary or list of sublists,
with each sublist having two elements - a URL pattern and params.
Where a param in the dictionary exists in the request, the dictionary
value will overwrite the one in the request. Where a param in the dictionary
does not exist in the request, it will be added to the request as a
new param. To filter out a param from the request, set that param
in the dictionary to None.
For example:
param_overrides = {'foo': 'bar'}
param_overrides = [
('.*somewhere.com.*', {'foo': 'bar'}),
('*.somewhere-else.com.*', {'x': 'y'}),
]
"""
return self.backend.modifier.params
@param_overrides.setter
def param_overrides(self, params):
self.backend.modifier.params = params
@param_overrides.deleter
def param_overrides(self):
del self.backend.modifier.params
@property
def body_overrides(self):
"""The body overrides for outgoing browser requests.
DEPRECATED. Use request_interceptor and response_interceptor.
For 'not GET' requests, the parameters are assumed to be encoded in the
request body.
The value of the body can be a string value or list of sublists,
with each sublist having two elements - a URL pattern and string value.
The string value will be encoded, then replace whole http body.
And body_overrides has higher priority than param_overrides When they conflict.
For example:
body_overrides = '{"foo":"bar"}'
body_overrides = [
('.*somewhere.com.*', '{"foo":"bar"}'),
('*.somewhere-else.com.*', '{"x":"y"}'),
]
"""
return self.backend.modifier.bodies
@body_overrides.setter
def body_overrides(self, bodies):
self.backend.modifier.bodies = bodies
@body_overrides.deleter
def body_overrides(self):
del self.backend.modifier.bodies
@property
def querystring_overrides(self):
"""The querystring overrides for outgoing browser requests.
DEPRECATED. Use request_interceptor.
The value of the querystring override can be a string or a list of sublists,
with each sublist having two elements, a URL pattern and the querystring.
The querystring override will overwrite the querystring in the request
or will be added to the request if the request has no querystring. To
remove a querystring from the request, set the value to empty string.
For example:
querystring_overrides = 'foo=bar&x=y'
querystring_overrides = [
('.*somewhere.com.*', 'foo=bar&x=y'),
('*.somewhere-else.com.*', 'a=b&c=d'),
]
"""
return self.backend.modifier.querystring
@querystring_overrides.setter
def querystring_overrides(self, querystrings):
self.backend.modifier.querystring = querystrings
@querystring_overrides.deleter
def querystring_overrides(self):
del self.backend.modifier.querystring
@property
def rewrite_rules(self):
"""The rules used to rewrite request URLs.
DEPRECATED. Use request_interceptor.
The value of the rewrite rules should be a list of sublists (or tuples)
with each sublist containing the pattern and replacement.
For example:
rewrite_rules = [
(r'(https?://)www.google.com/', r'\1www.bing.com/'),
(r'https://docs.python.org/2/', r'https://docs.python.org/3/'),
]
"""
return self.backend.modifier.rewrite_rules
@rewrite_rules.setter
def rewrite_rules(self, rewrite_rules):
self.backend.modifier.rewrite_rules = rewrite_rules
@rewrite_rules.deleter
def rewrite_rules(self):
del self.backend.modifier.rewrite_rules
@property
def scopes(self) -> List[str]:
"""The URL patterns used to scope request capture.
The value of the scopes should be a list (or tuple) of
regular expressions.
For example:
scopes = [
'.*stackoverflow.*',
'.*github.*'
]
"""
return self.backend.scopes
@scopes.setter
def scopes(self, scopes: List[str]):
self.backend.scopes = scopes
@scopes.deleter
def scopes(self):
self.backend.scopes = []
@property
def request_interceptor(self) -> callable:
"""A callable that will be used to intercept/modify requests.
The callable must accept a single argument for the request
being intercepted.
"""
return self.backend.request_interceptor
@request_interceptor.setter
def request_interceptor(self, interceptor: callable):
self.backend.request_interceptor = interceptor
@request_interceptor.deleter
def request_interceptor(self):
self.backend.request_interceptor = None
@property
def response_interceptor(self) -> callable:
"""A callable that will be used to intercept/modify responses.
The callable must accept two arguments: the response being
intercepted and the originating request.
"""
return self.backend.response_interceptor
@response_interceptor.setter
def response_interceptor(self, interceptor: callable):
if len(inspect.signature(interceptor).parameters) != 2:
raise RuntimeError('A response interceptor takes two parameters: the request and response')
self.backend.response_interceptor = interceptor
@response_interceptor.deleter
def response_interceptor(self):
self.backend.response_interceptor = None