Chromium Code Reviews| OLD | NEW |
|---|---|
| 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 """ |
| 23 Parses the given command line arguments and returns an object with the flags | 24 Parses the given command line arguments and returns an object with the flags |
| 24 as properties. | 25 as properties. |
| 25 """ | 26 """ |
| 26 parser = argparse.ArgumentParser() | 27 parser = argparse.ArgumentParser() |
| 27 parser.add_argument('--browser_args', nargs=1, type=str, help='Override ' | 28 parser.add_argument('--browser_args', nargs=1, type=str, help='Override ' |
| 28 'browser flags in code with these flags') | 29 'browser flags in code with these flags') |
| 29 parser.add_argument('--via_header_value', metavar='via_header', nargs=1, | 30 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 ' | 31 default='1.1 Chrome-Compression-Proxy', help='What the via should match to ' |
| 31 'be considered valid') | 32 'be considered valid') |
| 32 parser.add_argument('--chrome_exec', nargs=1, type=str, help='The path to ' | 33 parser.add_argument('--chrome_exec', nargs=1, type=str, help='The path to ' |
| 33 'the Chrome or Chromium executable') | 34 'the Chrome or Chromium executable') |
| 34 parser.add_argument('chrome_driver', nargs=1, type=str, help='The path to ' | 35 parser.add_argument('chrome_driver', nargs=1, type=str, help='The path to ' |
| 35 'the ChromeDriver executable. If not given, the default system chrome ' | 36 'the ChromeDriver executable. If not given, the default system chrome ' |
| 36 'will be used.') | 37 'will be used.') |
| 37 # TODO(robertogden) make this a logging statement | 38 # TODO(robertogden) make a logging statement here |
|
sclittle
2016/12/06 00:09:49
style nit: You should have a colon after the "TODO
Robert Ogden
2016/12/06 16:57:16
Done.
| |
| 38 print 'DEBUG: Args=', json.dumps(vars(parser.parse_args(sys.argv[1:]))) | |
| 39 return parser.parse_args(sys.argv[1:]) | 39 return parser.parse_args(sys.argv[1:]) |
| 40 | 40 |
| 41 def HandleException(test_name=None): | 41 def HandleException(test_name=None): |
| 42 """ | 42 """ |
| 43 Writes the exception being handled and a stack trace to stderr. | 43 Writes the exception being handled and a stack trace to stderr. |
| 44 """ | 44 """ |
| 45 sys.stderr.write("**************************************\n") | 45 sys.stderr.write("**************************************\n") |
| 46 sys.stderr.write("**************************************\n") | 46 sys.stderr.write("**************************************\n") |
| 47 sys.stderr.write("** **\n") | 47 sys.stderr.write("** **\n") |
| 48 sys.stderr.write("** UNCAUGHT EXCEPTION **\n") | 48 sys.stderr.write("** UNCAUGHT EXCEPTION **\n") |
| (...skipping 135 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 184 # TODO(robertogden) add timeout | 184 # TODO(robertogden) add timeout |
| 185 def ExecuteJavascript(self, script): | 185 def ExecuteJavascript(self, script): |
| 186 """ | 186 """ |
| 187 Executes the given javascript in the browser's current page as if it were on | 187 Executes the given javascript in the browser's current page as if it were on |
| 188 the console. Returns a string of whatever the evaluation was. | 188 the console. Returns a string of whatever the evaluation was. |
| 189 """ | 189 """ |
| 190 if not self._driver: | 190 if not self._driver: |
| 191 self._StartDriver() | 191 self._StartDriver() |
| 192 return self._driver.execute_script("return " + script) | 192 return self._driver.execute_script("return " + script) |
| 193 | 193 |
| 194 def GetPerformanceLogs(self, method_filter=r'Network\.responseReceived'): | |
|
sclittle
2016/12/06 00:09:49
For all of these class and function definitions, y
Robert Ogden
2016/12/06 16:57:16
Done.
| |
| 195 """ | |
| 196 Returns all logged Performance events from Chrome as a list of dicts, since | |
| 197 the last time this function was called. method_filter accepts a regex to | |
| 198 match against the method type of the events to return. | |
| 199 """ | |
| 200 all_messages = [] | |
| 201 for log in self._driver.execute('getLog', {'type': 'performance'})['value']: | |
| 202 message = json.loads(log['message'])['message'] | |
| 203 if re.match(method_filter, message['method']): | |
| 204 all_messages.append(message) | |
| 205 return all_messages | |
|
sclittle
2016/12/06 00:09:49
nit: This is minor, but if you don't need the enti
Robert Ogden
2016/12/06 16:57:16
Thought about that too. Kept it like this so that
| |
| 206 | |
| 207 def GetHTTPResponses(self, include_favicon=False): | |
| 208 """ | |
| 209 Parses the Performance Logs and returns a list of HTTPResponse objects, each | |
| 210 representing a single completed HTTP transaction by Chrome. Optionally | |
| 211 allows favicon requests to be included in the result as well. | |
| 212 This function should be called exactly once after every page load. | |
| 213 """ | |
| 214 def MakeHTTPResponse(log_dict): | |
| 215 params = log_dict['params'] | |
| 216 response_dict = params['response'] | |
| 217 response_headers = response_dict['headers'] | |
| 218 request_headers = response_dict['requestHeaders'] | |
| 219 url = response_dict['url'] | |
| 220 protocol = request_headers[':scheme'] | |
| 221 status = response_dict['status'] | |
| 222 request_type = params['type'] | |
| 223 return HTTPResponse(response_headers, request_headers, url, protocol, | |
|
sclittle
2016/12/06 00:09:49
nit: Instead of making so many local variables, co
Robert Ogden
2016/12/06 16:57:16
Done.
| |
| 224 status, request_type) | |
| 225 all_responses = [] | |
| 226 for message in self.GetPerformanceLogs(): | |
| 227 response = MakeHTTPResponse(message) | |
| 228 is_favicon = response.url.endswith('favicon.ico') | |
| 229 if not is_favicon or include_favicon: | |
| 230 all_responses.append(response) | |
| 231 return all_responses | |
| 232 | |
| 233 class HTTPResponse: | |
| 234 """ | |
| 235 This class represents a single HTTP transaction (request and response) by | |
| 236 Chrome. This class also includes several convenience functions for ChromeProxy | |
| 237 specific assertions. | |
| 238 """ | |
| 239 | |
| 240 def __init__(self, response_headers, request_headers, url, protocol, | |
| 241 status, request_type): | |
| 242 self._response_headers = response_headers | |
| 243 self._request_headers = request_headers | |
| 244 self._url = url | |
| 245 self._protocol = protocol | |
| 246 self._status = status | |
| 247 self._request_type = request_type | |
| 248 self._flags = ParseFlags() | |
| 249 | |
| 250 def __str__(self): | |
| 251 self_dict = { | |
| 252 'response_headers': self._response_headers, | |
| 253 'request_headers': self._request_headers, | |
| 254 'url': self._url, | |
| 255 'protocol': self._protocol, | |
| 256 'status': self._status, | |
| 257 'request_type': self._request_type | |
| 258 } | |
| 259 return json.dumps(self_dict) | |
| 260 | |
| 261 @property | |
| 262 def response_headers(self): | |
| 263 return self._response_headers | |
| 264 | |
| 265 @property | |
| 266 def request_headers(self): | |
| 267 return self._request_headers | |
| 268 | |
| 269 @property | |
| 270 def url(self): | |
| 271 return self._url | |
| 272 | |
| 273 @property | |
| 274 def protocol(self): | |
| 275 return self._protocol | |
| 276 | |
| 277 @property | |
| 278 def status(self): | |
| 279 return self._status | |
| 280 | |
| 281 @property | |
| 282 def request_type(self): | |
| 283 return self._request_type | |
| 284 | |
| 285 def UsedHTTPS(self): | |
| 286 return self._protocol == 'https' | |
|
sclittle
2016/12/06 00:09:49
What do you intent |UsedHTTPS| to be used for? I h
Robert Ogden
2016/12/06 16:57:15
Correct, there are a number of tests where we need
| |
| 287 | |
| 288 def ResponseHasViaHeader(self): | |
| 289 return 'via' in self._response_headers and (self._response_headers['via'] == | |
| 290 self._flags.via_header_value) | |
| 291 | |
| 292 def WasXHR(self): | |
| 293 return self.request_type == 'XHR' | |
| 194 | 294 |
| 195 class IntegrationTest: | 295 class IntegrationTest: |
| 196 """ | 296 """ |
| 197 A parent class for all integration tests with utility and assertion methods. | 297 A parent class for all integration tests with utility and assertion methods. |
| 198 All methods starting with the word 'test' (ignoring case) will be called with | 298 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. | 299 the RunAllTests() method which can be used in place of a main method. |
| 200 """ | 300 """ |
| 201 def RunAllTests(self): | 301 def RunAllTests(self): |
| 202 """ | 302 """ |
| 203 Runs all methods starting with the word 'test' (ignoring case) in the class. | 303 Runs all methods starting with the word 'test' (ignoring case) in the class. |
| (...skipping 13 matching lines...) Expand all Loading... | |
| 217 def Fail(self, msg): | 317 def Fail(self, msg): |
| 218 sys.stderr.write("**************************************\n") | 318 sys.stderr.write("**************************************\n") |
| 219 sys.stderr.write("**************************************\n") | 319 sys.stderr.write("**************************************\n") |
| 220 sys.stderr.write("** **\n") | 320 sys.stderr.write("** **\n") |
| 221 sys.stderr.write("** TEST FAILURE **\n") | 321 sys.stderr.write("** TEST FAILURE **\n") |
| 222 sys.stderr.write("** **\n") | 322 sys.stderr.write("** **\n") |
| 223 sys.stderr.write("**************************************\n") | 323 sys.stderr.write("**************************************\n") |
| 224 sys.stderr.write("**************************************\n") | 324 sys.stderr.write("**************************************\n") |
| 225 sys.stderr.write(msg, '\n') | 325 sys.stderr.write(msg, '\n') |
| 226 sys.exit(1) | 326 sys.exit(1) |
| OLD | NEW |