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
90b2becccaa6a25b59fa686f6ab72817e9bb4fac3Payton Turnageimport urlparse
10c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi
1196c77cba384edd7befc85530ab6f809cef429625Dan Shitry:
1296c77cba384edd7befc85530ab6f809cef429625Dan Shi    from selenium import webdriver
1396c77cba384edd7befc85530ab6f809cef429625Dan Shiexcept ImportError:
1496c77cba384edd7befc85530ab6f809cef429625Dan Shi    # Ignore import error, as this can happen when builder tries to call the
1596c77cba384edd7befc85530ab6f809cef429625Dan Shi    # setup method of test that imports chromedriver.
1696c77cba384edd7befc85530ab6f809cef429625Dan Shi    logging.error('selenium module failed to be imported.')
1796c77cba384edd7befc85530ab6f809cef429625Dan Shi    pass
18c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi
19c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shifrom autotest_lib.client.bin import utils
20c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shifrom autotest_lib.client.common_lib.cros import chrome
21c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi
22c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan ShiCHROMEDRIVER_EXE_PATH = '/usr/local/chromedriver/chromedriver'
2347a5129545db2ad42740968b8c20e93cb04bf5aebeepsX_SERVER_DISPLAY = ':0'
2447a5129545db2ad42740968b8c20e93cb04bf5aebeepsX_AUTHORITY = '/home/chronos/.Xauthority'
2547a5129545db2ad42740968b8c20e93cb04bf5aebeeps
26c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi
27c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shiclass chromedriver(object):
28c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi    """Wrapper class, a context manager type, for tests to use Chrome Driver."""
29c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi
30c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi    def __init__(self, extra_chrome_flags=[], subtract_extra_chrome_flags=[],
318949a8db9cd4d16f897d88241dfc0d658cf93d44Achuith Bhandarkar                 extension_paths=[], username=None, password=None,
328949a8db9cd4d16f897d88241dfc0d658cf93d44Achuith Bhandarkar                 server_port=None, skip_cleanup=False, url_base=None,
33e7756f78cfeffb677a68a5441d4b4e3854e5f7f5Bogineni Kasaiah                 extra_chromedriver_args=None, gaia_login=False,
3440520921e6ee98ba5c4ab76facb55d2c05d88967Jon Mann                 disable_default_apps=True, dont_override_profile=False, *args,
3540520921e6ee98ba5c4ab76facb55d2c05d88967Jon Mann                 **kwargs):
36c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        """Initialize.
37c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi
38c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        @param extra_chrome_flags: Extra chrome flags to pass to chrome, if any.
39c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        @param subtract_extra_chrome_flags: Remove default flags passed to
40c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi                chrome by chromedriver, if any.
4147a5129545db2ad42740968b8c20e93cb04bf5aebeeps        @param extension_paths: A list of paths to unzipped extensions. Note
4247a5129545db2ad42740968b8c20e93cb04bf5aebeeps                                that paths to crx files won't work.
43ab583045d1e3e21da15eeb5152c2f808f4aea8ffNathan Stoddard        @param username: Log in using this username instead of the default.
4498a0f7d6e5599a2b76310443a0b069189dfebda4Resetswitch        @param password: Log in using this password instead of the default.
4598a0f7d6e5599a2b76310443a0b069189dfebda4Resetswitch        @param server_port: Port number for the chromedriver server. If None,
4698a0f7d6e5599a2b76310443a0b069189dfebda4Resetswitch                            an available port is chosen at random.
47d1df3d5d21a9abff7028677ec2626c572f7a627eResetswitch        @param skip_cleanup: If True, leave the server and browser running
48d1df3d5d21a9abff7028677ec2626c572f7a627eResetswitch                             so that remote tests can run after this script
49d1df3d5d21a9abff7028677ec2626c572f7a627eResetswitch                             ends. Default is False.
500b2becccaa6a25b59fa686f6ab72817e9bb4fac3Payton Turnage        @param url_base: Optional base url for chromedriver.
510b2becccaa6a25b59fa686f6ab72817e9bb4fac3Payton Turnage        @param extra_chromedriver_args: List of extra arguments to forward to
520b2becccaa6a25b59fa686f6ab72817e9bb4fac3Payton Turnage                                        the chromedriver binary, if any.
539fbc9647e8c45833dac629889e5c72876f66e83cJon Mann        @param gaia_login: Logs in to real gaia.
54e7756f78cfeffb677a68a5441d4b4e3854e5f7f5Bogineni Kasaiah        @param disable_default_apps: For tests that exercise default apps.
5540520921e6ee98ba5c4ab76facb55d2c05d88967Jon Mann        @param dont_override_profile: Don't delete cryptohome before login.
5640520921e6ee98ba5c4ab76facb55d2c05d88967Jon Mann                                      Telemetry will output a warning with this
5740520921e6ee98ba5c4ab76facb55d2c05d88967Jon Mann                                      option.
58c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        """
59d1df3d5d21a9abff7028677ec2626c572f7a627eResetswitch        self._cleanup = not skip_cleanup
60c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        assert os.geteuid() == 0, 'Need superuser privileges'
61c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi
62c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        # Log in with telemetry
63bff9f9d0c4bf77f09a41262061d1a9310d94db2fbeeps        self._chrome = chrome.Chrome(extension_paths=extension_paths,
64ab583045d1e3e21da15eeb5152c2f808f4aea8ffNathan Stoddard                                     username=username,
65ab583045d1e3e21da15eeb5152c2f808f4aea8ffNathan Stoddard                                     password=password,
669fbc9647e8c45833dac629889e5c72876f66e83cJon Mann                                     extra_browser_args=extra_chrome_flags,
67e7756f78cfeffb677a68a5441d4b4e3854e5f7f5Bogineni Kasaiah                                     gaia_login=gaia_login,
6840520921e6ee98ba5c4ab76facb55d2c05d88967Jon Mann                                     disable_default_apps=disable_default_apps,
6940520921e6ee98ba5c4ab76facb55d2c05d88967Jon Mann                                     dont_override_profile=dont_override_profile
7040520921e6ee98ba5c4ab76facb55d2c05d88967Jon Mann                                     )
71bff9f9d0c4bf77f09a41262061d1a9310d94db2fbeeps        self._browser = self._chrome.browser
722bff39b1c4212ab37787e529bd934614606dd948Dan Shi        # Close all tabs owned and opened by Telemetry, as these cannot be
732bff39b1c4212ab37787e529bd934614606dd948Dan Shi        # transferred to ChromeDriver.
742bff39b1c4212ab37787e529bd934614606dd948Dan Shi        self._browser.tabs[0].Close()
75c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi
76c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        # Start ChromeDriver server
7798a0f7d6e5599a2b76310443a0b069189dfebda4Resetswitch        self._server = chromedriver_server(CHROMEDRIVER_EXE_PATH,
78d1df3d5d21a9abff7028677ec2626c572f7a627eResetswitch                                           port=server_port,
790b2becccaa6a25b59fa686f6ab72817e9bb4fac3Payton Turnage                                           skip_cleanup=skip_cleanup,
800b2becccaa6a25b59fa686f6ab72817e9bb4fac3Payton Turnage                                           url_base=url_base,
810b2becccaa6a25b59fa686f6ab72817e9bb4fac3Payton Turnage                                           extra_args=extra_chromedriver_args)
82c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi
832bff39b1c4212ab37787e529bd934614606dd948Dan Shi        # Open a new tab using Chrome remote debugging. ChromeDriver expects
842bff39b1c4212ab37787e529bd934614606dd948Dan Shi        # a tab opened for remote to work. Tabs opened using Telemetry will be
852bff39b1c4212ab37787e529bd934614606dd948Dan Shi        # owned by Telemetry, and will be inaccessible to ChromeDriver.
862bff39b1c4212ab37787e529bd934614606dd948Dan Shi        urllib2.urlopen('http://localhost:%i/json/new' %
872bff39b1c4212ab37787e529bd934614606dd948Dan Shi                        utils.get_chrome_remote_debugging_port())
882bff39b1c4212ab37787e529bd934614606dd948Dan Shi
89c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        chromeOptions = {'debuggerAddress':
90c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi                         ('localhost:%d' %
91c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi                          utils.get_chrome_remote_debugging_port())}
92c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        capabilities = {'chromeOptions':chromeOptions}
93c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        # Handle to chromedriver, for chrome automation.
9496c77cba384edd7befc85530ab6f809cef429625Dan Shi        try:
9596c77cba384edd7befc85530ab6f809cef429625Dan Shi            self.driver = webdriver.Remote(command_executor=self._server.url,
9696c77cba384edd7befc85530ab6f809cef429625Dan Shi                                           desired_capabilities=capabilities)
9796c77cba384edd7befc85530ab6f809cef429625Dan Shi        except NameError:
9896c77cba384edd7befc85530ab6f809cef429625Dan Shi            logging.error('selenium module failed to be imported.')
9996c77cba384edd7befc85530ab6f809cef429625Dan Shi            raise
100c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi
101c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi
102c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi    def __enter__(self):
103c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        return self
104c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi
105c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi
106c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi    def __exit__(self, *args):
107c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        """Clean up after running the test.
108c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi
109c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        """
110c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        if hasattr(self, 'driver') and self.driver:
111c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi            self.driver.close()
112c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi            del self.driver
113c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi
114d1df3d5d21a9abff7028677ec2626c572f7a627eResetswitch        if not hasattr(self, '_cleanup') or self._cleanup:
115d1df3d5d21a9abff7028677ec2626c572f7a627eResetswitch            if hasattr(self, '_server') and self._server:
116d1df3d5d21a9abff7028677ec2626c572f7a627eResetswitch                self._server.close()
117d1df3d5d21a9abff7028677ec2626c572f7a627eResetswitch                del self._server
118c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi
119d1df3d5d21a9abff7028677ec2626c572f7a627eResetswitch            if hasattr(self, '_browser') and self._browser:
120d1df3d5d21a9abff7028677ec2626c572f7a627eResetswitch                self._browser.Close()
121d1df3d5d21a9abff7028677ec2626c572f7a627eResetswitch                del self._browser
122c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi
123bff9f9d0c4bf77f09a41262061d1a9310d94db2fbeeps    def get_extension(self, extension_path):
124bff9f9d0c4bf77f09a41262061d1a9310d94db2fbeeps        """Gets an extension by proxying to the browser.
125bff9f9d0c4bf77f09a41262061d1a9310d94db2fbeeps
126bff9f9d0c4bf77f09a41262061d1a9310d94db2fbeeps        @param extension_path: Path to the extension loaded in the browser.
127bff9f9d0c4bf77f09a41262061d1a9310d94db2fbeeps
128bff9f9d0c4bf77f09a41262061d1a9310d94db2fbeeps        @return: A telemetry extension object representing the extension.
129bff9f9d0c4bf77f09a41262061d1a9310d94db2fbeeps        """
130bff9f9d0c4bf77f09a41262061d1a9310d94db2fbeeps        return self._chrome.get_extension(extension_path)
131bff9f9d0c4bf77f09a41262061d1a9310d94db2fbeeps
132bff9f9d0c4bf77f09a41262061d1a9310d94db2fbeeps
133a074a5192ab9722d6c3b5de47ba814ed3f85d931Mussa    @property
134a074a5192ab9722d6c3b5de47ba814ed3f85d931Mussa    def chrome_instance(self):
135a074a5192ab9722d6c3b5de47ba814ed3f85d931Mussa        """ The chrome instance used by this chrome driver instance. """
136a074a5192ab9722d6c3b5de47ba814ed3f85d931Mussa        return self._chrome
137a074a5192ab9722d6c3b5de47ba814ed3f85d931Mussa
138a074a5192ab9722d6c3b5de47ba814ed3f85d931Mussa
139c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shiclass chromedriver_server(object):
140c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi    """A running ChromeDriver server.
141c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi
142c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi    This code is migrated from chrome:
143c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi    src/chrome/test/chromedriver/server/server.py
144c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi    """
145c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi
1460b2becccaa6a25b59fa686f6ab72817e9bb4fac3Payton Turnage    def __init__(self, exe_path, port=None, skip_cleanup=False,
1470b2becccaa6a25b59fa686f6ab72817e9bb4fac3Payton Turnage                 url_base=None, extra_args=None):
148c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        """Starts the ChromeDriver server and waits for it to be ready.
149c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi
150c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        Args:
151c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi            exe_path: path to the ChromeDriver executable
15298a0f7d6e5599a2b76310443a0b069189dfebda4Resetswitch            port: server port. If None, an available port is chosen at random.
153d1df3d5d21a9abff7028677ec2626c572f7a627eResetswitch            skip_cleanup: If True, leave the server running so that remote
154d1df3d5d21a9abff7028677ec2626c572f7a627eResetswitch                          tests can run after this script ends. Default is
155d1df3d5d21a9abff7028677ec2626c572f7a627eResetswitch                          False.
1560b2becccaa6a25b59fa686f6ab72817e9bb4fac3Payton Turnage            url_base: Optional base url for chromedriver.
1570b2becccaa6a25b59fa686f6ab72817e9bb4fac3Payton Turnage            extra_args: List of extra arguments to forward to the chromedriver
1580b2becccaa6a25b59fa686f6ab72817e9bb4fac3Payton Turnage                        binary, if any.
159c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        Raises:
160c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi            RuntimeError if ChromeDriver fails to start
161c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        """
162c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        if not os.path.exists(exe_path):
163c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi            raise RuntimeError('ChromeDriver exe not found at: ' + exe_path)
164c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi
165d1df3d5d21a9abff7028677ec2626c572f7a627eResetswitch        chromedriver_args = [exe_path]
166d1df3d5d21a9abff7028677ec2626c572f7a627eResetswitch        if port:
167d1df3d5d21a9abff7028677ec2626c572f7a627eResetswitch            # Allow remote connections if a port was specified
168d1df3d5d21a9abff7028677ec2626c572f7a627eResetswitch            chromedriver_args.append('--whitelisted-ips')
169d1df3d5d21a9abff7028677ec2626c572f7a627eResetswitch        else:
17098a0f7d6e5599a2b76310443a0b069189dfebda4Resetswitch            port = utils.get_unused_port()
171d1df3d5d21a9abff7028677ec2626c572f7a627eResetswitch        chromedriver_args.append('--port=%d' % port)
17247a5129545db2ad42740968b8c20e93cb04bf5aebeeps
1730b2becccaa6a25b59fa686f6ab72817e9bb4fac3Payton Turnage        self.url = 'http://localhost:%d' % port
1740b2becccaa6a25b59fa686f6ab72817e9bb4fac3Payton Turnage        if url_base:
1750b2becccaa6a25b59fa686f6ab72817e9bb4fac3Payton Turnage            chromedriver_args.append('--url-base=%s' % url_base)
1760b2becccaa6a25b59fa686f6ab72817e9bb4fac3Payton Turnage            self.url = urlparse.urljoin(self.url, url_base)
1770b2becccaa6a25b59fa686f6ab72817e9bb4fac3Payton Turnage
1780b2becccaa6a25b59fa686f6ab72817e9bb4fac3Payton Turnage        if extra_args:
1790b2becccaa6a25b59fa686f6ab72817e9bb4fac3Payton Turnage            chromedriver_args.extend(extra_args)
1800b2becccaa6a25b59fa686f6ab72817e9bb4fac3Payton Turnage
181a624576542ae9b524100eac4584d36f20aba4d7aIlja H. Friedel        # TODO(ihf): Remove references to X after M45.
18247a5129545db2ad42740968b8c20e93cb04bf5aebeeps        # Chromedriver will look for an X server running on the display
18347a5129545db2ad42740968b8c20e93cb04bf5aebeeps        # specified through the DISPLAY environment variable.
18447a5129545db2ad42740968b8c20e93cb04bf5aebeeps        os.environ['DISPLAY'] = X_SERVER_DISPLAY
18547a5129545db2ad42740968b8c20e93cb04bf5aebeeps        os.environ['XAUTHORITY'] = X_AUTHORITY
18647a5129545db2ad42740968b8c20e93cb04bf5aebeeps
187c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        self.bg_job = utils.BgJob(chromedriver_args, stderr_level=logging.DEBUG)
188c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        if self.bg_job is None:
189c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi            raise RuntimeError('ChromeDriver server cannot be started')
190c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi
191c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        try:
192c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi            timeout_msg = 'Timeout on waiting for ChromeDriver to start.'
193c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi            utils.poll_for_condition(self.is_running,
194c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi                                     exception=utils.TimeoutError(timeout_msg),
195c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi                                     timeout=10,
196c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi                                     sleep_interval=.1)
197c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        except utils.TimeoutError:
198c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi            self.close_bgjob()
199c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi            raise RuntimeError('ChromeDriver server did not start')
200c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi
201c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        logging.debug('Chrome Driver server is up and listening at port %d.',
202c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi                      port)
203d1df3d5d21a9abff7028677ec2626c572f7a627eResetswitch        if not skip_cleanup:
204d1df3d5d21a9abff7028677ec2626c572f7a627eResetswitch            atexit.register(self.close)
205c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi
206c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi
207c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi    def is_running(self):
208c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        """Returns whether the server is up and running."""
209c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        try:
210c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi            urllib2.urlopen(self.url + '/status')
211c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi            return True
212c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        except urllib2.URLError as e:
213c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi            return False
214c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi
215c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi
216c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi    def close_bgjob(self):
217c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        """Close background job and log stdout and stderr."""
218c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        utils.nuke_subprocess(self.bg_job.sp)
219c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        utils.join_bg_jobs([self.bg_job], timeout=1)
220c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        result = self.bg_job.result
221c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        if result.stdout or result.stderr:
222c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi            logging.info('stdout of Chrome Driver:\n%s', result.stdout)
223c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi            logging.error('stderr of Chrome Driver:\n%s', result.stderr)
224c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi
225c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi
226c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi    def close(self):
227c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        """Kills the ChromeDriver server, if it is running."""
228c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        if self.bg_job is None:
229c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi            return
230c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi
231c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        try:
232c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi            urllib2.urlopen(self.url + '/shutdown', timeout=10).close()
233c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        except:
234c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi            pass
235c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi
236c1d263b436976b7516f77e84386ec0fbe2b3cfbeDan Shi        self.close_bgjob()
237