def _WaitForBrowserToComeUp(self): """ Wait for browser to come up. """ try: timeout = self.browser_options.browser_startup_timeout util.WaitFor(self.HasBrowserFinishedLaunching, timeout=timeout) except (exceptions.TimeoutException, exceptions.ProcessGoneException) as e: if not self.IsBrowserRunning(): raise exceptions.BrowserGoneException(self.browser, e) raise exceptions.BrowserConnectionGoneException(self.browser, e)
def pid(self): pids = self.device.GetPids(self._backend_settings.package) if not pids or self._backend_settings.package not in pids: raise exceptions.BrowserGoneException(self.browser) if len(pids[self._backend_settings.package]) > 1: raise Exception( 'At most one instance of process %s expected but found pids: ' '%s' % (self._backend_settings.package, pids)) return int(pids[self._backend_settings.package][0])
def ValidateAndMeasurePage(self, page, tab, results): # This value should be discarded on the first run when the # browser crashed. results.AddValue( string.StringValue(page, 'test', 't', str(self.has_crashed))) if not self.has_crashed: self.has_crashed = True raise exceptions.BrowserGoneException(tab.browser)
def testRaiseBrowserGoneExceptionFromRunPage(self): self.RunStories([ test_stories.DummyStory( 'foo', run_side_effect=exceptions.BrowserGoneException( None, 'i am a browser crash message')), test_stories.DummyStory('bar')]) test_results = self.ReadTestResults() self.assertEqual(['FAIL', 'PASS'], [test['status'] for test in test_results]) self.assertIn('i am a browser crash message', sys.stderr.getvalue())
def BindDevToolsClient(self): super(AndroidBrowserBackend, self).BindDevToolsClient() package = self.devtools_client.GetVersion().get('Android-Package') if package is None: logging.warning('Could not determine package name from DevTools client.') elif package == self._backend_settings.package: logging.info('Successfully connected to %s DevTools client', package) else: raise exceptions.BrowserGoneException( self.browser, 'Expected connection to %s but got %s.' % ( self._backend_settings.package, package))
def _WaitForBrowserToComeUp(self, wait_for_extensions=True): try: util.WaitFor(self.HasBrowserFinishedLaunching, timeout=30) except (util.TimeoutException, exceptions.ProcessGoneException) as e: if not self.IsBrowserRunning(): raise exceptions.BrowserGoneException(self.browser, e) raise exceptions.BrowserConnectionGoneException(self.browser, e) def AllExtensionsLoaded(): # Extension pages are loaded from an about:blank page, # so we need to check that the document URL is the extension # page in addition to the ready state. extension_ready_js = """ document.URL.lastIndexOf('chrome-extension://%s/', 0) == 0 && (document.readyState == 'complete' || document.readyState == 'interactive') """ for e in self._extensions_to_load: try: extension_objects = self.extension_backend[e.extension_id] except KeyError: return False for extension_object in extension_objects: try: res = extension_object.EvaluateJavaScript( extension_ready_js % e.extension_id) except exceptions.EvaluateException: # If the inspected page is not ready, we will get an error # when we evaluate a JS expression, but we can just keep polling # until the page is ready (crbug.com/251913). res = None # TODO(tengs): We don't have full support for getting the Chrome # version before launch, so for now we use a generic workaround to # check for an extension binding bug in old versions of Chrome. # See crbug.com/263162 for details. if res and extension_object.EvaluateJavaScript( 'chrome.runtime == null'): extension_object.Reload() if not res: return False return True if wait_for_extensions and self._supports_extensions: try: util.WaitFor(AllExtensionsLoaded, timeout=60) except util.TimeoutException: logging.error( 'ExtensionsToLoad: ' + repr([e.extension_id for e in self._extensions_to_load])) logging.error('Extension list: ' + pprint.pformat(self.extension_backend, indent=4)) raise
def _SetPreferencesIfNeeded(self, is_content_shell): # TODO(bulach): Once --enable-remote-debugging flag makes its way to the # oldest version under test (m27 goes to stable), remove this function. if (is_content_shell or not self._adb.Adb().CanAccessProtectedFileContents()): return prefs_file = self._profile_dir + 'Default/Preferences' # Reuse the previous preferences if available, otherwise take the slow path # (launch chrome and wait for it to be created). if AndroidBrowserBackend._default_preferences_file: self._adb.Adb().SetProtectedFileContents( prefs_file, AndroidBrowserBackend._default_preferences_file) return # Make sure we can find the apps' prefs file if not self._adb.FileExistsOnDevice(prefs_file): # Start it up the first time so we can tweak the prefs. self._adb.StartActivity(self._package, self._activity, True, None, None) retries = 0 timeout = 3 time.sleep(timeout) while not self._adb.Adb().GetProtectedFileContents(prefs_file): time.sleep(timeout) retries += 1 timeout *= 2 if retries == 3: logging.critical( 'android_browser_backend: Could not find ' 'preferences file %s for %s', prefs_file, self._package) raise exceptions.BrowserGoneException( 'Missing preferences file.') self._adb.CloseApplication(self._package) preferences = json.loads(''.join( self._adb.Adb().GetProtectedFileContents(prefs_file))) changed = False if 'devtools' not in preferences: preferences['devtools'] = {} changed = True if not preferences['devtools'].get('remote_enabled'): preferences['devtools']['remote_enabled'] = True changed = True AndroidBrowserBackend._default_preferences_file = json.dumps( preferences, indent=2) if changed: logging.warning('Manually enabled devtools protocol on %s' % self._package) self._adb.Adb().SetProtectedFileContents( prefs_file, AndroidBrowserBackend._default_preferences_file)
def _Connect(self): if self._socket: return try: self._socket = websocket.create_connection(self._debugger_url) except (websocket.WebSocketException): if self._browser_backend.IsBrowserRunning(): raise exceptions.TabCrashException(sys.exc_info()[1]) else: raise exceptions.BrowserGoneException() self._cur_socket_timeout = 0 self._next_request_id = 0
def Request(self, path, timeout=None, throw_network_exception=False): url = 'http://localhost:%i/json' % self._port if path: url += '/' + path try: req = urllib2.urlopen(url, timeout=timeout) return req.read() except (socket.error, httplib.BadStatusLine, urllib2.URLError) as e: if throw_network_exception: raise e if not self.IsBrowserRunning(): raise exceptions.BrowserGoneException() raise exceptions.BrowserConnectionGoneException()
def pid(self): try: pid = self.device.GetApplicationPids( self._backend_settings.package, at_most_one=True) except device_errors.CommandFailedError as exc: logging.warning('Dumping output of "ps" command for diagnosis:') for line in self.device.RunShellCommand(['ps']): logging.warning('- %s', line) # This may happen if we end up with two PIDs for the browser process. # Re-raise as an AppCrashException to get further debug information. raise exceptions.AppCrashException( self.browser, 'Error getting browser PID: %s' % exc) if not pid: raise exceptions.BrowserGoneException(self.browser) return int(pid)
def Request(self, path, timeout=30, throw_network_exception=False): url = 'http://127.0.0.1:%i/json' % self._port if path: url += '/' + path try: proxy_handler = urllib2.ProxyHandler({}) # Bypass any system proxy. opener = urllib2.build_opener(proxy_handler) with contextlib.closing(opener.open(url, timeout=timeout)) as req: return req.read() except (socket.error, httplib.BadStatusLine, urllib2.URLError) as e: if throw_network_exception: raise e if not self.IsBrowserRunning(): raise exceptions.BrowserGoneException(self.browser, e) raise exceptions.BrowserConnectionGoneException(self.browser, e)
def _ListTabs(self, timeout=None): def _IsTab(context): if 'type' in context: return context['type'] == 'page' # TODO: For compatibility with Chrome before r177683. # This check is not completely correct, see crbug.com/190592. return not context['url'].startswith('chrome-extension://') try: data = self._browser_backend.Request('', timeout=timeout) all_contexts = json.loads(data) tabs = filter(_IsTab, all_contexts) return tabs except (socket.error, httplib.BadStatusLine, urllib2.URLError): if not self._browser_backend.IsBrowserRunning(): raise exceptions.BrowserGoneException() raise exceptions.BrowserConnectionGoneException()
def _Connect(self, timeout=10): assert not self._socket logging.debug('InspectorBackend._Connect() to %s' % self.debugger_url) try: self._socket = websocket.create_connection(self.debugger_url, timeout=timeout) except (websocket.WebSocketException, util.TimeoutException): err_msg = sys.exc_info()[1] if not self._browser_backend.IsBrowserRunning(): raise exceptions.BrowserGoneException(err_msg) elif not self._browser_backend.HasBrowserFinishedLaunching(): raise exceptions.BrowserConnectionGoneException(err_msg) else: raise exceptions.TabCrashException(err_msg) self._cur_socket_timeout = 0 self._next_request_id = 0
def Foreground(self): package = self._backend_settings.package activity = self._backend_settings.activity self.device.StartActivity( intent.Intent(package=package, activity=activity, action=None, flags=[intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED]), blocking=False) # TODO(crbug.com/601052): The following waits for any UI node for the # package launched to appear on the screen. When the referenced bug is # fixed, remove this workaround and just switch blocking above to True. try: app_ui.AppUi(self.device).WaitForUiNode(package=package) except Exception: raise exceptions.BrowserGoneException(self.browser, 'Timed out waiting for browser to come back foreground.')
def _WaitForBrowserToComeUp(self, remote_devtools_port=None): """ Wait for browser to come up. Args: remote_devtools_port: The remote devtools port, if any. Otherwise assumed to be the same as self._port. """ try: timeout = self.browser_options.browser_startup_timeout py_utils.WaitFor(self.HasBrowserFinishedLaunching, timeout=timeout) except (py_utils.TimeoutException, exceptions.ProcessGoneException) as e: if not self.IsBrowserRunning(): raise exceptions.BrowserGoneException(self.browser, e) raise exceptions.BrowserConnectionGoneException(self.browser, e) self._devtools_client = devtools_client_backend.DevToolsClientBackend( self._port, self._browser_target, remote_devtools_port or self._port, self)
def BindDevToolsClient(self): """Find an existing DevTools agent and bind this browser backend to it.""" if self._devtools_client: # In case we are launching a second browser instance (as is done by # the CrOS backend), ensure that the old devtools_client is closed, # otherwise re-creating it will fail. self._devtools_client.Close() self._devtools_client = None try: self._devtools_client = py_utils.WaitFor( self._GetDevToolsClient, timeout=self.browser_options.browser_startup_timeout) except (py_utils.TimeoutException, exceptions.ProcessGoneException) as e: if not self.IsBrowserRunning(): logging.exception(e) # crbug.com/940075 raise exceptions.BrowserGoneException(self.browser, e) raise exceptions.BrowserConnectionGoneException(self.browser, e)
def GetDevtoolsRemotePort(self): # The DevTools socket name for WebView depends on the activity PID's. retries = 0 timeout = 1 pid = None while True: pids = self.adb.ExtractPid(self.package) if (len(pids) > 0): pid = pids[-1] break time.sleep(timeout) retries += 1 timeout *= 2 if retries == 4: logging.critical('android_browser_backend: Timeout while waiting for ' 'activity %s:%s to come up', self.package, self.activity) raise exceptions.BrowserGoneException('Timeout waiting for PID.') return 'localabstract:webview_devtools_remote_%s' % str(pid)
def GetDevtoolsRemotePort(self, device): # The DevTools socket name for WebView depends on the activity PID's. retries = 0 timeout = 1 pid = None while True: pids = device.GetPids(self.package) if not pids or self.package not in pids: time.sleep(timeout) retries += 1 timeout *= 2 if retries == 4: logging.critical('android_browser_backend: Timeout while waiting for ' 'activity %s:%s to come up', self.package, self.activity) raise exceptions.BrowserGoneException(self.browser, 'Timeout waiting for PID.') pid = pids[self.package] break return 'localabstract:webview_devtools_remote_%s' % str(pid)
def BindDevToolsClient(self): """Find an existing DevTools agent and bind this browser backend to it.""" if self._devtools_client: # In case we are launching a second browser instance (as is done by # the CrOS backend), ensure that the old devtools_client is closed, # otherwise re-creating it will fail. self._devtools_client.Close() self._devtools_client = None try: timeout = self.browser_options.browser_startup_timeout # TODO(crbug.com/787834): Subclasses should WaitFor the config if needed. devtools_config = py_utils.WaitFor(self._GetDevToolsClientConfig, timeout=timeout) self._devtools_client = devtools_config.WaitForAndCreate( timeout=timeout) except (py_utils.TimeoutException, exceptions.ProcessGoneException) as e: if not self.IsBrowserRunning(): raise exceptions.BrowserGoneException(self.browser, e) raise exceptions.BrowserConnectionGoneException(self.browser, e)
def _WaitForBrowserToComeUp(self, remote_devtools_port=None): """ Wait for browser to come up. Args: remote_devtools_port: The remote devtools port, if any. Otherwise assumed to be the same as self._port. """ if self._devtools_client: # In case we are launching a second browser instance (as is done by # the CrOS backend), ensure that the old devtools_client is closed, # otherwise re-creating it will fail. self._devtools_client.Close() self._devtools_client = None try: timeout = self.browser_options.browser_startup_timeout py_utils.WaitFor(self.HasBrowserFinishedLaunching, timeout=timeout) except (py_utils.TimeoutException, exceptions.ProcessGoneException) as e: if not self.IsBrowserRunning(): raise exceptions.BrowserGoneException(self.browser, e) raise exceptions.BrowserConnectionGoneException(self.browser, e) self._devtools_client = devtools_client_backend.DevToolsClientBackend( self._port, self._browser_target, remote_devtools_port or self._port, self)
def pid(self): try: # Although there might be multiple processes sharing the same name as # the browser app, the browser process is the only one being a direct # descendant of an Android zygote. (See crbug.com/785446) zygotes = self.device.ListProcesses('zygote') zygote_pids = set(p.pid for p in zygotes) assert zygote_pids, 'No Android zygote found' processes = self.device.ListProcesses(self._backend_settings.package) pids = [] for process in processes: if (process.name == self._backend_settings.package and process.ppid in zygote_pids): pids.append(process.pid) assert len(pids) <= 1, 'Found too many browsers: %r' % pids except Exception as exc: # Re-raise as an AppCrashException to get further diagnostic information. # In particular we also get the values of all local variables above. raise exceptions.AppCrashException( self.browser, 'Error getting browser PID: %s' % exc) if not pids: raise exceptions.BrowserGoneException(self.browser) return pids[0]
def _WaitForBrowserToComeUp(self, timeout=None): def IsBrowserUp(): try: self.Request('', timeout=timeout) except (exceptions.BrowserGoneException, exceptions.BrowserConnectionGoneException): return False else: return True try: util.WaitFor(IsBrowserUp, timeout=30) except util.TimeoutException: raise exceptions.BrowserGoneException(self.GetStackTrace()) def AllExtensionsLoaded(): # Extension pages are loaded from an about:blank page, # so we need to check that the document URL is the extension # page in addition to the ready state. extension_ready_js = """ document.URL.lastIndexOf('chrome-extension://%s/', 0) == 0 && (document.readyState == 'complete' || document.readyState == 'interactive') """ for e in self.options.extensions_to_load: if not e.extension_id in self._extension_dict_backend: return False extension_object = self._extension_dict_backend[e.extension_id] res = extension_object.EvaluateJavaScript(extension_ready_js % e.extension_id) if not res: return False return True if self._supports_extensions: util.WaitFor(AllExtensionsLoaded, timeout=30)
def MeasurePage(self, *_): if not self.has_crashed: self.has_crashed = True raise exceptions.BrowserGoneException()
def RunPage(self, *_): old_run_count = self.run_count self.run_count += 1 if old_run_count == 0: raise exceptions.BrowserGoneException('i am a browser instance')
def __init__(self, options, adb, package, is_content_shell, cmdline_file, activity, devtools_remote_port): super(AndroidBrowserBackend, self).__init__(is_content_shell=is_content_shell, supports_extensions=False, options=options) if len(options.extensions_to_load) > 0: raise browser_backend.ExtensionsNotSupportedException( 'Android browser does not support extensions.') # Initialize fields so that an explosion during init doesn't break in Close. self._options = options self._adb = adb self._package = package self._cmdline_file = cmdline_file self._saved_cmdline = None self._activity = activity if not options.keep_test_server_ports: adb_commands.ResetTestServerPortAllocation() self._port = adb_commands.AllocateTestServerPort() self._devtools_remote_port = devtools_remote_port self._profile_dir = '/data/data/%s/' % self._package if is_content_shell: self._profile_dir += 'app_content_shell/' else: self._profile_dir += 'app_chrome/' # Kill old browser. self._adb.CloseApplication(self._package) self._adb.KillAll('device_forwarder') self._adb.Forward('tcp:%d' % self._port, self._devtools_remote_port) if self._adb.Adb().CanAccessProtectedFileContents(): if not options.dont_override_profile: self._adb.RunShellCommand('su -c rm -r "%s"' % self._profile_dir) if options.profile_dir: if is_content_shell: logging.critical( 'Profiles cannot be used with content shell') sys.exit(1) self._adb.Push(options.profile_dir, self._profile_dir) # Set up the command line. self._saved_cmdline = ''.join( self._adb.Adb().GetProtectedFileContents(cmdline_file) or []) if is_content_shell: pseudo_exec_name = 'content_shell' else: pseudo_exec_name = 'chrome' args = [pseudo_exec_name] args.extend(self.GetBrowserStartupArgs()) def QuoteIfNeeded(arg): # Escape 'key=valueA valueB' to 'key="valueA valueB"' # Already quoted values, or values without space are left untouched. # This is required so CommandLine.java can parse valueB correctly rather # than as a separate switch. params = arg.split('=') if len(params) != 2: return arg key, values = params if ' ' not in values: return arg if values[0] in '"\'' and values[-1] == values[0]: return arg return '%s="%s"' % (key, values) args = map(QuoteIfNeeded, args) self._adb.Adb().SetProtectedFileContents(cmdline_file, ' '.join(args)) # TODO: Once --enable-remote-debugging flag makes its way to the oldest # version under test (m27 goes to stable), remove this hack. # Force devtools protocol on, if not already done and we can access # protected files. if (not is_content_shell and self._adb.Adb().CanAccessProtectedFileContents()): # Make sure we can find the apps' prefs file prefs_file = self._profile_dir + 'Default/Preferences' if not self._adb.FileExistsOnDevice(prefs_file): # Start it up the first time so we can tweak the prefs. self._adb.StartActivity(self._package, self._activity, True, None, None) retries = 0 timeout = 3 time.sleep(timeout) while not self._adb.Adb().GetProtectedFileContents(prefs_file): time.sleep(timeout) retries += 1 timeout *= 2 if retries == 3: logging.critical( 'android_browser_backend: Could not find ' 'preferences file %s for %s', prefs_file, self._package) raise exceptions.BrowserGoneException( 'Missing preferences file.') self._adb.CloseApplication(self._package) preferences = json.loads(''.join( self._adb.Adb().GetProtectedFileContents(prefs_file))) changed = False if 'devtools' not in preferences: preferences['devtools'] = {} changed = True if not preferences['devtools'].get('remote_enabled'): preferences['devtools']['remote_enabled'] = True changed = True if changed: logging.warning('Manually enabled devtools protocol on %s' % self._package) txt = json.dumps(preferences, indent=2) self._adb.Adb().SetProtectedFileContents(prefs_file, txt) # Start it up with a fresh log. self._adb.RunShellCommand('logcat -c') self._adb.StartActivity(self._package, self._activity, True, None, 'chrome://newtab/') try: self._WaitForBrowserToComeUp() self._PostBrowserStartupInitialization() except exceptions.BrowserGoneException: logging.critical('Failed to connect to browser.') if not self._adb.IsRootEnabled(): logging.critical( 'Ensure web debugging is enabled in Chrome at ' '"Settings > Developer tools > Enable USB Web debugging".') sys.exit(1) except: import traceback traceback.print_exc() self.Close() raise
def pid(self): pids = self._adb.ExtractPid(self._backend_settings.package) if not pids: raise exceptions.BrowserGoneException(self.browser) return int(pids[0])
def MeasurePage(self, _, tab, __): if not self.has_crashed: self.has_crashed = True raise exceptions.BrowserGoneException(tab.browser)
def RunPage(self, *_): old_run_count = self.run_count self.run_count += 1 if old_run_count == 0: raise exceptions.BrowserGoneException( None, 'i am a browser crash message')
def pid(self): pids = self.device.GetPids(self._package) if not pids or self._package not in pids: raise exceptions.BrowserGoneException(self.browser) return int(pids[self._package])
def RestartBrowserBeforeEachPage(self): old_run_count = self.run_count self.run_count += 1 if old_run_count == 0: raise exceptions.BrowserGoneException(None) return self._needs_browser_restart_after_each_page