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 |