# Copyright 2020 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. """ This script runs Chrome and automatically navigates through the given list of URLs the specified number of times. Usage: vpython3 auto-nav.py ... Optional flags: * --interval , -i : specify a number of seconds to wait between navigations, e.g., -i=5 * --start_prompt, -s: start Chrome, then wait for the user to press Enter before starting auto-navigation * --exit-prompt, -e: after auto-navigation, wait for the user to press Enter before shutting down chrome.exe * --idlewakeups_dir: Windows only; specify the directory containing idlewakeups.exe to print measurements taken by IdleWakeups, e.g., --idlewakeups_dir=tools/win/IdleWakeups/x64/Debug Optional flags to chrome.exe, example: -- --user-data-dir=temp --disable-features=SomeFeature Note: must be at end of command, following options terminator "--". The options terminator stops command-line options from being interpreted as options for this script, which would cause an unrecognized-argument error. """ # [VPYTHON:BEGIN] # python_version: "3.8" # wheel: < # name: "infra/python/wheels/selenium-py2_py3" # version: "version:3.14.0" # > # wheel: < # name: "infra/python/wheels/urllib3-py2_py3" # version: "version:1.24.3" # > # wheel: < # name: "infra/python/wheels/psutil/${vpython_platform}" # version: "version:5.7.2" # > # [VPYTHON:END] import argparse import os import subprocess import sys import time import urllib try: import psutil from selenium import webdriver except ImportError: print('Error importing required modules. Run with vpython3 instead of python.') sys.exit(1) DEFAULT_INTERVAL = 1 EXIT_CODE_ERROR = 1 # Splits list |positional_args| into two lists: |urls| and |chrome_args|, where # arguments starting with '-' are treated as chrome args, and the rest as URLs. def ParsePositionalArgs(positional_args): urls, chrome_args = [], [] for arg in positional_args: if arg.startswith('-'): chrome_args.append(arg) else: urls.append(arg) return [urls, chrome_args] # Returns an object containing the arguments parsed from this script's command # line. def ParseArgs(): # Customize usage and help to include options to be passed to chrome.exe. usage_text = '''%(prog)s [-h] [--interval INTERVAL] [--wait] [--idlewakeups_dir IDLEWAKEUPS_DIR] chrome_dir num_navigations url [url ...] [-- --chrome_option ...]''' additional_help_text = '''optional arguments to chrome.exe, example: -- --enable-features=MyFeature --browser-startup-dialog Must be at end of command, following the options terminator "--"''' parser = argparse.ArgumentParser( epilog=additional_help_text, usage=usage_text, formatter_class=argparse.RawDescriptionHelpFormatter) parser.add_argument( 'chrome_dir', help='Directory containing chrome.exe and chromedriver.exe') parser.add_argument('num_navigations', type=int, help='Number of times to navigate through list of URLs') parser.add_argument('--interval', '-i', type=int, help='Seconds to wait between navigations; default is 1') parser.add_argument('--start_prompt', '-s', action='store_true', help='Wait for confirmation before starting navigation') parser.add_argument('--exit_prompt', '-e', action='store_true', help='Wait for confirmation before exiting chrome.exe') parser.add_argument( '--idlewakeups_dir', help='Windows only; directory containing idlewakeups.exe, if using') parser.add_argument( 'url', nargs='+', help='URL(s) to navigate, separated by spaces; must include scheme, ' 'e.g., "https://"') args = parser.parse_args() args.url, chrome_args = ParsePositionalArgs(args.url) if not args.url: parser.print_usage() print(os.path.basename(__file__) + ': error: missing URL argument') exit(EXIT_CODE_ERROR) for url in args.url: if not urllib.parse.urlparse(url).scheme: print(os.path.basename(__file__) + ': error: URL is missing required scheme (e.g., "https://"): ' + url) exit(EXIT_CODE_ERROR) return [args, chrome_args] # If |path| does not exist, prints a generic error plus optional |error_message| # and exits. def ExitIfNotFound(path, error_message=None): if not os.path.exists(path): print('File not found: {}.'.format(path)) if error_message: print(error_message) exit(EXIT_CODE_ERROR) def main(): # Parse arguments and check that file paths received are valid. args, chrome_args = ParseArgs() ExitIfNotFound(os.path.join(args.chrome_dir, 'chrome.exe'), 'Build target "chrome" to generate it first.') chromedriver_exe = os.path.join(args.chrome_dir, 'chromedriver.exe') ExitIfNotFound(chromedriver_exe, 'Build target "chromedriver" to generate it first.') if args.idlewakeups_dir: idlewakeups_exe = os.path.join(args.idlewakeups_dir, 'idlewakeups.exe') ExitIfNotFound(idlewakeups_exe) # Start chrome.exe. Disable chrome.exe's extensive logging to make reading # this script's output easier. chrome_options = webdriver.ChromeOptions() chrome_options.add_experimental_option('excludeSwitches', ['enable-logging']) for arg in chrome_args: chrome_options.add_argument(arg) driver = webdriver.Chrome(os.path.abspath(chromedriver_exe), options=chrome_options) if args.start_prompt: driver.get(args.url[0]) input('Press Enter to begin navigation...') # Start IdleWakeups, if using, passing the browser process's ID as its target. # IdleWakeups will monitor the browser process and its children. Other running # chrome.exe processes (i.e., those not launched by this script) are excluded. if args.idlewakeups_dir: launched_processes = psutil.Process( driver.service.process.pid).children(recursive=False) if not launched_processes: print('Error getting browser process ID for IdleWakeups.') exit() # Assume the first child process created by |driver| is the browser process. idlewakeups = subprocess.Popen([ idlewakeups_exe, str(launched_processes[0].pid), '--stop-on-exit', '--tabbed' ], stdout=subprocess.PIPE) # Navigate through |args.url| list |args.num_navigations| times, then close # chrome.exe. interval = args.interval if args.interval else DEFAULT_INTERVAL for _ in range(args.num_navigations): for url in args.url: driver.get(url) time.sleep(interval) if args.exit_prompt: input('Press Enter to exit...') driver.quit() # Print IdleWakeups' output, if using. if args.idlewakeups_dir: print(idlewakeups.communicate()[0]) if __name__ == '__main__': sys.exit(main())