| 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 re |
| 9 import socket | 9 import socket |
| 10 import shlex | 10 import shlex |
| 11 import sys | 11 import sys |
| 12 import time | 12 import time |
| 13 import traceback | 13 import traceback |
| 14 import unittest |
| 14 | 15 |
| 15 sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, | 16 sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, |
| 16 os.pardir, 'third_party', 'webdriver', 'pylib')) | 17 os.pardir, 'third_party', 'webdriver', 'pylib')) |
| 17 from selenium import webdriver | 18 from selenium import webdriver |
| 18 from selenium.webdriver.chrome.options import Options | 19 from selenium.webdriver.chrome.options import Options |
| 19 | 20 |
| 20 # TODO(robertogden): Add logging. | 21 # TODO(robertogden): Add logging. |
| 21 | 22 |
| 22 | 23 |
| 23 def ParseFlags(): | 24 def ParseFlags(): |
| 24 """Parses the given command line arguments. | 25 """Parses the given command line arguments. |
| 25 | 26 |
| 26 Returns: | 27 Returns: |
| 27 A new Namespace object with class properties for each argument added below. | 28 A new Namespace object with class properties for each argument added below. |
| 28 See pydoc for argparse. | 29 See pydoc for argparse. |
| 29 """ | 30 """ |
| 30 parser = argparse.ArgumentParser() | 31 parser = argparse.ArgumentParser() |
| 31 parser.add_argument('--browser_args', nargs=1, type=str, help='Override ' | 32 parser.add_argument('--browser_args', type=str, help='Override browser flags ' |
| 32 'browser flags in code with these flags') | 33 'in code with these flags') |
| 33 parser.add_argument('--via_header_value', metavar='via_header', nargs=1, | 34 parser.add_argument('--via_header_value', |
| 34 default='1.1 Chrome-Compression-Proxy', help='What the via should match to ' | 35 default='1.1 Chrome-Compression-Proxy', help='What the via should match to ' |
| 35 'be considered valid') | 36 'be considered valid') |
| 36 parser.add_argument('--android', help='If given, attempts to run the test on ' | 37 parser.add_argument('--android', help='If given, attempts to run the test on ' |
| 37 'Android via adb. Ignores usage of --chrome_exec', action='store_true') | 38 'Android via adb. Ignores usage of --chrome_exec', action='store_true') |
| 38 parser.add_argument('--android_package', nargs=1, | 39 parser.add_argument('--android_package', |
| 39 default='com.android.chrome', help='Set the android package for Chrome') | 40 default='com.android.chrome', help='Set the android package for Chrome') |
| 40 parser.add_argument('--chrome_exec', nargs=1, type=str, help='The path to ' | 41 parser.add_argument('--chrome_exec', type=str, help='The path to ' |
| 41 'the Chrome or Chromium executable') | 42 'the Chrome or Chromium executable') |
| 42 parser.add_argument('chrome_driver', nargs=1, type=str, help='The path to ' | 43 parser.add_argument('chrome_driver', type=str, help='The path to ' |
| 43 'the ChromeDriver executable. If not given, the default system chrome ' | 44 'the ChromeDriver executable. If not given, the default system chrome ' |
| 44 'will be used.') | 45 'will be used.') |
| 45 parser.add_argument('--disable_buffer', help='Causes stdout and stderr from ' | 46 parser.add_argument('--disable_buffer', help='Causes stdout and stderr from ' |
| 46 'tests to output normally. Otherwise, the standard output and standard ' | 47 'tests to output normally. Otherwise, the standard output and standard ' |
| 47 'error streams are buffered during the test run, and output during a ' | 48 'error streams are buffered during the test run, and output during a ' |
| 48 'passing test is discarded. Output will always be echoed normally on test ' | 49 'passing test is discarded. Output will always be echoed normally on test ' |
| 49 'fail or error and is added to the failure messages.', action='store_true') | 50 'fail or error and is added to the failure messages.', action='store_true') |
| 50 parser.add_argument('-c', '--catch', help='Control-C during the test run ' | 51 parser.add_argument('-c', '--catch', help='Control-C during the test run ' |
| 51 'waits for the current test to end and then reports all the results so ' | 52 'waits for the current test to end and then reports all the results so ' |
| 52 'far. A second Control-C raises the normal KeyboardInterrupt exception.', | 53 'far. A second Control-C raises the normal KeyboardInterrupt exception.', |
| 53 action='store_true') | 54 action='store_true') |
| 54 parser.add_argument('-f', '--failfast', help='Stop the test run on the first ' | 55 parser.add_argument('-f', '--failfast', help='Stop the test run on the first ' |
| 55 'error or failure.', action='store_true') | 56 'error or failure.', action='store_true') |
| 56 # TODO(robertogden): Log sys.argv here. | 57 # TODO(robertogden): Log sys.argv here. |
| 57 return parser.parse_args(sys.argv[1:]) | 58 return parser.parse_args(sys.argv[1:]) |
| 58 | 59 |
| 59 def HandleException(test_name=None): | |
| 60 """Writes the exception being handled and a stack trace to stderr. | |
| 61 | |
| 62 Args: | |
| 63 test_name: The string name of the test that led to this exception. | |
| 64 """ | |
| 65 sys.stderr.write("**************************************\n") | |
| 66 sys.stderr.write("**************************************\n") | |
| 67 sys.stderr.write("** **\n") | |
| 68 sys.stderr.write("** UNCAUGHT EXCEPTION **\n") | |
| 69 sys.stderr.write("** **\n") | |
| 70 sys.stderr.write("**************************************\n") | |
| 71 sys.stderr.write("**************************************\n") | |
| 72 if test_name: | |
| 73 sys.stderr.write("Failed test: %s" % test_name) | |
| 74 traceback.print_exception(*sys.exc_info()) | |
| 75 sys.exit(1) | |
| 76 | 60 |
| 77 class TestDriver: | 61 class TestDriver: |
| 78 """The main driver for an integration test. | 62 """The main driver for an integration test. |
| 79 | 63 |
| 80 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 |
| 81 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 |
| 82 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 |
| 83 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. |
| 84 | 68 |
| 85 Attributes: | 69 Attributes: |
| (...skipping 25 matching lines...) Expand all Loading... |
| 111 override the argument. | 95 override the argument. |
| 112 """ | 96 """ |
| 113 def GetDictKey(argument): | 97 def GetDictKey(argument): |
| 114 return argument.split('=', 1)[0] | 98 return argument.split('=', 1)[0] |
| 115 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: |
| 116 # Build a dict of flags mapped to the whole argument. | 100 # Build a dict of flags mapped to the whole argument. |
| 117 original_args = {} | 101 original_args = {} |
| 118 for arg in self._chrome_args: | 102 for arg in self._chrome_args: |
| 119 original_args[GetDictKey(arg)] = arg | 103 original_args[GetDictKey(arg)] = arg |
| 120 # Override flags given in code with any command line arguments. | 104 # Override flags given in code with any command line arguments. |
| 121 for override_arg in shlex.split(self._flags.browser_args[0]): | 105 for override_arg in shlex.split(self._flags.browser_args): |
| 122 arg_key = GetDictKey(override_arg) | 106 arg_key = GetDictKey(override_arg) |
| 123 if arg_key in original_args: | 107 if arg_key in original_args: |
| 124 self._chrome_args.remove(original_args[arg_key]) | 108 self._chrome_args.remove(original_args[arg_key]) |
| 125 self._chrome_args.add(override_arg) | 109 self._chrome_args.add(override_arg) |
| 126 # Always add the flag that allows histograms to be queried in javascript. | 110 # Always add the flag that allows histograms to be queried in javascript. |
| 127 self._chrome_args.add('--enable-stats-collection-bindings') | 111 self._chrome_args.add('--enable-stats-collection-bindings') |
| 128 | 112 |
| 129 def _StartDriver(self): | 113 def _StartDriver(self): |
| 130 """Parses the flags to pass to Chromium, then starts the ChromeDriver. | 114 """Parses the flags to pass to Chromium, then starts the ChromeDriver. |
| 131 | 115 |
| 132 If running Android, the Android package name is passed to ChromeDriver here. | 116 If running Android, the Android package name is passed to ChromeDriver here. |
| 133 """ | 117 """ |
| 134 self._OverrideChromeArgs() | 118 self._OverrideChromeArgs() |
| 135 capabilities = { | 119 capabilities = { |
| 136 'loggingPrefs': {'performance': 'INFO'}, | 120 'loggingPrefs': {'performance': 'INFO'}, |
| 137 'chromeOptions': { | 121 'chromeOptions': { |
| 138 'args': list(self._chrome_args) | 122 'args': list(self._chrome_args) |
| 139 } | 123 } |
| 140 } | 124 } |
| 141 if self._flags.android: | 125 if self._flags.android: |
| 142 capabilities['chromeOptions'].update({ | 126 capabilities['chromeOptions'].update({ |
| 143 'androidPackage': self._flags.android_package, | 127 'androidPackage': self._flags.android_package, |
| 144 }) | 128 }) |
| 145 elif self._flags.chrome_exec: | 129 elif self._flags.chrome_exec: |
| 146 capabilities['chrome.binary'] = self._flags.chrome_exec | 130 capabilities['chrome.binary'] = self._flags.chrome_exec |
| 147 driver = webdriver.Chrome(executable_path=self._flags.chrome_driver[0], | 131 driver = webdriver.Chrome(executable_path=self._flags.chrome_driver, |
| 148 desired_capabilities=capabilities) | 132 desired_capabilities=capabilities) |
| 149 driver.command_executor._commands.update({ | 133 driver.command_executor._commands.update({ |
| 150 'getAvailableLogTypes': ('GET', '/session/$sessionId/log/types'), | 134 'getAvailableLogTypes': ('GET', '/session/$sessionId/log/types'), |
| 151 'getLog': ('POST', '/session/$sessionId/log')}) | 135 'getLog': ('POST', '/session/$sessionId/log')}) |
| 152 self._driver = driver | 136 self._driver = driver |
| 153 | 137 |
| 154 def _StopDriver(self): | 138 def _StopDriver(self): |
| 155 """Nicely stops the ChromeDriver. | 139 """Nicely stops the ChromeDriver. |
| 156 """ | 140 """ |
| 157 self._driver.quit() | 141 self._driver.quit() |
| (...skipping 198 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 356 _url: the fetched url | 340 _url: the fetched url |
| 357 _protocol: The protocol used to get the response. | 341 _protocol: The protocol used to get the response. |
| 358 _port: The remote port number used to get the response. | 342 _port: The remote port number used to get the response. |
| 359 _status: The integer status code of the response | 343 _status: The integer status code of the response |
| 360 _request_type: What caused this request (Document, XHR, etc) | 344 _request_type: What caused this request (Document, XHR, etc) |
| 361 _flags: A Namespace object from ParseFlags() | 345 _flags: A Namespace object from ParseFlags() |
| 362 """ | 346 """ |
| 363 | 347 |
| 364 def __init__(self, response_headers, request_headers, url, protocol, port, | 348 def __init__(self, response_headers, request_headers, url, protocol, port, |
| 365 status, request_type): | 349 status, request_type): |
| 366 self._response_headers = response_headers | 350 self._response_headers = {} |
| 367 self._request_headers = request_headers | 351 self._request_headers = {} |
| 368 self._url = url | 352 self._url = url |
| 369 self._protocol = protocol | 353 self._protocol = protocol |
| 370 self._port = port | 354 self._port = port |
| 371 self._status = status | 355 self._status = status |
| 372 self._request_type = request_type | 356 self._request_type = request_type |
| 373 self._flags = ParseFlags() | 357 self._flags = ParseFlags() |
| 358 # Make all header names lower case. |
| 359 for name in response_headers: |
| 360 self._response_headers[name.lower()] = response_headers[name] |
| 361 for name in request_headers: |
| 362 self._request_headers[name.lower()] = request_headers[name] |
| 374 | 363 |
| 375 def __str__(self): | 364 def __str__(self): |
| 376 self_dict = { | 365 self_dict = { |
| 377 'response_headers': self._response_headers, | 366 'response_headers': self._response_headers, |
| 378 'request_headers': self._request_headers, | 367 'request_headers': self._request_headers, |
| 379 'url': self._url, | 368 'url': self._url, |
| 380 'protocol': self._protocol, | 369 'protocol': self._protocol, |
| 381 'port': self._port, | 370 'port': self._port, |
| 382 'status': self._status, | 371 'status': self._status, |
| 383 'request_type': self._request_type | 372 'request_type': self._request_type |
| (...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 417 self._flags.via_header_value) | 406 self._flags.via_header_value) |
| 418 | 407 |
| 419 def WasXHR(self): | 408 def WasXHR(self): |
| 420 return self.request_type == 'XHR' | 409 return self.request_type == 'XHR' |
| 421 | 410 |
| 422 def UsedHTTP(self): | 411 def UsedHTTP(self): |
| 423 return self._protocol == 'http/1.1' | 412 return self._protocol == 'http/1.1' |
| 424 | 413 |
| 425 def UsedHTTP2(self): | 414 def UsedHTTP2(self): |
| 426 return self._protocol == 'h2' | 415 return self._protocol == 'h2' |
| 416 |
| 417 class IntegrationTest(unittest.TestCase): |
| 418 """This class adds ChromeProxy-specific assertions to the generic |
| 419 unittest.TestCase class. |
| 420 """ |
| 421 |
| 422 def assertHasChromeProxyViaHeader(self, http_response): |
| 423 """Asserts that the Via header in the given HTTPResponse matches the |
| 424 expected value as given on the command line. |
| 425 |
| 426 Args: |
| 427 http_response: The HTTPResponse object to check. |
| 428 """ |
| 429 expected_via_header = ParseFlags().via_header_value |
| 430 self.assertIn('via', http_response.response_headers) |
| 431 self.assertEqual(expected_via_header, http_response.response_headers['via']) |
| 432 |
| 433 def assertNotHasChromeProxyViaHeader(self, http_response): |
| 434 """Asserts that the Via header in the given HTTPResponse does not match the |
| 435 expected value as given on the command line. |
| 436 |
| 437 Args: |
| 438 http_response: The HTTPResponse object to check. |
| 439 """ |
| 440 expected_via_header = ParseFlags().via_header_value |
| 441 self.assertNotIn('via', http_response.response_headers) |
| 442 if 'via' in http_response.response_headers: |
| 443 self.assertNotIn(expected_via_header, |
| 444 http_response.response_headers['via']) |
| 445 |
| 446 @staticmethod |
| 447 def RunAllTests(): |
| 448 """A simple helper method to run all tests using unittest.main(). |
| 449 """ |
| 450 # The unittest library uses sys.argv itself and is easily confused by our |
| 451 # command line options. Pass it a simpler argv instead, while working in the |
| 452 # unittest command line args functionality. |
| 453 flags = ParseFlags() |
| 454 unittest.main(argv=[sys.argv[0]], verbosity=2, failfast=flags.failfast, |
| 455 catchbreak=flags.catch, buffer=(not flags.disable_buffer)) |
| OLD | NEW |