1*9c5db199SXin Li# Lint as: python2, python3 2*9c5db199SXin Li# Copyright (c) 2013 The Chromium OS Authors. All rights reserved. 3*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be 4*9c5db199SXin Li# found in the LICENSE file. 5*9c5db199SXin Li 6*9c5db199SXin Liimport atexit 7*9c5db199SXin Liimport logging 8*9c5db199SXin Liimport os 9*9c5db199SXin Lifrom six.moves import urllib 10*9c5db199SXin Liimport six.moves.urllib.parse 11*9c5db199SXin Li 12*9c5db199SXin Litry: 13*9c5db199SXin Li from selenium import webdriver 14*9c5db199SXin Liexcept ImportError: 15*9c5db199SXin Li # Ignore import error, as this can happen when builder tries to call the 16*9c5db199SXin Li # setup method of test that imports chromedriver. 17*9c5db199SXin Li logging.error('selenium module failed to be imported.') 18*9c5db199SXin Li pass 19*9c5db199SXin Li 20*9c5db199SXin Lifrom autotest_lib.client.bin import utils 21*9c5db199SXin Lifrom autotest_lib.client.common_lib.cros import chrome 22*9c5db199SXin Li 23*9c5db199SXin LiCHROMEDRIVER_EXE_PATH = '/usr/local/chromedriver/chromedriver' 24*9c5db199SXin LiX_SERVER_DISPLAY = ':0' 25*9c5db199SXin LiX_AUTHORITY = '/home/chronos/.Xauthority' 26*9c5db199SXin Li 27*9c5db199SXin Li 28*9c5db199SXin Liclass chromedriver(object): 29*9c5db199SXin Li """Wrapper class, a context manager type, for tests to use Chrome Driver.""" 30*9c5db199SXin Li 31*9c5db199SXin Li def __init__(self, 32*9c5db199SXin Li extra_chrome_flags=[], 33*9c5db199SXin Li subtract_extra_chrome_flags=[], 34*9c5db199SXin Li extension_paths=[], 35*9c5db199SXin Li username=None, 36*9c5db199SXin Li password=None, 37*9c5db199SXin Li server_port=None, 38*9c5db199SXin Li skip_cleanup=False, 39*9c5db199SXin Li url_base=None, 40*9c5db199SXin Li extra_chromedriver_args=None, 41*9c5db199SXin Li gaia_login=False, 42*9c5db199SXin Li disable_default_apps=True, 43*9c5db199SXin Li dont_override_profile=False, 44*9c5db199SXin Li chromeOptions={}, 45*9c5db199SXin Li *args, 46*9c5db199SXin Li **kwargs): 47*9c5db199SXin Li """Initialize. 48*9c5db199SXin Li 49*9c5db199SXin Li @param extra_chrome_flags: Extra chrome flags to pass to chrome, if any. 50*9c5db199SXin Li @param subtract_extra_chrome_flags: Remove default flags passed to 51*9c5db199SXin Li chrome by chromedriver, if any. 52*9c5db199SXin Li @param extension_paths: A list of paths to unzipped extensions. Note 53*9c5db199SXin Li that paths to crx files won't work. 54*9c5db199SXin Li @param username: Log in using this username instead of the default. 55*9c5db199SXin Li @param password: Log in using this password instead of the default. 56*9c5db199SXin Li @param server_port: Port number for the chromedriver server. If None, 57*9c5db199SXin Li an available port is chosen at random. 58*9c5db199SXin Li @param skip_cleanup: If True, leave the server and browser running 59*9c5db199SXin Li so that remote tests can run after this script 60*9c5db199SXin Li ends. Default is False. 61*9c5db199SXin Li @param url_base: Optional base url for chromedriver. 62*9c5db199SXin Li @param extra_chromedriver_args: List of extra arguments to forward to 63*9c5db199SXin Li the chromedriver binary, if any. 64*9c5db199SXin Li @param gaia_login: Logs in to real gaia. 65*9c5db199SXin Li @param disable_default_apps: For tests that exercise default apps. 66*9c5db199SXin Li @param dont_override_profile: Don't delete cryptohome before login. 67*9c5db199SXin Li Telemetry will output a warning with this 68*9c5db199SXin Li option. 69*9c5db199SXin Li """ 70*9c5db199SXin Li if not isinstance(chromeOptions, dict): 71*9c5db199SXin Li raise TypeError("chromeOptions must be of type dict.") 72*9c5db199SXin Li self._cleanup = not skip_cleanup 73*9c5db199SXin Li assert os.geteuid() == 0, 'Need superuser privileges' 74*9c5db199SXin Li 75*9c5db199SXin Li # When ChromeDriver starts Chrome on other platforms (Linux, Windows, 76*9c5db199SXin Li # etc.), it accepts flag inputs of the form "--flag_name" or 77*9c5db199SXin Li # "flag_name". Before starting Chrome with those flags, ChromeDriver 78*9c5db199SXin Li # reformats them all to "--flag_name". This behavior is copied 79*9c5db199SXin Li # to ChromeOS for consistency across platforms. 80*9c5db199SXin Li fixed_extra_chrome_flags = [ 81*9c5db199SXin Li f if f.startswith('--') else '--%s' % f for f in extra_chrome_flags] 82*9c5db199SXin Li 83*9c5db199SXin Li # Log in with telemetry 84*9c5db199SXin Li self._chrome = chrome.Chrome(extension_paths=extension_paths, 85*9c5db199SXin Li username=username, 86*9c5db199SXin Li password=password, 87*9c5db199SXin Li extra_browser_args=fixed_extra_chrome_flags, 88*9c5db199SXin Li gaia_login=gaia_login, 89*9c5db199SXin Li disable_default_apps=disable_default_apps, 90*9c5db199SXin Li dont_override_profile=dont_override_profile 91*9c5db199SXin Li ) 92*9c5db199SXin Li self._browser = self._chrome.browser 93*9c5db199SXin Li # Close all tabs owned and opened by Telemetry, as these cannot be 94*9c5db199SXin Li # transferred to ChromeDriver. 95*9c5db199SXin Li self._browser.tabs[0].Close() 96*9c5db199SXin Li 97*9c5db199SXin Li # Start ChromeDriver server 98*9c5db199SXin Li self._server = chromedriver_server(CHROMEDRIVER_EXE_PATH, 99*9c5db199SXin Li port=server_port, 100*9c5db199SXin Li skip_cleanup=skip_cleanup, 101*9c5db199SXin Li url_base=url_base, 102*9c5db199SXin Li extra_args=extra_chromedriver_args) 103*9c5db199SXin Li 104*9c5db199SXin Li # Open a new tab using Chrome remote debugging. ChromeDriver expects 105*9c5db199SXin Li # a tab opened for remote to work. Tabs opened using Telemetry will be 106*9c5db199SXin Li # owned by Telemetry, and will be inaccessible to ChromeDriver. 107*9c5db199SXin Li urllib.request.urlopen('http://localhost:%i/json/new' % 108*9c5db199SXin Li utils.get_chrome_remote_debugging_port()) 109*9c5db199SXin Li 110*9c5db199SXin Li chromeBaseOptions = { 111*9c5db199SXin Li 'debuggerAddress': 112*9c5db199SXin Li ('localhost:%d' % utils.get_chrome_remote_debugging_port()) 113*9c5db199SXin Li } 114*9c5db199SXin Li chromeOptions.update(chromeBaseOptions) 115*9c5db199SXin Li capabilities = {'chromeOptions':chromeOptions} 116*9c5db199SXin Li # Handle to chromedriver, for chrome automation. 117*9c5db199SXin Li try: 118*9c5db199SXin Li self.driver = webdriver.Remote(command_executor=self._server.url, 119*9c5db199SXin Li desired_capabilities=capabilities) 120*9c5db199SXin Li except NameError: 121*9c5db199SXin Li logging.error('selenium module failed to be imported.') 122*9c5db199SXin Li raise 123*9c5db199SXin Li 124*9c5db199SXin Li 125*9c5db199SXin Li def __enter__(self): 126*9c5db199SXin Li return self 127*9c5db199SXin Li 128*9c5db199SXin Li 129*9c5db199SXin Li def __exit__(self, *args): 130*9c5db199SXin Li """Clean up after running the test. 131*9c5db199SXin Li 132*9c5db199SXin Li """ 133*9c5db199SXin Li if hasattr(self, 'driver') and self.driver: 134*9c5db199SXin Li self.driver.close() 135*9c5db199SXin Li del self.driver 136*9c5db199SXin Li 137*9c5db199SXin Li if not hasattr(self, '_cleanup') or self._cleanup: 138*9c5db199SXin Li if hasattr(self, '_server') and self._server: 139*9c5db199SXin Li self._server.close() 140*9c5db199SXin Li del self._server 141*9c5db199SXin Li 142*9c5db199SXin Li if hasattr(self, '_browser') and self._browser: 143*9c5db199SXin Li self._browser.Close() 144*9c5db199SXin Li del self._browser 145*9c5db199SXin Li 146*9c5db199SXin Li def get_extension(self, extension_path): 147*9c5db199SXin Li """Gets an extension by proxying to the browser. 148*9c5db199SXin Li 149*9c5db199SXin Li @param extension_path: Path to the extension loaded in the browser. 150*9c5db199SXin Li 151*9c5db199SXin Li @return: A telemetry extension object representing the extension. 152*9c5db199SXin Li """ 153*9c5db199SXin Li return self._chrome.get_extension(extension_path) 154*9c5db199SXin Li 155*9c5db199SXin Li 156*9c5db199SXin Li @property 157*9c5db199SXin Li def chrome_instance(self): 158*9c5db199SXin Li """ The chrome instance used by this chrome driver instance. """ 159*9c5db199SXin Li return self._chrome 160*9c5db199SXin Li 161*9c5db199SXin Li 162*9c5db199SXin Liclass chromedriver_server(object): 163*9c5db199SXin Li """A running ChromeDriver server. 164*9c5db199SXin Li 165*9c5db199SXin Li This code is migrated from chrome: 166*9c5db199SXin Li src/chrome/test/chromedriver/server/server.py 167*9c5db199SXin Li """ 168*9c5db199SXin Li 169*9c5db199SXin Li def __init__(self, exe_path, port=None, skip_cleanup=False, 170*9c5db199SXin Li url_base=None, extra_args=None): 171*9c5db199SXin Li """Starts the ChromeDriver server and waits for it to be ready. 172*9c5db199SXin Li 173*9c5db199SXin Li Args: 174*9c5db199SXin Li exe_path: path to the ChromeDriver executable 175*9c5db199SXin Li port: server port. If None, an available port is chosen at random. 176*9c5db199SXin Li skip_cleanup: If True, leave the server running so that remote 177*9c5db199SXin Li tests can run after this script ends. Default is 178*9c5db199SXin Li False. 179*9c5db199SXin Li url_base: Optional base url for chromedriver. 180*9c5db199SXin Li extra_args: List of extra arguments to forward to the chromedriver 181*9c5db199SXin Li binary, if any. 182*9c5db199SXin Li Raises: 183*9c5db199SXin Li RuntimeError if ChromeDriver fails to start 184*9c5db199SXin Li """ 185*9c5db199SXin Li if not os.path.exists(exe_path): 186*9c5db199SXin Li raise RuntimeError('ChromeDriver exe not found at: ' + exe_path) 187*9c5db199SXin Li 188*9c5db199SXin Li chromedriver_args = [exe_path] 189*9c5db199SXin Li if port: 190*9c5db199SXin Li # Allow remote connections if a port was specified 191*9c5db199SXin Li chromedriver_args.append('--whitelisted-ips') 192*9c5db199SXin Li else: 193*9c5db199SXin Li port = utils.get_unused_port() 194*9c5db199SXin Li chromedriver_args.append('--port=%d' % port) 195*9c5db199SXin Li 196*9c5db199SXin Li self.url = 'http://localhost:%d' % port 197*9c5db199SXin Li if url_base: 198*9c5db199SXin Li chromedriver_args.append('--url-base=%s' % url_base) 199*9c5db199SXin Li self.url = six.moves.urllib.parse.urljoin(self.url, url_base) 200*9c5db199SXin Li 201*9c5db199SXin Li if extra_args: 202*9c5db199SXin Li chromedriver_args.extend(extra_args) 203*9c5db199SXin Li 204*9c5db199SXin Li # TODO(ihf): Remove references to X after M45. 205*9c5db199SXin Li # Chromedriver will look for an X server running on the display 206*9c5db199SXin Li # specified through the DISPLAY environment variable. 207*9c5db199SXin Li os.environ['DISPLAY'] = X_SERVER_DISPLAY 208*9c5db199SXin Li os.environ['XAUTHORITY'] = X_AUTHORITY 209*9c5db199SXin Li 210*9c5db199SXin Li self.bg_job = utils.BgJob(chromedriver_args, stderr_level=logging.DEBUG) 211*9c5db199SXin Li if self.bg_job is None: 212*9c5db199SXin Li raise RuntimeError('ChromeDriver server cannot be started') 213*9c5db199SXin Li 214*9c5db199SXin Li try: 215*9c5db199SXin Li timeout_msg = 'Timeout on waiting for ChromeDriver to start.' 216*9c5db199SXin Li utils.poll_for_condition(self.is_running, 217*9c5db199SXin Li exception=utils.TimeoutError(timeout_msg), 218*9c5db199SXin Li timeout=10, 219*9c5db199SXin Li sleep_interval=.1) 220*9c5db199SXin Li except utils.TimeoutError: 221*9c5db199SXin Li self.close_bgjob() 222*9c5db199SXin Li raise RuntimeError('ChromeDriver server did not start') 223*9c5db199SXin Li 224*9c5db199SXin Li logging.debug('Chrome Driver server is up and listening at port %d.', 225*9c5db199SXin Li port) 226*9c5db199SXin Li if not skip_cleanup: 227*9c5db199SXin Li atexit.register(self.close) 228*9c5db199SXin Li 229*9c5db199SXin Li 230*9c5db199SXin Li def is_running(self): 231*9c5db199SXin Li """Returns whether the server is up and running.""" 232*9c5db199SXin Li try: 233*9c5db199SXin Li urllib.request.urlopen(self.url + '/status') 234*9c5db199SXin Li return True 235*9c5db199SXin Li except urllib.error.URLError as e: 236*9c5db199SXin Li return False 237*9c5db199SXin Li 238*9c5db199SXin Li 239*9c5db199SXin Li def close_bgjob(self): 240*9c5db199SXin Li """Close background job and log stdout and stderr.""" 241*9c5db199SXin Li utils.nuke_subprocess(self.bg_job.sp) 242*9c5db199SXin Li utils.join_bg_jobs([self.bg_job], timeout=1) 243*9c5db199SXin Li result = self.bg_job.result 244*9c5db199SXin Li if result.stdout or result.stderr: 245*9c5db199SXin Li logging.info('stdout of Chrome Driver:\n%s', result.stdout) 246*9c5db199SXin Li logging.error('stderr of Chrome Driver:\n%s', result.stderr) 247*9c5db199SXin Li 248*9c5db199SXin Li 249*9c5db199SXin Li def close(self): 250*9c5db199SXin Li """Kills the ChromeDriver server, if it is running.""" 251*9c5db199SXin Li if self.bg_job is None: 252*9c5db199SXin Li return 253*9c5db199SXin Li 254*9c5db199SXin Li try: 255*9c5db199SXin Li urllib.request.urlopen(self.url + '/shutdown', timeout=10).close() 256*9c5db199SXin Li except: 257*9c5db199SXin Li pass 258*9c5db199SXin Li 259*9c5db199SXin Li self.close_bgjob() 260