1 # Copyright 2020 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.
5 This script runs Chrome and automatically navigates through the given list of
6 URLs the specified number of times.
8 Usage: vpython3 auto-nav.py <chrome dir> <number of navigations> <url> <url> ...
11 * --interval <seconds>, -i <seconds>: specify a number of seconds to wait
12 between navigations, e.g., -i=5
13 * --start_prompt, -s: start Chrome, then wait for the user to press Enter before
14 starting auto-navigation
15 * --exit-prompt, -e: after auto-navigation, wait for the user to press Enter
16 before shutting down chrome.exe
17 * --idlewakeups_dir: Windows only; specify the directory containing
18 idlewakeups.exe to print measurements taken by IdleWakeups,
19 e.g., --idlewakeups_dir=tools/win/IdleWakeups/x64/Debug
21 Optional flags to chrome.exe, example:
22 -- --user-data-dir=temp --disable-features=SomeFeature
23 Note: must be at end of command, following options terminator "--". The options
24 terminator stops command-line options from being interpreted as options for this
25 script, which would cause an unrecognized-argument error.
29 # python_version: "3.8"
31 # name: "infra/python/wheels/selenium-py2_py3"
32 # version: "version:3.14.0"
35 # name: "infra/python/wheels/urllib3-py2_py3"
36 # version: "version:1.24.3"
39 # name: "infra/python/wheels/psutil/${vpython_platform}"
40 # version: "version:5.7.2"
53 from selenium import webdriver
55 print('Error importing required modules. Run with vpython3 instead of python.')
61 # Splits list |positional_args| into two lists: |urls| and |chrome_args|, where
62 # arguments starting with '-' are treated as chrome args, and the rest as URLs.
63 def ParsePositionalArgs(positional_args):
64 urls, chrome_args = [], []
65 for arg in positional_args:
66 if arg.startswith('-'):
67 chrome_args.append(arg)
70 return [urls, chrome_args]
73 # Returns an object containing the arguments parsed from this script's command
76 # Customize usage and help to include options to be passed to chrome.exe.
77 usage_text = '''%(prog)s [-h] [--interval INTERVAL] [--wait]
78 [--idlewakeups_dir IDLEWAKEUPS_DIR]
79 chrome_dir num_navigations url [url ...]
80 [-- --chrome_option ...]'''
81 additional_help_text = '''optional arguments to chrome.exe, example:
82 -- --enable-features=MyFeature --browser-startup-dialog
83 Must be at end of command, following the options
85 parser = argparse.ArgumentParser(
86 epilog=additional_help_text,
88 formatter_class=argparse.RawDescriptionHelpFormatter)
90 'chrome_dir', help='Directory containing chrome.exe and chromedriver.exe')
91 parser.add_argument('num_navigations',
93 help='Number of times to navigate through list of URLs')
94 parser.add_argument('--interval',
97 help='Seconds to wait between navigations; default is 1')
98 parser.add_argument('--start_prompt',
101 help='Wait for confirmation before starting navigation')
102 parser.add_argument('--exit_prompt',
105 help='Wait for confirmation before exiting chrome.exe')
108 help='Windows only; directory containing idlewakeups.exe, if using')
112 help='URL(s) to navigate, separated by spaces; must include scheme, '
114 args = parser.parse_args()
115 args.url, chrome_args = ParsePositionalArgs(args.url)
118 print(os.path.basename(__file__) + ': error: missing URL argument')
119 exit(EXIT_CODE_ERROR)
121 if not urllib.parse.urlparse(url).scheme:
122 print(os.path.basename(__file__) +
123 ': error: URL is missing required scheme (e.g., "https://"): ' + url)
124 exit(EXIT_CODE_ERROR)
125 return [args, chrome_args]
128 # If |path| does not exist, prints a generic error plus optional |error_message|
130 def ExitIfNotFound(path, error_message=None):
131 if not os.path.exists(path):
132 print('File not found: {}.'.format(path))
135 exit(EXIT_CODE_ERROR)
139 # Parse arguments and check that file paths received are valid.
140 args, chrome_args = ParseArgs()
141 ExitIfNotFound(os.path.join(args.chrome_dir, 'chrome.exe'),
142 'Build target "chrome" to generate it first.')
143 chromedriver_exe = os.path.join(args.chrome_dir, 'chromedriver.exe')
144 ExitIfNotFound(chromedriver_exe,
145 'Build target "chromedriver" to generate it first.')
146 if args.idlewakeups_dir:
147 idlewakeups_exe = os.path.join(args.idlewakeups_dir, 'idlewakeups.exe')
148 ExitIfNotFound(idlewakeups_exe)
150 # Start chrome.exe. Disable chrome.exe's extensive logging to make reading
151 # this script's output easier.
152 chrome_options = webdriver.ChromeOptions()
153 chrome_options.add_experimental_option('excludeSwitches', ['enable-logging'])
154 for arg in chrome_args:
155 chrome_options.add_argument(arg)
156 driver = webdriver.Chrome(os.path.abspath(chromedriver_exe),
157 options=chrome_options)
159 if args.start_prompt:
160 driver.get(args.url[0])
161 input('Press Enter to begin navigation...')
163 # Start IdleWakeups, if using, passing the browser process's ID as its target.
164 # IdleWakeups will monitor the browser process and its children. Other running
165 # chrome.exe processes (i.e., those not launched by this script) are excluded.
166 if args.idlewakeups_dir:
167 launched_processes = psutil.Process(
168 driver.service.process.pid).children(recursive=False)
169 if not launched_processes:
170 print('Error getting browser process ID for IdleWakeups.')
172 # Assume the first child process created by |driver| is the browser process.
173 idlewakeups = subprocess.Popen([
175 str(launched_processes[0].pid), '--stop-on-exit', '--tabbed'
177 stdout=subprocess.PIPE)
179 # Navigate through |args.url| list |args.num_navigations| times, then close
181 interval = args.interval if args.interval else DEFAULT_INTERVAL
182 for _ in range(args.num_navigations):
188 input('Press Enter to exit...')
191 # Print IdleWakeups' output, if using.
192 if args.idlewakeups_dir:
193 print(idlewakeups.communicate()[0])
196 if __name__ == '__main__':