chromedriver.py revision 98a0f7d6e5599a2b76310443a0b069189dfebda4
1c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
2c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi# Use of this source code is governed by a BSD-style license that can be
3c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi# found in the LICENSE file.
4c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi
5c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shiimport atexit
6c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shiimport logging
7c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shiimport os
8c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shiimport urllib2
9c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi
1096c77cba384edd7befc85530ab6f809cef429625Dan Shitry:
1196c77cba384edd7befc85530ab6f809cef429625Dan Shi    from selenium import webdriver
1296c77cba384edd7befc85530ab6f809cef429625Dan Shiexcept ImportError:
1396c77cba384edd7befc85530ab6f809cef429625Dan Shi    # Ignore import error, as this can happen when builder tries to call the
1496c77cba384edd7befc85530ab6f809cef429625Dan Shi    # setup method of test that imports chromedriver.
1596c77cba384edd7befc85530ab6f809cef429625Dan Shi    logging.error('selenium module failed to be imported.')
1696c77cba384edd7befc85530ab6f809cef429625Dan Shi    pass
17c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi
18c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shifrom autotest_lib.client.bin import utils
19c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shifrom autotest_lib.client.common_lib.cros import chrome
20c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi
21c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan ShiCHROMEDRIVER_EXE_PATH = '/usr/local/chromedriver/chromedriver'
2247a5129545db2ad42740968b8c20e93cb04bf5aebeepsX_SERVER_DISPLAY = ':0'
2347a5129545db2ad42740968b8c20e93cb04bf5aebeepsX_AUTHORITY = '/home/chronos/.Xauthority'
2447a5129545db2ad42740968b8c20e93cb04bf5aebeeps
25c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi
26c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shiclass chromedriver(object):
27c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi    """Wrapper class, a context manager type, for tests to use Chrome Driver."""
28c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi
29c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi    def __init__(self, extra_chrome_flags=[], subtract_extra_chrome_flags=[],
30ab583045d1e3e21da15eeb5152c2f808f4aea8ffNathan Stoddard                 extension_paths=[], is_component=True, username=None,
3198a0f7d6e5599a2b76310443a0b069189dfebda4Resetswitch                 password=None, server_port=None, *args, **kwargs):
32c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        """Initialize.
33c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi
34c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        @param extra_chrome_flags: Extra chrome flags to pass to chrome, if any.
35c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        @param subtract_extra_chrome_flags: Remove default flags passed to
36c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi                chrome by chromedriver, if any.
3747a5129545db2ad42740968b8c20e93cb04bf5aebeeps        @param extension_paths: A list of paths to unzipped extensions. Note
3847a5129545db2ad42740968b8c20e93cb04bf5aebeeps                                that paths to crx files won't work.
3947a5129545db2ad42740968b8c20e93cb04bf5aebeeps        @param is_component: True if the manifest.json has a key.
40ab583045d1e3e21da15eeb5152c2f808f4aea8ffNathan Stoddard        @param username: Log in using this username instead of the default.
4198a0f7d6e5599a2b76310443a0b069189dfebda4Resetswitch        @param password: Log in using this password instead of the default.
4298a0f7d6e5599a2b76310443a0b069189dfebda4Resetswitch        @param server_port: Port number for the chromedriver server. If None,
4398a0f7d6e5599a2b76310443a0b069189dfebda4Resetswitch                            an available port is chosen at random.
44c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        """
45c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        assert os.geteuid() == 0, 'Need superuser privileges'
46c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi
47c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        # Log in with telemetry
48bff9f9d0c4bf77f09a41262061d1a9310d94db2fbeeps        self._chrome = chrome.Chrome(extension_paths=extension_paths,
49bff9f9d0c4bf77f09a41262061d1a9310d94db2fbeeps                                     is_component=is_component,
50ab583045d1e3e21da15eeb5152c2f808f4aea8ffNathan Stoddard                                     username=username,
51ab583045d1e3e21da15eeb5152c2f808f4aea8ffNathan Stoddard                                     password=password,
52bff9f9d0c4bf77f09a41262061d1a9310d94db2fbeeps                                     extra_browser_args=extra_chrome_flags)
53bff9f9d0c4bf77f09a41262061d1a9310d94db2fbeeps        self._browser = self._chrome.browser
542bff39b1c4212ab37787e529bd934614606dd948Dan Shi        # Close all tabs owned and opened by Telemetry, as these cannot be
552bff39b1c4212ab37787e529bd934614606dd948Dan Shi        # transferred to ChromeDriver.
562bff39b1c4212ab37787e529bd934614606dd948Dan Shi        self._browser.tabs[0].Close()
57c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi
58c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        # Start ChromeDriver server
5998a0f7d6e5599a2b76310443a0b069189dfebda4Resetswitch        self._server = chromedriver_server(CHROMEDRIVER_EXE_PATH,
6098a0f7d6e5599a2b76310443a0b069189dfebda4Resetswitch                                           port=server_port)
61c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi
622bff39b1c4212ab37787e529bd934614606dd948Dan Shi        # Open a new tab using Chrome remote debugging. ChromeDriver expects
632bff39b1c4212ab37787e529bd934614606dd948Dan Shi        # a tab opened for remote to work. Tabs opened using Telemetry will be
642bff39b1c4212ab37787e529bd934614606dd948Dan Shi        # owned by Telemetry, and will be inaccessible to ChromeDriver.
652bff39b1c4212ab37787e529bd934614606dd948Dan Shi        urllib2.urlopen('http://localhost:%i/json/new' %
662bff39b1c4212ab37787e529bd934614606dd948Dan Shi                        utils.get_chrome_remote_debugging_port())
672bff39b1c4212ab37787e529bd934614606dd948Dan Shi
68c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        chromeOptions = {'debuggerAddress':
69c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi                         ('localhost:%d' %
70c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi                          utils.get_chrome_remote_debugging_port())}
71c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        capabilities = {'chromeOptions':chromeOptions}
72c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        # Handle to chromedriver, for chrome automation.
7396c77cba384edd7befc85530ab6f809cef429625Dan Shi        try:
7496c77cba384edd7befc85530ab6f809cef429625Dan Shi            self.driver = webdriver.Remote(command_executor=self._server.url,
7596c77cba384edd7befc85530ab6f809cef429625Dan Shi                                           desired_capabilities=capabilities)
7696c77cba384edd7befc85530ab6f809cef429625Dan Shi        except NameError:
7796c77cba384edd7befc85530ab6f809cef429625Dan Shi            logging.error('selenium module failed to be imported.')
7896c77cba384edd7befc85530ab6f809cef429625Dan Shi            raise
79c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi
80c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi
81c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi    def __enter__(self):
82c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        return self
83c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi
84c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi
85c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi    def __exit__(self, *args):
86c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        """Clean up after running the test.
87c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi
88c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        """
89c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        if hasattr(self, 'driver') and self.driver:
90c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi            self.driver.close()
91c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi            del self.driver
92c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi
93c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        if hasattr(self, '_server') and self._server:
94c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi            self._server.close()
95c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi            del self._server
96c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi
97c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        if hasattr(self, '_browser') and self._browser:
98c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi            self._browser.Close()
99c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi            del self._browser
100c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi
101c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi
102bff9f9d0c4bf77f09a41262061d1a9310d94db2fbeeps    def get_extension(self, extension_path):
103bff9f9d0c4bf77f09a41262061d1a9310d94db2fbeeps        """Gets an extension by proxying to the browser.
104bff9f9d0c4bf77f09a41262061d1a9310d94db2fbeeps
105bff9f9d0c4bf77f09a41262061d1a9310d94db2fbeeps        @param extension_path: Path to the extension loaded in the browser.
106bff9f9d0c4bf77f09a41262061d1a9310d94db2fbeeps
107bff9f9d0c4bf77f09a41262061d1a9310d94db2fbeeps        @return: A telemetry extension object representing the extension.
108bff9f9d0c4bf77f09a41262061d1a9310d94db2fbeeps        """
109bff9f9d0c4bf77f09a41262061d1a9310d94db2fbeeps        return self._chrome.get_extension(extension_path)
110bff9f9d0c4bf77f09a41262061d1a9310d94db2fbeeps
111bff9f9d0c4bf77f09a41262061d1a9310d94db2fbeeps
112c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shiclass chromedriver_server(object):
113c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi    """A running ChromeDriver server.
114c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi
115c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi    This code is migrated from chrome:
116c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi    src/chrome/test/chromedriver/server/server.py
117c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi    """
118c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi
11998a0f7d6e5599a2b76310443a0b069189dfebda4Resetswitch    def __init__(self, exe_path, port=None):
120c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        """Starts the ChromeDriver server and waits for it to be ready.
121c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi
122c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        Args:
123c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi            exe_path: path to the ChromeDriver executable
12498a0f7d6e5599a2b76310443a0b069189dfebda4Resetswitch            port: server port. If None, an available port is chosen at random.
125c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        Raises:
126c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi            RuntimeError if ChromeDriver fails to start
127c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        """
128c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        if not os.path.exists(exe_path):
129c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi            raise RuntimeError('ChromeDriver exe not found at: ' + exe_path)
130c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi
13198a0f7d6e5599a2b76310443a0b069189dfebda4Resetswitch        if not port:
13298a0f7d6e5599a2b76310443a0b069189dfebda4Resetswitch            port = utils.get_unused_port()
133c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        chromedriver_args = [exe_path, '--port=%d' % port]
13447a5129545db2ad42740968b8c20e93cb04bf5aebeeps
13547a5129545db2ad42740968b8c20e93cb04bf5aebeeps        # Chromedriver will look for an X server running on the display
13647a5129545db2ad42740968b8c20e93cb04bf5aebeeps        # specified through the DISPLAY environment variable.
137a5ab166f36554278718e4b0c1412c49276ce1e25Ilja H. Friedel        utils.assert_has_X_server()
13847a5129545db2ad42740968b8c20e93cb04bf5aebeeps        os.environ['DISPLAY'] = X_SERVER_DISPLAY
13947a5129545db2ad42740968b8c20e93cb04bf5aebeeps        os.environ['XAUTHORITY'] = X_AUTHORITY
14047a5129545db2ad42740968b8c20e93cb04bf5aebeeps
141c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        self.bg_job = utils.BgJob(chromedriver_args, stderr_level=logging.DEBUG)
142c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        self.url = 'http://localhost:%d' % port
143c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        if self.bg_job is None:
144c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi            raise RuntimeError('ChromeDriver server cannot be started')
145c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi
146c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        try:
147c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi            timeout_msg = 'Timeout on waiting for ChromeDriver to start.'
148c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi            utils.poll_for_condition(self.is_running,
149c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi                                     exception=utils.TimeoutError(timeout_msg),
150c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi                                     timeout=10,
151c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi                                     sleep_interval=.1)
152c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        except utils.TimeoutError:
153c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi            self.close_bgjob()
154c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi            raise RuntimeError('ChromeDriver server did not start')
155c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi
156c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        logging.debug('Chrome Driver server is up and listening at port %d.',
157c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi                      port)
158c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        atexit.register(self.close)
159c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi
160c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi
161c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi    def is_running(self):
162c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        """Returns whether the server is up and running."""
163c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        try:
164c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi            urllib2.urlopen(self.url + '/status')
165c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi            return True
166c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        except urllib2.URLError as e:
167c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi            return False
168c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi
169c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi
170c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi    def close_bgjob(self):
171c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        """Close background job and log stdout and stderr."""
172c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        utils.nuke_subprocess(self.bg_job.sp)
173c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        utils.join_bg_jobs([self.bg_job], timeout=1)
174c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        result = self.bg_job.result
175c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        if result.stdout or result.stderr:
176c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi            logging.info('stdout of Chrome Driver:\n%s', result.stdout)
177c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi            logging.error('stderr of Chrome Driver:\n%s', result.stderr)
178c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi
179c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi
180c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi    def close(self):
181c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        """Kills the ChromeDriver server, if it is running."""
182c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        if self.bg_job is None:
183c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi            return
184c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi
185c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        try:
186c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi            urllib2.urlopen(self.url + '/shutdown', timeout=10).close()
187c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        except:
188c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi            pass
189c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi
190c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        self.close_bgjob()
191