2 # Copyright 2013 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
6 """PyAuto: Python Interface to Chromium's Automation Proxy.
8 PyAuto uses swig to expose Automation Proxy interfaces to Python.
9 For complete documentation on the functionality available,
10 run pydoc on this file.
12 Ref: http://dev.chromium.org/developers/testing/pyauto
15 Include the following in your PyAuto test script to make it run standalone.
17 from pyauto import Main
19 if __name__ == '__main__':
22 This script can be used as an executable to fire off other scripts, similar
24 python pyauto.py test_script
55 """Setup a few dirs where we expect to find dependency libraries."""
57 os.path.dirname(__file__),
58 pyauto_paths.GetThirdPartyDir(),
59 os.path.join(pyauto_paths.GetThirdPartyDir(), 'webdriver', 'pylib'),
61 sys.path += map(os.path.normpath, pyauto_paths.GetBuildDirs() + deps_dirs)
65 _PYAUTO_DOC_URL = 'http://dev.chromium.org/developers/testing/pyauto'
69 # Needed so that all additional classes (like: FilePath, GURL) exposed by
70 # swig interface get available in this module.
71 from pyautolib import *
73 print >>sys.stderr, 'Could not locate pyautolib shared libraries. ' \
74 'Did you build?\n Documentation: %s' % _PYAUTO_DOC_URL
75 # Mac requires python2.5 even when not the default 'python' (e.g. 10.6)
76 if 'darwin' == sys.platform and sys.version_info[:2] != (2,5):
77 print >>sys.stderr, '*\n* Perhaps use "python2.5", not "python" ?\n*'
80 # Should go after sys.path is set appropriately
87 from pyauto_errors import AutomationCommandFail
88 from pyauto_errors import AutomationCommandTimeout
89 from pyauto_errors import JavascriptRuntimeError
90 from pyauto_errors import JSONInterfaceError
91 from pyauto_errors import NTPThumbnailNotShownError
93 import simplejson as json # found in third_party
95 _CHROME_DRIVER_FACTORY = None
96 _DEFAULT_AUTOMATION_TIMEOUT = 45
102 class PyUITest(pyautolib.PyUITestBase, unittest.TestCase):
103 """Base class for UI Test Cases in Python.
105 A browser is created before executing each test, and is destroyed after
106 each test irrespective of whether the test passed or failed.
108 You should derive from this class and create methods with 'test' prefix,
109 and use methods inherited from PyUITestBase (the C++ side).
113 class MyTest(PyUITest):
115 def testNavigation(self):
116 self.NavigateToURL("http://www.google.com")
117 self.assertEqual("Google", self.GetActiveTabTitle())
120 def __init__(self, methodName='runTest', **kwargs):
121 """Initialize PyUITest.
123 When redefining __init__ in a derived class, make sure that:
124 o you make a call this __init__
125 o __init__ takes methodName as an arg. this is mandated by unittest module
128 methodName: the default method name. Internal use by unittest module
130 (The rest of the args can be in any order. They can even be skipped in
131 which case the defaults will be used.)
133 clear_profile: If True, clean the profile dir before use. Defaults to True
134 homepage: the home page. Defaults to "about:blank"
136 # Fetch provided keyword args, or fill in defaults.
137 clear_profile = kwargs.get('clear_profile', True)
138 homepage = kwargs.get('homepage', 'about:blank')
139 self._automation_timeout = _DEFAULT_AUTOMATION_TIMEOUT * 1000
141 pyautolib.PyUITestBase.__init__(self, clear_profile, homepage)
142 self.Initialize(pyautolib.FilePath(self.BrowserPath()))
143 unittest.TestCase.__init__(self, methodName)
145 # Give all pyauto tests easy access to pprint.PrettyPrinter functions.
146 self.pprint = pprint.pprint
147 self.pformat = pprint.pformat
149 # Set up remote proxies, if they were requested.
154 self.remotes = _REMOTE_PROXY
155 self.remote = _REMOTE_PROXY[0]
158 pyautolib.PyUITestBase.__del__(self)
160 def _SetExtraChromeFlags(self):
161 """Prepares the browser to launch with the specified extra Chrome flags.
163 This function is called right before the browser is launched for the first
166 for flag in self.ExtraChromeFlags():
167 if flag.startswith('--'):
169 split_pos = flag.find('=')
171 flag_name = flag[:split_pos]
172 flag_val = flag[split_pos + 1:]
173 self.AppendBrowserLaunchSwitch(flag_name, flag_val)
175 self.AppendBrowserLaunchSwitch(flag)
178 named_channel_id = None
180 named_channel_id = _OPTIONS.channel_id
181 if self.IsChromeOS(): # Enable testing interface on ChromeOS.
182 if self.get_clear_profile():
183 self.CleanupBrowserProfileOnChromeOS()
184 self.EnableCrashReportingOnChromeOS()
185 if not named_channel_id:
186 named_channel_id = self.EnableChromeTestingOnChromeOS()
188 self._SetExtraChromeFlags() # Flags already previously set for ChromeOS.
190 self._named_channel_id = named_channel_id
191 self.UseNamedChannelID(named_channel_id)
192 # Initialize automation and fire the browser (does not fire the browser
198 _BROWSER_PID = self.GetBrowserInfo()['browser_pid']
199 except JSONInterfaceError:
200 raise JSONInterfaceError('Unable to get browser_pid over automation '
201 'channel on first attempt. Something went very '
202 'wrong. Chrome probably did not launch.')
204 # Forcibly trigger all plugins to get registered. crbug.com/94123
205 # Sometimes flash files loaded too quickly after firing browser
206 # ends up getting downloaded, which seems to indicate that the plugin
207 # hasn't been registered yet.
208 if not self.IsChromeOS():
209 self.GetPluginsInfo()
211 if (self.IsChromeOS() and not self.GetLoginInfo()['is_logged_in'] and
212 self.ShouldOOBESkipToLogin()):
213 if self.GetOOBEScreenInfo()['screen_name'] != 'login':
215 if self.ShouldAutoLogin():
216 # Login with default creds.
217 sys.path.append('/usr/local') # to import autotest libs
218 from autotest.cros import constants
219 creds = constants.CREDENTIALS['$default']
220 self.Login(creds[0], creds[1])
221 assert self.GetLoginInfo()['is_logged_in']
222 logging.info('Logged in as %s.' % creds[0])
224 # If we are connected to any RemoteHosts, create PyAuto
225 # instances on the remote sides and set them up too.
226 for remote in self.remotes:
227 remote.CreateTarget(self)
231 """Override this method to launch browser differently.
233 Can be used to prevent launching the browser window by default in case a
234 test wants to do some additional setup before firing browser.
236 When using the named interface, it connects to an existing browser
239 On ChromeOS, a browser showing the login window is started. Tests can
240 initiate a user session by calling Login() or LoginAsGuest(). Cryptohome
241 vaults or flimflam profiles left over by previous tests can be cleared by
242 calling RemoveAllCryptohomeVaults() respectively CleanFlimflamDirs() before
243 logging in to improve isolation. Note that clearing flimflam profiles
244 requires a flimflam restart, briefly taking down network connectivity and
245 slowing down the test. This should be done for tests that use flimflam only.
250 for remote in self.remotes:
253 self.TearDown() # Destroy browser
255 # Method required by the Python standard library unittest.TestCase.
261 """Returns the path to Chromium binaries.
263 Expects the browser binaries to be in the
264 same location as the pyautolib binaries.
266 return os.path.normpath(os.path.dirname(pyautolib.__file__))
268 def ExtraChromeFlags(self):
269 """Return a list of extra chrome flags to use with Chrome for testing.
271 These are flags needed to facilitate testing. Override this function to
272 use a custom set of Chrome flags.
274 auth_ext_path = ('/usr/local/autotest/deps/pyauto_dep/' +
275 'test_src/chrome/browser/resources/gaia_auth')
276 if self.IsChromeOS():
278 '--homepage=about:blank',
279 '--allow-file-access',
280 '--allow-file-access-from-files',
281 '--enable-file-cookies',
282 '--disable-default-apps',
284 '--skip-oauth-login',
285 # Enables injection of test content script for webui login automation
286 '--auth-ext-path=%s' % auth_ext_path,
287 # Enable automation provider, chromeos net and chromeos login logs
288 '--vmodule=*/browser/automation/*=2,*/chromeos/net/*=2,' +
289 '*/chromeos/login/*=2',
294 def ShouldOOBESkipToLogin(self):
295 """Determine if we should skip the OOBE flow on ChromeOS.
297 This makes automation skip the OOBE flow during setUp() and land directly
298 to the login screen. Applies only if not logged in already.
300 Override and return False if OOBE flow is required, for OOBE tests, for
301 example. Calling this function directly will have no effect.
304 True, if the OOBE should be skipped and automation should
305 go to the 'Add user' login screen directly
306 False, if the OOBE should not be skipped.
308 assert self.IsChromeOS()
311 def ShouldAutoLogin(self):
312 """Determine if we should auto-login on ChromeOS at browser startup.
314 To be used for tests that expect user to be logged in before running test,
315 without caring which user. ShouldOOBESkipToLogin() should return True
316 for this to take effect.
318 Override and return False to not auto login, for tests where login is part
322 True, if chrome should auto login after startup.
325 assert self.IsChromeOS()
328 def CloseChromeOnChromeOS(self):
329 """Gracefully exit chrome on ChromeOS."""
331 def _GetListOfChromePids():
332 """Retrieves the list of currently-running Chrome process IDs.
335 A list of strings, where each string represents a currently-running
338 proc = subprocess.Popen(['pgrep', '^chrome$'], stdout=subprocess.PIPE)
340 return [x.strip() for x in proc.stdout.readlines()]
342 orig_pids = _GetListOfChromePids()
343 subprocess.call(['pkill', '^chrome$'])
345 def _AreOrigPidsDead(orig_pids):
346 """Determines whether all originally-running 'chrome' processes are dead.
349 orig_pids: A list of strings, where each string represents the PID for
350 an originally-running 'chrome' process.
353 True, if all originally-running 'chrome' processes have been killed, or
356 for new_pid in _GetListOfChromePids():
357 if new_pid in orig_pids:
361 self.WaitUntil(lambda: _AreOrigPidsDead(orig_pids))
364 def _IsRootSuid(path):
365 """Determine if |path| is a suid-root file."""
366 return os.path.isfile(path) and (os.stat(path).st_mode & stat.S_ISUID)
369 def SuidPythonPath():
370 """Path to suid_python binary on ChromeOS.
372 This is typically in the same directory as pyautolib.py
374 return os.path.join(PyUITest.BrowserPath(), 'suid-python')
377 def RunSuperuserActionOnChromeOS(action):
378 """Run the given action with superuser privs (on ChromeOS).
380 Uses the suid_actions.py script.
383 action: An action to perform.
384 See suid_actions.py for available options.
389 assert PyUITest._IsRootSuid(PyUITest.SuidPythonPath()), \
390 'Did not find suid-root python at %s' % PyUITest.SuidPythonPath()
391 file_path = os.path.join(os.path.dirname(__file__), 'chromeos',
393 args = [PyUITest.SuidPythonPath(), file_path, '--action=%s' % action]
394 proc = subprocess.Popen(
395 args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
396 stdout, stderr = proc.communicate()
397 return (stdout, stderr)
399 def EnableChromeTestingOnChromeOS(self):
400 """Enables the named automation interface on chromeos.
402 Restarts chrome so that you get a fresh instance.
403 Also sets some testing-friendly flags for chrome.
405 Expects suid python to be present in the same dir as pyautolib.py
407 assert PyUITest._IsRootSuid(self.SuidPythonPath()), \
408 'Did not find suid-root python at %s' % self.SuidPythonPath()
409 file_path = os.path.join(os.path.dirname(__file__), 'chromeos',
411 args = [self.SuidPythonPath(), file_path]
412 # Pass extra chrome flags for testing
413 for flag in self.ExtraChromeFlags():
414 args.append('--extra-chrome-flags=%s' % flag)
415 assert self.WaitUntil(lambda: self._IsSessionManagerReady(0))
416 proc = subprocess.Popen(args, stdout=subprocess.PIPE)
417 automation_channel_path = proc.communicate()[0].strip()
418 assert len(automation_channel_path), 'Could not enable testing interface'
419 return automation_channel_path
422 def EnableCrashReportingOnChromeOS():
423 """Enables crash reporting on ChromeOS.
425 Writes the "/home/chronos/Consent To Send Stats" file with a 32-char
426 readable string. See comment in session_manager_setup.sh which does this
429 Note that crash reporting will work only if breakpad is built in, ie in a
430 'Google Chrome' build (not Chromium).
432 consent_file = '/home/chronos/Consent To Send Stats'
433 def _HasValidConsentFile():
434 if not os.path.isfile(consent_file):
436 stat = os.stat(consent_file)
437 return (len(open(consent_file).read()) and
438 (1000, 1000) == (stat.st_uid, stat.st_gid))
439 if not _HasValidConsentFile():
440 client_id = hashlib.md5('abcdefgh').hexdigest()
441 # Consent file creation and chown to chronos needs to be atomic
442 # to avoid races with the session_manager. crosbug.com/18413
443 # Therefore, create a temp file, chown, then rename it as consent file.
444 temp_file = consent_file + '.tmp'
445 open(temp_file, 'w').write(client_id)
446 # This file must be owned by chronos:chronos!
447 os.chown(temp_file, 1000, 1000);
448 shutil.move(temp_file, consent_file)
449 assert _HasValidConsentFile(), 'Could not create %s' % consent_file
452 def _IsSessionManagerReady(old_pid):
453 """Is the ChromeOS session_manager running and ready to accept DBus calls?
455 Called after session_manager is killed to know when it has restarted.
458 old_pid: The pid that session_manager had before it was killed,
459 to ensure that we don't look at the DBus interface
460 of an old session_manager process.
462 pgrep_process = subprocess.Popen(['pgrep', 'session_manager'],
463 stdout=subprocess.PIPE)
464 new_pid = pgrep_process.communicate()[0].strip()
465 if not new_pid or old_pid == new_pid:
470 bus = dbus.SystemBus()
471 proxy = bus.get_object('org.chromium.SessionManager',
472 '/org/chromium/SessionManager')
473 dbus.Interface(proxy, 'org.chromium.SessionManagerInterface')
474 except dbus.DBusException:
479 def CleanupBrowserProfileOnChromeOS():
480 """Cleanup browser profile dir on ChromeOS.
482 This does not clear cryptohome.
484 Browser should not be running, or else there will be locked files.
486 profile_dir = '/home/chronos/user'
487 for item in os.listdir(profile_dir):
488 # Deleting .pki causes stateful partition to get erased.
489 if item not in ['log', 'flimflam'] and not item.startswith('.'):
490 pyauto_utils.RemovePath(os.path.join(profile_dir, item))
492 chronos_dir = '/home/chronos'
493 for item in os.listdir(chronos_dir):
494 if item != 'user' and not item.startswith('.'):
495 pyauto_utils.RemovePath(os.path.join(chronos_dir, item))
498 def CleanupFlimflamDirsOnChromeOS():
499 """Clean the contents of flimflam profiles and restart flimflam."""
500 PyUITest.RunSuperuserActionOnChromeOS('CleanFlimflamDirs')
503 def RemoveAllCryptohomeVaultsOnChromeOS():
504 """Remove any existing cryptohome vaults."""
505 PyUITest.RunSuperuserActionOnChromeOS('RemoveAllCryptohomeVaults')
508 def _IsInodeNew(path, old_inode):
509 """Determine whether an inode has changed. POSIX only.
512 path: The file path to check for changes.
513 old_inode: The old inode number.
516 True if the path exists and its inode number is different from old_inode.
520 stat_result = os.stat(path)
525 return stat_result.st_ino != old_inode
527 def RestartBrowser(self, clear_profile=True, pre_launch_hook=None):
528 """Restart the browser.
530 For use with tests that require to restart the browser.
533 clear_profile: If True, the browser profile is cleared before restart.
534 Defaults to True, that is restarts browser with a clean
536 pre_launch_hook: If specified, must be a callable that is invoked before
537 the browser is started again. Not supported in ChromeOS.
539 if self.IsChromeOS():
540 assert pre_launch_hook is None, 'Not supported in ChromeOS'
543 self.CleanupBrowserProfileOnChromeOS()
544 self.CloseChromeOnChromeOS()
545 self.EnableChromeTestingOnChromeOS()
549 orig_clear_state = self.get_clear_profile()
550 self.CloseBrowserAndServer()
551 self.set_clear_profile(clear_profile)
554 logging.debug('Restarting browser with clear_profile=%s',
555 self.get_clear_profile())
556 self.LaunchBrowserAndServer()
557 self.set_clear_profile(orig_clear_state) # Reset to original state.
561 """Returns the path to the data dir chrome/test/data."""
562 return os.path.normpath(
563 os.path.join(os.path.dirname(__file__), os.pardir, "data"))
566 def ChromeOSDataDir():
567 """Returns the path to the data dir chromeos/test/data."""
568 return os.path.normpath(
569 os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, os.pardir,
570 "chromeos", "test", "data"))
573 def GetFileURLForPath(*path):
574 """Get file:// url for the given path.
576 Also quotes the url using urllib.quote().
579 path: Variable number of strings that can be joined.
581 path_str = os.path.join(*path)
582 abs_path = os.path.abspath(path_str)
583 if sys.platform == 'win32':
584 # Don't quote the ':' in drive letter ( say, C: ) on win.
585 # Also, replace '\' with '/' as expected in a file:/// url.
586 drive, rest = os.path.splitdrive(abs_path)
587 quoted_path = drive.upper() + urllib.quote((rest.replace('\\', '/')))
588 return 'file:///' + quoted_path
590 quoted_path = urllib.quote(abs_path)
591 return 'file://' + quoted_path
594 def GetFileURLForDataPath(*relative_path):
595 """Get file:// url for the given path relative to the chrome test data dir.
597 Also quotes the url using urllib.quote().
600 relative_path: Variable number of strings that can be joined.
602 return PyUITest.GetFileURLForPath(PyUITest.DataDir(), *relative_path)
605 def GetHttpURLForDataPath(*relative_path):
606 """Get http:// url for the given path in the data dir.
608 The URL will be usable only after starting the http server.
611 assert _HTTP_SERVER, 'HTTP Server not yet started'
612 return _HTTP_SERVER.GetURL(os.path.join('files', *relative_path)).spec()
615 def ContentDataDir():
616 """Get path to content/test/data."""
617 return os.path.join(PyUITest.DataDir(), os.pardir, os.pardir, os.pardir,
618 'content', 'test', 'data')
621 def GetFileURLForContentDataPath(*relative_path):
622 """Get file:// url for the given path relative to content test data dir.
624 Also quotes the url using urllib.quote().
627 relative_path: Variable number of strings that can be joined.
629 return PyUITest.GetFileURLForPath(PyUITest.ContentDataDir(), *relative_path)
632 def GetFtpURLForDataPath(ftp_server, *relative_path):
633 """Get ftp:// url for the given path in the data dir.
636 ftp_server: handle to ftp server, an instance of SpawnedTestServer
637 relative_path: any number of path elements
639 The URL will be usable only after starting the ftp server.
641 assert ftp_server, 'FTP Server not yet started'
642 return ftp_server.GetURL(os.path.join(*relative_path)).spec()
647 return 'darwin' == sys.platform
651 """Are we on Linux? ChromeOS is linux too."""
652 return sys.platform.startswith('linux')
657 return 'win32' == sys.platform
661 """Are we on Windows 7?"""
662 if not PyUITest.IsWin():
664 ver = sys.getwindowsversion()
665 return (ver[3], ver[0], ver[1]) == (2, 6, 1)
669 """Are we on Windows Vista?"""
670 if not PyUITest.IsWin():
672 ver = sys.getwindowsversion()
673 return (ver[3], ver[0], ver[1]) == (2, 6, 0)
677 """Are we on Windows XP?"""
678 if not PyUITest.IsWin():
680 ver = sys.getwindowsversion()
681 return (ver[3], ver[0], ver[1]) == (2, 5, 1)
685 """Are we on ChromeOS (or Chromium OS)?
687 Checks for "CHROMEOS_RELEASE_NAME=" in /etc/lsb-release.
689 lsb_release = '/etc/lsb-release'
690 if not PyUITest.IsLinux() or not os.path.isfile(lsb_release):
692 for line in open(lsb_release).readlines():
693 if line.startswith('CHROMEOS_RELEASE_NAME='):
699 """Are we on Mac/Linux?"""
700 return PyUITest.IsMac() or PyUITest.IsLinux()
705 # TODO: figure out the machine's langugage.
710 """Return the platform name."""
711 # Since ChromeOS is also Linux, we check for it first.
712 if PyUITest.IsChromeOS():
714 elif PyUITest.IsLinux():
716 elif PyUITest.IsMac():
718 elif PyUITest.IsWin():
724 def EvalDataFrom(filename):
725 """Return eval of python code from given file.
727 The datastructure used in the file will be preserved.
729 data_file = os.path.join(filename)
730 contents = open(data_file).read()
734 print >>sys.stderr, '%s is an invalid data file.' % data_file
740 """What is the ChromeOS board name"""
741 if PyUITest.IsChromeOS():
742 for line in open('/etc/lsb-release'):
744 if line.startswith('CHROMEOS_RELEASE_BOARD='):
745 return line.split('=')[1]
750 """Terminate the given pid.
752 If the pid refers to a renderer, use KillRendererProcess instead.
755 subprocess.call(['taskkill.exe', '/T', '/F', '/PID', str(pid)])
757 os.kill(pid, signal.SIGTERM)
760 def GetPrivateInfo():
761 """Fetch info from private_tests_info.txt in private dir.
764 a dictionary of items from private_tests_info.txt
766 private_file = os.path.join(
767 PyUITest.DataDir(), 'pyauto_private', 'private_tests_info.txt')
768 assert os.path.exists(private_file), '%s missing' % private_file
769 return PyUITest.EvalDataFrom(private_file)
771 def WaitUntil(self, function, timeout=-1, retry_sleep=0.25, args=[],
772 expect_retval=None, return_retval=False, debug=True):
773 """Poll on a condition until timeout.
775 Waits until the |function| evalues to |expect_retval| or until |timeout|
776 secs, whichever occurs earlier.
778 This is better than using a sleep, since it waits (almost) only as much
781 WARNING: This method call should be avoided as far as possible in favor
782 of a real wait from chromium (like wait-until-page-loaded).
783 Only use in case there's really no better option.
786 Wait for "file.txt" to get created:
787 WaitUntil(os.path.exists, args=["file.txt"])
789 Same as above, but using lambda:
790 WaitUntil(lambda: os.path.exists("file.txt"))
793 function: the function whose truth value is to be evaluated
794 timeout: the max timeout (in secs) for which to wait. The default
795 action is to wait for kWaitForActionMaxMsec, as set in
797 Use None to wait indefinitely.
798 retry_sleep: the sleep interval (in secs) before retrying |function|.
799 Defaults to 0.25 secs.
800 args: the args to pass to |function|
801 expect_retval: the expected return value for |function|. This forms the
802 exit criteria. In case this is None (the default),
803 |function|'s return value is checked for truth,
804 so 'non-empty-string' should match with True
805 return_retval: If True, return the value returned by the last call to
807 debug: if True, displays debug info at each retry.
810 The return value of the |function| (when return_retval == True)
811 True, if returning when |function| evaluated to True (when
812 return_retval == False)
813 False, when returning due to timeout
815 if timeout == -1: # Default
816 timeout = self._automation_timeout / 1000.0
817 assert callable(function), "function should be a callable"
821 while timeout is None or time.time() - begin <= timeout:
822 retval = function(*args)
823 if (expect_retval is None and retval) or \
824 (expect_retval is not None and expect_retval == retval):
825 return retval if return_retval else True
826 if debug and time.time() - debug_begin > 5:
828 if function.func_name == (lambda: True).func_name:
829 function_info = inspect.getsource(function).strip()
831 function_info = '%s()' % function.func_name
832 logging.debug('WaitUntil(%s:%d %s) still waiting. '
833 'Expecting %s. Last returned %s.',
834 os.path.basename(inspect.getsourcefile(function)),
835 inspect.getsourcelines(function)[1],
837 True if expect_retval is None else expect_retval,
839 time.sleep(retry_sleep)
840 return retval if return_retval else False
842 def StartFTPServer(self, data_dir):
843 """Start a local file server hosting data files over ftp://
846 data_dir: path where ftp files should be served
849 handle to FTP Server, an instance of SpawnedTestServer
851 ftp_server = pyautolib.SpawnedTestServer(
852 pyautolib.SpawnedTestServer.TYPE_FTP,
854 pyautolib.FilePath(data_dir))
855 assert ftp_server.Start(), 'Could not start ftp server'
856 logging.debug('Started ftp server at "%s".', data_dir)
859 def StopFTPServer(self, ftp_server):
860 """Stop the local ftp server."""
861 assert ftp_server, 'FTP Server not yet started'
862 assert ftp_server.Stop(), 'Could not stop ftp server'
863 logging.debug('Stopped ftp server.')
865 def StartHTTPServer(self, data_dir):
866 """Starts a local HTTP SpawnedTestServer serving files from |data_dir|.
869 data_dir: path where the SpawnedTestServer should serve files from.
870 This will be appended to the source dir to get the final document root.
873 handle to the HTTP SpawnedTestServer
875 http_server = pyautolib.SpawnedTestServer(
876 pyautolib.SpawnedTestServer.TYPE_HTTP,
878 pyautolib.FilePath(data_dir))
879 assert http_server.Start(), 'Could not start HTTP server'
880 logging.debug('Started HTTP server at "%s".', data_dir)
883 def StopHTTPServer(self, http_server):
884 assert http_server, 'HTTP server not yet started'
885 assert http_server.Stop(), 'Cloud not stop the HTTP server'
886 logging.debug('Stopped HTTP server.')
888 def StartHttpsServer(self, cert_type, data_dir):
889 """Starts a local HTTPS SpawnedTestServer serving files from |data_dir|.
892 cert_type: An instance of SSLOptions.ServerCertificate for three
893 certificate types: ok, expired, or mismatch.
894 data_dir: The path where SpawnedTestServer should serve files from.
895 This is appended to the source dir to get the final
899 Handle to the HTTPS SpawnedTestServer
901 https_server = pyautolib.SpawnedTestServer(
902 pyautolib.SpawnedTestServer.TYPE_HTTPS,
903 pyautolib.SSLOptions(cert_type),
904 pyautolib.FilePath(data_dir))
905 assert https_server.Start(), 'Could not start HTTPS server.'
906 logging.debug('Start HTTPS server at "%s".' % data_dir)
909 def StopHttpsServer(self, https_server):
910 assert https_server, 'HTTPS server not yet started.'
911 assert https_server.Stop(), 'Could not stop the HTTPS server.'
912 logging.debug('Stopped HTTPS server.')
914 class ActionTimeoutChanger(object):
915 """Facilitate temporary changes to PyAuto command timeout.
917 Automatically resets to original timeout when object is destroyed.
919 _saved_timeout = -1 # Saved timeout value
921 def __init__(self, ui_test, new_timeout):
925 ui_test: a PyUITest object
926 new_timeout: new timeout to use (in milli secs)
928 self._saved_timeout = ui_test._automation_timeout
929 ui_test._automation_timeout = new_timeout
930 self._ui_test = ui_test
933 """Reset command_execution_timeout_ms to original value."""
934 self._ui_test._automation_timeout = self._saved_timeout
936 class JavascriptExecutor(object):
937 """Abstract base class for JavaScript injection.
939 Derived classes should override Execute method."""
940 def Execute(self, script):
943 class JavascriptExecutorInTab(JavascriptExecutor):
944 """Wrapper for injecting JavaScript in a tab."""
945 def __init__(self, ui_test, tab_index=0, windex=0, frame_xpath=''):
948 Refer to ExecuteJavascript() for the complete argument list
952 ui_test: a PyUITest object
954 self._ui_test = ui_test
956 self.tab_index = tab_index
957 self.frame_xpath = frame_xpath
959 def Execute(self, script):
960 """Execute script in the tab."""
961 return self._ui_test.ExecuteJavascript(script,
966 class JavascriptExecutorInRenderView(JavascriptExecutor):
967 """Wrapper for injecting JavaScript in an extension view."""
968 def __init__(self, ui_test, view, frame_xpath=''):
971 Refer to ExecuteJavascriptInRenderView() for the complete argument list
975 ui_test: a PyUITest object
977 self._ui_test = ui_test
979 self.frame_xpath = frame_xpath
981 def Execute(self, script):
982 """Execute script in the render view."""
983 return self._ui_test.ExecuteJavascriptInRenderView(script,
987 def _GetResultFromJSONRequestDiagnostics(self):
988 """Same as _GetResultFromJSONRequest without throwing a timeout exception.
990 This method is used to diagnose if a command returns without causing a
991 timout exception to be thrown. This should be used for debugging purposes
995 True if the request returned; False if it timed out.
997 result = self._SendJSONRequest(-1,
998 json.dumps({'command': 'GetBrowserInfo',}),
999 self._automation_timeout)
1001 # The diagnostic command did not complete, Chrome is probably in a bad
1006 def _GetResultFromJSONRequest(self, cmd_dict, windex=0, timeout=-1):
1007 """Issue call over the JSON automation channel and fetch output.
1009 This method packages the given dictionary into a json string, sends it
1010 over the JSON automation channel, loads the json output string returned,
1011 and returns it back as a dictionary.
1014 cmd_dict: the command dictionary. It must have a 'command' key
1017 'command': 'SetOmniboxText',
1020 windex: 0-based window index on which to work. Default: 0 (first window)
1021 Use -ve windex or None if the automation command does not apply
1022 to a browser window. Example: for chromeos login
1024 timeout: request timeout (in milliseconds)
1027 a dictionary for the output returned by the automation channel.
1030 pyauto_errors.JSONInterfaceError if the automation call returns an error.
1032 if timeout == -1: # Default
1033 timeout = self._automation_timeout
1034 if windex is None: # Do not target any window
1036 result = self._SendJSONRequest(windex, json.dumps(cmd_dict), timeout)
1038 additional_info = 'No information available.'
1039 # Windows does not support os.kill until Python 2.7.
1040 if not self.IsWin() and _BROWSER_PID:
1041 browser_pid_exists = True
1042 # Does the browser PID exist?
1044 # Does not actually kill the process
1045 os.kill(int(_BROWSER_PID), 0)
1047 browser_pid_exists = False
1048 if browser_pid_exists:
1049 if self._GetResultFromJSONRequestDiagnostics():
1050 # Browser info, worked, that means this hook had a problem
1051 additional_info = ('The browser process ID %d still exists. '
1052 'PyAuto was able to obtain browser info. It '
1053 'is possible this hook is broken.'
1056 additional_info = ('The browser process ID %d still exists. '
1057 'PyAuto was not able to obtain browser info. '
1058 'It is possible the browser is hung.'
1061 additional_info = ('The browser process ID %d no longer exists. '
1062 'Perhaps the browser crashed.' % _BROWSER_PID)
1063 elif not _BROWSER_PID:
1064 additional_info = ('The browser PID was not obtained. Does this test '
1065 'have a unique startup configuration?')
1066 # Mask private data if it is in the JSON dictionary
1067 cmd_dict_copy = copy.copy(cmd_dict)
1068 if 'password' in cmd_dict_copy.keys():
1069 cmd_dict_copy['password'] = '**********'
1070 if 'username' in cmd_dict_copy.keys():
1071 cmd_dict_copy['username'] = 'removed_username'
1072 raise JSONInterfaceError('Automation call %s received empty response. '
1073 'Additional information:\n%s' % (cmd_dict_copy,
1075 ret_dict = json.loads(result)
1076 if ret_dict.has_key('error'):
1077 if ret_dict.get('is_interface_timeout'):
1078 raise AutomationCommandTimeout(ret_dict['error'])
1079 elif ret_dict.get('is_interface_error'):
1080 raise JSONInterfaceError(ret_dict['error'])
1082 raise AutomationCommandFail(ret_dict['error'])
1085 def NavigateToURL(self, url, windex=0, tab_index=None, navigation_count=1):
1086 """Navigate the given tab to the given URL.
1088 Note that this method also activates the corresponding tab/window if it's
1089 not active already. Blocks until |navigation_count| navigations have
1093 url: The URL to which to navigate, can be a string or GURL object.
1094 windex: The index of the browser window to work on. Defaults to the first
1096 tab_index: The index of the tab to work on. Defaults to the active tab.
1097 navigation_count: the number of navigations to wait for. Defaults to 1.
1100 pyauto_errors.JSONInterfaceError if the automation call returns an error.
1102 if isinstance(url, GURL):
1104 if tab_index is None:
1105 tab_index = self.GetActiveTabIndex(windex)
1107 'command': 'NavigateToURL',
1110 'tab_index': tab_index,
1111 'navigation_count': navigation_count,
1113 self._GetResultFromJSONRequest(cmd_dict, windex=None)
1115 def NavigateToURLAsync(self, url, windex=0, tab_index=None):
1116 """Initiate a URL navigation.
1118 A wrapper for NavigateToURL with navigation_count set to 0.
1120 self.NavigateToURL(url, windex, tab_index, 0)
1122 def ApplyAccelerator(self, accelerator, windex=0):
1123 """Apply the accelerator with the given id.
1125 Note that this method schedules the accelerator, but does not wait for it to
1126 actually finish doing anything.
1129 accelerator: The accelerator id, IDC_BACK, IDC_NEWTAB, etc. The list of
1130 ids can be found at chrome/app/chrome_command_ids.h.
1131 windex: The index of the browser window to work on. Defaults to the first
1135 pyauto_errors.JSONInterfaceError if the automation call returns an error.
1139 'command': 'ApplyAccelerator',
1140 'accelerator': accelerator,
1143 self._GetResultFromJSONRequest(cmd_dict, windex=None)
1145 def RunCommand(self, accelerator, windex=0):
1146 """Apply the accelerator with the given id and wait for it to finish.
1148 This is like ApplyAccelerator except that it waits for the command to finish
1152 accelerator: The accelerator id. The list of ids can be found at
1153 chrome/app/chrome_command_ids.h.
1154 windex: The index of the browser window to work on. Defaults to the first
1158 pyauto_errors.JSONInterfaceError if the automation call returns an error.
1161 'command': 'RunCommand',
1162 'accelerator': accelerator,
1165 self._GetResultFromJSONRequest(cmd_dict, windex=None)
1167 def IsMenuCommandEnabled(self, accelerator, windex=0):
1168 """Check if a command is enabled for a window.
1170 Returns true if the command with the given accelerator id is enabled on the
1174 accelerator: The accelerator id. The list of ids can be found at
1175 chrome/app/chrome_command_ids.h.
1176 windex: The index of the browser window to work on. Defaults to the first
1180 True if the command is enabled for the given window.
1183 'command': 'IsMenuCommandEnabled',
1184 'accelerator': accelerator,
1187 return self._GetResultFromJSONRequest(cmd_dict, windex=None).get('enabled')
1189 def TabGoForward(self, tab_index=0, windex=0):
1190 """Navigate a tab forward in history.
1192 Equivalent to clicking the Forward button in the UI. Activates the tab as a
1196 pyauto_errors.JSONInterfaceError if the automation call returns an error.
1198 self.ActivateTab(tab_index, windex)
1199 self.RunCommand(IDC_FORWARD, windex)
1201 def TabGoBack(self, tab_index=0, windex=0):
1202 """Navigate a tab backwards in history.
1204 Equivalent to clicking the Back button in the UI. Activates the tab as a
1208 pyauto_errors.JSONInterfaceError if the automation call returns an error.
1210 self.ActivateTab(tab_index, windex)
1211 self.RunCommand(IDC_BACK, windex)
1213 def ReloadTab(self, tab_index=0, windex=0):
1214 """Reload the given tab.
1216 Blocks until the page has reloaded.
1219 tab_index: The index of the tab to reload. Defaults to 0.
1220 windex: The index of the browser window to work on. Defaults to the first
1224 pyauto_errors.JSONInterfaceError if the automation call returns an error.
1226 self.ActivateTab(tab_index, windex)
1227 self.RunCommand(IDC_RELOAD, windex)
1229 def CloseTab(self, tab_index=0, windex=0, wait_until_closed=True):
1230 """Close the given tab.
1232 Note: Be careful closing the last tab in a window as it may close the
1236 tab_index: The index of the tab to reload. Defaults to 0.
1237 windex: The index of the browser window to work on. Defaults to the first
1239 wait_until_closed: Whether to block until the tab finishes closing.
1242 pyauto_errors.JSONInterfaceError if the automation call returns an error.
1245 'command': 'CloseTab',
1246 'tab_index': tab_index,
1248 'wait_until_closed': wait_until_closed,
1250 self._GetResultFromJSONRequest(cmd_dict, windex=None)
1252 def WaitForTabToBeRestored(self, tab_index=0, windex=0, timeout=-1):
1253 """Wait for the given tab to be restored.
1256 tab_index: The index of the tab to reload. Defaults to 0.
1257 windex: The index of the browser window to work on. Defaults to the first
1259 timeout: Timeout in milliseconds.
1262 pyauto_errors.JSONInterfaceError if the automation call returns an error.
1265 'command': 'CloseTab',
1266 'tab_index': tab_index,
1269 self._GetResultFromJSONRequest(cmd_dict, windex=None, timeout=timeout)
1271 def ReloadActiveTab(self, windex=0):
1272 """Reload an active tab.
1274 Warning: Depending on the concept of an active tab is dangerous as it can
1275 change during the test. Use ReloadTab and supply a tab_index explicitly.
1278 windex: The index of the browser window to work on. Defaults to the first
1282 pyauto_errors.JSONInterfaceError if the automation call returns an error.
1284 self.ReloadTab(self.GetActiveTabIndex(windex), windex)
1286 def GetActiveTabIndex(self, windex=0):
1287 """Get the index of the currently active tab in the given browser window.
1289 Warning: Depending on the concept of an active tab is dangerous as it can
1290 change during the test. Supply the tab_index explicitly, if possible.
1293 windex: The index of the browser window to work on. Defaults to the first
1297 An integer index for the currently active tab.
1300 'command': 'GetActiveTabIndex',
1303 return self._GetResultFromJSONRequest(cmd_dict,
1304 windex=None).get('tab_index')
1306 def ActivateTab(self, tab_index=0, windex=0):
1307 """Activates the given tab in the specified window.
1309 Warning: Depending on the concept of an active tab is dangerous as it can
1310 change during the test. Instead use functions that accept a tab_index
1314 tab_index: Integer index of the tab to activate; defaults to 0.
1315 windex: Integer index of the browser window to use; defaults to the first
1319 pyauto_errors.JSONInterfaceError if the automation call returns an error.
1322 'command': 'ActivateTab',
1323 'tab_index': tab_index,
1326 self.BringBrowserToFront(windex)
1327 self._GetResultFromJSONRequest(cmd_dict, windex=None)
1329 def BringBrowserToFront(self, windex=0):
1330 """Activate the browser's window and bring it to front.
1333 windex: Integer index of the browser window to use; defaults to the first
1337 pyauto_errors.JSONInterfaceError if the automation call returns an error.
1340 'command': 'BringBrowserToFront',
1343 self._GetResultFromJSONRequest(cmd_dict, windex=None)
1345 def GetBrowserWindowCount(self):
1346 """Get the browser window count.
1352 Integer count of the number of browser windows. Includes popups.
1355 pyauto_errors.JSONInterfaceError if the automation call returns an error.
1357 cmd_dict = {'command': 'GetBrowserWindowCount'}
1358 return self._GetResultFromJSONRequest(cmd_dict, windex=None)['count']
1360 def OpenNewBrowserWindow(self, show):
1361 """Create a new browser window.
1364 show: Boolean indicating whether to show the window.
1367 pyauto_errors.JSONInterfaceError if the automation call returns an error.
1370 'command': 'OpenNewBrowserWindow',
1373 self._GetResultFromJSONRequest(cmd_dict, windex=None)
1375 def CloseBrowserWindow(self, windex=0):
1376 """Create a new browser window.
1379 windex: Index of the browser window to close; defaults to 0.
1382 pyauto_errors.JSONInterfaceError if the automation call returns an error.
1385 'command': 'CloseBrowserWindow',
1388 self._GetResultFromJSONRequest(cmd_dict, windex=None)
1390 def AppendTab(self, url, windex=0):
1391 """Append a new tab.
1393 Creates a new tab at the end of given browser window and activates
1394 it. Blocks until the specified |url| is loaded.
1397 url: The url to load, can be string or a GURL object.
1398 windex: The index of the browser window to work on. Defaults to the first
1402 True if the url loads successfully in the new tab. False otherwise.
1405 pyauto_errors.JSONInterfaceError if the automation call returns an error.
1407 if isinstance(url, GURL):
1410 'command': 'AppendTab',
1414 return self._GetResultFromJSONRequest(cmd_dict, windex=None).get('result')
1416 def GetTabCount(self, windex=0):
1417 """Gets the number of tab in the given browser window.
1420 windex: Integer index of the browser window to use; defaults to the first
1427 pyauto_errors.JSONInterfaceError if the automation call returns an error.
1430 'command': 'GetTabCount',
1433 return self._GetResultFromJSONRequest(cmd_dict, windex=None)['tab_count']
1435 def GetTabInfo(self, tab_index=0, windex=0):
1436 """Gets information about the specified tab.
1439 tab_index: Integer index of the tab to activate; defaults to 0.
1440 windex: Integer index of the browser window to use; defaults to the first
1444 A dictionary containing information about the tab.
1446 { u'title': "Hello World",
1447 u'url': "http://foo.bar", }
1450 pyauto_errors.JSONInterfaceError if the automation call returns an error.
1453 'command': 'GetTabInfo',
1454 'tab_index': tab_index,
1457 return self._GetResultFromJSONRequest(cmd_dict, windex=None)
1459 def GetActiveTabTitle(self, windex=0):
1460 """Gets the title of the active tab.
1462 Warning: Depending on the concept of an active tab is dangerous as it can
1463 change during the test. Use GetTabInfo and supply a tab_index explicitly.
1466 windex: Integer index of the browser window to use; defaults to the first
1470 The tab title as a string.
1473 pyauto_errors.JSONInterfaceError if the automation call returns an error.
1475 return self.GetTabInfo(self.GetActiveTabIndex(windex), windex)['title']
1477 def GetActiveTabURL(self, windex=0):
1478 """Gets the URL of the active tab.
1480 Warning: Depending on the concept of an active tab is dangerous as it can
1481 change during the test. Use GetTabInfo and supply a tab_index explicitly.
1484 windex: Integer index of the browser window to use; defaults to the first
1488 The tab URL as a GURL object.
1491 pyauto_errors.JSONInterfaceError if the automation call returns an error.
1493 return GURL(str(self.GetTabInfo(self.GetActiveTabIndex(windex),
1496 def ActionOnSSLBlockingPage(self, tab_index=0, windex=0, proceed=True):
1497 """Take action on an interstitial page.
1499 Calling this when an interstitial page is not showing is an error.
1502 tab_index: Integer index of the tab to activate; defaults to 0.
1503 windex: Integer index of the browser window to use; defaults to the first
1505 proceed: Whether to proceed to the URL or not.
1508 pyauto_errors.JSONInterfaceError if the automation call returns an error.
1511 'command': 'ActionOnSSLBlockingPage',
1512 'tab_index': tab_index,
1516 return self._GetResultFromJSONRequest(cmd_dict, windex=None)
1518 def GetBookmarkModel(self, windex=0):
1519 """Return the bookmark model as a BookmarkModel object.
1521 This is a snapshot of the bookmark model; it is not a proxy and
1522 does not get updated as the bookmark model changes.
1524 bookmarks_as_json = self._GetBookmarksAsJSON(windex)
1525 if not bookmarks_as_json:
1526 raise JSONInterfaceError('Could not resolve browser proxy.')
1527 return bookmark_model.BookmarkModel(bookmarks_as_json)
1529 def _GetBookmarksAsJSON(self, windex=0):
1530 """Get bookmarks as a JSON dictionary; used by GetBookmarkModel()."""
1532 'command': 'GetBookmarksAsJSON',
1535 self.WaitForBookmarkModelToLoad(windex)
1536 return self._GetResultFromJSONRequest(cmd_dict,
1537 windex=None)['bookmarks_as_json']
1539 def WaitForBookmarkModelToLoad(self, windex=0):
1540 """Gets the status of the bookmark bar as a dictionary.
1543 windex: Integer index of the browser window to use; defaults to the first
1547 pyauto_errors.JSONInterfaceError if the automation call returns an error.
1550 'command': 'WaitForBookmarkModelToLoad',
1553 return self._GetResultFromJSONRequest(cmd_dict, windex=None)
1555 def GetBookmarkBarStatus(self, windex=0):
1556 """Gets the status of the bookmark bar as a dictionary.
1559 windex: Integer index of the browser window to use; defaults to the first
1566 u'animating': False,
1567 u'detached': False, }
1570 pyauto_errors.JSONInterfaceError if the automation call returns an error.
1573 'command': 'GetBookmarkBarStatus',
1576 return self._GetResultFromJSONRequest(cmd_dict, windex=None)
1578 def GetBookmarkBarStatus(self, windex=0):
1579 """Gets the status of the bookmark bar as a dictionary.
1582 windex: Integer index of the browser window to use; defaults to the first
1589 u'animating': False,
1590 u'detached': False, }
1593 pyauto_errors.JSONInterfaceError if the automation call returns an error.
1596 'command': 'GetBookmarkBarStatus',
1599 return self._GetResultFromJSONRequest(cmd_dict, windex=None)
1601 def GetBookmarkBarStatus(self, windex=0):
1602 """Gets the status of the bookmark bar as a dictionary.
1605 windex: Integer index of the browser window to use; defaults to the first
1612 u'animating': False,
1613 u'detached': False, }
1616 pyauto_errors.JSONInterfaceError if the automation call returns an error.
1619 'command': 'GetBookmarkBarStatus',
1622 return self._GetResultFromJSONRequest(cmd_dict, windex=None)
1624 def GetBookmarkBarVisibility(self, windex=0):
1625 """Returns the visibility of the bookmark bar.
1628 windex: Integer index of the browser window to use; defaults to the first
1632 True if the bookmark bar is visible, false otherwise.
1635 pyauto_errors.JSONInterfaceError if the automation call returns an error.
1637 return self.GetBookmarkBarStatus(windex)['visible']
1639 def AddBookmarkGroup(self, parent_id, index, title, windex=0):
1640 """Adds a bookmark folder.
1643 parent_id: The parent bookmark folder.
1644 index: The location in the parent's list to insert this bookmark folder.
1645 title: The name of the bookmark folder.
1646 windex: Integer index of the browser window to use; defaults to the first
1650 True if the bookmark bar is detached, false otherwise.
1653 pyauto_errors.JSONInterfaceError if the automation call returns an error.
1655 if isinstance(parent_id, basestring):
1656 parent_id = int(parent_id)
1658 'command': 'AddBookmark',
1659 'parent_id': parent_id,
1665 self.WaitForBookmarkModelToLoad(windex)
1666 self._GetResultFromJSONRequest(cmd_dict, windex=None)
1668 def AddBookmarkURL(self, parent_id, index, title, url, windex=0):
1669 """Add a bookmark URL.
1672 parent_id: The parent bookmark folder.
1673 index: The location in the parent's list to insert this bookmark.
1674 title: The name of the bookmark.
1675 url: The url of the bookmark.
1676 windex: Integer index of the browser window to use; defaults to the first
1680 pyauto_errors.JSONInterfaceError if the automation call returns an error.
1682 if isinstance(parent_id, basestring):
1683 parent_id = int(parent_id)
1685 'command': 'AddBookmark',
1686 'parent_id': parent_id,
1693 self.WaitForBookmarkModelToLoad(windex)
1694 self._GetResultFromJSONRequest(cmd_dict, windex=None)
1696 def ReparentBookmark(self, id, new_parent_id, index, windex=0):
1700 id: The bookmark to move.
1701 new_parent_id: The new parent bookmark folder.
1702 index: The location in the parent's list to insert this bookmark.
1703 windex: Integer index of the browser window to use; defaults to the first
1707 pyauto_errors.JSONInterfaceError if the automation call returns an error.
1709 if isinstance(id, basestring):
1711 if isinstance(new_parent_id, basestring):
1712 new_parent_id = int(new_parent_id)
1714 'command': 'ReparentBookmark',
1716 'new_parent_id': new_parent_id,
1720 self.WaitForBookmarkModelToLoad(windex)
1721 self._GetResultFromJSONRequest(cmd_dict, windex=None)
1723 def SetBookmarkTitle(self, id, title, windex=0):
1724 """Change the title of a bookmark.
1727 id: The bookmark to rename.
1728 title: The new title for the bookmark.
1729 windex: Integer index of the browser window to use; defaults to the first
1733 pyauto_errors.JSONInterfaceError if the automation call returns an error.
1735 if isinstance(id, basestring):
1738 'command': 'SetBookmarkTitle',
1743 self.WaitForBookmarkModelToLoad(windex)
1744 self._GetResultFromJSONRequest(cmd_dict, windex=None)
1746 def SetBookmarkURL(self, id, url, windex=0):
1747 """Change the URL of a bookmark.
1750 id: The bookmark to change.
1751 url: The new url for the bookmark.
1752 windex: Integer index of the browser window to use; defaults to the first
1756 pyauto_errors.JSONInterfaceError if the automation call returns an error.
1758 if isinstance(id, basestring):
1761 'command': 'SetBookmarkURL',
1766 self.WaitForBookmarkModelToLoad(windex)
1767 self._GetResultFromJSONRequest(cmd_dict, windex=None)
1769 def RemoveBookmark(self, id, windex=0):
1770 """Remove a bookmark.
1773 id: The bookmark to remove.
1774 windex: Integer index of the browser window to use; defaults to the first
1778 pyauto_errors.JSONInterfaceError if the automation call returns an error.
1780 if isinstance(id, basestring):
1783 'command': 'RemoveBookmark',
1787 self.WaitForBookmarkModelToLoad(windex)
1788 self._GetResultFromJSONRequest(cmd_dict, windex=None)
1790 def GetDownloadsInfo(self, windex=0):
1791 """Return info about downloads.
1793 This includes all the downloads recognized by the history system.
1796 an instance of downloads_info.DownloadInfo
1798 return download_info.DownloadInfo(
1799 self._GetResultFromJSONRequest({'command': 'GetDownloadsInfo'},
1802 def GetOmniboxInfo(self, windex=0):
1803 """Return info about Omnibox.
1805 This represents a snapshot of the omnibox. If you expect changes
1806 you need to call this method again to get a fresh snapshot.
1807 Note that this DOES NOT shift focus to the omnibox; you've to ensure that
1808 the omnibox is in focus or else you won't get any interesting info.
1810 It's OK to call this even when the omnibox popup is not showing. In this
1811 case however, there won't be any matches, but other properties (like the
1812 current text in the omnibox) will still be fetched.
1814 Due to the nature of the omnibox, this function is sensitive to mouse
1815 focus. DO NOT HOVER MOUSE OVER OMNIBOX OR CHANGE WINDOW FOCUS WHEN USING
1819 windex: the index of the browser window to work on.
1820 Default: 0 (first window)
1823 an instance of omnibox_info.OmniboxInfo
1825 return omnibox_info.OmniboxInfo(
1826 self._GetResultFromJSONRequest({'command': 'GetOmniboxInfo'},
1829 def SetOmniboxText(self, text, windex=0):
1830 """Enter text into the omnibox. This shifts focus to the omnibox.
1833 text: the text to be set.
1834 windex: the index of the browser window to work on.
1835 Default: 0 (first window)
1837 # Ensure that keyword data is loaded from the profile.
1838 # This would normally be triggered by the user inputting this text.
1839 self._GetResultFromJSONRequest({'command': 'LoadSearchEngineInfo'})
1841 'command': 'SetOmniboxText',
1844 self._GetResultFromJSONRequest(cmd_dict, windex=windex)
1846 # TODO(ace): Remove this hack, update bug 62783.
1847 def WaitUntilOmniboxReadyHack(self, windex=0):
1848 """Wait until the omnibox is ready for input.
1850 This is a hack workaround for linux platform, which returns from
1851 synchronous window creation methods before the omnibox is fully functional.
1853 No-op on non-linux platforms.
1856 windex: the index of the browser to work on.
1859 return self.WaitUntil(
1860 lambda : self.GetOmniboxInfo(windex).Properties('has_focus'))
1862 def WaitUntilOmniboxQueryDone(self, windex=0):
1863 """Wait until omnibox has finished populating results.
1865 Uses WaitUntil() so the wait duration is capped by the timeout values
1866 used by automation, which WaitUntil() uses.
1869 windex: the index of the browser window to work on.
1870 Default: 0 (first window)
1872 return self.WaitUntil(
1873 lambda : not self.GetOmniboxInfo(windex).IsQueryInProgress())
1875 def OmniboxMovePopupSelection(self, count, windex=0):
1876 """Move omnibox popup selection up or down.
1879 count: number of rows by which to move.
1880 -ve implies down, +ve implies up
1881 windex: the index of the browser window to work on.
1882 Default: 0 (first window)
1885 'command': 'OmniboxMovePopupSelection',
1888 self._GetResultFromJSONRequest(cmd_dict, windex=windex)
1890 def OmniboxAcceptInput(self, windex=0):
1891 """Accepts the current string of text in the omnibox.
1893 This is equivalent to clicking or hiting enter on a popup selection.
1894 Blocks until the page loads.
1897 windex: the index of the browser window to work on.
1898 Default: 0 (first window)
1901 'command': 'OmniboxAcceptInput',
1903 self._GetResultFromJSONRequest(cmd_dict, windex=windex)
1905 def GetCookie(self, url, windex=0):
1906 """Get the value of the cookie at url in context of the specified browser.
1909 url: Either a GURL object or url string specifing the cookie url.
1910 windex: The index of the browser window to work on. Defaults to the first
1914 pyauto_errors.JSONInterfaceError if the automation call returns an error.
1916 if isinstance(url, GURL):
1919 'command': 'GetCookiesInBrowserContext',
1923 return self._GetResultFromJSONRequest(cmd_dict, windex=None)['cookies']
1925 def DeleteCookie(self, url, cookie_name, windex=0):
1926 """Delete the cookie at url with name cookie_name.
1929 url: Either a GURL object or url string specifing the cookie url.
1930 cookie_name: The name of the cookie to delete as a string.
1931 windex: The index of the browser window to work on. Defaults to the first
1935 pyauto_errors.JSONInterfaceError if the automation call returns an error.
1937 if isinstance(url, GURL):
1940 'command': 'DeleteCookieInBrowserContext',
1942 'cookie_name': cookie_name,
1945 self._GetResultFromJSONRequest(cmd_dict, windex=None)
1947 def SetCookie(self, url, value, windex=0):
1948 """Set the value of the cookie at url to value in the context of a browser.
1951 url: Either a GURL object or url string specifing the cookie url.
1952 value: A string to set as the cookie's value.
1953 windex: The index of the browser window to work on. Defaults to the first
1957 pyauto_errors.JSONInterfaceError if the automation call returns an error.
1959 if isinstance(url, GURL):
1962 'command': 'SetCookieInBrowserContext',
1967 self._GetResultFromJSONRequest(cmd_dict, windex=None)
1969 def GetSearchEngineInfo(self, windex=0):
1970 """Return info about search engines.
1973 windex: The window index, default is 0.
1976 An ordered list of dictionaries describing info about each search engine.
1979 [ { u'display_url': u'{google:baseURL}search?q=%s',
1980 u'host': u'www.google.com',
1981 u'in_default_list': True,
1982 u'is_default': True,
1984 u'keyword': u'google.com',
1985 u'path': u'/search',
1986 u'short_name': u'Google',
1987 u'supports_replacement': True,
1988 u'url': u'{google:baseURL}search?q={searchTerms}'},
1989 { u'display_url': u'http://search.yahoo.com/search?p=%s',
1990 u'host': u'search.yahoo.com',
1991 u'in_default_list': True,
1992 u'is_default': False,
1994 u'keyword': u'yahoo.com',
1995 u'path': u'/search',
1996 u'short_name': u'Yahoo!',
1997 u'supports_replacement': True,
1998 u'url': u'http://search.yahoo.com/search?p={searchTerms}'},
2000 # Ensure that the search engine profile is loaded into data model.
2001 self._GetResultFromJSONRequest({'command': 'LoadSearchEngineInfo'},
2003 cmd_dict = {'command': 'GetSearchEngineInfo'}
2004 return self._GetResultFromJSONRequest(
2005 cmd_dict, windex=windex)['search_engines']
2007 def AddSearchEngine(self, title, keyword, url, windex=0):
2008 """Add a search engine, as done through the search engines UI.
2011 title: name for search engine.
2012 keyword: keyword, used to initiate a custom search from omnibox.
2013 url: url template for this search engine's query.
2014 '%s' is replaced by search query string when used to search.
2015 windex: The window index, default is 0.
2017 # Ensure that the search engine profile is loaded into data model.
2018 self._GetResultFromJSONRequest({'command': 'LoadSearchEngineInfo'},
2020 cmd_dict = {'command': 'AddOrEditSearchEngine',
2022 'new_keyword': keyword,
2024 self._GetResultFromJSONRequest(cmd_dict, windex=windex)
2026 def EditSearchEngine(self, keyword, new_title, new_keyword, new_url,
2028 """Edit info for existing search engine.
2031 keyword: existing search engine keyword.
2032 new_title: new name for this search engine.
2033 new_keyword: new keyword for this search engine.
2034 new_url: new url for this search engine.
2035 windex: The window index, default is 0.
2037 # Ensure that the search engine profile is loaded into data model.
2038 self._GetResultFromJSONRequest({'command': 'LoadSearchEngineInfo'},
2040 cmd_dict = {'command': 'AddOrEditSearchEngine',
2042 'new_title': new_title,
2043 'new_keyword': new_keyword,
2045 self._GetResultFromJSONRequest(cmd_dict, windex=windex)
2047 def DeleteSearchEngine(self, keyword, windex=0):
2048 """Delete search engine with given keyword.
2051 keyword: the keyword string of the search engine to delete.
2052 windex: The window index, default is 0.
2054 # Ensure that the search engine profile is loaded into data model.
2055 self._GetResultFromJSONRequest({'command': 'LoadSearchEngineInfo'},
2057 cmd_dict = {'command': 'PerformActionOnSearchEngine', 'keyword': keyword,
2059 self._GetResultFromJSONRequest(cmd_dict, windex=windex)
2061 def MakeSearchEngineDefault(self, keyword, windex=0):
2062 """Make search engine with given keyword the default search.
2065 keyword: the keyword string of the search engine to make default.
2066 windex: The window index, default is 0.
2068 # Ensure that the search engine profile is loaded into data model.
2069 self._GetResultFromJSONRequest({'command': 'LoadSearchEngineInfo'},
2071 cmd_dict = {'command': 'PerformActionOnSearchEngine', 'keyword': keyword,
2072 'action': 'default'}
2073 self._GetResultFromJSONRequest(cmd_dict, windex=windex)
2075 def GetLocalStatePrefsInfo(self):
2076 """Return info about preferences.
2078 This represents a snapshot of the local state preferences. If you expect
2079 local state preferences to have changed, you need to call this method again
2080 to get a fresh snapshot.
2083 an instance of prefs_info.PrefsInfo
2085 return prefs_info.PrefsInfo(
2086 self._GetResultFromJSONRequest({'command': 'GetLocalStatePrefsInfo'},
2089 def SetLocalStatePrefs(self, path, value):
2090 """Set local state preference for the given path.
2092 Preferences are stored by Chromium as a hierarchical dictionary.
2093 dot-separated paths can be used to refer to a particular preference.
2094 example: "session.restore_on_startup"
2096 Some preferences are managed, that is, they cannot be changed by the
2097 user. It's up to the user to know which ones can be changed. Typically,
2098 the options available via Chromium preferences can be changed.
2101 path: the path the preference key that needs to be changed
2102 example: "session.restore_on_startup"
2103 One of the equivalent names in chrome/common/pref_names.h could
2105 value: the value to be set. It could be plain values like int, bool,
2106 string or complex ones like list.
2107 The user has to ensure that the right value is specified for the
2108 right key. It's useful to dump the preferences first to determine
2109 what type is expected for a particular preference path.
2112 'command': 'SetLocalStatePrefs',
2117 self._GetResultFromJSONRequest(cmd_dict, windex=None)
2119 def GetPrefsInfo(self, windex=0):
2120 """Return info about preferences.
2122 This represents a snapshot of the preferences. If you expect preferences
2123 to have changed, you need to call this method again to get a fresh
2127 windex: The window index, default is 0.
2129 an instance of prefs_info.PrefsInfo
2132 'command': 'GetPrefsInfo',
2135 return prefs_info.PrefsInfo(
2136 self._GetResultFromJSONRequest(cmd_dict, windex=None))
2138 def SetPrefs(self, path, value, windex=0):
2139 """Set preference for the given path.
2141 Preferences are stored by Chromium as a hierarchical dictionary.
2142 dot-separated paths can be used to refer to a particular preference.
2143 example: "session.restore_on_startup"
2145 Some preferences are managed, that is, they cannot be changed by the
2146 user. It's up to the user to know which ones can be changed. Typically,
2147 the options available via Chromium preferences can be changed.
2150 path: the path the preference key that needs to be changed
2151 example: "session.restore_on_startup"
2152 One of the equivalent names in chrome/common/pref_names.h could
2154 value: the value to be set. It could be plain values like int, bool,
2155 string or complex ones like list.
2156 The user has to ensure that the right value is specified for the
2157 right key. It's useful to dump the preferences first to determine
2158 what type is expected for a particular preference path.
2159 windex: window index to work on. Defaults to 0 (first window).
2162 'command': 'SetPrefs',
2167 self._GetResultFromJSONRequest(cmd_dict, windex=None)
2169 def SendWebkitKeyEvent(self, key_type, key_code, tab_index=0, windex=0):
2170 """Send a webkit key event to the browser.
2173 key_type: the raw key type such as 0 for up and 3 for down.
2174 key_code: the hex value associated with the keypress (virtual key code).
2175 tab_index: tab index to work on. Defaults to 0 (first tab).
2176 windex: window index to work on. Defaults to 0 (first window).
2179 'command': 'SendWebkitKeyEvent',
2182 'isSystemKey': False,
2183 'unmodifiedText': '',
2185 'windowsKeyCode': key_code,
2188 'tab_index': tab_index,
2190 # Sending request for key event.
2191 self._GetResultFromJSONRequest(cmd_dict, windex=None)
2193 def SendWebkitCharEvent(self, char, tab_index=0, windex=0):
2194 """Send a webkit char to the browser.
2197 char: the char value to be sent to the browser.
2198 tab_index: tab index to work on. Defaults to 0 (first tab).
2199 windex: window index to work on. Defaults to 0 (first window).
2202 'command': 'SendWebkitKeyEvent',
2203 'type': 2, # kCharType
2205 'isSystemKey': False,
2206 'unmodifiedText': char,
2208 'windowsKeyCode': ord((char).upper()),
2211 'tab_index': tab_index,
2213 # Sending request for a char.
2214 self._GetResultFromJSONRequest(cmd_dict, windex=None)
2216 def SetDownloadShelfVisible(self, is_visible, windex=0):
2217 """Set download shelf visibility for the specified browser window.
2220 is_visible: A boolean indicating the desired shelf visibility.
2221 windex: The window index, defaults to 0 (the first window).
2224 pyauto_errors.JSONInterfaceError if the automation call returns an error.
2227 'command': 'SetDownloadShelfVisible',
2228 'is_visible': is_visible,
2231 self._GetResultFromJSONRequest(cmd_dict, windex=None)
2233 def IsDownloadShelfVisible(self, windex=0):
2234 """Determine whether the download shelf is visible in the given window.
2237 windex: The window index, defaults to 0 (the first window).
2240 A boolean indicating the shelf visibility.
2243 pyauto_errors.JSONInterfaceError if the automation call returns an error.
2246 'command': 'IsDownloadShelfVisible',
2249 return self._GetResultFromJSONRequest(cmd_dict, windex=None)['is_visible']
2251 def GetDownloadDirectory(self, tab_index=None, windex=0):
2252 """Get the path to the download directory.
2254 Warning: Depending on the concept of an active tab is dangerous as it can
2255 change during the test. Always supply a tab_index explicitly.
2258 tab_index: The index of the tab to work on. Defaults to the active tab.
2259 windex: The index of the browser window to work on. Defaults to 0.
2262 The path to the download directory as a FilePath object.
2265 pyauto_errors.JSONInterfaceError if the automation call returns an error.
2267 if tab_index is None:
2268 tab_index = self.GetActiveTabIndex(windex)
2270 'command': 'GetDownloadDirectory',
2271 'tab_index': tab_index,
2274 return FilePath(str(self._GetResultFromJSONRequest(cmd_dict,
2275 windex=None)['path']))
2277 def WaitForAllDownloadsToComplete(self, pre_download_ids=[], windex=0,
2279 """Wait for all pending downloads to complete.
2281 This function assumes that any downloads to wait for have already been
2282 triggered and have started (it is ok if those downloads complete before this
2283 function is called).
2286 pre_download_ids: A list of numbers representing the IDs of downloads that
2287 exist *before* downloads to wait for have been
2288 triggered. Defaults to []; use GetDownloadsInfo() to get
2289 these IDs (only necessary if a test previously
2291 windex: The window index, defaults to 0 (the first window).
2292 timeout: The maximum amount of time (in milliseconds) to wait for
2293 downloads to complete.
2296 'command': 'WaitForAllDownloadsToComplete',
2297 'pre_download_ids': pre_download_ids,
2299 self._GetResultFromJSONRequest(cmd_dict, windex=windex, timeout=timeout)
2301 def PerformActionOnDownload(self, id, action, window_index=0):
2302 """Perform the given action on the download with the given id.
2305 id: The id of the download.
2306 action: The action to perform on the download.
2308 'open': Opens the download (waits until it has completed first).
2309 'toggle_open_files_like_this': Toggles the 'Always Open Files
2310 Of This Type' option.
2311 'remove': Removes the file from downloads (not from disk).
2312 'decline_dangerous_download': Equivalent to 'Discard' option
2313 after downloading a dangerous download (ex. an executable).
2314 'save_dangerous_download': Equivalent to 'Save' option after
2315 downloading a dangerous file.
2316 'pause': Pause the download. If the download completed before
2317 this call or is already paused, it's a no-op.
2318 'resume': Resume the download. If the download completed before
2319 this call or was not paused, it's a no-op.
2320 'cancel': Cancel the download.
2321 window_index: The window index, default is 0.
2324 A dictionary representing the updated download item (except in the case
2325 of 'decline_dangerous_download', 'toggle_open_files_like_this', and
2326 'remove', which return an empty dict).
2328 { u'PercentComplete': 100,
2329 u'file_name': u'file.txt',
2330 u'full_path': u'/path/to/file.txt',
2333 u'is_paused': False,
2334 u'is_temporary': False,
2335 u'open_when_complete': False,
2336 u'referrer_url': u'',
2337 u'state': u'COMPLETE',
2338 u'danger_type': u'DANGEROUS_FILE',
2339 u'url': u'file://url/to/file.txt'
2342 cmd_dict = { # Prepare command for the json interface
2343 'command': 'PerformActionOnDownload',
2347 return self._GetResultFromJSONRequest(cmd_dict, windex=window_index)
2349 def DownloadAndWaitForStart(self, file_url, windex=0):
2350 """Trigger download for the given url and wait for downloads to start.
2352 It waits for download by looking at the download info from Chrome, so
2353 anything which isn't registered by the history service won't be noticed.
2354 This is not thread-safe, but it's fine to call this method to start
2355 downloading multiple files in parallel. That is after starting a
2356 download, it's fine to start another one even if the first one hasn't
2360 num_downloads = len(self.GetDownloadsInfo(windex).Downloads())
2361 except JSONInterfaceError:
2364 self.NavigateToURL(file_url, windex) # Trigger download.
2365 # It might take a while for the download to kick in, hold on until then.
2366 self.assertTrue(self.WaitUntil(
2367 lambda: len(self.GetDownloadsInfo(windex).Downloads()) >
2370 def SetWindowDimensions(
2371 self, x=None, y=None, width=None, height=None, windex=0):
2372 """Set window dimensions.
2374 All args are optional and current values will be preserved.
2375 Arbitrarily large values will be handled gracefully by the browser.
2381 height: window height
2382 windex: window index to work on. Defaults to 0 (first window)
2384 cmd_dict = { # Prepare command for the json interface
2385 'command': 'SetWindowDimensions',
2392 cmd_dict['width'] = width
2394 cmd_dict['height'] = height
2395 self._GetResultFromJSONRequest(cmd_dict, windex=windex)
2397 def WaitForInfobarCount(self, count, windex=0, tab_index=0):
2398 """Wait until infobar count becomes |count|.
2400 Note: Wait duration is capped by the automation timeout.
2403 count: requested number of infobars
2404 windex: window index. Defaults to 0 (first window)
2405 tab_index: tab index Defaults to 0 (first tab)
2408 pyauto_errors.JSONInterfaceError if the automation call returns an error.
2410 # TODO(phajdan.jr): We need a solid automation infrastructure to handle
2411 # these cases. See crbug.com/53647.
2412 def _InfobarCount():
2413 windows = self.GetBrowserInfo()['windows']
2414 if windex >= len(windows): # not enough windows
2416 tabs = windows[windex]['tabs']
2417 if tab_index >= len(tabs): # not enough tabs
2419 return len(tabs[tab_index]['infobars'])
2421 return self.WaitUntil(_InfobarCount, expect_retval=count)
2423 def PerformActionOnInfobar(
2424 self, action, infobar_index, windex=0, tab_index=0):
2425 """Perform actions on an infobar.
2428 action: the action to be performed.
2429 Actions depend on the type of the infobar. The user needs to
2430 call the right action for the right infobar.
2432 - "dismiss": closes the infobar (for all infobars)
2433 - "accept", "cancel": click accept / cancel (for confirm infobars)
2434 - "allow", "deny": click allow / deny (for media stream infobars)
2435 infobar_index: 0-based index of the infobar on which to perform the action
2436 windex: 0-based window index Defaults to 0 (first window)
2437 tab_index: 0-based tab index. Defaults to 0 (first tab)
2440 pyauto_errors.JSONInterfaceError if the automation call returns an error.
2443 'command': 'PerformActionOnInfobar',
2445 'infobar_index': infobar_index,
2446 'tab_index': tab_index,
2448 if action not in ('dismiss', 'accept', 'allow', 'deny', 'cancel'):
2449 raise JSONInterfaceError('Invalid action %s' % action)
2450 self._GetResultFromJSONRequest(cmd_dict, windex=windex)
2452 def GetBrowserInfo(self):
2453 """Return info about the browser.
2455 This includes things like the version number, the executable name,
2456 executable path, pid info about the renderer/plugin/extension processes,
2457 window dimensions. (See sample below)
2459 For notification pid info, see 'GetActiveNotifications'.
2465 { u'browser_pid': 93737,
2466 # Child processes are the processes for plugins and other workers.
2467 u'child_process_path': u'.../Chromium.app/Contents/'
2468 'Versions/6.0.412.0/Chromium Helper.app/'
2469 'Contents/MacOS/Chromium Helper',
2470 u'child_processes': [ { u'name': u'Shockwave Flash',
2472 u'type': u'Plug-in'}],
2473 u'extension_views': [ {
2474 u'name': u'Webpage Screenshot',
2476 u'extension_id': u'dgcoklnmbeljaehamekjpeidmbicddfj',
2477 u'url': u'chrome-extension://dgcoklnmbeljaehamekjpeidmbicddfj/'
2481 u'render_process_id': 2,
2482 u'render_view_id': 1},
2483 u'view_type': u'EXTENSION_BACKGROUND_PAGE'}]
2485 u'BrowserProcessExecutableName': u'Chromium',
2486 u'BrowserProcessExecutablePath': u'Chromium.app/Contents/MacOS/'
2488 u'ChromeVersion': u'6.0.412.0',
2489 u'HelperProcessExecutableName': u'Chromium Helper',
2490 u'HelperProcessExecutablePath': u'Chromium Helper.app/Contents/'
2491 'MacOS/Chromium Helper',
2492 u'command_line_string': "COMMAND_LINE_STRING --WITH-FLAGS",
2493 u'branding': 'Chromium',
2494 u'is_official': False,}
2495 # The order of the windows and tabs listed here will be the same as
2496 # what shows up on screen.
2497 u'windows': [ { u'index': 0,
2499 u'incognito': False,
2500 u'profile_path': u'Default',
2501 u'fullscreen': False,
2502 u'visible_page_actions':
2503 [u'dgcoklnmbeljaehamekjpeidmbicddfj',
2504 u'osfcklnfasdofpcldmalwpicslasdfgd']
2510 u'renderer_pid': 93747,
2511 u'url': u'http://www.google.com/' }, {
2515 u'renderer_pid': 93919,
2516 u'url': u'https://chrome.google.com/'}, {
2519 u'buttons': [u'Allow', u'Deny'],
2520 u'link_text': u'Learn more',
2521 u'text': u'slides.html5rocks.com wants to track '
2522 'your physical location',
2523 u'type': u'confirm_infobar'}],
2525 u'renderer_pid': 93929,
2526 u'url': u'http://slides.html5rocks.com/#slide14'},
2534 pyauto_errors.JSONInterfaceError if the automation call returns an error.
2536 cmd_dict = { # Prepare command for the json interface
2537 'command': 'GetBrowserInfo',
2539 return self._GetResultFromJSONRequest(cmd_dict, windex=None)
2543 return self.GetBrowserInfo()['properties']['aura']
2545 def GetProcessInfo(self):
2546 """Returns information about browser-related processes that currently exist.
2548 This will also return information about other currently-running browsers
2549 besides just Chrome.
2552 A dictionary containing browser-related process information as identified
2553 by class MemoryDetails in src/chrome/browser/memory_details.h. The
2554 dictionary contains a single key 'browsers', mapped to a list of
2555 dictionaries containing information about each browser process name.
2556 Each of those dictionaries contains a key 'processes', mapped to a list
2557 of dictionaries containing the specific information for each process
2558 with the given process name.
2560 The memory values given in |committed_mem| and |working_set_mem| are in
2564 { 'browsers': [ { 'name': 'Chromium',
2565 'process_name': 'chrome',
2566 'processes': [ { 'child_process_type': 'Browser',
2567 'committed_mem': { 'image': 0,
2570 'is_diagnostics': False,
2574 'renderer_type': 'Unknown',
2577 'working_set_mem': { 'priv': 43672,
2580 { 'child_process_type': 'Tab',
2581 'committed_mem': { 'image': 0,
2584 'is_diagnostics': False,
2588 'renderer_type': 'Tab',
2589 'titles': ['about:blank'],
2591 'working_set_mem': { 'priv': 16768,
2594 ...<more processes>...]}]}
2597 pyauto_errors.JSONInterfaceError if the automation call returns an error.
2599 cmd_dict = { # Prepare command for the json interface.
2600 'command': 'GetProcessInfo',
2602 return self._GetResultFromJSONRequest(cmd_dict, windex=None)
2604 def GetNavigationInfo(self, tab_index=0, windex=0):
2605 """Get info about the navigation state of a given tab.
2608 tab_index: The tab index, default is 0.
2609 window_index: The window index, default is 0.
2615 { u'favicon_url': u'https://www.google.com/favicon.ico',
2616 u'page_type': u'NORMAL_PAGE',
2617 u'ssl': { u'displayed_insecure_content': False,
2618 u'ran_insecure_content': False,
2619 u'security_style': u'SECURITY_STYLE_AUTHENTICATED'}}
2621 Values for security_style can be:
2622 SECURITY_STYLE_UNKNOWN
2623 SECURITY_STYLE_UNAUTHENTICATED
2624 SECURITY_STYLE_AUTHENTICATION_BROKEN
2625 SECURITY_STYLE_AUTHENTICATED
2627 Values for page_type can be:
2632 cmd_dict = { # Prepare command for the json interface
2633 'command': 'GetNavigationInfo',
2634 'tab_index': tab_index,
2636 return self._GetResultFromJSONRequest(cmd_dict, windex=windex)
2638 def GetSecurityState(self, tab_index=0, windex=0):
2639 """Get security details for a given tab.
2642 tab_index: The tab index, default is 0.
2643 window_index: The window index, default is 0.
2648 { "security_style": SECURITY_STYLE_AUTHENTICATED,
2649 "ssl_cert_status": 3, // bitmask of status flags
2650 "insecure_content_status": 1, // bitmask of status flags
2653 cmd_dict = { # Prepare command for the json interface
2654 'command': 'GetSecurityState',
2655 'tab_index': tab_index,
2658 return self._GetResultFromJSONRequest(cmd_dict, windex=None)
2660 def GetHistoryInfo(self, search_text='', windex=0):
2661 """Return info about browsing history.
2664 search_text: the string to search in history. Defaults to empty string
2665 which means that all history would be returned. This is
2666 functionally equivalent to searching for a text in the
2667 chrome://history UI. So partial matches work too.
2668 When non-empty, the history items returned will contain a
2669 "snippet" field corresponding to the snippet visible in
2670 the chrome://history/ UI.
2671 windex: index of the browser window, defaults to 0.
2674 an instance of history_info.HistoryInfo
2676 cmd_dict = { # Prepare command for the json interface
2677 'command': 'GetHistoryInfo',
2678 'search_text': search_text,
2680 return history_info.HistoryInfo(
2681 self._GetResultFromJSONRequest(cmd_dict, windex=windex))
2683 def InstallExtension(self, extension_path, with_ui=False, from_webstore=None,
2684 windex=0, tab_index=0):
2685 """Installs an extension from the given path.
2687 The path must be absolute and may be a crx file or an unpacked extension
2688 directory. Returns the extension ID if successfully installed and loaded.
2689 Otherwise, throws an exception. The extension must not already be installed.
2692 extension_path: The absolute path to the extension to install. If the
2693 extension is packed, it must have a .crx extension.
2694 with_ui: Whether the extension install confirmation UI should be shown.
2695 from_webstore: If True, forces a .crx extension to be recognized as one
2696 from the webstore. Can be used to force install an extension with
2697 'experimental' permissions.
2698 windex: Integer index of the browser window to use; defaults to 0
2702 The ID of the installed extension.
2705 pyauto_errors.JSONInterfaceError if the automation call returns an error.
2708 'command': 'InstallExtension',
2709 'path': extension_path,
2712 'tab_index': tab_index,
2716 cmd_dict['from_webstore'] = True
2717 return self._GetResultFromJSONRequest(cmd_dict, windex=None)['id']
2719 def GetExtensionsInfo(self, windex=0):
2720 """Returns information about all installed extensions.
2723 windex: Integer index of the browser window to use; defaults to 0
2727 A list of dictionaries representing each of the installed extensions.
2729 [ { u'api_permissions': [u'bookmarks', u'experimental', u'tabs'],
2730 u'background_url': u'',
2731 u'description': u'Bookmark Manager',
2732 u'effective_host_permissions': [u'chrome://favicon/*',
2733 u'chrome://resources/*'],
2734 u'host_permissions': [u'chrome://favicon/*', u'chrome://resources/*'],
2735 u'id': u'eemcgdkfndhakfknompkggombfjjjeno',
2736 u'is_component': True,
2737 u'is_internal': False,
2738 u'name': u'Bookmark Manager',
2739 u'options_url': u'',
2740 u'public_key': u'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDQcByy+eN9jza\
2741 zWF/DPn7NW47sW7lgmpk6eKc0BQM18q8hvEM3zNm2n7HkJv/R6f\
2742 U+X5mtqkDuKvq5skF6qqUF4oEyaleWDFhd1xFwV7JV+/DU7bZ00\
2743 w2+6gzqsabkerFpoP33ZRIw7OviJenP0c0uWqDWF8EGSyMhB3tx\
2745 u'version': u'0.1' },
2746 { u'api_permissions': [...],
2747 u'background_url': u'chrome-extension://\
2748 lkdedmbpkaiahjjibfdmpoefffnbdkli/\
2750 u'description': u'Extension which lets you read your Facebook news \
2751 feed and wall. You can also post status updates.',
2752 u'effective_host_permissions': [...],
2753 u'host_permissions': [...],
2754 u'id': u'lkdedmbpkaiahjjibfdmpoefffnbdkli',
2755 u'name': u'Facebook for Google Chrome',
2756 u'options_url': u'',
2757 u'public_key': u'...',
2758 u'version': u'2.0.9'
2759 u'is_enabled': True,
2760 u'allowed_in_incognito': True} ]
2762 cmd_dict = { # Prepare command for the json interface
2763 'command': 'GetExtensionsInfo',
2766 return self._GetResultFromJSONRequest(cmd_dict, windex=None)['extensions']
2768 def UninstallExtensionById(self, id, windex=0):
2769 """Uninstall the extension with the given id.
2772 id: The string id of the extension.
2773 windex: Integer index of the browser window to use; defaults to 0
2777 True, if the extension was successfully uninstalled, or
2780 cmd_dict = { # Prepare command for the json interface
2781 'command': 'UninstallExtensionById',
2785 return self._GetResultFromJSONRequest(cmd_dict, windex=None)['success']
2787 def SetExtensionStateById(self, id, enable, allow_in_incognito, windex=0):
2788 """Set extension state: enable/disable, allow/disallow in incognito mode.
2791 id: The string id of the extension.
2792 enable: A boolean, enable extension.
2793 allow_in_incognito: A boolean, allow extension in incognito.
2794 windex: Integer index of the browser window to use; defaults to 0
2797 cmd_dict = { # Prepare command for the json interface
2798 'command': 'SetExtensionStateById',
2801 'allow_in_incognito': allow_in_incognito,
2804 self._GetResultFromJSONRequest(cmd_dict, windex=None)
2806 def TriggerPageActionById(self, id, tab_index=0, windex=0):
2807 """Trigger page action asynchronously in the active tab.
2809 The page action icon must be displayed before invoking this function.
2812 id: The string id of the extension.
2813 tab_index: Integer index of the tab to use; defaults to 0 (first tab).
2814 windex: Integer index of the browser window to use; defaults to 0
2817 cmd_dict = { # Prepare command for the json interface
2818 'command': 'TriggerPageActionById',
2821 'tab_index': tab_index,
2823 self._GetResultFromJSONRequest(cmd_dict, windex=None)
2825 def TriggerBrowserActionById(self, id, tab_index=0, windex=0):
2826 """Trigger browser action asynchronously in the active tab.
2829 id: The string id of the extension.
2830 tab_index: Integer index of the tab to use; defaults to 0 (first tab).
2831 windex: Integer index of the browser window to use; defaults to 0
2834 cmd_dict = { # Prepare command for the json interface
2835 'command': 'TriggerBrowserActionById',
2838 'tab_index': tab_index,
2840 self._GetResultFromJSONRequest(cmd_dict, windex=None)
2842 def UpdateExtensionsNow(self, windex=0):
2843 """Auto-updates installed extensions.
2845 Waits until all extensions are updated, loaded, and ready for use.
2846 This is equivalent to clicking the "Update extensions now" button on the
2847 chrome://extensions page.
2850 windex: Integer index of the browser window to use; defaults to 0
2854 pyauto_errors.JSONInterfaceError if the automation returns an error.
2856 cmd_dict = { # Prepare command for the json interface.
2857 'command': 'UpdateExtensionsNow',
2860 self._GetResultFromJSONRequest(cmd_dict, windex=None)
2862 def WaitUntilExtensionViewLoaded(self, name=None, extension_id=None,
2863 url=None, view_type=None):
2864 """Wait for a loaded extension view matching all the given properties.
2866 If no matching extension views are found, wait for one to be loaded.
2867 If there are more than one matching extension view, return one at random.
2868 Uses WaitUntil so timeout is capped by automation timeout.
2869 Refer to extension_view dictionary returned in GetBrowserInfo()
2870 for sample input/output values.
2873 name: (optional) Name of the extension.
2874 extension_id: (optional) ID of the extension.
2875 url: (optional) URL of the extension view.
2876 view_type: (optional) Type of the extension view.
2877 ['EXTENSION_BACKGROUND_PAGE'|'EXTENSION_POPUP'|'EXTENSION_INFOBAR'|
2881 The 'view' property of the extension view.
2882 None, if no view loaded.
2885 pyauto_errors.JSONInterfaceError if the automation returns an error.
2887 def _GetExtensionViewLoaded():
2888 extension_views = self.GetBrowserInfo()['extension_views']
2889 for extension_view in extension_views:
2890 if ((name and name != extension_view['name']) or
2891 (extension_id and extension_id != extension_view['extension_id']) or
2892 (url and url != extension_view['url']) or
2893 (view_type and view_type != extension_view['view_type'])):
2895 if extension_view['loaded']:
2896 return extension_view['view']
2899 if self.WaitUntil(lambda: _GetExtensionViewLoaded()):
2900 return _GetExtensionViewLoaded()
2903 def WaitUntilExtensionViewClosed(self, view):
2904 """Wait for the given extension view to to be closed.
2906 Uses WaitUntil so timeout is capped by automation timeout.
2907 Refer to extension_view dictionary returned by GetBrowserInfo()
2908 for sample input value.
2911 view: 'view' property of extension view.
2914 pyauto_errors.JSONInterfaceError if the automation returns an error.
2916 def _IsExtensionViewClosed():
2917 extension_views = self.GetBrowserInfo()['extension_views']
2918 for extension_view in extension_views:
2919 if view == extension_view['view']:
2923 return self.WaitUntil(lambda: _IsExtensionViewClosed())
2925 def GetPluginsInfo(self, windex=0):
2926 """Return info about plugins.
2928 This is the info available from about:plugins
2931 an instance of plugins_info.PluginsInfo
2933 return plugins_info.PluginsInfo(
2934 self._GetResultFromJSONRequest({'command': 'GetPluginsInfo'},
2937 def EnablePlugin(self, path):
2938 """Enable the plugin at the given path.
2940 Use GetPluginsInfo() to fetch path info about a plugin.
2943 pyauto_errors.JSONInterfaceError if the automation call returns an error.
2946 'command': 'EnablePlugin',
2949 self._GetResultFromJSONRequest(cmd_dict)
2951 def DisablePlugin(self, path):
2952 """Disable the plugin at the given path.
2954 Use GetPluginsInfo() to fetch path info about a plugin.
2957 pyauto_errors.JSONInterfaceError if the automation call returns an error.
2960 'command': 'DisablePlugin',
2963 self._GetResultFromJSONRequest(cmd_dict)
2965 def GetTabContents(self, tab_index=0, window_index=0):
2966 """Get the html contents of a tab (a la "view source").
2968 As an implementation detail, this saves the html in a file, reads
2969 the file into a buffer, then deletes it.
2972 tab_index: tab index, defaults to 0.
2973 window_index: window index, defaults to 0.
2975 html content of a page as a string.
2977 tempdir = tempfile.mkdtemp()
2978 # Make it writable by chronos on chromeos
2979 os.chmod(tempdir, 0777)
2980 filename = os.path.join(tempdir, 'content.html')
2981 cmd_dict = { # Prepare command for the json interface
2982 'command': 'SaveTabContents',
2983 'tab_index': tab_index,
2984 'filename': filename
2986 self._GetResultFromJSONRequest(cmd_dict, windex=window_index)
2993 shutil.rmtree(tempdir, ignore_errors=True)
2995 def AddSavedPassword(self, password_dict, windex=0):
2996 """Adds the given username-password combination to the saved passwords.
2999 password_dict: a dictionary that represents a password. Example:
3000 { 'username_value': 'user@example.com', # Required
3001 'password_value': 'test.password', # Required
3002 'signon_realm': 'https://www.example.com/', # Required
3003 'time': 1279317810.0, # Can get from time.time()
3004 'origin_url': 'https://www.example.com/login',
3005 'username_element': 'username', # The HTML element
3006 'password_element': 'password', # The HTML element
3007 'submit_element': 'submit', # The HTML element
3008 'action_target': 'https://www.example.com/login/',
3009 'blacklist': False }
3010 windex: window index; defaults to 0 (first window).
3012 *Blacklist notes* To blacklist a site, add a blacklist password with the
3013 following dictionary items: origin_url, signon_realm, username_element,
3014 password_element, action_target, and 'blacklist': True. Then all sites that
3015 have password forms matching those are blacklisted.
3018 True if adding the password succeeded, false otherwise. In incognito
3019 mode, adding the password should fail.
3022 pyauto_errors.JSONInterfaceError if the automation call returns an error.
3024 cmd_dict = { # Prepare command for the json interface
3025 'command': 'AddSavedPassword',
3026 'password': password_dict
3028 return self._GetResultFromJSONRequest(
3029 cmd_dict, windex=windex)['password_added']
3031 def RemoveSavedPassword(self, password_dict, windex=0):
3032 """Removes the password matching the provided password dictionary.
3035 password_dict: A dictionary that represents a password.
3036 For an example, see the dictionary in AddSavedPassword.
3037 windex: The window index, default is 0 (first window).
3039 cmd_dict = { # Prepare command for the json interface
3040 'command': 'RemoveSavedPassword',
3041 'password': password_dict
3043 self._GetResultFromJSONRequest(cmd_dict, windex=windex)
3045 def GetSavedPasswords(self):
3046 """Return the passwords currently saved.
3049 A list of dictionaries representing each password. For an example
3050 dictionary see AddSavedPassword documentation. The overall structure will
3052 [ {password1 dictionary}, {password2 dictionary} ]
3054 cmd_dict = { # Prepare command for the json interface
3055 'command': 'GetSavedPasswords'
3057 return self._GetResultFromJSONRequest(cmd_dict)['passwords']
3059 def SetTheme(self, crx_file_path, windex=0):
3060 """Installs the given theme synchronously.
3062 A theme file is a file with a .crx suffix, like an extension. The theme
3063 file must be specified with an absolute path. This method call waits until
3064 the theme is installed and will trigger the "theme installed" infobar.
3065 If the install is unsuccessful, will throw an exception.
3067 Uses InstallExtension().
3070 The ID of the installed theme.
3073 pyauto_errors.JSONInterfaceError if the automation call returns an error.
3075 return self.InstallExtension(crx_file_path, True, windex)
3077 def GetActiveNotifications(self):
3078 """Gets a list of the currently active/shown HTML5 notifications.
3081 a list containing info about each active notification, with the
3082 first item in the list being the notification on the bottom of the
3083 notification stack. The 'content_url' key can refer to a URL or a data
3084 URI. The 'pid' key-value pair may be invalid if the notification is
3088 [ { u'content_url': u'data:text/html;charset=utf-8,%3C!DOCTYPE%l%3E%0Atm...'
3089 u'display_source': 'www.corp.google.com',
3090 u'origin_url': 'http://www.corp.google.com/',
3092 { u'content_url': 'http://www.gmail.com/special_notification.html',
3093 u'display_source': 'www.gmail.com',
3094 u'origin_url': 'http://www.gmail.com/',
3098 pyauto_errors.JSONInterfaceError if the automation call returns an error.
3100 return [x for x in self.GetAllNotifications() if 'pid' in x]
3102 def GetAllNotifications(self):
3103 """Gets a list of all active and queued HTML5 notifications.
3105 An active notification is one that is currently shown to the user. Chrome's
3106 notification system will limit the number of notifications shown (currently
3107 by only allowing a certain percentage of the screen to be taken up by them).
3108 A notification will be queued if there are too many active notifications.
3109 Once other notifications are closed, another will be shown from the queue.
3112 a list containing info about each notification, with the first
3113 item in the list being the notification on the bottom of the
3114 notification stack. The 'content_url' key can refer to a URL or a data
3115 URI. The 'pid' key-value pair will only be present for active
3119 [ { u'content_url': u'data:text/html;charset=utf-8,%3C!DOCTYPE%l%3E%0Atm...'
3120 u'display_source': 'www.corp.google.com',
3121 u'origin_url': 'http://www.corp.google.com/',
3123 { u'content_url': 'http://www.gmail.com/special_notification.html',
3124 u'display_source': 'www.gmail.com',
3125 u'origin_url': 'http://www.gmail.com/'}]
3128 pyauto_errors.JSONInterfaceError if the automation call returns an error.
3131 'command': 'GetAllNotifications',
3133 return self._GetResultFromJSONRequest(cmd_dict)['notifications']
3135 def CloseNotification(self, index):
3136 """Closes the active HTML5 notification at the given index.
3139 index: the index of the notification to close. 0 refers to the
3140 notification on the bottom of the notification stack.
3143 pyauto_errors.JSONInterfaceError if the automation call returns an error.
3146 'command': 'CloseNotification',
3149 return self._GetResultFromJSONRequest(cmd_dict)
3151 def WaitForNotificationCount(self, count):
3152 """Waits for the number of active HTML5 notifications to reach the given
3156 pyauto_errors.JSONInterfaceError if the automation call returns an error.
3159 'command': 'WaitForNotificationCount',
3162 self._GetResultFromJSONRequest(cmd_dict)
3164 def FindInPage(self, search_string, forward=True,
3165 match_case=False, find_next=False,
3166 tab_index=0, windex=0, timeout=-1):
3167 """Find the match count for the given search string and search parameters.
3168 This is equivalent to using the find box.
3171 search_string: The string to find on the page.
3172 forward: Boolean to set if the search direction is forward or backwards
3173 match_case: Boolean to set for case sensitive search.
3174 find_next: Boolean to set to continue the search or start from beginning.
3175 tab_index: The tab index, default is 0.
3176 windex: The window index, default is 0.
3177 timeout: request timeout (in milliseconds), default is -1.
3180 number of matches found for the given search string and parameters
3182 { u'match_count': 10,
3185 u'match_right': 200,
3186 u'match_bottom': 200}
3189 pyauto_errors.JSONInterfaceError if the automation call returns an error.
3192 'command': 'FindInPage',
3193 'tab_index' : tab_index,
3194 'search_string' : search_string,
3195 'forward' : forward,
3196 'match_case' : match_case,
3197 'find_next' : find_next,
3199 return self._GetResultFromJSONRequest(cmd_dict, windex=windex,
3202 def OpenFindInPage(self, windex=0):
3203 """Opens the "Find in Page" box.
3206 windex: Index of the window; defaults to 0.
3209 pyauto_errors.JSONInterfaceError if the automation call returns an error.
3212 'command': 'OpenFindInPage',
3215 self._GetResultFromJSONRequest(cmd_dict, windex=None)
3217 def IsFindInPageVisible(self, windex=0):
3218 """Returns the visibility of the "Find in Page" box.
3221 windex: Index of the window; defaults to 0.
3224 A boolean indicating the visibility state of the "Find in Page" box.
3227 pyauto_errors.JSONInterfaceError if the automation call returns an error.
3230 'command': 'IsFindInPageVisible',
3233 return self._GetResultFromJSONRequest(cmd_dict, windex=None)['is_visible']
3236 def AddDomEventObserver(self, event_name='', automation_id=-1,
3238 """Adds a DomEventObserver associated with the AutomationEventQueue.
3240 An app raises a matching event in Javascript by calling:
3241 window.domAutomationController.sendWithId(automation_id, event_name)
3244 event_name: The event name to watch for. By default an event is raised
3246 automation_id: The Automation Id of the sent message. By default all
3247 messages sent from the window.domAutomationController are
3248 observed. Note that other PyAuto functions also send
3249 messages through window.domAutomationController with
3250 arbirary Automation Ids and they will be observed.
3251 recurring: If False the observer will be removed after it generates one
3252 event, otherwise it will continue observing and generating
3253 events until explicity removed with RemoveEventObserver(id).
3256 The id of the created observer, which can be used with GetNextEvent(id)
3257 and RemoveEventObserver(id).
3260 pyauto_errors.JSONInterfaceError if the automation call returns an error.
3263 'command': 'AddDomEventObserver',
3264 'event_name': event_name,
3265 'automation_id': automation_id,
3266 'recurring': recurring,
3268 return self._GetResultFromJSONRequest(cmd_dict, windex=None)['observer_id']
3270 def AddDomMutationObserver(self, mutation_type, xpath,
3271 attribute='textContent', expected_value=None,
3272 automation_id=44444,
3273 exec_js=None, **kwargs):
3274 """Sets up an event observer watching for a specific DOM mutation.
3276 Creates an observer that raises an event when a mutation of the given type
3277 occurs on a DOM node specified by |selector|.
3280 mutation_type: One of 'add', 'remove', 'change', or 'exists'.
3281 xpath: An xpath specifying the DOM node to watch. The node must already
3282 exist if |mutation_type| is 'change'.
3283 attribute: Attribute to match |expected_value| against, if given. Defaults
3285 expected_value: Optional regular expression to match against the node's
3286 textContent attribute after the mutation. Defaults to None.
3287 automation_id: The automation_id used to route the observer javascript
3288 messages. Defaults to 44444.
3289 exec_js: A callable of the form f(self, js, **kwargs) used to inject the
3290 MutationObserver javascript. Defaults to None, which uses
3291 PyUITest.ExecuteJavascript.
3293 Any additional keyword arguments are passed on to ExecuteJavascript and
3294 can be used to select the tab where the DOM MutationObserver is created.
3297 The id of the created observer, which can be used with GetNextEvent(id)
3298 and RemoveEventObserver(id).
3301 pyauto_errors.JSONInterfaceError if the automation call returns an error.
3302 pyauto_errors.JavascriptRuntimeError if the injected javascript
3303 MutationObserver returns an error.
3305 assert mutation_type in ('add', 'remove', 'change', 'exists'), \
3306 'Unexpected value "%s" for mutation_type.' % mutation_type
3308 'command': 'AddDomEventObserver',
3309 'event_name': '__dom_mutation_observer__:$(id)',
3310 'automation_id': automation_id,
3314 self._GetResultFromJSONRequest(cmd_dict, windex=None)['observer_id'])
3315 expected_string = ('null' if expected_value is None else '"%s"' %
3316 expected_value.replace('"', r'\"'))
3317 jsfile = os.path.join(os.path.abspath(os.path.dirname(__file__)),
3318 'dom_mutation_observer.js')
3319 with open(jsfile, 'r') as f:
3320 js = ('(' + f.read() + ')(%d, %d, "%s", "%s", "%s", %s);' %
3321 (automation_id, observer_id, mutation_type,
3322 xpath.replace('"', r'\"'), attribute, expected_string))
3323 exec_js = exec_js or PyUITest.ExecuteJavascript
3325 jsreturn = exec_js(self, js, **kwargs)
3326 except JSONInterfaceError:
3327 raise JSONInterfaceError('Failed to inject DOM mutation observer.')
3328 if jsreturn != 'success':
3329 self.RemoveEventObserver(observer_id)
3330 raise JavascriptRuntimeError(jsreturn)
3333 def WaitForDomNode(self, xpath, attribute='textContent',
3334 expected_value=None, exec_js=None, timeout=-1,
3335 msg='Expected DOM node failed to appear.', **kwargs):
3336 """Waits until a node specified by an xpath exists in the DOM.
3338 NOTE: This does NOT poll. It returns as soon as the node appears, or
3339 immediately if the node already exists.
3342 xpath: An xpath specifying the DOM node to watch.
3343 attribute: Attribute to match |expected_value| against, if given. Defaults
3345 expected_value: Optional regular expression to match against the node's
3346 textContent attribute. Defaults to None.
3347 exec_js: A callable of the form f(self, js, **kwargs) used to inject the
3348 MutationObserver javascript. Defaults to None, which uses
3349 PyUITest.ExecuteJavascript.
3350 msg: An optional error message used if a JSONInterfaceError is caught
3351 while waiting for the DOM node to appear.
3352 timeout: Time to wait for the node to exist before raising an exception,
3353 defaults to the default automation timeout.
3355 Any additional keyword arguments are passed on to ExecuteJavascript and
3356 can be used to select the tab where the DOM MutationObserver is created.
3359 pyauto_errors.JSONInterfaceError if the automation call returns an error.
3360 pyauto_errors.JavascriptRuntimeError if the injected javascript
3361 MutationObserver returns an error.
3363 observer_id = self.AddDomMutationObserver('exists', xpath, attribute,
3364 expected_value, exec_js=exec_js,
3367 self.GetNextEvent(observer_id, timeout=timeout)
3368 except JSONInterfaceError:
3369 raise JSONInterfaceError(msg)
3371 def GetNextEvent(self, observer_id=-1, blocking=True, timeout=-1):
3372 """Waits for an observed event to occur.
3374 The returned event is removed from the Event Queue. If there is already a
3375 matching event in the queue it is returned immediately, otherwise the call
3376 blocks until a matching event occurs. If blocking is disabled and no
3377 matching event is in the queue this function will immediately return None.
3380 observer_id: The id of the observer to wait for, matches any event by
3382 blocking: If True waits until there is a matching event in the queue,
3383 if False and there is no event waiting in the queue returns None
3385 timeout: Time to wait for a matching event, defaults to the default
3389 Event response dictionary, or None if blocking is disabled and there is no
3390 matching event in the queue.
3393 'name': 'login completed',
3394 'type': 'raised_event'}
3397 pyauto_errors.JSONInterfaceError if the automation call returns an error.
3400 'command': 'GetNextEvent',
3401 'observer_id' : observer_id,
3402 'blocking' : blocking,
3404 return self._GetResultFromJSONRequest(cmd_dict, windex=None,
3407 def RemoveEventObserver(self, observer_id):
3408 """Removes an Event Observer from the AutomationEventQueue.
3410 Expects a valid observer_id.
3413 observer_id: The id of the observer to remove.
3416 pyauto_errors.JSONInterfaceError if the automation call returns an error.
3419 'command': 'RemoveEventObserver',
3420 'observer_id' : observer_id,
3422 return self._GetResultFromJSONRequest(cmd_dict, windex=None)
3424 def ClearEventQueue(self):
3425 """Removes all events currently in the AutomationEventQueue.
3428 pyauto_errors.JSONInterfaceError if the automation call returns an error.
3431 'command': 'ClearEventQueue',
3433 return self._GetResultFromJSONRequest(cmd_dict, windex=None)
3435 def WaitUntilNavigationCompletes(self, tab_index=0, windex=0):
3436 """Wait until the specified tab is done navigating.
3438 It is safe to call ExecuteJavascript() as soon as the call returns. If
3439 there is no outstanding navigation the call will return immediately.
3442 tab_index: index of the tab.
3443 windex: index of the window.
3446 pyauto_errors.JSONInterfaceError if the automation call returns an error.
3449 'command': 'WaitUntilNavigationCompletes',
3450 'tab_index': tab_index,
3453 return self._GetResultFromJSONRequest(cmd_dict)
3455 def ExecuteJavascript(self, js, tab_index=0, windex=0, frame_xpath=''):
3456 """Executes a script in the specified frame of a tab.
3458 By default, execute the script in the top frame of the first tab in the
3459 first window. The invoked javascript function must send a result back via
3460 the domAutomationController.send function, or this function will never
3464 js: script to be executed.
3465 windex: index of the window.
3466 tab_index: index of the tab.
3467 frame_xpath: XPath of the frame to execute the script. Default is no
3468 frame. Example: '//frames[1]'.
3471 a value that was sent back via the domAutomationController.send method
3474 pyauto_errors.JSONInterfaceError if the automation call returns an error.
3477 'command': 'ExecuteJavascript',
3480 'tab_index' : tab_index,
3481 'frame_xpath' : frame_xpath,
3483 result = self._GetResultFromJSONRequest(cmd_dict)['result']
3484 # Wrap result in an array before deserializing because valid JSON has an
3485 # array or an object as the root.
3486 json_string = '[' + result + ']'
3487 return json.loads(json_string)[0]
3489 def ExecuteJavascriptInRenderView(self, js, view, frame_xpath=''):
3490 """Executes a script in the specified frame of an render view.
3492 The invoked javascript function must send a result back via the
3493 domAutomationController.send function, or this function will never return.
3496 js: script to be executed.
3497 view: A dictionary representing a unique id for the render view as
3498 returned for example by.
3499 self.GetBrowserInfo()['extension_views'][]['view'].
3501 { 'render_process_id': 1,
3502 'render_view_id' : 2}
3504 frame_xpath: XPath of the frame to execute the script. Default is no
3509 a value that was sent back via the domAutomationController.send method
3512 pyauto_errors.JSONInterfaceError if the automation call returns an error.
3515 'command': 'ExecuteJavascriptInRenderView',
3518 'frame_xpath' : frame_xpath,
3520 result = self._GetResultFromJSONRequest(cmd_dict, windex=None)['result']
3521 # Wrap result in an array before deserializing because valid JSON has an
3522 # array or an object as the root.
3523 json_string = '[' + result + ']'
3524 return json.loads(json_string)[0]
3526 def ExecuteJavascriptInOOBEWebUI(self, js, frame_xpath=''):
3527 """Executes a script in the specified frame of the OOBE WebUI.
3529 By default, execute the script in the top frame of the OOBE window. This
3530 also works for all OOBE pages, including the enterprise enrollment
3531 screen and login page. The invoked javascript function must send a result
3532 back via the domAutomationController.send function, or this function will
3536 js: Script to be executed.
3537 frame_xpath: XPath of the frame to execute the script. Default is no
3538 frame. Example: '//frames[1]'
3541 A value that was sent back via the domAutomationController.send method.
3544 pyauto_errors.JSONInterfaceError if the automation call returns an error.
3547 'command': 'ExecuteJavascriptInOOBEWebUI',
3550 'frame_xpath': frame_xpath,
3552 result = self._GetResultFromJSONRequest(cmd_dict, windex=None)['result']
3553 # Wrap result in an array before deserializing because valid JSON has an
3554 # array or an object as the root.
3555 return json.loads('[' + result + ']')[0]
3558 def GetDOMValue(self, expr, tab_index=0, windex=0, frame_xpath=''):
3559 """Executes a Javascript expression and returns the value.
3561 This is a wrapper for ExecuteJavascript, eliminating the need to
3562 explicitly call domAutomationController.send function.
3565 expr: expression value to be returned.
3566 tab_index: index of the tab.
3567 windex: index of the window.
3568 frame_xpath: XPath of the frame to execute the script. Default is no
3569 frame. Example: '//frames[1]'.
3572 a string that was sent back via the domAutomationController.send method.
3574 js = 'window.domAutomationController.send(%s);' % expr
3575 return self.ExecuteJavascript(js, tab_index, windex, frame_xpath)
3577 def CallJavascriptFunc(self, function, args=[], tab_index=0, windex=0):
3578 """Executes a script which calls a given javascript function.
3580 The invoked javascript function must send a result back via the
3581 domAutomationController.send function, or this function will never return.
3583 Defaults to first tab in first window.
3586 function: name of the function.
3587 args: list of all the arguments to pass into the called function. These
3588 should be able to be converted to a string using the |str| function.
3589 tab_index: index of the tab within the given window.
3590 windex: index of the window.
3593 a string that was sent back via the domAutomationController.send method
3595 converted_args = map(lambda arg: json.dumps(arg), args)
3596 js = '%s(%s)' % (function, ', '.join(converted_args))
3597 logging.debug('Executing javascript: %s', js)
3598 return self.ExecuteJavascript(js, tab_index, windex)
3600 def HeapProfilerDump(self, process_type, reason, tab_index=0, windex=0):
3601 """Dumps a heap profile. It works only on Linux and ChromeOS.
3603 We need an environment variable "HEAPPROFILE" set to a directory and a
3604 filename prefix, for example, "/tmp/prof". In a case of this example,
3605 heap profiles will be dumped into "/tmp/prof.(pid).0002.heap",
3606 "/tmp/prof.(pid).0003.heap", and so on. Nothing happens when this
3607 function is called without the env.
3609 Also, this requires the --enable-memory-benchmarking command line flag.
3612 process_type: A string which is one of 'browser' or 'renderer'.
3613 reason: A string which describes the reason for dumping a heap profile.
3614 The reason will be included in the logged message.
3616 'To check memory leaking'
3618 tab_index: tab index to work on if 'process_type' == 'renderer'.
3619 Defaults to 0 (first tab).
3620 windex: window index to work on if 'process_type' == 'renderer'.
3621 Defaults to 0 (first window).
3624 pyauto_errors.JSONInterfaceError if the automation call returns an error.
3626 assert process_type in ('browser', 'renderer')
3627 if self.IsLinux(): # IsLinux() also implies IsChromeOS().
3629 if (!chrome.memoryBenchmarking ||
3630 !chrome.memoryBenchmarking.isHeapProfilerRunning()) {
3631 domAutomationController.send('memory benchmarking disabled');
3633 chrome.memoryBenchmarking.heapProfilerDump("%s", "%s");
3634 domAutomationController.send('success');
3636 """ % (process_type, reason.replace('"', '\\"'))
3637 result = self.ExecuteJavascript(js, tab_index, windex)
3638 if result != 'success':
3639 raise JSONInterfaceError('Heap profiler dump failed: ' + result)
3641 logging.warn('Heap-profiling is not supported in this OS.')
3643 def GetNTPThumbnails(self):
3644 """Return a list of info about the sites in the NTP most visited section.
3646 [{ u'title': u'Google',
3647 u'url': u'http://www.google.com'},
3650 u'url': u'http://www.yahoo.com'}]
3652 return self._GetNTPInfo()['most_visited']
3654 def GetNTPThumbnailIndex(self, thumbnail):
3655 """Returns the index of the given NTP thumbnail, or -1 if it is not shown.
3658 thumbnail: a thumbnail dict received from |GetNTPThumbnails|
3660 thumbnails = self.GetNTPThumbnails()
3661 for i in range(len(thumbnails)):
3662 if thumbnails[i]['url'] == thumbnail['url']:
3666 def RemoveNTPThumbnail(self, thumbnail):
3667 """Removes the NTP thumbnail and returns true on success.
3670 thumbnail: a thumbnail dict received from |GetNTPThumbnails|
3672 self._CheckNTPThumbnailShown(thumbnail)
3674 'command': 'RemoveNTPMostVisitedThumbnail',
3675 'url': thumbnail['url']
3677 self._GetResultFromJSONRequest(cmd_dict)
3679 def RestoreAllNTPThumbnails(self):
3680 """Restores all the removed NTP thumbnails.
3682 the default thumbnails may come back into the Most Visited sites
3683 section after doing this
3686 'command': 'RestoreAllNTPMostVisitedThumbnails'
3688 self._GetResultFromJSONRequest(cmd_dict)
3690 def GetNTPDefaultSites(self):
3691 """Returns a list of URLs for all the default NTP sites, regardless of
3692 whether they are showing or not.
3694 These sites are the ones present in the NTP on a fresh install of Chrome.
3696 return self._GetNTPInfo()['default_sites']
3698 def RemoveNTPDefaultThumbnails(self):
3699 """Removes all thumbnails for default NTP sites, regardless of whether they
3700 are showing or not."""
3701 cmd_dict = { 'command': 'RemoveNTPMostVisitedThumbnail' }
3702 for site in self.GetNTPDefaultSites():
3703 cmd_dict['url'] = site
3704 self._GetResultFromJSONRequest(cmd_dict)
3706 def GetNTPRecentlyClosed(self):
3707 """Return a list of info about the items in the NTP recently closed section.
3711 u'url': u'http://www.bing.com',
3713 u'timestamp': 2139082.03912, # Seconds since epoch (Jan 1, 1970)
3714 u'direction': u'ltr'},
3717 u'timestamp': 2130821.90812,
3721 u'url': u'http://www.cnn.com',
3723 u'timestamp': 2129082.12098,
3724 u'direction': u'ltr'}]},
3727 u'url': u'http://www.altavista.com',
3728 u'title': u'Altavista',
3729 u'timestamp': 21390820.12903,
3730 u'direction': u'rtl'}]
3732 return self._GetNTPInfo()['recently_closed']
3734 def GetNTPApps(self):
3735 """Retrieves information about the apps listed on the NTP.
3737 In the sample data below, the "launch_type" will be one of the following
3738 strings: "pinned", "regular", "fullscreen", "window", or "unknown".
3743 u'app_launch_index': 2,
3744 u'description': u'Web Store',
3745 u'icon_big': u'chrome://theme/IDR_APP_DEFAULT_ICON',
3746 u'icon_small': u'chrome://favicon/https://chrome.google.com/webstore',
3747 u'id': u'ahfgeienlihckogmohjhadlkjgocpleb',
3748 u'is_component_extension': True,
3749 u'is_disabled': False,
3750 u'launch_container': 2,
3751 u'launch_type': u'regular',
3752 u'launch_url': u'https://chrome.google.com/webstore',
3753 u'name': u'Chrome Web Store',
3754 u'options_url': u'',
3757 u'app_launch_index': 1,
3758 u'description': u'A countdown app',
3759 u'icon_big': (u'chrome-extension://aeabikdlfbfeihglecobdkdflahfgcpd/'
3760 u'countdown128.png'),
3761 u'icon_small': (u'chrome://favicon/chrome-extension://'
3762 u'aeabikdlfbfeihglecobdkdflahfgcpd/'
3763 u'launchLocalPath.html'),
3764 u'id': u'aeabikdlfbfeihglecobdkdflahfgcpd',
3765 u'is_component_extension': False,
3766 u'is_disabled': False,
3767 u'launch_container': 2,
3768 u'launch_type': u'regular',
3769 u'launch_url': (u'chrome-extension://aeabikdlfbfeihglecobdkdflahfgcpd/'
3770 u'launchLocalPath.html'),
3771 u'name': u'Countdown',
3772 u'options_url': u'',
3777 A list of dictionaries in which each dictionary contains the information
3778 for a single app that appears in the "Apps" section of the NTP.
3780 return self._GetNTPInfo()['apps']
3782 def _GetNTPInfo(self):
3783 """Get info about the New Tab Page (NTP).
3785 This does not retrieve the actual info displayed in a particular NTP; it
3786 retrieves the current state of internal data that would be used to display
3787 an NTP. This includes info about the apps, the most visited sites,
3788 the recently closed tabs and windows, and the default NTP sites.
3793 u'most_visited': [ ... ],
3794 u'recently_closed': [ ... ],
3795 u'default_sites': [ ... ]
3799 A dictionary containing all the NTP info. See details about the different
3800 sections in their respective methods: GetNTPApps(), GetNTPThumbnails(),
3801 GetNTPRecentlyClosed(), and GetNTPDefaultSites().
3804 pyauto_errors.JSONInterfaceError if the automation call returns an error.
3807 'command': 'GetNTPInfo',
3809 return self._GetResultFromJSONRequest(cmd_dict)
3811 def _CheckNTPThumbnailShown(self, thumbnail):
3812 if self.GetNTPThumbnailIndex(thumbnail) == -1:
3813 raise NTPThumbnailNotShownError()
3815 def LaunchApp(self, app_id, windex=0):
3816 """Opens the New Tab Page and launches the specified app from it.
3818 This method will not return until after the contents of a new tab for the
3819 launched app have stopped loading.
3822 app_id: The string ID of the app to launch.
3823 windex: The index of the browser window to work on. Defaults to 0 (the
3827 pyauto_errors.JSONInterfaceError if the automation call returns an error.
3829 self.AppendTab(GURL('chrome://newtab'), windex) # Also activates this tab.
3831 'command': 'LaunchApp',
3834 return self._GetResultFromJSONRequest(cmd_dict, windex=windex)
3836 def SetAppLaunchType(self, app_id, launch_type, windex=0):
3837 """Sets the launch type for the specified app.
3840 app_id: The string ID of the app whose launch type should be set.
3841 launch_type: The string launch type, which must be one of the following:
3842 'pinned': Launch in a pinned tab.
3843 'regular': Launch in a regular tab.
3844 'fullscreen': Launch in a fullscreen tab.
3845 'window': Launch in a new browser window.
3846 windex: The index of the browser window to work on. Defaults to 0 (the
3850 pyauto_errors.JSONInterfaceError if the automation call returns an error.
3852 self.assertTrue(launch_type in ('pinned', 'regular', 'fullscreen',
3854 msg='Unexpected launch type value: "%s"' % launch_type)
3856 'command': 'SetAppLaunchType',
3858 'launch_type': launch_type,
3860 return self._GetResultFromJSONRequest(cmd_dict, windex=windex)
3862 def GetV8HeapStats(self, tab_index=0, windex=0):
3863 """Returns statistics about the v8 heap in the renderer process for a tab.
3866 tab_index: The tab index, default is 0.
3867 window_index: The window index, default is 0.
3870 A dictionary containing v8 heap statistics. Memory values are in bytes.
3872 { 'renderer_id': 6223,
3873 'v8_memory_allocated': 21803776,
3874 'v8_memory_used': 10565392 }
3876 cmd_dict = { # Prepare command for the json interface.
3877 'command': 'GetV8HeapStats',
3878 'tab_index': tab_index,
3880 return self._GetResultFromJSONRequest(cmd_dict, windex=windex)
3882 def GetFPS(self, tab_index=0, windex=0):
3883 """Returns the current FPS associated with the renderer process for a tab.
3885 FPS is the rendered frames per second.
3888 tab_index: The tab index, default is 0.
3889 window_index: The window index, default is 0.
3892 A dictionary containing FPS info.
3894 { 'renderer_id': 23567,
3896 'fps': 29.404298782348633 }
3898 cmd_dict = { # Prepare command for the json interface.
3899 'command': 'GetFPS',
3900 'tab_index': tab_index,
3902 return self._GetResultFromJSONRequest(cmd_dict, windex=windex)
3904 def IsFullscreenForBrowser(self, windex=0):
3905 """Returns true if the window is currently fullscreen and was initially
3906 transitioned to fullscreen by a browser (vs tab) mode transition."""
3907 return self._GetResultFromJSONRequest(
3908 { 'command': 'IsFullscreenForBrowser' },
3909 windex=windex).get('result')
3911 def IsFullscreenForTab(self, windex=0):
3912 """Returns true if fullscreen has been caused by a tab."""
3913 return self._GetResultFromJSONRequest(
3914 { 'command': 'IsFullscreenForTab' },
3915 windex=windex).get('result')
3917 def IsMouseLocked(self, windex=0):
3918 """Returns true if the mouse is currently locked."""
3919 return self._GetResultFromJSONRequest(
3920 { 'command': 'IsMouseLocked' },
3921 windex=windex).get('result')
3923 def IsMouseLockPermissionRequested(self, windex=0):
3924 """Returns true if the user is currently prompted to give permision for
3926 return self._GetResultFromJSONRequest(
3927 { 'command': 'IsMouseLockPermissionRequested' },
3928 windex=windex).get('result')
3930 def IsFullscreenPermissionRequested(self, windex=0):
3931 """Returns true if the user is currently prompted to give permision for
3933 return self._GetResultFromJSONRequest(
3934 { 'command': 'IsFullscreenPermissionRequested' },
3935 windex=windex).get('result')
3937 def IsFullscreenBubbleDisplayed(self, windex=0):
3938 """Returns true if the fullscreen and mouse lock bubble is currently
3940 return self._GetResultFromJSONRequest(
3941 { 'command': 'IsFullscreenBubbleDisplayed' },
3942 windex=windex).get('result')
3944 def IsFullscreenBubbleDisplayingButtons(self, windex=0):
3945 """Returns true if the fullscreen and mouse lock bubble is currently
3946 displayed and presenting buttons."""
3947 return self._GetResultFromJSONRequest(
3948 { 'command': 'IsFullscreenBubbleDisplayingButtons' },
3949 windex=windex).get('result')
3951 def AcceptCurrentFullscreenOrMouseLockRequest(self, windex=0):
3952 """Activate the accept button on the fullscreen and mouse lock bubble."""
3953 return self._GetResultFromJSONRequest(
3954 { 'command': 'AcceptCurrentFullscreenOrMouseLockRequest' },
3957 def DenyCurrentFullscreenOrMouseLockRequest(self, windex=0):
3958 """Activate the deny button on the fullscreen and mouse lock bubble."""
3959 return self._GetResultFromJSONRequest(
3960 { 'command': 'DenyCurrentFullscreenOrMouseLockRequest' },
3963 def KillRendererProcess(self, pid):
3964 """Kills the given renderer process.
3966 This will return only after the browser has received notice of the renderer
3970 pid: the process id of the renderer to kill
3973 pyauto_errors.JSONInterfaceError if the automation call returns an error.
3976 'command': 'KillRendererProcess',
3979 return self._GetResultFromJSONRequest(cmd_dict)
3981 def NewWebDriver(self, port=0):
3982 """Returns a new remote WebDriver instance.
3985 port: The port to start WebDriver on; by default the service selects an
3986 open port. It is an error to request a port number and request a
3987 different port later.
3990 selenium.webdriver.remote.webdriver.WebDriver instance
3992 from chrome_driver_factory import ChromeDriverFactory
3993 global _CHROME_DRIVER_FACTORY
3994 if _CHROME_DRIVER_FACTORY is None:
3995 _CHROME_DRIVER_FACTORY = ChromeDriverFactory(port=port)
3996 self.assertTrue(_CHROME_DRIVER_FACTORY.GetPort() == port or port == 0,
3997 msg='Requested a WebDriver on a specific port while already'
3998 ' running on a different port.')
3999 return _CHROME_DRIVER_FACTORY.NewChromeDriver(self)
4001 def CreateNewAutomationProvider(self, channel_id):
4002 """Creates a new automation provider.
4004 The provider will open a named channel in server mode.
4006 channel_id: the channel_id to open the server channel with
4009 'command': 'CreateNewAutomationProvider',
4010 'channel_id': channel_id
4012 self._GetResultFromJSONRequest(cmd_dict)
4014 def OpenNewBrowserWindowWithNewProfile(self):
4015 """Creates a new multi-profiles user, and then opens and shows a new
4016 tabbed browser window with the new profile.
4018 This is equivalent to 'Add new user' action with multi-profiles.
4020 To account for crbug.com/108761 on Win XP, this call polls until the
4021 profile count increments by 1.
4024 pyauto_errors.JSONInterfaceError if the automation call returns an error.
4026 num_profiles = len(self.GetMultiProfileInfo()['profiles'])
4027 cmd_dict = { # Prepare command for the json interface
4028 'command': 'OpenNewBrowserWindowWithNewProfile'
4030 self._GetResultFromJSONRequest(cmd_dict, windex=None)
4031 # TODO(nirnimesh): Remove when crbug.com/108761 is fixed
4033 lambda: len(self.GetMultiProfileInfo()['profiles']),
4034 expect_retval=(num_profiles + 1))
4036 def OpenProfileWindow(self, path, num_loads=1):
4037 """Open browser window for an existing profile.
4039 This is equivalent to picking a profile from the multi-profile menu.
4041 Multi-profile should be enabled and the requested profile should already
4042 exist. Creates a new window for the given profile. Use
4043 OpenNewBrowserWindowWithNewProfile() to create a new profile.
4046 path: profile path of the profile to be opened.
4047 num_loads: the number of loads to wait for, when a new browser window
4048 is created. Useful when restoring a window with many tabs.
4050 cmd_dict = { # Prepare command for the json interface
4051 'command': 'OpenProfileWindow',
4053 'num_loads': num_loads,
4055 return self._GetResultFromJSONRequest(cmd_dict, windex=None)
4057 def GetMultiProfileInfo(self):
4058 """Fetch info about all multi-profile users.
4065 'profiles': [{'name': 'First user',
4066 'path': '/tmp/.org.chromium.Chromium.Tyx17X/Default'},
4068 'path': '/tmp/.org.chromium.Chromium.Tyx17X/profile_1'}],
4071 Profiles will be listed in the same order as visible in preferences.
4074 pyauto_errors.JSONInterfaceError if the automation call returns an error.
4076 cmd_dict = { # Prepare command for the json interface
4077 'command': 'GetMultiProfileInfo'
4079 return self._GetResultFromJSONRequest(cmd_dict, windex=None)
4081 def RefreshPolicies(self):
4082 """Refreshes all the available policy providers.
4084 Each policy provider will reload its policy source and push the updated
4085 policies. This call waits for the new policies to be applied; any policies
4086 installed before this call is issued are guaranteed to be ready after it
4089 # TODO(craigdh): Determine the root cause of RefreshPolicies' flakiness.
4090 # See crosbug.com/30221
4091 timeout = PyUITest.ActionTimeoutChanger(self, 3 * 60 * 1000)
4092 cmd_dict = { 'command': 'RefreshPolicies' }
4093 self._GetResultFromJSONRequest(cmd_dict, windex=None)
4095 def SubmitForm(self, form_id, tab_index=0, windex=0, frame_xpath=''):
4096 """Submits the given form ID, and returns after it has been submitted.
4099 form_id: the id attribute of the form to submit.
4101 Returns: true on success.
4104 document.getElementById("%s").submit();
4105 window.addEventListener("unload", function() {
4106 window.domAutomationController.send("done");
4109 if self.ExecuteJavascript(js, tab_index, windex, frame_xpath) != 'done':
4111 # Wait until the form is submitted and the page completes loading.
4112 return self.WaitUntil(
4113 lambda: self.GetDOMValue('document.readyState',
4114 tab_index, windex, frame_xpath),
4115 expect_retval='complete')
4117 def SimulateAsanMemoryBug(self):
4118 """Simulates a memory bug for Address Sanitizer to catch.
4120 Address Sanitizer (if it was built it) will catch the bug and abort
4122 This method returns immediately before it actually causes a crash.
4124 cmd_dict = { 'command': 'SimulateAsanMemoryBug' }
4125 self._GetResultFromJSONRequest(cmd_dict, windex=None)
4129 def GetLoginInfo(self):
4130 """Returns information about login and screen locker state.
4132 This includes things like whether a user is logged in, the username
4133 of the logged in user, and whether the screen is locked.
4138 { u'is_guest': False,
4140 u'email': u'example@gmail.com',
4141 u'user_image': 2, # non-negative int, 'profile', 'file'
4142 u'is_screen_locked': False,
4143 u'login_ui_type': 'nativeui', # or 'webui'
4144 u'is_logged_in': True}
4147 pyauto_errors.JSONInterfaceError if the automation call returns an error.
4149 cmd_dict = { 'command': 'GetLoginInfo' }
4150 return self._GetResultFromJSONRequest(cmd_dict, windex=None)
4152 def WaitForSessionManagerRestart(self, function):
4153 """Call a function and wait for the ChromeOS session_manager to restart.
4156 function: The function to call.
4158 assert callable(function)
4159 pgrep_process = subprocess.Popen(['pgrep', 'session_manager'],
4160 stdout=subprocess.PIPE)
4161 old_pid = pgrep_process.communicate()[0].strip()
4163 return self.WaitUntil(lambda: self._IsSessionManagerReady(old_pid))
4165 def _WaitForInodeChange(self, path, function):
4166 """Call a function and wait for the specified file path to change.
4169 path: The file path to check for changes.
4170 function: The function to call.
4172 assert callable(function)
4173 old_inode = os.stat(path).st_ino
4175 return self.WaitUntil(lambda: self._IsInodeNew(path, old_inode))
4177 def ShowCreateAccountUI(self):
4178 """Go to the account creation page.
4180 This is the same as clicking the "Create Account" link on the
4181 ChromeOS login screen. Does not actually create a new account.
4182 Should be displaying the login screen to work.
4185 pyauto_errors.JSONInterfaceError if the automation call returns an error.
4187 cmd_dict = { 'command': 'ShowCreateAccountUI' }
4188 # See note below under LoginAsGuest(). ShowCreateAccountUI() logs
4189 # the user in as guest in order to access the account creation page.
4190 assert self._WaitForInodeChange(
4191 self._named_channel_id,
4192 lambda: self._GetResultFromJSONRequest(cmd_dict, windex=None)), \
4193 'Chrome did not reopen the testing channel after login as guest.'
4196 def SkipToLogin(self, skip_image_selection=True):
4197 """Skips OOBE to the login screen.
4199 Assumes that we're at the beginning of OOBE.
4202 skip_image_selection: Boolean indicating whether the user image selection
4203 screen should also be skipped.
4206 pyauto_errors.JSONInterfaceError if the automation call returns an error.
4208 cmd_dict = { 'command': 'SkipToLogin',
4209 'skip_image_selection': skip_image_selection }
4210 result = self._GetResultFromJSONRequest(cmd_dict, windex=None)
4211 assert result['next_screen'] == 'login', 'Unexpected wizard transition'
4213 def GetOOBEScreenInfo(self):
4214 """Queries info about the current OOBE screen.
4217 A dictionary with the following keys:
4219 'screen_name': The title of the current OOBE screen as a string.
4222 pyauto_errors.JSONInterfaceError if the automation call returns an error.
4224 cmd_dict = { 'command': 'GetOOBEScreenInfo' }
4225 return self._GetResultFromJSONRequest(cmd_dict, windex=None)
4227 def AcceptOOBENetworkScreen(self):
4228 """Accepts OOBE network screen and advances to the next one.
4230 Assumes that we're already at the OOBE network screen.
4233 A dictionary with the following keys:
4235 'next_screen': The title of the next OOBE screen as a string.
4238 pyauto_errors.JSONInterfaceError if the automation call returns an error.
4240 cmd_dict = { 'command': 'AcceptOOBENetworkScreen' }
4241 return self._GetResultFromJSONRequest(cmd_dict, windex=None)
4243 def AcceptOOBEEula(self, accepted, usage_stats_reporting=False):
4244 """Accepts OOBE EULA and advances to the next screen.
4246 Assumes that we're already at the OOBE EULA screen.
4249 accepted: Boolean indicating whether the EULA should be accepted.
4250 usage_stats_reporting: Boolean indicating whether UMA should be enabled.
4253 A dictionary with the following keys:
4255 'next_screen': The title of the next OOBE screen as a string.
4258 pyauto_errors.JSONInterfaceError if the automation call returns an error.
4260 cmd_dict = { 'command': 'AcceptOOBEEula',
4261 'accepted': accepted,
4262 'usage_stats_reporting': usage_stats_reporting }
4263 return self._GetResultFromJSONRequest(cmd_dict, windex=None)
4265 def CancelOOBEUpdate(self):
4266 """Skips update on OOBE and advances to the next screen.
4269 A dictionary with the following keys:
4271 'next_screen': The title of the next OOBE screen as a string.
4274 pyauto_errors.JSONInterfaceError if the automation call returns an error.
4276 cmd_dict = { 'command': 'CancelOOBEUpdate' }
4277 return self._GetResultFromJSONRequest(cmd_dict, windex=None)
4279 def PickUserImage(self, image):
4280 """Chooses image for the newly created user.
4282 Should be called immediately after login.
4285 image_type: type of user image to choose. Possible values:
4286 - "profile": Google profile image
4287 - non-negative int: one of the default images
4290 A dictionary with the following keys:
4292 'next_screen': The title of the next OOBE screen as a string.
4295 pyauto_errors.JSONInterfaceError if the automation call returns an error.
4297 cmd_dict = { 'command': 'PickUserImage',
4299 return self._GetResultFromJSONRequest(cmd_dict, windex=None)
4301 def LoginAsGuest(self):
4302 """Login to chromeos as a guest user.
4304 Waits until logged in.
4305 Should be displaying the login screen to work.
4308 pyauto_errors.JSONInterfaceError if the automation call returns an error.
4310 cmd_dict = { 'command': 'LoginAsGuest' }
4311 # Currently, logging in as guest causes session_manager to
4312 # restart Chrome, which will close the testing channel.
4313 # We need to call SetUp() again to reconnect to the new channel.
4314 assert self._WaitForInodeChange(
4315 self._named_channel_id,
4316 lambda: self._GetResultFromJSONRequest(cmd_dict, windex=None)), \
4317 'Chrome did not reopen the testing channel after login as guest.'
4320 def Login(self, username, password, timeout=120 * 1000):
4321 """Login to chromeos.
4323 Waits until logged in and browser is ready.
4324 Should be displaying the login screen to work.
4326 Note that in case of webui auth-extension-based login, gaia auth errors
4327 will not be noticed here, because the browser has no knowledge of it. In
4328 this case the GetNextEvent automation command will always time out.
4331 username: the username to log in as.
4332 password: the user's password.
4333 timeout: timeout in ms; defaults to two minutes.
4336 An error string if an error occured.
4340 pyauto_errors.JSONInterfaceError if the automation call returns an error.
4342 self._GetResultFromJSONRequest({'command': 'AddLoginEventObserver'},
4345 'command': 'SubmitLoginForm',
4346 'username': username,
4347 'password': password,
4349 self._GetResultFromJSONRequest(cmd_dict, windex=None)
4350 self.AddDomEventObserver('loginfail', automation_id=4444)
4352 if self.GetNextEvent(timeout=timeout).get('name') == 'loginfail':
4353 raise JSONInterfaceError('Login denied by auth server.')
4354 except JSONInterfaceError as e:
4355 raise JSONInterfaceError('Login failed. Perhaps Chrome crashed, '
4356 'failed to start, or the login flow is '
4357 'broken? Error message: %s' % str(e))
4360 """Log out from ChromeOS and wait for session_manager to come up.
4362 This is equivalent to pressing the 'Sign out' button from the
4363 aura shell tray when logged in.
4365 Should be logged in to work. Re-initializes the automation channel
4368 clear_profile_orig = self.get_clear_profile()
4369 self.set_clear_profile(False)
4370 assert self.GetLoginInfo()['is_logged_in'], \
4371 'Trying to log out when already logged out.'
4373 cmd_dict = { 'command': 'SignOut' }
4374 self._GetResultFromJSONRequest(cmd_dict, windex=None)
4375 assert self.WaitForSessionManagerRestart(_SignOut), \
4376 'Session manager did not restart after logout.'
4378 self.set_clear_profile(clear_profile_orig)
4380 def LockScreen(self):
4381 """Locks the screen on chromeos.
4383 Waits until screen is locked.
4384 Should be logged in and screen should not be locked to work.
4387 pyauto_errors.JSONInterfaceError if the automation call returns an error.
4389 cmd_dict = { 'command': 'LockScreen' }
4390 self._GetResultFromJSONRequest(cmd_dict, windex=None)
4392 def UnlockScreen(self, password):
4393 """Unlocks the screen on chromeos, authenticating the user's password first.
4395 Waits until screen is unlocked.
4396 Screen locker should be active for this to work.
4399 An error string if an error occured.
4403 pyauto_errors.JSONInterfaceError if the automation call returns an error.
4406 'command': 'UnlockScreen',
4407 'password': password,
4409 result = self._GetResultFromJSONRequest(cmd_dict, windex=None)
4410 return result.get('error_string')
4412 def SignoutInScreenLocker(self):
4413 """Signs out of chromeos using the screen locker's "Sign out" feature.
4415 Effectively the same as clicking the "Sign out" link on the screen locker.
4416 Screen should be locked for this to work.
4419 pyauto_errors.JSONInterfaceError if the automation call returns an error.
4421 cmd_dict = { 'command': 'SignoutInScreenLocker' }
4422 assert self.WaitForSessionManagerRestart(
4423 lambda: self._GetResultFromJSONRequest(cmd_dict, windex=None)), \
4424 'Session manager did not restart after logout.'
4427 def GetBatteryInfo(self):
4428 """Get details about battery state.
4431 A dictionary with the following keys:
4433 'battery_is_present': bool
4434 'line_power_on': bool
4435 if 'battery_is_present':
4436 'battery_percentage': float (0 ~ 100)
4437 'battery_fully_charged': bool
4439 'battery_time_to_full': int (seconds)
4441 'battery_time_to_empty': int (seconds)
4443 If it is still calculating the time left, 'battery_time_to_full'
4444 and 'battery_time_to_empty' will be absent.
4446 Use 'battery_fully_charged' instead of 'battery_percentage'
4447 or 'battery_time_to_full' to determine whether the battery
4448 is fully charged, since the percentage is only approximate.
4451 { u'battery_is_present': True,
4452 u'line_power_on': False,
4453 u'battery_time_to_empty': 29617,
4454 u'battery_percentage': 100.0,
4455 u'battery_fully_charged': False }
4458 pyauto_errors.JSONInterfaceError if the automation call returns an error.
4460 cmd_dict = { 'command': 'GetBatteryInfo' }
4461 return self._GetResultFromJSONRequest(cmd_dict, windex=None)
4463 def GetPanelInfo(self):
4464 """Get details about open ChromeOS panels.
4466 A panel is actually a type of browser window, so all of
4467 this information is also available using GetBrowserInfo().
4472 [{ 'incognito': False,
4473 'renderer_pid': 4820,
4474 'title': u'Downloads',
4475 'url': u'chrome://active-downloads/'}]
4478 pyauto_errors.JSONInterfaceError if the automation call returns an error.
4481 for browser in self.GetBrowserInfo()['windows']:
4482 if browser['type'] != 'panel':
4486 panels.append(panel)
4487 tab = browser['tabs'][0]
4488 panel['incognito'] = browser['incognito']
4489 panel['renderer_pid'] = tab['renderer_pid']
4490 panel['title'] = self.GetActiveTabTitle(browser['index'])
4491 panel['url'] = tab['url']
4495 def EnableSpokenFeedback(self, enabled):
4496 """Enables or disables spoken feedback accessibility mode.
4499 enabled: Boolean value indicating the desired state of spoken feedback.
4502 pyauto_errors.JSONInterfaceError if the automation call returns an error.
4505 'command': 'EnableSpokenFeedback',
4508 return self._GetResultFromJSONRequest(cmd_dict, windex=None)
4510 def IsSpokenFeedbackEnabled(self):
4511 """Check whether spoken feedback accessibility mode is enabled.
4514 True if spoken feedback is enabled, False otherwise.
4517 pyauto_errors.JSONInterfaceError if the automation call returns an error.
4519 cmd_dict = { 'command': 'IsSpokenFeedbackEnabled', }
4520 result = self._GetResultFromJSONRequest(cmd_dict, windex=None)
4521 return result.get('spoken_feedback')
4523 def GetTimeInfo(self, windex=0):
4524 """Gets info about the ChromeOS status bar clock.
4526 Set the 24-hour clock by using:
4527 self.SetPrefs('settings.clock.use_24hour_clock', True)
4532 {u'display_date': u'Tuesday, July 26, 2011',
4533 u'display_time': u'4:30',
4534 u'timezone': u'America/Los_Angeles'}
4537 pyauto_errors.JSONInterfaceError if the automation call returns an error.
4539 cmd_dict = { 'command': 'GetTimeInfo' }
4540 if self.GetLoginInfo()['is_logged_in']:
4541 return self._GetResultFromJSONRequest(cmd_dict, windex=windex)
4543 return self._GetResultFromJSONRequest(cmd_dict, windex=None)
4545 def SetTimezone(self, timezone):
4546 """Sets the timezone on ChromeOS. A user must be logged in.
4548 The timezone is the relative path to the timezone file in
4549 /usr/share/zoneinfo. For example, /usr/share/zoneinfo/America/Los_Angeles is
4550 'America/Los_Angeles'. For a list of valid timezones see
4551 'chromeos/settings/timezone_settings.cc'.
4553 This method does not return indication of success or failure.
4554 If the timezone is it falls back to a valid timezone.
4557 pyauto_errors.JSONInterfaceError if the automation call returns an error.
4560 'command': 'SetTimezone',
4561 'timezone': timezone,
4563 self._GetResultFromJSONRequest(cmd_dict, windex=None)
4565 def UpdateCheck(self):
4566 """Checks for a ChromeOS update. Blocks until finished updating.
4569 pyauto_errors.JSONInterfaceError if the automation call returns an error.
4571 cmd_dict = { 'command': 'UpdateCheck' }
4572 self._GetResultFromJSONRequest(cmd_dict, windex=None)
4574 def GetVolumeInfo(self):
4575 """Gets the volume and whether the device is muted.
4580 (47.763456790123456, False)
4583 pyauto_errors.JSONInterfaceError if the automation call returns an error.
4585 cmd_dict = { 'command': 'GetVolumeInfo' }
4586 return self._GetResultFromJSONRequest(cmd_dict, windex=None)
4588 def SetVolume(self, volume):
4589 """Sets the volume on ChromeOS. Only valid if not muted.
4592 volume: The desired volume level as a percent from 0 to 100.
4595 pyauto_errors.JSONInterfaceError if the automation call returns an error.
4597 assert volume >= 0 and volume <= 100
4599 'command': 'SetVolume',
4600 'volume': float(volume),
4602 return self._GetResultFromJSONRequest(cmd_dict, windex=None)
4604 def SetMute(self, mute):
4605 """Sets whether ChromeOS is muted or not.
4608 mute: True to mute, False to unmute.
4611 pyauto_errors.JSONInterfaceError if the automation call returns an error.
4613 cmd_dict = { 'command': 'SetMute' }
4615 'command': 'SetMute',
4618 return self._GetResultFromJSONRequest(cmd_dict, windex=None)
4622 def OpenCrosh(self):
4625 Equivalent to pressing Ctrl-Alt-t.
4626 Opens in the last active (non-incognito) window.
4628 Waits long enough for crosh to load, but does not wait for the crosh
4629 prompt. Use WaitForHtermText() for that.
4631 cmd_dict = { 'command': 'OpenCrosh' }
4632 self._GetResultFromJSONRequest(cmd_dict, windex=None)
4634 def WaitForHtermText(self, text, msg=None, tab_index=0, windex=0):
4635 """Waits for the given text in a hterm tab.
4637 Can be used to wait for the crosh> prompt or ssh prompt.
4639 This does not poll. It uses dom mutation observers to wait
4640 for the given text to show up.
4643 text: the text to wait for. Can be a regex.
4644 msg: the failure message to emit if the text could not be found.
4645 tab_index: the tab for the hterm tab. Default: 0.
4646 windex: the window index for the hterm tab. Default: 0.
4648 self.WaitForDomNode(
4649 xpath='//*[contains(text(), "%s")]' % text, frame_xpath='//iframe',
4650 msg=msg, tab_index=tab_index, windex=windex)
4652 def GetHtermRowsText(self, start, end, tab_index=0, windex=0):
4653 """Fetch rows from a html terminal tab.
4655 Works for both crosh and ssh tab.
4656 Uses term_.getRowsText(start, end) javascript call.
4659 start: start line number (0-based).
4660 end: the end line (one beyond the line of interest).
4661 tab_index: the tab for the hterm tab. Default: 0.
4662 windex: the window index for the hterm tab. Default: 0.
4664 return self.ExecuteJavascript(
4665 'domAutomationController.send(term_.getRowsText(%d, %d))' % (
4667 tab_index=tab_index, windex=windex)
4669 def SendKeysToHterm(self, text, tab_index=0, windex=0):
4670 """Send keys to a html terminal tab.
4672 Works for both crosh and ssh tab.
4673 Uses term_.onVTKeystroke(str) javascript call.
4676 text: the text to send.
4677 tab_index: the tab for the hterm tab. Default: 0.
4678 windex: the window index for the hterm tab. Default: 0.
4680 return self.ExecuteJavascript(
4681 'term_.onVTKeystroke("%s");'
4682 'domAutomationController.send("done")' % text,
4683 tab_index=tab_index, windex=windex)
4686 def GetMemoryStatsChromeOS(self, duration):
4687 """Identifies and returns different kinds of current memory usage stats.
4689 This function samples values each second for |duration| seconds, then
4690 outputs the min, max, and ending values for each measurement type.
4693 duration: The number of seconds to sample data before outputting the
4694 minimum, maximum, and ending values for each measurement type.
4697 A dictionary containing memory usage information. Each measurement type
4698 is associated with the min, max, and ending values from among all
4699 sampled values. Values are specified in KB.
4701 'gem_obj': { # GPU memory usage.
4706 'gtt': { ... }, # GPU memory usage (graphics translation table).
4707 'mem_free': { ... }, # CPU free memory.
4708 'mem_available': { ... }, # CPU available memory.
4709 'mem_shared': { ... }, # CPU shared memory.
4710 'mem_cached': { ... }, # CPU cached memory.
4711 'mem_anon': { ... }, # CPU anon memory (active + inactive).
4712 'mem_file': { ... }, # CPU file memory (active + inactive).
4713 'mem_slab': { ... }, # CPU slab memory.
4714 'browser_priv': { ... }, # Chrome browser private memory.
4715 'browser_shared': { ... }, # Chrome browser shared memory.
4716 'gpu_priv': { ... }, # Chrome GPU private memory.
4717 'gpu_shared': { ... }, # Chrome GPU shared memory.
4718 'renderer_priv': { ... }, # Total private memory of all renderers.
4719 'renderer_shared': { ... }, # Total shared memory of all renderers.
4722 logging.debug('Sampling memory information for %d seconds...' % duration)
4725 for _ in xrange(duration):
4727 gem_obj_path = '/sys/kernel/debug/dri/0/i915_gem_objects'
4728 if os.path.exists(gem_obj_path):
4729 p = subprocess.Popen('grep bytes %s' % gem_obj_path,
4730 stdout=subprocess.PIPE, shell=True)
4731 stdout = p.communicate()[0]
4733 gem_obj = re.search(
4734 '\d+ objects, (\d+) bytes\n', stdout).group(1)
4735 if 'gem_obj' not in stats:
4736 stats['gem_obj'] = []
4737 stats['gem_obj'].append(int(gem_obj) / 1024.0)
4739 gtt_path = '/sys/kernel/debug/dri/0/i915_gem_gtt'
4740 if os.path.exists(gtt_path):
4741 p = subprocess.Popen('grep bytes %s' % gtt_path,
4742 stdout=subprocess.PIPE, shell=True)
4743 stdout = p.communicate()[0]
4746 'Total [\d]+ objects, ([\d]+) bytes', stdout).group(1)
4747 if 'gtt' not in stats:
4749 stats['gtt'].append(int(gtt) / 1024.0)
4753 with open('/proc/meminfo') as f:
4755 mem_free = re.search('MemFree:\s*([\d]+) kB', stdout).group(1)
4757 if 'mem_free' not in stats:
4758 stats['mem_free'] = []
4759 stats['mem_free'].append(int(mem_free))
4761 mem_dirty = re.search('Dirty:\s*([\d]+) kB', stdout).group(1)
4762 mem_active_file = re.search(
4763 'Active\(file\):\s*([\d]+) kB', stdout).group(1)
4764 mem_inactive_file = re.search(
4765 'Inactive\(file\):\s*([\d]+) kB', stdout).group(1)
4767 with open('/proc/sys/vm/min_filelist_kbytes') as f:
4768 mem_min_file = f.read()
4770 # Available memory =
4771 # MemFree + ActiveFile + InactiveFile - DirtyMem - MinFileMem
4772 if 'mem_available' not in stats:
4773 stats['mem_available'] = []
4774 stats['mem_available'].append(
4775 int(mem_free) + int(mem_active_file) + int(mem_inactive_file) -
4776 int(mem_dirty) - int(mem_min_file))
4778 mem_shared = re.search('Shmem:\s*([\d]+) kB', stdout).group(1)
4779 if 'mem_shared' not in stats:
4780 stats['mem_shared'] = []
4781 stats['mem_shared'].append(int(mem_shared))
4783 mem_cached = re.search('Cached:\s*([\d]+) kB', stdout).group(1)
4784 if 'mem_cached' not in stats:
4785 stats['mem_cached'] = []
4786 stats['mem_cached'].append(int(mem_cached))
4788 mem_anon_active = re.search('Active\(anon\):\s*([\d]+) kB',
4790 mem_anon_inactive = re.search('Inactive\(anon\):\s*([\d]+) kB',
4792 if 'mem_anon' not in stats:
4793 stats['mem_anon'] = []
4794 stats['mem_anon'].append(int(mem_anon_active) + int(mem_anon_inactive))
4796 mem_file_active = re.search('Active\(file\):\s*([\d]+) kB',
4798 mem_file_inactive = re.search('Inactive\(file\):\s*([\d]+) kB',
4800 if 'mem_file' not in stats:
4801 stats['mem_file'] = []
4802 stats['mem_file'].append(int(mem_file_active) + int(mem_file_inactive))
4804 mem_slab = re.search('Slab:\s*([\d]+) kB', stdout).group(1)
4805 if 'mem_slab' not in stats:
4806 stats['mem_slab'] = []
4807 stats['mem_slab'].append(int(mem_slab))
4809 # Chrome process memory.
4810 pinfo = self.GetProcessInfo()['browsers'][0]['processes']
4811 total_renderer_priv = 0
4812 total_renderer_shared = 0
4813 for process in pinfo:
4814 mem_priv = process['working_set_mem']['priv']
4815 mem_shared = process['working_set_mem']['shared']
4816 if process['child_process_type'] == 'Browser':
4817 if 'browser_priv' not in stats:
4818 stats['browser_priv'] = []
4819 stats['browser_priv'].append(int(mem_priv))
4820 if 'browser_shared' not in stats:
4821 stats['browser_shared'] = []
4822 stats['browser_shared'].append(int(mem_shared))
4823 elif process['child_process_type'] == 'GPU':
4824 if 'gpu_priv' not in stats:
4825 stats['gpu_priv'] = []
4826 stats['gpu_priv'].append(int(mem_priv))
4827 if 'gpu_shared' not in stats:
4828 stats['gpu_shared'] = []
4829 stats['gpu_shared'].append(int(mem_shared))
4830 elif process['child_process_type'] == 'Tab':
4831 # Sum the memory of all renderer processes.
4832 total_renderer_priv += int(mem_priv)
4833 total_renderer_shared += int(mem_shared)
4834 if 'renderer_priv' not in stats:
4835 stats['renderer_priv'] = []
4836 stats['renderer_priv'].append(int(total_renderer_priv))
4837 if 'renderer_shared' not in stats:
4838 stats['renderer_shared'] = []
4839 stats['renderer_shared'].append(int(total_renderer_shared))
4843 # Compute min, max, and ending values to return.
4845 for measurement_type in stats:
4846 values = stats[measurement_type]
4847 result[measurement_type] = {
4855 ## ChromeOS section -- end
4858 class ExtraBrowser(PyUITest):
4859 """Launches a new browser with some extra flags.
4861 The new browser is launched with its own fresh profile.
4862 This class does not apply to ChromeOS.
4864 def __init__(self, chrome_flags=[], methodName='runTest', **kwargs):
4865 """Accepts extra chrome flags for launching a new browser instance.
4868 chrome_flags: list of extra flags when launching a new browser.
4870 assert not PyUITest.IsChromeOS(), \
4871 'This function cannot be used to launch a new browser in ChromeOS.'
4872 PyUITest.__init__(self, methodName=methodName, **kwargs)
4873 self._chrome_flags = chrome_flags
4874 PyUITest.setUp(self)
4877 """Tears down the browser and then calls super class's destructor"""
4878 PyUITest.tearDown(self)
4879 PyUITest.__del__(self)
4881 def ExtraChromeFlags(self):
4882 """Prepares the browser to launch with specified Chrome flags."""
4883 return PyUITest.ExtraChromeFlags(self) + self._chrome_flags
4886 class _RemoteProxy():
4887 """Class for PyAuto remote method calls.
4889 Use this class along with RemoteHost.testRemoteHost to establish a PyAuto
4890 connection with another machine and make remote PyAuto calls. The RemoteProxy
4891 mimics a PyAuto object, so all json-style PyAuto calls can be made on it.
4893 The remote host acts as a dumb executor that receives method call requests,
4894 executes them, and sends all of the results back to the RemoteProxy, including
4895 the return value, thrown exceptions, and console output.
4897 The remote host should be running the same version of PyAuto as the proxy.
4898 A mismatch could lead to undefined behavior.
4901 class MyTest(pyauto.PyUITest):
4902 def testRemoteExample(self):
4903 remote = pyauto._RemoteProxy(('127.0.0.1', 7410))
4904 remote.NavigateToURL('http://www.google.com')
4905 title = remote.GetActiveTabTitle()
4906 self.assertEqual(title, 'Google')
4908 class RemoteException(Exception):
4911 def __init__(self, host):
4912 self.RemoteConnect(host)
4914 def RemoteConnect(self, host):
4916 while time.time() - begin < 50:
4917 self._socket = socket.socket()
4918 if not self._socket.connect_ex(host):
4922 # Make one last attempt, but raise a socket error on failure.
4923 self._socket = socket.socket()
4924 self._socket.connect(host)
4926 def RemoteDisconnect(self):
4928 self._socket.shutdown(socket.SHUT_RDWR)
4929 self._socket.close()
4932 def CreateTarget(self, target):
4933 """Registers the methods and creates a remote instance of a target.
4935 Any RPC calls will then be made on the remote target instance. Note that the
4936 remote instance will be a brand new instance and will have none of the state
4937 of the local instance. The target's class should have a constructor that
4940 self._Call('CreateTarget', target.__class__)
4941 self._RegisterClassMethods(target)
4943 def _RegisterClassMethods(self, remote_class):
4944 # Make remote-call versions of all remote_class methods.
4945 for method_name, _ in inspect.getmembers(remote_class, inspect.ismethod):
4946 # Ignore private methods and duplicates.
4947 if method_name[0] in string.letters and \
4948 getattr(self, method_name, None) is None:
4949 setattr(self, method_name, functools.partial(self._Call, method_name))
4951 def _Call(self, method_name, *args, **kwargs):
4953 request = pickle.dumps((method_name, args, kwargs))
4954 if self._socket.send(request) != len(request):
4955 raise self.RemoteException('Error sending remote method call request.')
4958 response = self._socket.recv(4096)
4960 raise self.RemoteException('Client disconnected during method call.')
4961 result, stdout, stderr, exception = pickle.loads(response)
4963 # Print any output the client captured, throw any exceptions, and return.
4964 sys.stdout.write(stdout)
4965 sys.stderr.write(stderr)
4967 raise self.RemoteException('%s raised by remote client: %s' %
4968 (exception[0], exception[1]))
4972 class PyUITestSuite(pyautolib.PyUITestSuiteBase, unittest.TestSuite):
4973 """Base TestSuite for PyAuto UI tests."""
4975 def __init__(self, args):
4976 pyautolib.PyUITestSuiteBase.__init__(self, args)
4978 # Figure out path to chromium binaries
4979 browser_dir = os.path.normpath(os.path.dirname(pyautolib.__file__))
4980 logging.debug('Loading pyauto libs from %s', browser_dir)
4981 self.InitializeWithPath(pyautolib.FilePath(browser_dir))
4982 os.environ['PATH'] = browser_dir + os.pathsep + os.environ['PATH']
4984 unittest.TestSuite.__init__(self)
4985 cr_source_root = os.path.normpath(os.path.join(
4986 os.path.dirname(__file__), os.pardir, os.pardir, os.pardir))
4987 self.SetCrSourceRoot(pyautolib.FilePath(cr_source_root))
4989 # Start http server, if needed.
4991 if _OPTIONS and not _OPTIONS.no_http_server:
4992 self._StartHTTPServer()
4993 if _OPTIONS and _OPTIONS.remote_host:
4994 self._ConnectToRemoteHosts(_OPTIONS.remote_host.split(','))
4997 # python unittest module is setup such that the suite gets deleted before
4998 # the test cases, which is odd because our test cases depend on
4999 # initializtions like exitmanager, autorelease pool provided by the
5000 # suite. Forcibly delete the test cases before the suite.
5002 pyautolib.PyUITestSuiteBase.__del__(self)
5006 self._StopHTTPServer()
5008 global _CHROME_DRIVER_FACTORY
5009 if _CHROME_DRIVER_FACTORY is not None:
5010 _CHROME_DRIVER_FACTORY.Stop()
5012 def _StartHTTPServer(self):
5013 """Start a local file server hosting data files over http://"""
5015 assert not _HTTP_SERVER, 'HTTP Server already started'
5016 http_data_dir = _OPTIONS.http_data_dir
5017 http_server = pyautolib.SpawnedTestServer(
5018 pyautolib.SpawnedTestServer.TYPE_HTTP,
5020 pyautolib.FilePath(http_data_dir))
5021 assert http_server.Start(), 'Could not start http server'
5022 _HTTP_SERVER = http_server
5023 logging.debug('Started http server at "%s".', http_data_dir)
5025 def _StopHTTPServer(self):
5026 """Stop the local http server."""
5028 assert _HTTP_SERVER, 'HTTP Server not yet started'
5029 assert _HTTP_SERVER.Stop(), 'Could not stop http server'
5031 logging.debug('Stopped http server.')
5033 def _ConnectToRemoteHosts(self, addresses):
5034 """Connect to remote PyAuto instances using a RemoteProxy.
5036 The RemoteHost instances must already be running."""
5037 global _REMOTE_PROXY
5038 assert not _REMOTE_PROXY, 'Already connected to a remote host.'
5040 for address in addresses:
5041 if address == 'localhost' or address == '127.0.0.1':
5042 self._StartLocalRemoteHost()
5043 _REMOTE_PROXY.append(_RemoteProxy((address, 7410)))
5045 def _StartLocalRemoteHost(self):
5046 """Start a remote PyAuto instance on the local machine."""
5047 # Add the path to our main class to the RemoteHost's
5048 # environment, so it can load that class at runtime.
5050 main_path = os.path.dirname(__main__.__file__)
5052 if env.get('PYTHONPATH', None):
5053 env['PYTHONPATH'] += ':' + main_path
5055 env['PYTHONPATH'] = main_path
5058 subprocess.Popen([sys.executable, os.path.join(os.path.dirname(__file__),
5059 'remote_host.py')], env=env)
5062 class _GTestTextTestResult(unittest._TextTestResult):
5063 """A test result class that can print formatted text results to a stream.
5065 Results printed in conformance with gtest output format, like:
5066 [ RUN ] autofill.AutofillTest.testAutofillInvalid: "test desc."
5067 [ OK ] autofill.AutofillTest.testAutofillInvalid
5068 [ RUN ] autofill.AutofillTest.testFillProfile: "test desc."
5069 [ OK ] autofill.AutofillTest.testFillProfile
5070 [ RUN ] autofill.AutofillTest.testFillProfileCrazyCharacters: "Test."
5071 [ OK ] autofill.AutofillTest.testFillProfileCrazyCharacters
5073 def __init__(self, stream, descriptions, verbosity):
5074 unittest._TextTestResult.__init__(self, stream, descriptions, verbosity)
5076 def _GetTestURI(self, test):
5077 if sys.version_info[:2] <= (2, 4):
5078 return '%s.%s' % (unittest._strclass(test.__class__),
5079 test._TestCase__testMethodName)
5080 return '%s.%s.%s' % (test.__class__.__module__,
5081 test.__class__.__name__,
5082 test._testMethodName)
5084 def getDescription(self, test):
5085 return '%s: "%s"' % (self._GetTestURI(test), test.shortDescription())
5087 def startTest(self, test):
5088 unittest.TestResult.startTest(self, test)
5089 self.stream.writeln('[ RUN ] %s' % self.getDescription(test))
5091 def addSuccess(self, test):
5092 unittest.TestResult.addSuccess(self, test)
5093 self.stream.writeln('[ OK ] %s' % self._GetTestURI(test))
5095 def addError(self, test, err):
5096 unittest.TestResult.addError(self, test, err)
5097 self.stream.writeln('[ ERROR ] %s' % self._GetTestURI(test))
5099 def addFailure(self, test, err):
5100 unittest.TestResult.addFailure(self, test, err)
5101 self.stream.writeln('[ FAILED ] %s' % self._GetTestURI(test))
5104 class PyAutoTextTestRunner(unittest.TextTestRunner):
5105 """Test Runner for PyAuto tests that displays results in textual format.
5107 Results are displayed in conformance with gtest output.
5109 def __init__(self, verbosity=1):
5110 unittest.TextTestRunner.__init__(self,
5112 verbosity=verbosity)
5114 def _makeResult(self):
5115 return _GTestTextTestResult(self.stream, self.descriptions, self.verbosity)
5118 # Implementation inspired from unittest.main()
5120 """Main program for running PyAuto tests."""
5122 _options, _args = None, None
5123 _tests_filename = 'PYAUTO_TESTS'
5129 'chromeos': 'chromeos',
5136 def _ParseArgs(self):
5137 """Parse command line args."""
5138 parser = optparse.OptionParser()
5140 '', '--channel-id', type='string', default='',
5141 help='Name of channel id, if using named interface.')
5143 '', '--chrome-flags', type='string', default='',
5144 help='Flags passed to Chrome. This is in addition to the usual flags '
5145 'like suppressing first-run dialogs, enabling automation. '
5146 'See chrome/common/chrome_switches.cc for the list of flags '
5147 'chrome understands.')
5149 '', '--http-data-dir', type='string',
5150 default=os.path.join('chrome', 'test', 'data'),
5151 help='Relative path from which http server should serve files.')
5153 '-L', '--list-tests', action='store_true', default=False,
5154 help='List all tests, and exit.')
5157 help='Specify sharding params. Example: 1/3 implies split the list of '
5158 'tests into 3 groups of which this is the 1st.')
5160 '', '--log-file', type='string', default=None,
5161 help='Provide a path to a file to which the logger will log')
5163 '', '--no-http-server', action='store_true', default=False,
5164 help='Do not start an http server to serve files in data dir.')
5166 '', '--remote-host', type='string', default=None,
5167 help='Connect to remote hosts for remote automation. If "localhost" '
5168 '"127.0.0.1" is specified, a remote host will be launched '
5169 'automatically on the local machine.')
5171 '', '--repeat', type='int', default=1,
5172 help='Number of times to repeat the tests. Useful to determine '
5173 'flakiness. Defaults to 1.')
5175 '-S', '--suite', type='string', default='FULL',
5176 help='Name of the suite to load. Defaults to "FULL".')
5178 '-v', '--verbose', action='store_true', default=False,
5179 help='Make PyAuto verbose.')
5181 '-D', '--wait-for-debugger', action='store_true', default=False,
5182 help='Block PyAuto on startup for attaching debugger.')
5184 self._options, self._args = parser.parse_args()
5186 _OPTIONS = self._options # Export options so other classes can access.
5188 # Set up logging. All log messages will be prepended with a timestamp.
5189 format = '%(asctime)s %(levelname)-8s %(message)s'
5191 level = logging.INFO
5192 if self._options.verbose:
5195 logging.basicConfig(level=level, format=format,
5196 filename=self._options.log_file)
5199 """Returns the path to dir containing tests.
5201 This is typically the dir containing the tests description file.
5202 This method should be overridden by derived class to point to other dirs
5205 return os.path.dirname(__file__)
5208 def _ImportTestsFromName(name):
5209 """Get a list of all test names from the given string.
5212 name: dot-separated string for a module, a test case or a test method.
5213 Examples: omnibox (a module)
5214 omnibox.OmniboxTest (a test case)
5215 omnibox.OmniboxTest.testA (a test method)
5218 [omnibox.OmniboxTest.testA, omnibox.OmniboxTest.testB, ...]
5220 def _GetTestsFromTestCase(class_obj):
5221 """Return all test method names from given class object."""
5222 return [class_obj.__name__ + '.' + x for x in dir(class_obj) if
5223 x.startswith('test')]
5225 def _GetTestsFromModule(module):
5226 """Return all test method names from the given module object."""
5228 for name in dir(module):
5229 obj = getattr(module, name)
5230 if (isinstance(obj, (type, types.ClassType)) and
5231 issubclass(obj, PyUITest) and obj != PyUITest):
5232 tests.extend([module.__name__ + '.' + x for x in
5233 _GetTestsFromTestCase(obj)])
5238 parts = name.split('.')
5239 parts_copy = parts[:]
5242 module = __import__('.'.join(parts_copy))
5246 if not parts_copy: raise
5247 # We have the module. Pick the exact test method or class asked for.
5251 obj = getattr(obj, part)
5253 if type(obj) == types.ModuleType:
5254 return _GetTestsFromModule(obj)
5255 elif (isinstance(obj, (type, types.ClassType)) and
5256 issubclass(obj, PyUITest) and obj != PyUITest):
5257 return [module.__name__ + '.' + x for x in _GetTestsFromTestCase(obj)]
5258 elif type(obj) == types.UnboundMethodType:
5261 logging.warn('No tests in "%s"', name)
5264 def _HasTestCases(self, module_string):
5265 """Determines if we have any PyUITest test case classes in the module
5266 identified by |module_string|."""
5267 module = __import__(module_string)
5268 for name in dir(module):
5269 obj = getattr(module, name)
5270 if (isinstance(obj, (type, types.ClassType)) and
5271 issubclass(obj, PyUITest)):
5275 def _ExpandTestNames(self, args):
5276 """Returns a list of tests loaded from the given args.
5278 The given args can be either a module (ex: module1) or a testcase
5279 (ex: module2.MyTestCase) or a test (ex: module1.MyTestCase.testX)
5280 or a suite name (ex: @FULL). If empty, the tests in the already imported
5284 args: [module1, module2, module3.testcase, module4.testcase.testX]
5285 These modules or test cases or tests should be importable.
5286 Suites can be specified by prefixing @. Example: @FULL
5289 a list of expanded test names. Example:
5291 'module1.TestCase1.testA',
5292 'module1.TestCase1.testB',
5293 'module2.TestCase2.testX',
5294 'module3.testcase.testY',
5295 'module4.testcase.testX'
5299 def _TestsFromDescriptionFile(suite):
5300 pyauto_tests_file = os.path.join(self.TestsDir(), self._tests_filename)
5302 logging.debug("Reading %s (@%s)", pyauto_tests_file, suite)
5304 logging.debug("Reading %s", pyauto_tests_file)
5305 if not os.path.exists(pyauto_tests_file):
5306 logging.warn("%s missing. Cannot load tests.", pyauto_tests_file)
5309 return self._ExpandTestNamesFrom(pyauto_tests_file, suite)
5311 if not args: # Load tests ourselves
5312 if self._HasTestCases('__main__'): # we are running a test script
5313 module_name = os.path.splitext(os.path.basename(sys.argv[0]))[0]
5314 args.append(module_name) # run the test cases found in it
5315 else: # run tests from the test description file
5316 args = _TestsFromDescriptionFile(self._options.suite)
5317 else: # Check args with @ prefix for suites
5320 if arg.startswith('@'):
5322 out_args += _TestsFromDescriptionFile(suite)
5324 out_args.append(arg)
5328 def _ExpandTestNamesFrom(self, filename, suite):
5329 """Load test names from the given file.
5332 filename: the file to read the tests from
5333 suite: the name of the suite to load from |filename|.
5336 a list of test names
5337 [module.testcase.testX, module.testcase.testY, ..]
5339 suites = PyUITest.EvalDataFrom(filename)
5340 platform = sys.platform
5341 if PyUITest.IsChromeOS(): # check if it's chromeos
5342 platform = 'chromeos'
5343 assert platform in self._platform_map, '%s unsupported' % platform
5344 def _NamesInSuite(suite_name):
5345 logging.debug('Expanding suite %s', suite_name)
5346 platforms = suites.get(suite_name)
5347 names = platforms.get('all', []) + \
5348 platforms.get(self._platform_map[platform], [])
5350 # Recursively include suites if any. Suites begin with @.
5352 if name.startswith('@'): # Include another suite
5353 ret.extend(_NamesInSuite(name[1:]))
5358 assert suite in suites, '%s: No such suite in %s' % (suite, filename)
5359 all_names = _NamesInSuite(suite)
5362 # Find all excluded tests. Excluded tests begin with '-'.
5363 for name in all_names:
5364 if name.startswith('-'): # Exclude
5365 excluded.extend(self._ImportTestsFromName(name[1:]))
5367 args.extend(self._ImportTestsFromName(name))
5368 for name in excluded:
5372 logging.warn('Cannot exclude %s. Not included. Ignoring', name)
5374 logging.debug('Excluded %d test(s): %s', len(excluded), excluded)
5378 """Run the tests."""
5379 if self._options.wait_for_debugger:
5380 raw_input('Attach debugger to process %s and hit <enter> ' % os.getpid())
5382 suite_args = [sys.argv[0]]
5383 chrome_flags = self._options.chrome_flags
5384 # Set CHROME_HEADLESS. It enables crash reporter on posix.
5385 os.environ['CHROME_HEADLESS'] = '1'
5386 os.environ['EXTRA_CHROME_FLAGS'] = chrome_flags
5387 test_names = self._ExpandTestNames(self._args)
5389 # Shard, if requested (--shard).
5390 if self._options.shard:
5391 matched = re.match('(\d+)/(\d+)', self._options.shard)
5393 print >>sys.stderr, 'Invalid sharding params: %s' % self._options.shard
5395 shard_index = int(matched.group(1)) - 1
5396 num_shards = int(matched.group(2))
5397 if shard_index < 0 or shard_index >= num_shards:
5398 print >>sys.stderr, 'Invalid sharding params: %s' % self._options.shard
5400 test_names = pyauto_utils.Shard(test_names, shard_index, num_shards)
5402 test_names *= self._options.repeat
5403 logging.debug("Loading %d tests from %s", len(test_names), test_names)
5404 if self._options.list_tests: # List tests and exit
5405 for name in test_names:
5408 pyauto_suite = PyUITestSuite(suite_args)
5409 loaded_tests = unittest.defaultTestLoader.loadTestsFromNames(test_names)
5410 pyauto_suite.addTests(loaded_tests)
5412 if self._options.verbose:
5414 result = PyAutoTextTestRunner(verbosity=verbosity).run(pyauto_suite)
5415 del loaded_tests # Need to destroy test cases before the suite
5417 successful = result.wasSuccessful()
5419 pyauto_tests_file = os.path.join(self.TestsDir(), self._tests_filename)
5420 print >>sys.stderr, 'Tests can be disabled by editing %s. ' \
5421 'Ref: %s' % (pyauto_tests_file, _PYAUTO_DOC_URL)
5422 sys.exit(not successful)
5425 if __name__ == '__main__':