auto-nav.py 7.2 KB

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