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, | |
|
RyanSturm
2016/11/29 19:31:46
nit: s/via_header_matchers/via_header_value/
| |
| 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') | |
|
RyanSturm
2016/11/29 19:31:46
Can you add that if no chrome_exec is supplied, th
| |
| 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(): | |
| 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 traceback.print_exception(*sys.exc_info()) | |
| 52 sys.exit(1) | |
| 53 | |
| 54 class TestDriver: | |
| 55 """ | |
| 56 This class is the tool that is used by every integration test to interact with | |
| 57 the Chromium browser and validate proper functionality. This class sits on top | |
| 58 of the Selenium Chrome Webdriver with added utility and helper functions for | |
| 59 Chrome-Proxy. This class should be used with Python's 'with' operator. | |
| 60 """ | |
| 61 | |
| 62 def __init__(self): | |
| 63 self._flags = ParseFlags() | |
| 64 self._driver = None | |
| 65 self._args = {} | |
|
tbansal1
2016/11/29 18:38:53
Can this be renamed to chrome_args?
Robert Ogden
2016/11/29 18:53:54
Done.
| |
| 66 self._url = '' | |
| 67 | |
| 68 def __enter__(self): | |
| 69 return self | |
| 70 | |
| 71 def __exit__(self, exc_type, exc_value, tb): | |
| 72 if self._driver: | |
| 73 self._StopDriver() | |
| 74 | |
| 75 def _OverrideChromeArgs(self): | |
| 76 """ | |
| 77 Overrides any given flags in the code with those given on the command line. | |
| 78 """ | |
| 79 if self._flags.browser_args and len(self._flags.browser_args) > 0: | |
| 80 for a in shlex.split(self._flags.browser_args): | |
|
RyanSturm
2016/11/29 19:31:46
nit: s/a/arg/
| |
| 81 self._args[a] = True | |
| 82 | |
| 83 def _StartDriver(self): | |
| 84 """ | |
| 85 Parses the flags to pass to Chromium, then starts the ChromeDriver. | |
| 86 """ | |
| 87 opts = Options() | |
|
RyanSturm
2016/11/29 19:31:46
nit: s/opts/options/
| |
| 88 for a in self._args: | |
|
RyanSturm
2016/11/29 19:31:46
nit: s/a/arg/
| |
| 89 opts.add_argument(a) | |
| 90 caps = {'loggingPrefs': {'performance': 'INFO'}} | |
|
RyanSturm
2016/11/29 19:31:46
nit: s/caps/capabilities/
| |
| 91 if self._flags.chrome_exec: | |
| 92 caps['chrome.binary'] = self._flags.chrome_exec | |
| 93 driver = webdriver.Chrome(executable_path=self._flags.chrome_driver[0], | |
| 94 chrome_options=opts, desired_capabilities=caps) | |
| 95 driver.command_executor._commands.update({ | |
| 96 'getAvailableLogTypes': ('GET', '/session/$sessionId/log/types'), | |
| 97 'getLog': ('POST', '/session/$sessionId/log')}) | |
| 98 self._driver = driver | |
| 99 | |
| 100 def _StopDriver(self): | |
| 101 """ | |
| 102 Nicely stops the ChromeDriver. | |
| 103 """ | |
| 104 self._driver.quit() | |
| 105 del self._driver | |
| 106 | |
| 107 def AddChromeArgs(self, args): | |
| 108 """ | |
| 109 Adds multiple arguments that will be passed to Chromium at start. | |
| 110 """ | |
| 111 if not self._args: | |
|
RyanSturm
2016/11/29 19:31:46
can self._args ever not be a value after __init__?
| |
| 112 self._args = {} | |
| 113 for a in args: | |
| 114 self._args[a] = True | |
| 115 | |
| 116 def AddChromeArg(self, arg): | |
|
tbansal1
2016/11/29 18:38:53
Is this being called somewhere?
Robert Ogden
2016/11/29 18:53:54
Not yet, but I think it is an important method to
| |
| 117 """ | |
| 118 Adds a single argument that will be passed to Chromium at start. | |
| 119 """ | |
| 120 if not self._args: | |
| 121 self._args = {} | |
| 122 self._args[arg] = True | |
| 123 | |
| 124 def RemoveChromeArgs(self, args): | |
| 125 """ | |
| 126 Removes multiple arguments that will no longer be passed to Chromium at | |
| 127 start. | |
| 128 """ | |
| 129 if not self._args: | |
| 130 self._args = {} | |
| 131 return | |
| 132 for a in args: | |
| 133 del self._args[a] | |
| 134 | |
| 135 def RemoveChromeArg(self, arg): | |
|
tbansal1
2016/11/29 18:38:53
Is this being called somewhere?
Robert Ogden
2016/11/29 18:53:54
Same as above.
| |
| 136 """ | |
| 137 Removes a single argument that will no longer be passed to Chromium at | |
| 138 start. | |
| 139 """ | |
| 140 if not self._args: | |
| 141 self._args = {} | |
| 142 return | |
| 143 del self._args[arg] | |
| 144 | |
| 145 def ClearChromeArgs(self): | |
| 146 """ | |
| 147 Removes all arguments from Chromium at start. | |
| 148 """ | |
| 149 self._args = {} | |
| 150 | |
| 151 def ClearCache(self): | |
| 152 """ | |
| 153 Clears the browser cache. Important note: ChromeDriver automatically starts | |
| 154 a clean copy of Chrome on every instantiation. | |
| 155 """ | |
| 156 self.ExecuteJavascript('if(window.chrome && chrome.benchmarking && ' | |
| 157 'chrome.benchmarking.clearCache){chrome.benchmarking.clearCache(); ' | |
| 158 'chrome.benchmarking.clearPredictorCache();chrome.benchmarking.' | |
| 159 'clearHostResolverCache();}') | |
| 160 | |
| 161 # TODO(robertogden) use a smart page instead | |
| 162 def SetURL(self, url): | |
| 163 """ | |
| 164 Sets the URL that the browser will navigate to during the test. | |
| 165 """ | |
| 166 self._url = url | |
| 167 | |
| 168 # TODO(robertogden) add timeout | |
| 169 def LoadPage(self): | |
| 170 """ | |
| 171 Starts Chromium with any arguments previously given and navigates to the | |
| 172 previously given URL. | |
| 173 """ | |
| 174 if not self._driver: | |
| 175 self._StartDriver() | |
| 176 self._driver.get(self._url) | |
| 177 | |
| 178 # TODO(robertogden) add timeout | |
| 179 def ExecuteJavascript(self, script): | |
| 180 """ | |
| 181 Executes the given javascript in the browser's current page as if it were on | |
| 182 the console. Returns a string of whatever the evaluation was. | |
| 183 """ | |
| 184 if not self._driver: | |
| 185 self._StartDriver() | |
| 186 return self._driver.execute_script("return " + script) | |
| 187 | |
| 188 | |
| 189 class IntegrationTest: | |
| 190 """ | |
| 191 A parent class for all integration tests with utility and assertion methods. | |
| 192 All methods starting with the word 'test' (ignoring case) will be called with | |
| 193 the RunAllTests() method which can be used in place of a main method. | |
| 194 """ | |
| 195 def RunAllTests(self): | |
| 196 """ | |
| 197 Runs all methods starting with the word 'test' (ignoring case) in the class. | |
| 198 Can be used in place of a main method to run all tests in a class. | |
| 199 """ | |
| 200 methodList = [method for method in dir(self) if callable(getattr(self, | |
| 201 method)) and method.lower().startswith('test')] | |
| 202 for method in methodList: | |
| 203 try: | |
| 204 getattr(self, method)() | |
| 205 except Exception as e: | |
| 206 HandleException(e) | |
|
tbansal1
2016/11/29 18:38:53
Pass the name of the test in the HandleException s
Robert Ogden
2016/11/29 18:53:54
Done.
RyanSturm
2016/11/29 19:31:46
I think tbansal meant pass method and the exceptio
| |
| 207 | |
| 208 # TODO(robertogden) add some nice assertion functions | |
| 209 | |
| 210 def Fail(self, msg): | |
| 211 sys.stderr.write("**************************************\n") | |
| 212 sys.stderr.write("**************************************\n") | |
| 213 sys.stderr.write("** **\n") | |
| 214 sys.stderr.write("** TEST FAILURE **\n") | |
| 215 sys.stderr.write("** **\n") | |
| 216 sys.stderr.write("**************************************\n") | |
| 217 sys.stderr.write("**************************************\n") | |
| 218 sys.stderr.write(msg, '\n') | |
| 219 sys.exit(1) | |
| OLD | NEW |