GoogleGit

blob: a43453c1fd7a3185f0fbe18d3706135968523a4a [file] [log] [blame]
  1. # Copyright (c) 2013 The Chromium OS 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. import atexit
  5. import logging
  6. import os
  7. import urllib2
  8. import urlparse
  9. try:
  10. from selenium import webdriver
  11. except ImportError:
  12. # Ignore import error, as this can happen when builder tries to call the
  13. # setup method of test that imports chromedriver.
  14. logging.error('selenium module failed to be imported.')
  15. pass
  16. from autotest_lib.client.bin import utils
  17. from autotest_lib.client.common_lib.cros import chrome
  18. CHROMEDRIVER_EXE_PATH = '/usr/local/chromedriver/chromedriver'
  19. X_SERVER_DISPLAY = ':0'
  20. X_AUTHORITY = '/home/chronos/.Xauthority'
  21. class chromedriver(object):
  22. """Wrapper class, a context manager type, for tests to use Chrome Driver."""
  23. def __init__(self, extra_chrome_flags=[], subtract_extra_chrome_flags=[],
  24. extension_paths=[], is_component=True, username=None,
  25. password=None, server_port=None, skip_cleanup=False,
  26. url_base=None, extra_chromedriver_args=None, *args, **kwargs):
  27. """Initialize.
  28. @param extra_chrome_flags: Extra chrome flags to pass to chrome, if any.
  29. @param subtract_extra_chrome_flags: Remove default flags passed to
  30. chrome by chromedriver, if any.
  31. @param extension_paths: A list of paths to unzipped extensions. Note
  32. that paths to crx files won't work.
  33. @param is_component: True if the manifest.json has a key.
  34. @param username: Log in using this username instead of the default.
  35. @param password: Log in using this password instead of the default.
  36. @param server_port: Port number for the chromedriver server. If None,
  37. an available port is chosen at random.
  38. @param skip_cleanup: If True, leave the server and browser running
  39. so that remote tests can run after this script
  40. ends. Default is False.
  41. @param url_base: Optional base url for chromedriver.
  42. @param extra_chromedriver_args: List of extra arguments to forward to
  43. the chromedriver binary, if any.
  44. """
  45. self._cleanup = not skip_cleanup
  46. assert os.geteuid() == 0, 'Need superuser privileges'
  47. # Log in with telemetry
  48. self._chrome = chrome.Chrome(extension_paths=extension_paths,
  49. is_component=is_component,
  50. username=username,
  51. password=password,
  52. extra_browser_args=extra_chrome_flags)
  53. self._browser = self._chrome.browser
  54. # Close all tabs owned and opened by Telemetry, as these cannot be
  55. # transferred to ChromeDriver.
  56. self._browser.tabs[0].Close()
  57. # Start ChromeDriver server
  58. self._server = chromedriver_server(CHROMEDRIVER_EXE_PATH,
  59. port=server_port,
  60. skip_cleanup=skip_cleanup,
  61. url_base=url_base,
  62. extra_args=extra_chromedriver_args)
  63. # Open a new tab using Chrome remote debugging. ChromeDriver expects
  64. # a tab opened for remote to work. Tabs opened using Telemetry will be
  65. # owned by Telemetry, and will be inaccessible to ChromeDriver.
  66. urllib2.urlopen('http://localhost:%i/json/new' %
  67. utils.get_chrome_remote_debugging_port())
  68. chromeOptions = {'debuggerAddress':
  69. ('localhost:%d' %
  70. utils.get_chrome_remote_debugging_port())}
  71. capabilities = {'chromeOptions':chromeOptions}
  72. # Handle to chromedriver, for chrome automation.
  73. try:
  74. self.driver = webdriver.Remote(command_executor=self._server.url,
  75. desired_capabilities=capabilities)
  76. except NameError:
  77. logging.error('selenium module failed to be imported.')
  78. raise
  79. def __enter__(self):
  80. return self
  81. def __exit__(self, *args):
  82. """Clean up after running the test.
  83. """
  84. if hasattr(self, 'driver') and self.driver:
  85. self.driver.close()
  86. del self.driver
  87. if not hasattr(self, '_cleanup') or self._cleanup:
  88. if hasattr(self, '_server') and self._server:
  89. self._server.close()
  90. del self._server
  91. if hasattr(self, '_browser') and self._browser:
  92. self._browser.Close()
  93. del self._browser
  94. def get_extension(self, extension_path):
  95. """Gets an extension by proxying to the browser.
  96. @param extension_path: Path to the extension loaded in the browser.
  97. @return: A telemetry extension object representing the extension.
  98. """
  99. return self._chrome.get_extension(extension_path)
  100. class chromedriver_server(object):
  101. """A running ChromeDriver server.
  102. This code is migrated from chrome:
  103. src/chrome/test/chromedriver/server/server.py
  104. """
  105. def __init__(self, exe_path, port=None, skip_cleanup=False,
  106. url_base=None, extra_args=None):
  107. """Starts the ChromeDriver server and waits for it to be ready.
  108. Args:
  109. exe_path: path to the ChromeDriver executable
  110. port: server port. If None, an available port is chosen at random.
  111. skip_cleanup: If True, leave the server running so that remote
  112. tests can run after this script ends. Default is
  113. False.
  114. url_base: Optional base url for chromedriver.
  115. extra_args: List of extra arguments to forward to the chromedriver
  116. binary, if any.
  117. Raises:
  118. RuntimeError if ChromeDriver fails to start
  119. """
  120. if not os.path.exists(exe_path):
  121. raise RuntimeError('ChromeDriver exe not found at: ' + exe_path)
  122. chromedriver_args = [exe_path]
  123. if port:
  124. # Allow remote connections if a port was specified
  125. chromedriver_args.append('--whitelisted-ips')
  126. else:
  127. port = utils.get_unused_port()
  128. chromedriver_args.append('--port=%d' % port)
  129. self.url = 'http://localhost:%d' % port
  130. if url_base:
  131. chromedriver_args.append('--url-base=%s' % url_base)
  132. self.url = urlparse.urljoin(self.url, url_base)
  133. if extra_args:
  134. chromedriver_args.extend(extra_args)
  135. # TODO(ihf): Remove references to X after M45.
  136. # Chromedriver will look for an X server running on the display
  137. # specified through the DISPLAY environment variable.
  138. os.environ['DISPLAY'] = X_SERVER_DISPLAY
  139. os.environ['XAUTHORITY'] = X_AUTHORITY
  140. self.bg_job = utils.BgJob(chromedriver_args, stderr_level=logging.DEBUG)
  141. if self.bg_job is None:
  142. raise RuntimeError('ChromeDriver server cannot be started')
  143. try:
  144. timeout_msg = 'Timeout on waiting for ChromeDriver to start.'
  145. utils.poll_for_condition(self.is_running,
  146. exception=utils.TimeoutError(timeout_msg),
  147. timeout=10,
  148. sleep_interval=.1)
  149. except utils.TimeoutError:
  150. self.close_bgjob()
  151. raise RuntimeError('ChromeDriver server did not start')
  152. logging.debug('Chrome Driver server is up and listening at port %d.',
  153. port)
  154. if not skip_cleanup:
  155. atexit.register(self.close)
  156. def is_running(self):
  157. """Returns whether the server is up and running."""
  158. try:
  159. urllib2.urlopen(self.url + '/status')
  160. return True
  161. except urllib2.URLError as e:
  162. return False
  163. def close_bgjob(self):
  164. """Close background job and log stdout and stderr."""
  165. utils.nuke_subprocess(self.bg_job.sp)
  166. utils.join_bg_jobs([self.bg_job], timeout=1)
  167. result = self.bg_job.result
  168. if result.stdout or result.stderr:
  169. logging.info('stdout of Chrome Driver:\n%s', result.stdout)
  170. logging.error('stderr of Chrome Driver:\n%s', result.stderr)
  171. def close(self):
  172. """Kills the ChromeDriver server, if it is running."""
  173. if self.bg_job is None:
  174. return
  175. try:
  176. urllib2.urlopen(self.url + '/shutdown', timeout=10).close()
  177. except:
  178. pass
  179. self.close_bgjob()