blob: 404c4447d353821c5bdadd8fac9460af32dc3ea1 [file] [log] [blame]
Dan Shic1d263b2013-10-04 17:31:38 -07001# 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
5import atexit
6import logging
7import os
8import urllib2
9
Dan Shi96c77cb2013-11-19 10:30:37 -080010try:
11 from selenium import webdriver
12except ImportError:
13 # Ignore import error, as this can happen when builder tries to call the
14 # setup method of test that imports chromedriver.
15 logging.error('selenium module failed to be imported.')
16 pass
Dan Shic1d263b2013-10-04 17:31:38 -070017
Dan Shic1d263b2013-10-04 17:31:38 -070018from autotest_lib.client.bin import utils
19from autotest_lib.client.common_lib.cros import chrome
20
21CHROMEDRIVER_EXE_PATH = '/usr/local/chromedriver/chromedriver'
beeps47a51292013-11-20 08:55:23 -080022X_SERVER_DISPLAY = ':0'
23X_AUTHORITY = '/home/chronos/.Xauthority'
24
Dan Shic1d263b2013-10-04 17:31:38 -070025
26class chromedriver(object):
27 """Wrapper class, a context manager type, for tests to use Chrome Driver."""
28
29 def __init__(self, extra_chrome_flags=[], subtract_extra_chrome_flags=[],
Nathan Stoddardab583042014-08-11 10:41:47 -070030 extension_paths=[], is_component=True, username=None,
Resetswitch98a0f7d2015-05-04 14:48:08 -070031 password=None, server_port=None, *args, **kwargs):
Dan Shic1d263b2013-10-04 17:31:38 -070032 """Initialize.
33
34 @param extra_chrome_flags: Extra chrome flags to pass to chrome, if any.
35 @param subtract_extra_chrome_flags: Remove default flags passed to
36 chrome by chromedriver, if any.
beeps47a51292013-11-20 08:55:23 -080037 @param extension_paths: A list of paths to unzipped extensions. Note
38 that paths to crx files won't work.
39 @param is_component: True if the manifest.json has a key.
Nathan Stoddardab583042014-08-11 10:41:47 -070040 @param username: Log in using this username instead of the default.
Resetswitch98a0f7d2015-05-04 14:48:08 -070041 @param password: Log in using this password instead of the default.
42 @param server_port: Port number for the chromedriver server. If None,
43 an available port is chosen at random.
Dan Shic1d263b2013-10-04 17:31:38 -070044 """
45 assert os.geteuid() == 0, 'Need superuser privileges'
46
47 # Log in with telemetry
beepsbff9f9d2013-12-06 11:14:08 -080048 self._chrome = chrome.Chrome(extension_paths=extension_paths,
49 is_component=is_component,
Nathan Stoddardab583042014-08-11 10:41:47 -070050 username=username,
51 password=password,
beepsbff9f9d2013-12-06 11:14:08 -080052 extra_browser_args=extra_chrome_flags)
53 self._browser = self._chrome.browser
Dan Shi2bff39b2014-03-28 11:36:13 -070054 # Close all tabs owned and opened by Telemetry, as these cannot be
55 # transferred to ChromeDriver.
56 self._browser.tabs[0].Close()
Dan Shic1d263b2013-10-04 17:31:38 -070057
58 # Start ChromeDriver server
Resetswitch98a0f7d2015-05-04 14:48:08 -070059 self._server = chromedriver_server(CHROMEDRIVER_EXE_PATH,
60 port=server_port)
Dan Shic1d263b2013-10-04 17:31:38 -070061
Dan Shi2bff39b2014-03-28 11:36:13 -070062 # Open a new tab using Chrome remote debugging. ChromeDriver expects
63 # a tab opened for remote to work. Tabs opened using Telemetry will be
64 # owned by Telemetry, and will be inaccessible to ChromeDriver.
65 urllib2.urlopen('http://localhost:%i/json/new' %
66 utils.get_chrome_remote_debugging_port())
67
Dan Shic1d263b2013-10-04 17:31:38 -070068 chromeOptions = {'debuggerAddress':
69 ('localhost:%d' %
70 utils.get_chrome_remote_debugging_port())}
71 capabilities = {'chromeOptions':chromeOptions}
72 # Handle to chromedriver, for chrome automation.
Dan Shi96c77cb2013-11-19 10:30:37 -080073 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
Dan Shic1d263b2013-10-04 17:31:38 -070079
80
81 def __enter__(self):
82 return self
83
84
85 def __exit__(self, *args):
86 """Clean up after running the test.
87
88 """
89 if hasattr(self, 'driver') and self.driver:
90 self.driver.close()
91 del self.driver
92
93 if hasattr(self, '_server') and self._server:
94 self._server.close()
95 del self._server
96
97 if hasattr(self, '_browser') and self._browser:
98 self._browser.Close()
99 del self._browser
100
101
beepsbff9f9d2013-12-06 11:14:08 -0800102 def get_extension(self, extension_path):
103 """Gets an extension by proxying to the browser.
104
105 @param extension_path: Path to the extension loaded in the browser.
106
107 @return: A telemetry extension object representing the extension.
108 """
109 return self._chrome.get_extension(extension_path)
110
111
Dan Shic1d263b2013-10-04 17:31:38 -0700112class chromedriver_server(object):
113 """A running ChromeDriver server.
114
115 This code is migrated from chrome:
116 src/chrome/test/chromedriver/server/server.py
117 """
118
Resetswitch98a0f7d2015-05-04 14:48:08 -0700119 def __init__(self, exe_path, port=None):
Dan Shic1d263b2013-10-04 17:31:38 -0700120 """Starts the ChromeDriver server and waits for it to be ready.
121
122 Args:
123 exe_path: path to the ChromeDriver executable
Resetswitch98a0f7d2015-05-04 14:48:08 -0700124 port: server port. If None, an available port is chosen at random.
Dan Shic1d263b2013-10-04 17:31:38 -0700125 Raises:
126 RuntimeError if ChromeDriver fails to start
127 """
128 if not os.path.exists(exe_path):
129 raise RuntimeError('ChromeDriver exe not found at: ' + exe_path)
130
Resetswitch98a0f7d2015-05-04 14:48:08 -0700131 if not port:
132 port = utils.get_unused_port()
Dan Shic1d263b2013-10-04 17:31:38 -0700133 chromedriver_args = [exe_path, '--port=%d' % port]
beeps47a51292013-11-20 08:55:23 -0800134
135 # Chromedriver will look for an X server running on the display
136 # specified through the DISPLAY environment variable.
Ilja H. Friedela5ab1662014-10-15 19:26:40 -0700137 utils.assert_has_X_server()
beeps47a51292013-11-20 08:55:23 -0800138 os.environ['DISPLAY'] = X_SERVER_DISPLAY
139 os.environ['XAUTHORITY'] = X_AUTHORITY
140
Dan Shic1d263b2013-10-04 17:31:38 -0700141 self.bg_job = utils.BgJob(chromedriver_args, stderr_level=logging.DEBUG)
142 self.url = 'http://localhost:%d' % port
143 if self.bg_job is None:
144 raise RuntimeError('ChromeDriver server cannot be started')
145
146 try:
147 timeout_msg = 'Timeout on waiting for ChromeDriver to start.'
148 utils.poll_for_condition(self.is_running,
149 exception=utils.TimeoutError(timeout_msg),
150 timeout=10,
151 sleep_interval=.1)
152 except utils.TimeoutError:
153 self.close_bgjob()
154 raise RuntimeError('ChromeDriver server did not start')
155
156 logging.debug('Chrome Driver server is up and listening at port %d.',
157 port)
158 atexit.register(self.close)
159
160
161 def is_running(self):
162 """Returns whether the server is up and running."""
163 try:
164 urllib2.urlopen(self.url + '/status')
165 return True
166 except urllib2.URLError as e:
167 return False
168
169
170 def close_bgjob(self):
171 """Close background job and log stdout and stderr."""
172 utils.nuke_subprocess(self.bg_job.sp)
173 utils.join_bg_jobs([self.bg_job], timeout=1)
174 result = self.bg_job.result
175 if result.stdout or result.stderr:
176 logging.info('stdout of Chrome Driver:\n%s', result.stdout)
177 logging.error('stderr of Chrome Driver:\n%s', result.stderr)
178
179
180 def close(self):
181 """Kills the ChromeDriver server, if it is running."""
182 if self.bg_job is None:
183 return
184
185 try:
186 urllib2.urlopen(self.url + '/shutdown', timeout=10).close()
187 except:
188 pass
189
190 self.close_bgjob()