Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(294)

Side by Side Diff: tools/chrome_proxy/webdriver/common.py

Issue 2550433002: Add GetResponses() and related funcionality (Closed)
Patch Set: Fix doc. Remove proxy scheme. Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | tools/chrome_proxy/webdriver/simple_smoke.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 # Copyright 2016 The Chromium Authors. All rights reserved. 1 # Copyright 2016 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 import argparse 5 import argparse
6 import json 6 import json
7 import os 7 import os
8 import re
8 import shlex 9 import shlex
9 import sys 10 import sys
10 import time 11 import time
11 import traceback 12 import traceback
12 13
13 sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, 14 sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir,
14 os.pardir, 'third_party', 'webdriver', 'pylib')) 15 os.pardir, 'third_party', 'webdriver', 'pylib'))
15 from selenium import webdriver 16 from selenium import webdriver
16 from selenium.webdriver.chrome.options import Options 17 from selenium.webdriver.chrome.options import Options
17 18
18 # TODO(robertogden) add logging 19 # TODO(robertogden): Add logging.
19 20
20 21
21 def ParseFlags(): 22 def ParseFlags():
22 """ 23 """Parses the given command line arguments.
23 Parses the given command line arguments and returns an object with the flags 24
24 as properties. 25 Returns:
26 A new Namespace object with class properties for each argument added below.
27 See pydoc for argparse.
25 """ 28 """
26 parser = argparse.ArgumentParser() 29 parser = argparse.ArgumentParser()
27 parser.add_argument('--browser_args', nargs=1, type=str, help='Override ' 30 parser.add_argument('--browser_args', nargs=1, type=str, help='Override '
28 'browser flags in code with these flags') 31 'browser flags in code with these flags')
29 parser.add_argument('--via_header_value', metavar='via_header', nargs=1, 32 parser.add_argument('--via_header_value', metavar='via_header', nargs=1,
30 default='1.1 Chrome-Compression-Proxy', help='What the via should match to ' 33 default='1.1 Chrome-Compression-Proxy', help='What the via should match to '
31 'be considered valid') 34 'be considered valid')
32 parser.add_argument('--chrome_exec', nargs=1, type=str, help='The path to ' 35 parser.add_argument('--chrome_exec', nargs=1, type=str, help='The path to '
33 'the Chrome or Chromium executable') 36 'the Chrome or Chromium executable')
34 parser.add_argument('chrome_driver', nargs=1, type=str, help='The path to ' 37 parser.add_argument('chrome_driver', nargs=1, type=str, help='The path to '
35 'the ChromeDriver executable. If not given, the default system chrome ' 38 'the ChromeDriver executable. If not given, the default system chrome '
36 'will be used.') 39 'will be used.')
37 # TODO(robertogden) make this a logging statement 40 # TODO(robertogden): Log sys.argv here.
38 print 'DEBUG: Args=', json.dumps(vars(parser.parse_args(sys.argv[1:])))
39 return parser.parse_args(sys.argv[1:]) 41 return parser.parse_args(sys.argv[1:])
40 42
41 def HandleException(test_name=None): 43 def HandleException(test_name=None):
42 """ 44 """Writes the exception being handled and a stack trace to stderr.
43 Writes the exception being handled and a stack trace to stderr. 45
46 Args:
47 test_name: The string name of the test that led to this exception.
44 """ 48 """
45 sys.stderr.write("**************************************\n") 49 sys.stderr.write("**************************************\n")
46 sys.stderr.write("**************************************\n") 50 sys.stderr.write("**************************************\n")
47 sys.stderr.write("** **\n") 51 sys.stderr.write("** **\n")
48 sys.stderr.write("** UNCAUGHT EXCEPTION **\n") 52 sys.stderr.write("** UNCAUGHT EXCEPTION **\n")
49 sys.stderr.write("** **\n") 53 sys.stderr.write("** **\n")
50 sys.stderr.write("**************************************\n") 54 sys.stderr.write("**************************************\n")
51 sys.stderr.write("**************************************\n") 55 sys.stderr.write("**************************************\n")
52 if test_name: 56 if test_name:
53 sys.stderr.write("Failed test: %s" % test_name) 57 sys.stderr.write("Failed test: %s" % test_name)
54 traceback.print_exception(*sys.exc_info()) 58 traceback.print_exception(*sys.exc_info())
55 sys.exit(1) 59 sys.exit(1)
56 60
57 class TestDriver: 61 class TestDriver:
58 """ 62 """The main driver for an integration test.
63
59 This class is the tool that is used by every integration test to interact with 64 This class is the tool that is used by every integration test to interact with
60 the Chromium browser and validate proper functionality. This class sits on top 65 the Chromium browser and validate proper functionality. This class sits on top
61 of the Selenium Chrome Webdriver with added utility and helper functions for 66 of the Selenium Chrome Webdriver with added utility and helper functions for
62 Chrome-Proxy. This class should be used with Python's 'with' operator. 67 Chrome-Proxy. This class should be used with Python's 'with' operator.
68
69 Attributes:
70 _flags: A Namespace object from the call to ParseFlags()
71 _driver: A reference to the driver object from the Chrome Driver library.
72 _chrome_args: A set of string arguments to start Chrome with.
73 _url: The string URL that Chrome will navigate to for this test.
63 """ 74 """
64 75
65 def __init__(self): 76 def __init__(self):
66 self._flags = ParseFlags() 77 self._flags = ParseFlags()
67 self._driver = None 78 self._driver = None
68 self._chrome_args = set() 79 self._chrome_args = set()
69 self._url = '' 80 self._url = ''
70 81
71 def __enter__(self): 82 def __enter__(self):
72 return self 83 return self
73 84
74 def __exit__(self, exc_type, exc_value, tb): 85 def __exit__(self, exc_type, exc_value, tb):
75 if self._driver: 86 if self._driver:
76 self._StopDriver() 87 self._StopDriver()
77 88
78 def _OverrideChromeArgs(self): 89 def _OverrideChromeArgs(self):
79 """ 90 """Overrides any given arguments in the code with those given on the command
80 Overrides any given arguments in the code with those given on the command 91 line.
81 line. Arguments that need to be overridden may contain different values for 92
93 Arguments that need to be overridden may contain different values for
82 a flag given in the code. In that case, check by the flag whether to 94 a flag given in the code. In that case, check by the flag whether to
83 override the argument. 95 override the argument.
84 """ 96 """
85 def GetDictKey(argument): 97 def GetDictKey(argument):
86 return argument.split('=', 1)[0] 98 return argument.split('=', 1)[0]
87 if self._flags.browser_args and len(self._flags.browser_args) > 0: 99 if self._flags.browser_args and len(self._flags.browser_args) > 0:
88 # Build a dict of flags mapped to the whole argument. 100 # Build a dict of flags mapped to the whole argument.
89 original_args = {} 101 original_args = {}
90 for arg in self._chrome_args: 102 for arg in self._chrome_args:
91 original_args[GetDictKey(arg)] = arg 103 original_args[GetDictKey(arg)] = arg
92 # Override by flag. 104 # Override flags given in code with any command line arguments.
93 for override_arg in shlex.split(self._flags.browser_args[0]): 105 for override_arg in shlex.split(self._flags.browser_args[0]):
94 arg_key = GetDictKey(override_arg) 106 arg_key = GetDictKey(override_arg)
95 if arg_key in original_args: 107 if arg_key in original_args:
96 self._chrome_args.remove(original_args[arg_key]) 108 self._chrome_args.remove(original_args[arg_key])
97 self._chrome_args.add(override_arg) 109 self._chrome_args.add(override_arg)
98 110
99 def _StartDriver(self): 111 def _StartDriver(self):
100 """ 112 """Parses the flags to pass to Chromium, then starts the ChromeDriver.
101 Parses the flags to pass to Chromium, then starts the ChromeDriver.
102 """ 113 """
103 self._OverrideChromeArgs() 114 self._OverrideChromeArgs()
104 options = Options() 115 options = Options()
105 for arg in self._chrome_args: 116 for arg in self._chrome_args:
106 options.add_argument(arg) 117 options.add_argument(arg)
107 capabilities = {'loggingPrefs': {'performance': 'INFO'}} 118 capabilities = {'loggingPrefs': {'performance': 'INFO'}}
108 if self._flags.chrome_exec: 119 if self._flags.chrome_exec:
109 capabilities['chrome.binary'] = self._flags.chrome_exec 120 capabilities['chrome.binary'] = self._flags.chrome_exec
110 driver = webdriver.Chrome(executable_path=self._flags.chrome_driver[0], 121 driver = webdriver.Chrome(executable_path=self._flags.chrome_driver[0],
111 chrome_options=options, desired_capabilities=capabilities) 122 chrome_options=options, desired_capabilities=capabilities)
112 driver.command_executor._commands.update({ 123 driver.command_executor._commands.update({
113 'getAvailableLogTypes': ('GET', '/session/$sessionId/log/types'), 124 'getAvailableLogTypes': ('GET', '/session/$sessionId/log/types'),
114 'getLog': ('POST', '/session/$sessionId/log')}) 125 'getLog': ('POST', '/session/$sessionId/log')})
115 self._driver = driver 126 self._driver = driver
116 127
117 def _StopDriver(self): 128 def _StopDriver(self):
118 """ 129 """Nicely stops the ChromeDriver.
119 Nicely stops the ChromeDriver.
120 """ 130 """
121 self._driver.quit() 131 self._driver.quit()
122 del self._driver 132 self._driver = None
123 133
124 def AddChromeArgs(self, args): 134 def AddChromeArgs(self, args):
125 """ 135 """Adds multiple arguments that will be passed to Chromium at start.
126 Adds multiple arguments that will be passed to Chromium at start. 136
137 Args:
138 args: An iterable of strings, each an argument to pass to Chrome at start.
127 """ 139 """
128 for arg in args: 140 for arg in args:
129 self._chrome_args.add(arg) 141 self._chrome_args.add(arg)
130 142
131 def AddChromeArg(self, arg): 143 def AddChromeArg(self, arg):
132 """ 144 """Adds a single argument that will be passed to Chromium at start.
133 Adds a single argument that will be passed to Chromium at start. 145
146 Args:
147 arg: a string argument to pass to Chrome at start
134 """ 148 """
135 self._chrome_args.add(arg) 149 self._chrome_args.add(arg)
136 150
137 def RemoveChromeArgs(self, args): 151 def RemoveChromeArgs(self, args):
138 """ 152 """Removes multiple arguments that will no longer be passed to Chromium at
139 Removes multiple arguments that will no longer be passed to Chromium at
140 start. 153 start.
154
155 Args:
156 args: An iterable of strings to no longer use the next time Chrome
157 starts.
141 """ 158 """
142 for arg in args: 159 for arg in args:
143 self._chrome_args.discard(arg) 160 self._chrome_args.discard(arg)
144 161
145 def RemoveChromeArg(self, arg): 162 def RemoveChromeArg(self, arg):
146 """ 163 """Removes a single argument that will no longer be passed to Chromium at
147 Removes a single argument that will no longer be passed to Chromium at
148 start. 164 start.
165
166 Args:
167 arg: A string flag to no longer use the next time Chrome starts.
149 """ 168 """
150 self._chrome_args.discard(arg) 169 self._chrome_args.discard(arg)
151 170
152 def ClearChromeArgs(self): 171 def ClearChromeArgs(self):
153 """ 172 """Removes all arguments from Chromium at start.
154 Removes all arguments from Chromium at start.
155 """ 173 """
156 self._chrome_args.clear() 174 self._chrome_args.clear()
157 175
158 def ClearCache(self): 176 def ClearCache(self):
159 """ 177 """Clears the browser cache.
160 Clears the browser cache. Important note: ChromeDriver automatically starts 178
179 Important note: ChromeDriver automatically starts
161 a clean copy of Chrome on every instantiation. 180 a clean copy of Chrome on every instantiation.
162 """ 181 """
163 self.ExecuteJavascript('if(window.chrome && chrome.benchmarking && ' 182 self.ExecuteJavascript('if(window.chrome && chrome.benchmarking && '
164 'chrome.benchmarking.clearCache){chrome.benchmarking.clearCache(); ' 183 'chrome.benchmarking.clearCache){chrome.benchmarking.clearCache(); '
165 'chrome.benchmarking.clearPredictorCache();chrome.benchmarking.' 184 'chrome.benchmarking.clearPredictorCache();chrome.benchmarking.'
166 'clearHostResolverCache();}') 185 'clearHostResolverCache();}')
167 186
168 def SetURL(self, url): 187 def SetURL(self, url):
169 """ 188 """Sets the URL that the browser will navigate to during the test.
170 Sets the URL that the browser will navigate to during the test. 189
190 Args:
191 url: The string URL to navigate to
171 """ 192 """
172 self._url = url 193 self._url = url
173 194
174 # TODO(robertogden) add timeout 195 # TODO(robertogden): Add timeout.
175 def LoadPage(self): 196 def LoadPage(self):
176 """ 197 """Starts Chromium with any arguments previously given and navigates to the
177 Starts Chromium with any arguments previously given and navigates to the 198 given URL.
178 previously given URL.
179 """ 199 """
180 if not self._driver: 200 if not self._driver:
181 self._StartDriver() 201 self._StartDriver()
182 self._driver.get(self._url) 202 self._driver.get(self._url)
183 203
184 # TODO(robertogden) add timeout 204 # TODO(robertogden): Add timeout.
185 def ExecuteJavascript(self, script): 205 def ExecuteJavascript(self, script):
186 """ 206 """Executes the given javascript in the browser's current page as if it were
187 Executes the given javascript in the browser's current page as if it were on 207 on the console.
188 the console. Returns a string of whatever the evaluation was. 208
209 Args:
210 script: A string of Javascript code.
211 Returns:
212 A string of the verbatim output from the Javascript execution.
189 """ 213 """
190 if not self._driver: 214 if not self._driver:
191 self._StartDriver() 215 self._StartDriver()
192 return self._driver.execute_script("return " + script) 216 return self._driver.execute_script("return " + script)
193 217
218 def GetPerformanceLogs(self, method_filter=r'Network\.responseReceived'):
219 """Returns all logged Performance events from Chrome.
220
221 Args:
222 method_filter: A regex expression to match the method of logged events
223 against. Only logs who's method matches the regex will be returned.
224 Returns:
225 Performance logs as a list of dicts, since the last time this function was
226 called.
227 """
228 all_messages = []
229 for log in self._driver.execute('getLog', {'type': 'performance'})['value']:
230 message = json.loads(log['message'])['message']
231 if re.match(method_filter, message['method']):
232 all_messages.append(message)
233 return all_messages
234
235 def GetHTTPResponses(self, include_favicon=False):
236 """Parses the Performance Logs and returns a list of HTTPResponse objects.
237
238 This function should be called exactly once after every page load.
239
240 Args:
241 include_favicon: A bool that if True will include responses for favicons.
242 Returns:
243 A list of HTTPResponse objects, each representing a single completed HTTP
244 transaction by Chrome.
245 """
246 def MakeHTTPResponse(log_dict):
247 params = log_dict['params']
248 response_dict = params['response']
249 http_response_dict = {
250 'response_headers': response_dict['headers'],
251 'request_headers': response_dict['requestHeaders'],
252 'url': response_dict['url'],
253 'status': response_dict['status'],
254 'request_type': params['type']
255 }
256 return HTTPResponse(**http_response_dict)
257 all_responses = []
258 for message in self.GetPerformanceLogs():
259 response = MakeHTTPResponse(message)
260 is_favicon = response.url.endswith('favicon.ico')
261 if not is_favicon or include_favicon:
262 all_responses.append(response)
263 return all_responses
264
265 class HTTPResponse:
266 """This class represents a single HTTP transaction (request and response) by
267 Chrome.
268
269 This class also includes several convenience functions for ChromeProxy
270 specific assertions.
271
272 Attributes:
273 _response_headers: A dict of response headers.
274 _request_headers: A dict of request headers.
275 _url: the fetched url
276 _status: The integer status code of the response
277 _request_type: What caused this request (Document, XHR, etc)
278 _flags: A Namespace object from ParseFlags()
279 """
280
281 def __init__(self, response_headers, request_headers, url, status,
282 request_type):
283 self._response_headers = response_headers
284 self._request_headers = request_headers
285 self._url = url
286 self._status = status
287 self._request_type = request_type
288 self._flags = ParseFlags()
289
290 def __str__(self):
291 self_dict = {
292 'response_headers': self._response_headers,
293 'request_headers': self._request_headers,
294 'url': self._url,
295 'status': self._status,
296 'request_type': self._request_type
297 }
298 return json.dumps(self_dict)
299
300 @property
301 def response_headers(self):
302 return self._response_headers
303
304 @property
305 def request_headers(self):
306 return self._request_headers
307
308 @property
309 def url(self):
310 return self._url
311
312 @property
313 def status(self):
314 return self._status
315
316 @property
317 def request_type(self):
318 return self._request_type
319
320 def ResponseHasViaHeader(self):
321 return 'via' in self._response_headers and (self._response_headers['via'] ==
322 self._flags.via_header_value)
323
324 def WasXHR(self):
325 return self.request_type == 'XHR'
194 326
195 class IntegrationTest: 327 class IntegrationTest:
196 """ 328 """A parent class for all integration tests with utility and assertion
197 A parent class for all integration tests with utility and assertion methods. 329 methods.
330
198 All methods starting with the word 'test' (ignoring case) will be called with 331 All methods starting with the word 'test' (ignoring case) will be called with
199 the RunAllTests() method which can be used in place of a main method. 332 the RunAllTests() method which can be used in place of a main method.
200 """ 333 """
201 def RunAllTests(self): 334 def RunAllTests(self):
202 """ 335 """Runs all methods starting with the word 'test' (ignoring case) in the
203 Runs all methods starting with the word 'test' (ignoring case) in the class. 336 class.
337
204 Can be used in place of a main method to run all tests in a class. 338 Can be used in place of a main method to run all tests in a class.
205 """ 339 """
206 methodList = [method for method in dir(self) if callable(getattr(self, 340 methodList = [method for method in dir(self) if callable(getattr(self,
207 method)) and method.lower().startswith('test')] 341 method)) and method.lower().startswith('test')]
208 for method in methodList: 342 for method in methodList:
209 try: 343 try:
210 getattr(self, method)() 344 getattr(self, method)()
211 except Exception as e: 345 except Exception as e:
212 # Uses the Exception tuple from sys.exec_info() 346 # Uses the Exception tuple from sys.exec_info().
213 HandleException(method) 347 HandleException(method)
214 348
215 # TODO(robertogden) add some nice assertion functions 349 # TODO(robertogden): Add some nice assertion functions.
216 350
217 def Fail(self, msg): 351 def Fail(self, msg):
352 """Called when a test fails an assertion.
353
354 Args:
355 msg: The string message to print to stderr
356 """
218 sys.stderr.write("**************************************\n") 357 sys.stderr.write("**************************************\n")
219 sys.stderr.write("**************************************\n") 358 sys.stderr.write("**************************************\n")
220 sys.stderr.write("** **\n") 359 sys.stderr.write("** **\n")
221 sys.stderr.write("** TEST FAILURE **\n") 360 sys.stderr.write("** TEST FAILURE **\n")
222 sys.stderr.write("** **\n") 361 sys.stderr.write("** **\n")
223 sys.stderr.write("**************************************\n") 362 sys.stderr.write("**************************************\n")
224 sys.stderr.write("**************************************\n") 363 sys.stderr.write("**************************************\n")
225 sys.stderr.write(msg, '\n') 364 sys.stderr.write(msg, '\n')
226 sys.exit(1) 365 sys.exit(1)
OLDNEW
« no previous file with comments | « no previous file | tools/chrome_proxy/webdriver/simple_smoke.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698