Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 #!/usr/bin/python | 1 #!/usr/bin/python |
| 2 | 2 |
| 3 # Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file | 3 # Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file |
| 4 # for details. All rights reserved. Use of this source code is governed by a | 4 # for details. All rights reserved. Use of this source code is governed by a |
| 5 # BSD-style license that can be found in the LICENSE file. | 5 # BSD-style license that can be found in the LICENSE file. |
| 6 # | 6 # |
| 7 | 7 |
| 8 """Script to actually open a browser and perform the test, and reports back with | 8 """Script to actually open a browser and perform the test, and reports back with |
| 9 the result. | 9 the result. It uses Selenium WebDriver for running the tests. |
|
Emily Fortuna
2012/02/21 18:19:35
Selenium RC and Selenium Webdriver (Selenium Webdr
Jennifer Messerly
2012/02/21 18:58:43
Done.
| |
| 10 | |
| 11 If started without arguments, this script runs a batch of in-browser tests in | |
| 12 the same browser process. Batching gives faster throughput and makes tests less subject to browser starting flakiness, issues with too many browser processes ru nning, etc. | |
|
Emily Fortuna
2012/02/21 18:19:35
line > 80 char.
Also maybe give an example how the
Jennifer Messerly
2012/02/21 18:58:43
Done.
| |
| 10 """ | 13 """ |
| 11 | 14 |
| 12 import os | 15 import os |
| 13 import optparse | 16 import optparse |
| 14 import platform | 17 import platform |
| 15 import selenium | 18 import selenium |
| 16 from selenium.webdriver.support.ui import WebDriverWait | 19 from selenium.webdriver.support.ui import WebDriverWait |
| 17 import shutil | 20 import shutil |
| 18 import socket | 21 import socket |
| 19 import sys | 22 import sys |
| 20 import time | 23 import time |
| 24 import signal | |
| 25 | |
| 26 TIMEOUT_ERROR_MSG = 'FAIL (timeout)' | |
| 21 | 27 |
| 22 def perf_test_done(driver): | 28 def perf_test_done(driver): |
| 23 """Checks if the performance test has completed.""" | 29 """Checks if the performance test has completed.""" |
| 24 return perf_test_done_helper(driver.page_source) | 30 return perf_test_done_helper(driver.page_source) |
| 25 | 31 |
| 26 def perf_test_done_helper(source): | 32 def perf_test_done_helper(source): |
| 27 """Tests to see if our performance test is done by printing a score.""" | 33 """Tests to see if our performance test is done by printing a score.""" |
| 28 #This code is written this way to work around a current instability in the | 34 #This code is written this way to work around a current instability in the |
| 29 # python webdriver bindings if you call driver.get_element_by_id. | 35 # python webdriver bindings if you call driver.get_element_by_id. |
| 30 #TODO(efortuna): Access these elements in a nicer way using DOM parser. | 36 #TODO(efortuna): Access these elements in a nicer way using DOM parser. |
| 31 string = '<div id="status">' | 37 string = '<div id="status">' |
| 32 index = source.find(string) | 38 index = source.find(string) |
| 33 end_index = source.find('</div>', index+1) | 39 end_index = source.find('</div>', index+1) |
| 34 source = source[index + len(string):end_index] | 40 source = source[index + len(string):end_index] |
| 35 return 'Score:' in source | 41 return 'Score:' in source |
| 36 | 42 |
| 37 def run_test_in_browser(browser, html_out, timeout, is_perf): | 43 def run_test_in_browser(browser, html_out, timeout, is_perf): |
| 38 """Run the desired test in the browser using Selenium 2.0 WebDriver syntax, | 44 """Run the desired test in the browser using Selenium 2.0 WebDriver syntax, |
| 39 and wait for the test to complete. This is the newer syntax, that currently | 45 and wait for the test to complete. This is the newer syntax, that currently |
| 40 supports Firefox, Chrome, IE, Opera (and some mobile browsers).""" | 46 supports Firefox, Chrome, IE, Opera (and some mobile browsers).""" |
| 41 browser.get("file://" + html_out) | 47 if isinstance(browser, selenium.selenium): |
| 48 return run_test_in_browser_selenium1(browser, html_out, timeout, is_perf) | |
|
Emily Fortuna
2012/02/21 18:19:35
I know I came up with the name originally here, b
Jennifer Messerly
2012/02/21 18:58:43
Done.
| |
| 49 | |
| 50 browser.get("file://" + html_out) | |
| 42 source = '' | 51 source = '' |
| 43 try: | 52 try: |
| 44 if is_perf: | 53 if is_perf: |
| 45 # We're running a performance test. | 54 # We're running a performance test. |
| 46 element = WebDriverWait(browser, float(timeout)).until(perf_test_done) | 55 element = WebDriverWait(browser, float(timeout)).until(perf_test_done) |
| 47 else: | 56 else: |
| 48 element = WebDriverWait(browser, float(timeout)).until( | 57 element = WebDriverWait(browser, float(timeout)).until( |
| 49 lambda driver : ('PASS' in driver.page_source) or | 58 lambda driver : ('PASS' in driver.page_source) or |
| 50 ('FAIL' in driver.page_source)) | 59 ('FAIL' in driver.page_source)) |
| 51 source = browser.page_source | 60 source = browser.page_source |
| 52 except selenium.common.exceptions.TimeoutException: | 61 except selenium.common.exceptions.TimeoutException: |
| 53 source = 'FAIL (timeout)' | 62 source = TIMEOUT_ERROR_MSG |
| 54 finally: | |
| 55 # A timeout exception is thrown if nothing happens within the time limit. | |
| 56 if browser != 'chrome': | |
| 57 browser.close() | |
| 58 try: | |
| 59 browser.quit() | |
| 60 except selenium.common.exceptions.WebDriverException: | |
| 61 #TODO(efortuna): figure out why this crashes.... and avoid? | |
| 62 pass | |
| 63 return source | 63 return source |
| 64 | 64 |
| 65 def run_test_in_browser_selenium1(sel, html_out, timeout, is_perf): | 65 def run_test_in_browser_selenium1(sel, html_out, timeout, is_perf): |
| 66 """ Run the desired test in the browser using Selenium 1.0 syntax, and wait | 66 """ Run the desired test in the browser using Selenium 1.0 syntax, and wait |
| 67 for the test to complete. This is used for Safari, since it is not currently | 67 for the test to complete. This is used for Safari, since it is not currently |
| 68 supported on Selenium 2.0.""" | 68 supported on Selenium 2.0.""" |
| 69 sel.open('file://' + html_out) | 69 sel.open('file://' + html_out) |
| 70 source = sel.get_html_source() | 70 source = sel.get_html_source() |
| 71 def end_condition(source): | 71 def end_condition(source): |
| 72 return 'PASS' in source or 'FAIL' in source | 72 return 'PASS' in source or 'FAIL' in source |
| 73 if is_perf: | 73 if is_perf: |
| 74 end_condition = perf_test_done_helper | 74 end_condition = perf_test_done_helper |
| 75 | 75 |
| 76 elapsed = 0 | 76 elapsed = 0 |
| 77 while (not end_condition(source)) and elapsed <= timeout: | 77 while (not end_condition(source)) and elapsed <= timeout: |
| 78 sec = .25 | 78 sec = .25 |
| 79 time.sleep(sec) | 79 time.sleep(sec) |
| 80 elapsed += sec | 80 elapsed += sec |
| 81 source = sel.get_html_source() | 81 source = sel.get_html_source() |
| 82 sel.stop() | |
| 83 return source | 82 return source |
| 84 | 83 |
| 85 def parse_args(): | 84 def parse_args(args=None): |
| 86 parser = optparse.OptionParser() | 85 parser = optparse.OptionParser() |
| 87 parser.add_option('--out', dest='out', | 86 parser.add_option('--out', dest='out', |
| 88 help = 'The path for html output file that we will running our test from', | 87 help = 'The path for html output file that we will running our test from', |
| 89 action = 'store', default = '') | 88 action = 'store', default = '') |
| 90 parser.add_option('--browser', dest='browser', | 89 parser.add_option('--browser', dest='browser', |
| 91 help = 'The browser type (default = chrome)', | 90 help = 'The browser type (default = chrome)', |
| 92 action = 'store', default = 'chrome') | 91 action = 'store', default = 'chrome') |
| 93 # TODO(efortuna): Put this back up to be more than the default timeout in | 92 # TODO(efortuna): Put this back up to be more than the default timeout in |
| 94 # test.dart. Right now it needs to be less than 60 so that when test.dart | 93 # test.dart. Right now it needs to be less than 60 so that when test.dart |
| 95 # times out, this script also closes the browser windows. | 94 # times out, this script also closes the browser windows. |
| 96 parser.add_option('--timeout', dest = 'timeout', | 95 parser.add_option('--timeout', dest = 'timeout', |
| 97 help = 'Amount of time (seconds) to wait before timeout', type = 'int', | 96 help = 'Amount of time (seconds) to wait before timeout', type = 'int', |
| 98 action = 'store', default=58) | 97 action = 'store', default=58) |
| 99 parser.add_option('--perf', dest = 'is_perf', | 98 parser.add_option('--perf', dest = 'is_perf', |
| 100 help = 'Add this flag if we are running a browser performance test', | 99 help = 'Add this flag if we are running a browser performance test', |
| 101 action = 'store_true', default=False) | 100 action = 'store_true', default=False) |
| 102 args, ignored = parser.parse_args() | 101 args, ignored = parser.parse_args(args=args) |
| 103 return args.out, args.browser, args.timeout, args.is_perf | 102 return args.out, args.browser, args.timeout, args.is_perf |
| 104 | 103 |
| 105 def Main(): | 104 def start_browser(browser, html_out): |
|
Emily Fortuna
2012/02/21 18:19:35
Maybe call this get_browser instead of start_brows
Jennifer Messerly
2012/02/21 18:58:43
I worry about calling it "get" in that it sounds l
| |
| 106 # Note: you need ChromeDriver *in your path* to run Chrome, in addition to | |
| 107 # installing Chrome. | |
| 108 browser = None | |
| 109 html_out, browser, timeout, is_perf = parse_args() | |
| 110 | |
| 111 if browser == 'chrome': | 105 if browser == 'chrome': |
| 112 browser = selenium.webdriver.Chrome() | 106 # Note: you need ChromeDriver *in your path* to run Chrome, in addition to |
| 107 # installing Chrome. Also note that the build bot runs have a different path | |
| 108 # from a normal user -- check the build logs. | |
| 109 return selenium.webdriver.Chrome() | |
| 113 elif browser == 'ff': | 110 elif browser == 'ff': |
| 114 profile = selenium.webdriver.firefox.firefox_profile.FirefoxProfile() | 111 profile = selenium.webdriver.firefox.firefox_profile.FirefoxProfile() |
| 115 profile.set_preference('dom.max_script_run_time', 0) | 112 profile.set_preference('dom.max_script_run_time', 0) |
| 116 profile.set_preference('dom.max_chrome_script_run_time', 0) | 113 profile.set_preference('dom.max_chrome_script_run_time', 0) |
| 117 browser = selenium.webdriver.Firefox(firefox_profile=profile) | 114 return selenium.webdriver.Firefox(firefox_profile=profile) |
| 118 elif browser == 'ie' and platform.system() == 'Windows': | 115 elif browser == 'ie' and platform.system() == 'Windows': |
| 119 browser = selenium.webdriver.Ie() | 116 return selenium.webdriver.Ie() |
| 120 elif browser == 'safari' and platform.system() == 'Darwin': | 117 elif browser == 'safari' and platform.system() == 'Darwin': |
| 121 # TODO(efortuna): Ensure our preferences (no pop-up blocking) file is the | 118 # TODO(efortuna): Ensure our preferences (no pop-up blocking) file is the |
| 122 # same (Safari auto-deletes when it has too many "crashes," or in our case, | 119 # same (Safari auto-deletes when it has too many "crashes," or in our case, |
| 123 # timeouts). Come up with a less hacky way to do this. | 120 # timeouts). Come up with a less hacky way to do this. |
| 124 shutil.copy(os.path.dirname(__file__) + '/com.apple.Safari.plist', | 121 # !!!! |
|
Emily Fortuna
2012/02/21 18:19:35
Do we have a workaround for this yet? We probably
Jennifer Messerly
2012/02/21 18:58:43
Oops! Thanks for catching that. Fixed--now checks
| |
| 125 '/Library/Preferences/com.apple.Safari.plist') | 122 #shutil.copy(os.path.dirname(__file__) + '/com.apple.Safari.plist', |
| 123 # '/Library/Preferences/com.apple.Safari.plist') | |
| 126 sel = selenium.selenium('localhost', 4444, "*safari", 'file://' + html_out) | 124 sel = selenium.selenium('localhost', 4444, "*safari", 'file://' + html_out) |
| 127 try: | 125 try: |
| 128 sel.start() | 126 sel.start() |
| 127 return sel | |
| 129 except socket.error: | 128 except socket.error: |
| 130 print 'ERROR: Could not connect to Selenium RC server. Are you running' +\ | 129 print 'ERROR: Could not connect to Selenium RC server. Are you running' +\ |
| 131 ' java -jar selenium-server-standalone-2.15.0.jar? If not, start ' + \ | 130 ' java -jar selenium-server-standalone-2.15.0.jar? If not, start ' + \ |
| 132 'it before running this test.' | 131 'it before running this test.' |
| 133 return 1 | 132 sys.exit(1) |
| 134 else: | 133 else: |
| 135 raise Exception('Incompatible browser and platform combination.') | 134 raise Exception('Incompatible browser and platform combination.') |
| 136 source = '' | |
| 137 if browser == 'safari': | |
| 138 source = run_test_in_browser_selenium1(sel, html_out, timeout, is_perf) | |
| 139 else: | |
| 140 source = run_test_in_browser(browser, html_out, timeout, is_perf) | |
| 141 | 135 |
| 136 def close_browser(browser): | |
| 137 if browser is None: | |
| 138 return | |
| 139 if isinstance(browser, selenium.selenium): | |
| 140 browser.stop() | |
| 141 return | |
| 142 | |
| 143 # A timeout exception is thrown if nothing happens within the time limit. | |
| 144 print '!!! trying to close browser !!!' | |
|
Emily Fortuna
2012/02/21 18:19:35
Do we always want to print this? Perhaps a more in
Jennifer Messerly
2012/02/21 18:58:43
Oops. removed.
| |
| 145 if browser != 'chrome': | |
| 146 browser.close() | |
| 147 try: | |
| 148 browser.quit() | |
| 149 except selenium.common.exceptions.WebDriverException: | |
| 150 # TODO(efortuna): figure out why this crashes.... and avoid? | |
|
Emily Fortuna
2012/02/21 18:19:35
nit: Yes, this was my mistake, but let's make that
Jennifer Messerly
2012/02/21 18:58:43
Done.
| |
| 151 pass | |
| 152 | |
| 153 def report_results(is_perf, source): | |
| 142 if is_perf: | 154 if is_perf: |
| 143 # We're running a performance test. | 155 # We're running a performance test. |
| 144 print source | 156 print source |
| 145 if 'NaN' in source: | 157 if 'NaN' in source: |
| 146 return 1 | 158 return 1 |
| 147 else: | 159 else: |
| 148 return 0 | 160 return 0 |
| 149 else: | 161 else: |
| 150 # We're running a correctness test. Mark test as passing if all individual | 162 # We're running a correctness test. Mark test as passing if all individual |
| 151 # test cases pass. | 163 # test cases pass. |
| 152 if 'FAIL' not in source and 'PASS' in source: | 164 if 'FAIL' not in source and 'PASS' in source: |
| 153 print 'Content-Type: text/plain\nPASS' | 165 print 'Content-Type: text/plain\nPASS' |
| 154 return 0 | 166 return 0 |
| 155 else: | 167 else: |
| 156 #The hacky way to get document.getElementById('body').innerHTML for this | 168 #The hacky way to get document.getElementById('body').innerHTML for this |
| 157 # webpage, without the JavaScript. | 169 # webpage, without the JavaScript. |
| 158 #TODO(efortuna): Access these elements in a nicer way using DOM parser. | 170 #TODO(efortuna): Access these elements in a nicer way using DOM parser. |
| 159 index = source.find('<body>') | 171 index = source.find('<body>') |
| 160 index += len('<body>') | 172 index += len('<body>') |
| 161 end_index = source.find('<script') | 173 end_index = source.find('<script') |
| 162 print source[index : end_index] | 174 print unicode(source[index : end_index]).encode("utf-8") |
|
Emily Fortuna
2012/02/21 18:19:35
good catch here.
Jennifer Messerly
2012/02/21 18:58:43
Done.
| |
| 163 return 1 | 175 return 1 |
| 164 | 176 |
| 177 def run_batch_tests(): | |
|
Emily Fortuna
2012/02/21 18:19:35
Add some comments saying what's going on in this m
Jennifer Messerly
2012/02/21 18:58:43
Done.
| |
| 178 print '>>> BATCH START' | |
| 179 browser = None | |
| 180 current_browser_name = None | |
| 181 | |
| 182 # test.dart doesn't give us a chance to shut down gracefully, so handle | |
| 183 # SIGTERM instead. TODO(jmesserly): make this more robust | |
|
Emily Fortuna
2012/02/21 18:19:35
FYI: I've filed a feature request about this here:
Jennifer Messerly
2012/02/21 18:58:43
Done.
| |
| 184 def sigterm(number, frame): | |
| 185 close_browser(browser) | |
| 186 signal.signal(signal.SIGTERM, sigterm) | |
| 187 | |
| 188 try: | |
| 189 while True: | |
| 190 line = sys.stdin.readline() | |
| 191 if line == '--terminate\n': | |
| 192 break | |
| 193 | |
| 194 html_out, browser_name, timeout, is_perf = parse_args(line.split()) | |
| 195 | |
| 196 # Sanity checks that test.dart is passing flags we can handle. | |
| 197 if is_perf: | |
| 198 print 'Batch test runner not compatible with perf testing' | |
| 199 return 1 | |
| 200 if browser and current_browser_name != browser_name: | |
| 201 print('Batch test runner got multiple browsers: %s and %s' | |
| 202 % (current_browser_name, browser_name)) | |
| 203 return 1 | |
| 204 | |
| 205 # Start the browser on the first run | |
| 206 if browser is None: | |
| 207 current_browser_name = browser_name | |
| 208 browser = start_browser(browser_name, html_out) | |
| 209 | |
| 210 source = run_test_in_browser(browser, html_out, timeout, is_perf) | |
| 211 | |
| 212 # print one of: | |
| 213 # >>> TEST {PASS, FAIL, OK, CRASH, FAIL, TIMEOUT} | |
| 214 status = report_results(is_perf, source) | |
| 215 if status == 0: | |
| 216 print '>>> TEST PASS' | |
|
Emily Fortuna
2012/02/21 18:19:35
I believe you need to print out the following "mag
Jennifer Messerly
2012/02/21 18:58:43
This is printed still by report_results. But I'm n
Emily Fortuna
2012/02/21 19:35:33
Oh, I see. Seems fine then.
| |
| 217 elif source == TIMEOUT_ERROR_MSG: | |
| 218 print '>>> TEST TIMEOUT' | |
| 219 else: | |
| 220 print '>>> TEST FAIL' | |
| 221 sys.stdout.flush() | |
| 222 finally: | |
| 223 close_browser(browser) | |
| 224 | |
| 225 | |
| 226 def main(args): | |
| 227 # Run in batch mode if the --batch flag is passed. | |
| 228 # TODO(jmesserly): reconcile with the existing args parsing | |
| 229 if '--batch' in args: | |
| 230 return run_batch_tests() | |
| 231 | |
| 232 # Run a single test | |
| 233 html_out, browser_name, timeout, is_perf = parse_args() | |
| 234 browser = start_browser(browser_name, html_out) | |
| 235 | |
| 236 try: | |
| 237 output = run_test_in_browser(browser, html_out, timeout, is_perf) | |
| 238 return report_results(is_perf, output) | |
| 239 finally: | |
| 240 close_browser(browser) | |
| 165 | 241 |
| 166 if __name__ == "__main__": | 242 if __name__ == "__main__": |
| 167 sys.exit(Main()) | 243 sys.exit(main(sys.argv)) |
| OLD | NEW |