Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 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 | |
| 3 # found in the LICENSE file. | |
| 4 | |
| 5 import argparse | |
| 6 import json | |
| 7 import os | |
| 8 import shlex | |
| 9 import sys | |
| 10 import time | |
| 11 import traceback | |
| 12 | |
| 13 sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, | |
| 14 os.pardir, 'third_party', 'webdriver', 'pylib')) | |
| 15 from selenium import webdriver | |
| 16 from selenium.webdriver.chrome.options import Options | |
| 17 | |
| 18 # TODO(robertogden) add logging | |
| 19 | |
| 20 | |
| 21 def ParseFlags(): | |
| 22 """ | |
| 23 Parses the given command line arguments and returns an object with the flags | |
| 24 as properties. | |
| 25 """ | |
| 26 parser = argparse.ArgumentParser() | |
| 27 parser.add_argument('--browser_args', nargs=1, type=str, help='Override ' | |
| 28 'browser flags in code with these flags') | |
| 29 parser.add_argument('--via_header_matches', metavar='via_header', nargs=1, | |
| 30 default='1.1 Chrome-Compression-Proxy', help='What the via should match to ' | |
| 31 'be considered valid') | |
| 32 parser.add_argument('--chrome_exec', nargs=1, type=str, help='The path to ' | |
| 33 'the Chrome or Chromium executable') | |
| 34 parser.add_argument('chrome_driver', nargs=1, type=str, help='The path to ' | |
| 35 'the ChromeDriver executable') | |
| 36 # TODO(robertogden) make this a logging statement | |
| 37 print 'DEBUG: Args=', json.dumps(vars(parser.parse_args(sys.argv[1:]))) | |
| 38 return parser.parse_args(sys.argv[1:]) | |
| 39 | |
| 40 def HandleException(test_name=None): | |
| 41 """ | |
| 42 Writes the exception being handled and a stack trace to stderr. | |
| 43 """ | |
| 44 sys.stderr.write("**************************************\n") | |
| 45 sys.stderr.write("**************************************\n") | |
| 46 sys.stderr.write("** **\n") | |
| 47 sys.stderr.write("** UNCAUGHT EXCEPTION **\n") | |
| 48 sys.stderr.write("** **\n") | |
| 49 sys.stderr.write("**************************************\n") | |
| 50 sys.stderr.write("**************************************\n") | |
| 51 if test_name: | |
| 52 sys.stderr.write("Failed test: %s" % test_name) | |
| 53 traceback.print_exception(*sys.exc_info()) | |
| 54 sys.exit(1) | |
| 55 | |
| 56 class TestDriver: | |
| 57 """ | |
| 58 This class is the tool that is used by every integration test to interact with | |
| 59 the Chromium browser and validate proper functionality. This class sits on top | |
| 60 of the Selenium Chrome Webdriver with added utility and helper functions for | |
| 61 Chrome-Proxy. This class should be used with Python's 'with' operator. | |
| 62 """ | |
| 63 | |
| 64 def __init__(self): | |
| 65 self._flags = ParseFlags() | |
| 66 self._driver = None | |
| 67 self.chrome_args = {} | |
|
RyanSturm
2016/11/29 19:31:46
can this be a set instead of a dict? I don't see m
Robert Ogden
2016/11/29 22:39:36
Done in follow up CL
| |
| 68 self._url = '' | |
| 69 | |
| 70 def __enter__(self): | |
| 71 return self | |
| 72 | |
| 73 def __exit__(self, exc_type, exc_value, tb): | |
| 74 if self._driver: | |
| 75 self._StopDriver() | |
| 76 | |
| 77 def _OverrideChromeArgs(self): | |
| 78 """ | |
| 79 Overrides any given flags in the code with those given on the command line. | |
| 80 """ | |
| 81 if self._flags.browser_args and len(self._flags.browser_args) > 0: | |
| 82 for a in shlex.split(self._flags.browser_args): | |
| 83 self.chrome_args[a] = True | |
| 84 | |
| 85 def _StartDriver(self): | |
| 86 """ | |
| 87 Parses the flags to pass to Chromium, then starts the ChromeDriver. | |
| 88 """ | |
| 89 opts = Options() | |
| 90 for a in self.chrome_args: | |
| 91 opts.add_argument(a) | |
| 92 caps = {'loggingPrefs': {'performance': 'INFO'}} | |
| 93 if self._flags.chrome_exec: | |
| 94 caps['chrome.binary'] = self._flags.chrome_exec | |
| 95 driver = webdriver.Chrome(executable_path=self._flags.chrome_driver[0], | |
| 96 chrome_options=opts, desired_capabilities=caps) | |
| 97 driver.command_executor._commands.update({ | |
| 98 'getAvailableLogTypes': ('GET', '/session/$sessionId/log/types'), | |
| 99 'getLog': ('POST', '/session/$sessionId/log')}) | |
| 100 self._driver = driver | |
| 101 | |
| 102 def _StopDriver(self): | |
| 103 """ | |
| 104 Nicely stops the ChromeDriver. | |
| 105 """ | |
| 106 self._driver.quit() | |
| 107 del self._driver | |
| 108 | |
| 109 def AddChromeArgs(self, args): | |
| 110 """ | |
| 111 Adds multiple arguments that will be passed to Chromium at start. | |
| 112 """ | |
| 113 if not self.chrome_args: | |
| 114 self.chrome_args = {} | |
| 115 for a in args: | |
| 116 self.chrome_args[a] = True | |
| 117 | |
| 118 def AddChromeArg(self, arg): | |
| 119 """ | |
| 120 Adds a single argument that will be passed to Chromium at start. | |
| 121 """ | |
| 122 if not self.chrome_args: | |
| 123 self.chrome_args = {} | |
| 124 self.chrome_args[arg] = True | |
| 125 | |
| 126 def RemoveChromeArgs(self, args): | |
| 127 """ | |
| 128 Removes multiple arguments that will no longer be passed to Chromium at | |
| 129 start. | |
| 130 """ | |
| 131 if not self.chrome_args: | |
| 132 self.chrome_args = {} | |
| 133 return | |
| 134 for a in args: | |
| 135 del self.chrome_args[a] | |
| 136 | |
| 137 def RemoveChromeArg(self, arg): | |
| 138 """ | |
| 139 Removes a single argument that will no longer be passed to Chromium at | |
| 140 start. | |
| 141 """ | |
| 142 if not self.chrome_args: | |
| 143 self.chrome_args = {} | |
| 144 return | |
| 145 del self.chrome_args[arg] | |
| 146 | |
| 147 def ClearChromeArgs(self): | |
| 148 """ | |
| 149 Removes all arguments from Chromium at start. | |
| 150 """ | |
| 151 self.chrome_args = {} | |
| 152 | |
| 153 def ClearCache(self): | |
| 154 """ | |
| 155 Clears the browser cache. Important note: ChromeDriver automatically starts | |
| 156 a clean copy of Chrome on every instantiation. | |
| 157 """ | |
| 158 self.ExecuteJavascript('if(window.chrome && chrome.benchmarking && ' | |
| 159 'chrome.benchmarking.clearCache){chrome.benchmarking.clearCache(); ' | |
| 160 'chrome.benchmarking.clearPredictorCache();chrome.benchmarking.' | |
| 161 'clearHostResolverCache();}') | |
| 162 | |
| 163 # TODO(robertogden) use a smart page instead | |
| 164 def SetURL(self, url): | |
| 165 """ | |
| 166 Sets the URL that the browser will navigate to during the test. | |
| 167 """ | |
| 168 self._url = url | |
| 169 | |
| 170 # TODO(robertogden) add timeout | |
| 171 def LoadPage(self): | |
| 172 """ | |
| 173 Starts Chromium with any arguments previously given and navigates to the | |
| 174 previously given URL. | |
| 175 """ | |
| 176 if not self._driver: | |
| 177 self._StartDriver() | |
| 178 self._driver.get(self._url) | |
| 179 | |
| 180 # TODO(robertogden) add timeout | |
| 181 def ExecuteJavascript(self, script): | |
| 182 """ | |
| 183 Executes the given javascript in the browser's current page as if it were on | |
| 184 the console. Returns a string of whatever the evaluation was. | |
| 185 """ | |
| 186 if not self._driver: | |
| 187 self._StartDriver() | |
| 188 return self._driver.execute_script("return " + script) | |
|
RyanSturm
2016/11/29 19:31:46
What happens if LoadPage was not called (i.e., _St
Robert Ogden
2016/11/29 22:39:36
Chrome would start but would stay on a blank page.
| |
| 189 | |
| 190 | |
| 191 class IntegrationTest: | |
| 192 """ | |
| 193 A parent class for all integration tests with utility and assertion methods. | |
| 194 All methods starting with the word 'test' (ignoring case) will be called with | |
| 195 the RunAllTests() method which can be used in place of a main method. | |
| 196 """ | |
| 197 def RunAllTests(self): | |
| 198 """ | |
| 199 Runs all methods starting with the word 'test' (ignoring case) in the class. | |
| 200 Can be used in place of a main method to run all tests in a class. | |
| 201 """ | |
| 202 methodList = [method for method in dir(self) if callable(getattr(self, | |
| 203 method)) and method.lower().startswith('test')] | |
| 204 for method in methodList: | |
| 205 try: | |
| 206 getattr(self, method)() | |
| 207 except Exception as e: | |
| 208 HandleException(method) | |
| 209 | |
| 210 # TODO(robertogden) add some nice assertion functions | |
| 211 | |
| 212 def Fail(self, msg): | |
| 213 sys.stderr.write("**************************************\n") | |
| 214 sys.stderr.write("**************************************\n") | |
| 215 sys.stderr.write("** **\n") | |
| 216 sys.stderr.write("** TEST FAILURE **\n") | |
| 217 sys.stderr.write("** **\n") | |
| 218 sys.stderr.write("**************************************\n") | |
| 219 sys.stderr.write("**************************************\n") | |
| 220 sys.stderr.write(msg, '\n') | |
| 221 sys.exit(1) | |
| OLD | NEW |