| # Copyright 2018 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Test Chrome using chromedriver. |
| |
| If the webdriver API or the chromedriver.exe binary can't be found this |
| becomes a no-op. This is to allow running locally while the waterfalls get |
| setup. Once all locations have been provisioned and are expected to contain |
| these items the checks should be removed to ensure this is run on each test |
| and fails if anything is incorrect. |
| """ |
| |
| import argparse |
| import atexit |
| import contextlib |
| import logging |
| import os |
| import shutil |
| import sys |
| import tempfile |
| import time |
| |
| import chrome_helper |
| |
| THIS_DIR = os.path.dirname(os.path.abspath(__file__)) |
| SRC_DIR = os.path.join(THIS_DIR, '..', '..', '..') |
| WEBDRIVER_PATH = os.path.join( |
| SRC_DIR, r'third_party', 'webdriver', 'pylib') |
| TEST_HTML_FILE = 'file://' + os.path.join(THIS_DIR, 'test_page.html') |
| |
| |
| # Try and import webdriver |
| sys.path.insert(0, WEBDRIVER_PATH) |
| try: |
| from selenium import webdriver |
| from selenium.webdriver import ChromeOptions |
| except ImportError: |
| # If a system doesn't have the webdriver API this is a no-op phase |
| logging.info( |
| 'Chromedriver API (selenium.webdriver) is not installed available in ' |
| '%s. Exiting test_chrome_with_chromedriver.py' % WEBDRIVER_PATH) |
| sys.exit(0) |
| |
| |
| @contextlib.contextmanager |
| def CreateChromedriver(args): |
| """Create a webdriver object ad close it after.""" |
| |
| def DeleteWithRetry(path, func): |
| # There seems to be a race condition on the bots that causes the paths |
| # to not delete because they are being used. This allows up to 2 seconds |
| # to delete |
| for _ in xrange(4): |
| try: |
| return func(path) |
| except WindowsError: |
| time.sleep(0.5) |
| raise |
| |
| def CollectCrashReports(user_data_dir, output_dir): |
| """Searches for Chrome crash reports, collecting them for analysis. |
| |
| Args: |
| user_data_dir: The full path of the User Data dir. |
| output_dir: If not None, a path to which collected crash reports are to be |
| moved. |
| |
| Returns: |
| The number of crash reports found. |
| """ |
| report_dir = os.path.join(user_data_dir, 'Crashpad', 'reports') |
| dumps = [] |
| try: |
| dumps = os.listdir(report_dir) |
| except OSError: |
| # Assume this is file not found, meaning no crash reports. |
| return 0 |
| for dump in dumps: |
| dump_path = os.path.join(report_dir, dump) |
| if (output_dir): |
| target_path = os.path.join(output_dir, dump) |
| try: |
| shutil.copyfile(dump_path, target_path) |
| logging.error('Saved Chrome crash dump to %s', target_path) |
| except OSError: |
| logging.exception('Failed to copy Chrome crash dump from %s to %s', |
| dump_path, target_path) |
| else: |
| logging.error('Found Chrome crash dump at %s', dump_path) |
| return len(dumps) |
| |
| driver = None |
| user_data_dir = tempfile.mkdtemp() |
| fd, log_file = tempfile.mkstemp() |
| os.close(fd) |
| chrome_options = ChromeOptions() |
| chrome_options.binary_location = args.chrome_path |
| chrome_options.add_argument('user-data-dir=' + user_data_dir) |
| chrome_options.add_argument('log-file=' + log_file) |
| chrome_options.add_argument('enable-logging') |
| chrome_options.add_argument('v=1') |
| emit_log = False |
| try: |
| driver = webdriver.Chrome( |
| args.chromedriver_path, |
| chrome_options=chrome_options) |
| yield driver |
| except: |
| emit_log = True |
| raise |
| finally: |
| if driver: |
| driver.quit() |
| chrome_helper.WaitForChromeExit(args.chrome_path) |
| report_count = CollectCrashReports(user_data_dir, args.output_dir) |
| if report_count: |
| emit_log = True |
| try: |
| DeleteWithRetry(user_data_dir, shutil.rmtree) |
| except: |
| emit_log = True |
| raise |
| finally: |
| if emit_log: |
| with open(log_file) as fh: |
| logging.error(fh.read()) |
| if args.output_dir: |
| target = os.path.join(args.output_dir, os.path.basename(log_file)) |
| shutil.copyfile(log_file, target) |
| logging.error('Saved Chrome log to %s', target) |
| DeleteWithRetry(log_file, os.remove) |
| if report_count: |
| raise Exception('Failing test due to %s crash reports found' % |
| report_count) |
| |
| |
| def main(): |
| """Main entry point.""" |
| parser = parser = argparse.ArgumentParser( |
| description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) |
| parser.add_argument('-q', '--quiet', action='store_true', default=False, |
| help='Reduce test runner output') |
| parser.add_argument( |
| '--chromedriver-path', default='chromedriver.exe', metavar='FILENAME', |
| help='Path to chromedriver') |
| parser.add_argument('--output-dir', metavar='DIR', |
| help='Directory into which crash dumps and other output ' |
| ' files are to be written') |
| parser.add_argument( |
| 'chrome_path', metavar='FILENAME', help='Path to chrome installer') |
| args = parser.parse_args() |
| |
| # This test is run from src, but this script is called with a cwd of |
| # chrome/test/mini_installer, so relative paths need to be compensated for. |
| if not os.path.exists(args.chromedriver_path): |
| args.chromedriver_path = os.path.join( |
| '..', '..', '..', args.chromedriver_path) |
| if not os.path.exists(args.chrome_path): |
| args.chrome_path = os.path.join( |
| '..', '..', '..', args.chrome_path) |
| |
| logging.basicConfig( |
| format='[%(asctime)s:%(filename)s(%(lineno)d)] %(message)s', |
| datefmt='%m%d/%H%M%S', level=logging.ERROR if args.quiet else logging.INFO) |
| |
| if not args.chrome_path: |
| logging.error('The path to the chrome binary is required.') |
| return -1 |
| # Check that chromedriver is correct. |
| if not os.path.exists(args.chromedriver_path): |
| # If we can't find chromedriver exit as a no-op. |
| logging.info( |
| 'Cant find %s. Exiting test_chrome_with_chromedriver', |
| args.chromedriver_path) |
| return 0 |
| with CreateChromedriver(args) as driver: |
| driver.get(TEST_HTML_FILE) |
| assert driver.title == 'Chromedriver Test Page', ( |
| 'The page title was not correct.') |
| element = driver.find_element_by_tag_name('body') |
| assert element.text == 'This is the test page', ( |
| 'The page body was not correct') |
| return 0 |
| |
| |
| if __name__ == '__main__': |
| sys.exit(main()) |