1 #!/usr/bin/env python 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. 5 6 """PyAuto: Python Interface to Chromium's Automation Proxy. 7 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. 11 12 Ref: http://dev.chromium.org/developers/testing/pyauto 13 14 15 Include the following in your PyAuto test script to make it run standalone. 16 17 from pyauto import Main 18 19 if __name__ == '__main__': 20 Main() 21 22 This script can be used as an executable to fire off other scripts, similar 23 to unittest.py 24 python pyauto.py test_script 25 """ 26 27 import cStringIO 28 import copy 29 import functools 30 import hashlib 31 import inspect 32 import logging 33 import optparse 34 import os 35 import pickle 36 import pprint 37 import re 38 import shutil 39 import signal 40 import socket 41 import stat 42 import string 43 import subprocess 44 import sys 45 import tempfile 46 import time 47 import types 48 import unittest 49 import urllib 50 51 import pyauto_paths 52 53 54 def _LocateBinDirs(): 55 """Setup a few dirs where we expect to find dependency libraries.""" 56 deps_dirs = [ 57 os.path.dirname(__file__), 58 pyauto_paths.GetThirdPartyDir(), 59 os.path.join(pyauto_paths.GetThirdPartyDir(), 'webdriver', 'pylib'), 60 ] 61 sys.path += map(os.path.normpath, pyauto_paths.GetBuildDirs() + deps_dirs) 62 63 _LocateBinDirs() 64 65 _PYAUTO_DOC_URL = 'http://dev.chromium.org/developers/testing/pyauto' 66 67 try: 68 import pyautolib 69 # Needed so that all additional classes (like: FilePath, GURL) exposed by 70 # swig interface get available in this module. 71 from pyautolib import * 72 except ImportError: 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*' 78 raise 79 80 # Should go after sys.path is set appropriately 81 import bookmark_model 82 import download_info 83 import history_info 84 import omnibox_info 85 import plugins_info 86 import prefs_info 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 92 import pyauto_utils 93 import simplejson as json # found in third_party 94 95 _CHROME_DRIVER_FACTORY = None 96 _DEFAULT_AUTOMATION_TIMEOUT = 45 97 _HTTP_SERVER = None 98 _REMOTE_PROXY = None 99 _OPTIONS = None 100 _BROWSER_PID = None 101 102 class PyUITest(pyautolib.PyUITestBase, unittest.TestCase): 103 """Base class for UI Test Cases in Python. 104 105 A browser is created before executing each test, and is destroyed after 106 each test irrespective of whether the test passed or failed. 107 108 You should derive from this class and create methods with 'test' prefix, 109 and use methods inherited from PyUITestBase (the C++ side). 110 111 Example: 112 113 class MyTest(PyUITest): 114 115 def testNavigation(self): 116 self.NavigateToURL("http://www.google.com") 117 self.assertEqual("Google", self.GetActiveTabTitle()) 118 """ 119 120 def __init__(self, methodName='runTest', **kwargs): 121 """Initialize PyUITest. 122 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 126 127 Args: 128 methodName: the default method name. Internal use by unittest module 129 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.) 132 133 clear_profile: If True, clean the profile dir before use. Defaults to True 134 homepage: the home page. Defaults to "about:blank" 135 """ 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 140 141 pyautolib.PyUITestBase.__init__(self, clear_profile, homepage) 142 self.Initialize(pyautolib.FilePath(self.BrowserPath())) 143 unittest.TestCase.__init__(self, methodName) 144 145 # Give all pyauto tests easy access to pprint.PrettyPrinter functions. 146 self.pprint = pprint.pprint 147 self.pformat = pprint.pformat 148 149 # Set up remote proxies, if they were requested. 150 self.remotes = [] 151 self.remote = None 152 global _REMOTE_PROXY 153 if _REMOTE_PROXY: 154 self.remotes = _REMOTE_PROXY 155 self.remote = _REMOTE_PROXY[0] 156 157 def __del__(self): 158 pyautolib.PyUITestBase.__del__(self) 159 160 def _SetExtraChromeFlags(self): 161 """Prepares the browser to launch with the specified extra Chrome flags. 162 163 This function is called right before the browser is launched for the first 164 time. 165 """ 166 for flag in self.ExtraChromeFlags(): 167 if flag.startswith('--'): 168 flag = flag[2:] 169 split_pos = flag.find('=') 170 if split_pos >= 0: 171 flag_name = flag[:split_pos] 172 flag_val = flag[split_pos + 1:] 173 self.AppendBrowserLaunchSwitch(flag_name, flag_val) 174 else: 175 self.AppendBrowserLaunchSwitch(flag) 176 177 def __SetUp(self): 178 named_channel_id = None 179 if _OPTIONS: 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() 187 else: 188 self._SetExtraChromeFlags() # Flags already previously set for ChromeOS. 189 if named_channel_id: 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 193 # on ChromeOS). 194 self.SetUp() 195 196 global _BROWSER_PID 197 try: 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.') 203 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() 210 211 if (self.IsChromeOS() and not self.GetLoginInfo()['is_logged_in'] and 212 self.ShouldOOBESkipToLogin()): 213 if self.GetOOBEScreenInfo()['screen_name'] != 'login': 214 self.SkipToLogin() 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]) 223 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) 228 remote.setUp() 229 230 def setUp(self): 231 """Override this method to launch browser differently. 232 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. 235 236 When using the named interface, it connects to an existing browser 237 instance. 238 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. 246 """ 247 self.__SetUp() 248 249 def tearDown(self): 250 for remote in self.remotes: 251 remote.tearDown() 252 253 self.TearDown() # Destroy browser 254 255 # Method required by the Python standard library unittest.TestCase. 256 def runTest(self): 257 pass 258 259 @staticmethod 260 def BrowserPath(): 261 """Returns the path to Chromium binaries. 262 263 Expects the browser binaries to be in the 264 same location as the pyautolib binaries. 265 """ 266 return os.path.normpath(os.path.dirname(pyautolib.__file__)) 267 268 def ExtraChromeFlags(self): 269 """Return a list of extra chrome flags to use with Chrome for testing. 270 271 These are flags needed to facilitate testing. Override this function to 272 use a custom set of Chrome flags. 273 """ 274 auth_ext_path = ('/usr/local/autotest/deps/pyauto_dep/' + 275 'test_src/chrome/browser/resources/gaia_auth') 276 if self.IsChromeOS(): 277 return [ 278 '--homepage=about:blank', 279 '--allow-file-access', 280 '--allow-file-access-from-files', 281 '--enable-file-cookies', 282 '--disable-default-apps', 283 '--dom-automation', 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', 290 ] 291 else: 292 return [] 293 294 def ShouldOOBESkipToLogin(self): 295 """Determine if we should skip the OOBE flow on ChromeOS. 296 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. 299 300 Override and return False if OOBE flow is required, for OOBE tests, for 301 example. Calling this function directly will have no effect. 302 303 Returns: 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. 307 """ 308 assert self.IsChromeOS() 309 return True 310 311 def ShouldAutoLogin(self): 312 """Determine if we should auto-login on ChromeOS at browser startup. 313 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. 317 318 Override and return False to not auto login, for tests where login is part 319 of the use case. 320 321 Returns: 322 True, if chrome should auto login after startup. 323 False, otherwise. 324 """ 325 assert self.IsChromeOS() 326 return True 327 328 def CloseChromeOnChromeOS(self): 329 """Gracefully exit chrome on ChromeOS.""" 330 331 def _GetListOfChromePids(): 332 """Retrieves the list of currently-running Chrome process IDs. 333 334 Returns: 335 A list of strings, where each string represents a currently-running 336 'chrome' process ID. 337 """ 338 proc = subprocess.Popen(['pgrep', '^chrome$'], stdout=subprocess.PIPE) 339 proc.wait() 340 return [x.strip() for x in proc.stdout.readlines()] 341 342 orig_pids = _GetListOfChromePids() 343 subprocess.call(['pkill', '^chrome$']) 344 345 def _AreOrigPidsDead(orig_pids): 346 """Determines whether all originally-running 'chrome' processes are dead. 347 348 Args: 349 orig_pids: A list of strings, where each string represents the PID for 350 an originally-running 'chrome' process. 351 352 Returns: 353 True, if all originally-running 'chrome' processes have been killed, or 354 False otherwise. 355 """ 356 for new_pid in _GetListOfChromePids(): 357 if new_pid in orig_pids: 358 return False 359 return True 360 361 self.WaitUntil(lambda: _AreOrigPidsDead(orig_pids)) 362 363 @staticmethod 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) 367 368 @staticmethod 369 def SuidPythonPath(): 370 """Path to suid_python binary on ChromeOS. 371 372 This is typically in the same directory as pyautolib.py 373 """ 374 return os.path.join(PyUITest.BrowserPath(), 'suid-python') 375 376 @staticmethod 377 def RunSuperuserActionOnChromeOS(action): 378 """Run the given action with superuser privs (on ChromeOS). 379 380 Uses the suid_actions.py script. 381 382 Args: 383 action: An action to perform. 384 See suid_actions.py for available options. 385 386 Returns: 387 (stdout, stderr) 388 """ 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', 392 'suid_actions.py') 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) 398 399 def EnableChromeTestingOnChromeOS(self): 400 """Enables the named automation interface on chromeos. 401 402 Restarts chrome so that you get a fresh instance. 403 Also sets some testing-friendly flags for chrome. 404 405 Expects suid python to be present in the same dir as pyautolib.py 406 """ 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', 410 'enable_testing.py') 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 420 421 @staticmethod 422 def EnableCrashReportingOnChromeOS(): 423 """Enables crash reporting on ChromeOS. 424 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 427 too. 428 429 Note that crash reporting will work only if breakpad is built in, ie in a 430 'Google Chrome' build (not Chromium). 431 """ 432 consent_file = '/home/chronos/Consent To Send Stats' 433 def _HasValidConsentFile(): 434 if not os.path.isfile(consent_file): 435 return False 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 450 451 @staticmethod 452 def _IsSessionManagerReady(old_pid): 453 """Is the ChromeOS session_manager running and ready to accept DBus calls? 454 455 Called after session_manager is killed to know when it has restarted. 456 457 Args: 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. 461 """ 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: 466 return False 467 468 import dbus 469 try: 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: 475 return False 476 return True 477 478 @staticmethod 479 def CleanupBrowserProfileOnChromeOS(): 480 """Cleanup browser profile dir on ChromeOS. 481 482 This does not clear cryptohome. 483 484 Browser should not be running, or else there will be locked files. 485 """ 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)) 491 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)) 496 497 @staticmethod 498 def CleanupFlimflamDirsOnChromeOS(): 499 """Clean the contents of flimflam profiles and restart flimflam.""" 500 PyUITest.RunSuperuserActionOnChromeOS('CleanFlimflamDirs') 501 502 @staticmethod 503 def RemoveAllCryptohomeVaultsOnChromeOS(): 504 """Remove any existing cryptohome vaults.""" 505 PyUITest.RunSuperuserActionOnChromeOS('RemoveAllCryptohomeVaults') 506 507 @staticmethod 508 def _IsInodeNew(path, old_inode): 509 """Determine whether an inode has changed. POSIX only. 510 511 Args: 512 path: The file path to check for changes. 513 old_inode: The old inode number. 514 515 Returns: 516 True if the path exists and its inode number is different from old_inode. 517 False otherwise. 518 """ 519 try: 520 stat_result = os.stat(path) 521 except OSError: 522 return False 523 if not stat_result: 524 return False 525 return stat_result.st_ino != old_inode 526 527 def RestartBrowser(self, clear_profile=True, pre_launch_hook=None): 528 """Restart the browser. 529 530 For use with tests that require to restart the browser. 531 532 Args: 533 clear_profile: If True, the browser profile is cleared before restart. 534 Defaults to True, that is restarts browser with a clean 535 profile. 536 pre_launch_hook: If specified, must be a callable that is invoked before 537 the browser is started again. Not supported in ChromeOS. 538 """ 539 if self.IsChromeOS(): 540 assert pre_launch_hook is None, 'Not supported in ChromeOS' 541 self.TearDown() 542 if clear_profile: 543 self.CleanupBrowserProfileOnChromeOS() 544 self.CloseChromeOnChromeOS() 545 self.EnableChromeTestingOnChromeOS() 546 self.SetUp() 547 return 548 # Not chromeos 549 orig_clear_state = self.get_clear_profile() 550 self.CloseBrowserAndServer() 551 self.set_clear_profile(clear_profile) 552 if pre_launch_hook: 553 pre_launch_hook() 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. 558 559 @staticmethod 560 def DataDir(): 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")) 564 565 @staticmethod 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")) 571 572 @staticmethod 573 def GetFileURLForPath(*path): 574 """Get file:// url for the given path. 575 576 Also quotes the url using urllib.quote(). 577 578 Args: 579 path: Variable number of strings that can be joined. 580 """ 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 589 else: 590 quoted_path = urllib.quote(abs_path) 591 return 'file://' + quoted_path 592 593 @staticmethod 594 def GetFileURLForDataPath(*relative_path): 595 """Get file:// url for the given path relative to the chrome test data dir. 596 597 Also quotes the url using urllib.quote(). 598 599 Args: 600 relative_path: Variable number of strings that can be joined. 601 """ 602 return PyUITest.GetFileURLForPath(PyUITest.DataDir(), *relative_path) 603 604 @staticmethod 605 def GetHttpURLForDataPath(*relative_path): 606 """Get http:// url for the given path in the data dir. 607 608 The URL will be usable only after starting the http server. 609 """ 610 global _HTTP_SERVER 611 assert _HTTP_SERVER, 'HTTP Server not yet started' 612 return _HTTP_SERVER.GetURL(os.path.join('files', *relative_path)).spec() 613 614 @staticmethod 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') 619 620 @staticmethod 621 def GetFileURLForContentDataPath(*relative_path): 622 """Get file:// url for the given path relative to content test data dir. 623 624 Also quotes the url using urllib.quote(). 625 626 Args: 627 relative_path: Variable number of strings that can be joined. 628 """ 629 return PyUITest.GetFileURLForPath(PyUITest.ContentDataDir(), *relative_path) 630 631 @staticmethod 632 def GetFtpURLForDataPath(ftp_server, *relative_path): 633 """Get ftp:// url for the given path in the data dir. 634 635 Args: 636 ftp_server: handle to ftp server, an instance of SpawnedTestServer 637 relative_path: any number of path elements 638 639 The URL will be usable only after starting the ftp server. 640 """ 641 assert ftp_server, 'FTP Server not yet started' 642 return ftp_server.GetURL(os.path.join(*relative_path)).spec() 643 644 @staticmethod 645 def IsMac(): 646 """Are we on Mac?""" 647 return 'darwin' == sys.platform 648 649 @staticmethod 650 def IsLinux(): 651 """Are we on Linux? ChromeOS is linux too.""" 652 return sys.platform.startswith('linux') 653 654 @staticmethod 655 def IsWin(): 656 """Are we on Win?""" 657 return 'win32' == sys.platform 658 659 @staticmethod 660 def IsWin7(): 661 """Are we on Windows 7?""" 662 if not PyUITest.IsWin(): 663 return False 664 ver = sys.getwindowsversion() 665 return (ver[3], ver[0], ver[1]) == (2, 6, 1) 666 667 @staticmethod 668 def IsWinVista(): 669 """Are we on Windows Vista?""" 670 if not PyUITest.IsWin(): 671 return False 672 ver = sys.getwindowsversion() 673 return (ver[3], ver[0], ver[1]) == (2, 6, 0) 674 675 @staticmethod 676 def IsWinXP(): 677 """Are we on Windows XP?""" 678 if not PyUITest.IsWin(): 679 return False 680 ver = sys.getwindowsversion() 681 return (ver[3], ver[0], ver[1]) == (2, 5, 1) 682 683 @staticmethod 684 def IsChromeOS(): 685 """Are we on ChromeOS (or Chromium OS)? 686 687 Checks for "CHROMEOS_RELEASE_NAME=" in /etc/lsb-release. 688 """ 689 lsb_release = '/etc/lsb-release' 690 if not PyUITest.IsLinux() or not os.path.isfile(lsb_release): 691 return False 692 for line in open(lsb_release).readlines(): 693 if line.startswith('CHROMEOS_RELEASE_NAME='): 694 return True 695 return False 696 697 @staticmethod 698 def IsPosix(): 699 """Are we on Mac/Linux?""" 700 return PyUITest.IsMac() or PyUITest.IsLinux() 701 702 @staticmethod 703 def IsEnUS(): 704 """Are we en-US?""" 705 # TODO: figure out the machine's langugage. 706 return True 707 708 @staticmethod 709 def GetPlatform(): 710 """Return the platform name.""" 711 # Since ChromeOS is also Linux, we check for it first. 712 if PyUITest.IsChromeOS(): 713 return 'chromeos' 714 elif PyUITest.IsLinux(): 715 return 'linux' 716 elif PyUITest.IsMac(): 717 return 'mac' 718 elif PyUITest.IsWin(): 719 return 'win' 720 else: 721 return 'unknown' 722 723 @staticmethod 724 def EvalDataFrom(filename): 725 """Return eval of python code from given file. 726 727 The datastructure used in the file will be preserved. 728 """ 729 data_file = os.path.join(filename) 730 contents = open(data_file).read() 731 try: 732 ret = eval(contents) 733 except: 734 print >>sys.stderr, '%s is an invalid data file.' % data_file 735 raise 736 return ret 737 738 @staticmethod 739 def ChromeOSBoard(): 740 """What is the ChromeOS board name""" 741 if PyUITest.IsChromeOS(): 742 for line in open('/etc/lsb-release'): 743 line = line.strip() 744 if line.startswith('CHROMEOS_RELEASE_BOARD='): 745 return line.split('=')[1] 746 return None 747 748 @staticmethod 749 def Kill(pid): 750 """Terminate the given pid. 751 752 If the pid refers to a renderer, use KillRendererProcess instead. 753 """ 754 if PyUITest.IsWin(): 755 subprocess.call(['taskkill.exe', '/T', '/F', '/PID', str(pid)]) 756 else: 757 os.kill(pid, signal.SIGTERM) 758 759 @staticmethod 760 def GetPrivateInfo(): 761 """Fetch info from private_tests_info.txt in private dir. 762 763 Returns: 764 a dictionary of items from private_tests_info.txt 765 """ 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) 770 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. 774 775 Waits until the |function| evalues to |expect_retval| or until |timeout| 776 secs, whichever occurs earlier. 777 778 This is better than using a sleep, since it waits (almost) only as much 779 as needed. 780 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. 784 785 EXAMPLES:- 786 Wait for "file.txt" to get created: 787 WaitUntil(os.path.exists, args=["file.txt"]) 788 789 Same as above, but using lambda: 790 WaitUntil(lambda: os.path.exists("file.txt")) 791 792 Args: 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 796 ui_test.cc 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 806 |function()| 807 debug: if True, displays debug info at each retry. 808 809 Returns: 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 814 """ 815 if timeout == -1: # Default 816 timeout = self._automation_timeout / 1000.0 817 assert callable(function), "function should be a callable" 818 begin = time.time() 819 debug_begin = begin 820 retval = None 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: 827 debug_begin += 5 828 if function.func_name == (lambda: True).func_name: 829 function_info = inspect.getsource(function).strip() 830 else: 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], 836 function_info, 837 True if expect_retval is None else expect_retval, 838 retval) 839 time.sleep(retry_sleep) 840 return retval if return_retval else False 841 842 def StartFTPServer(self, data_dir): 843 """Start a local file server hosting data files over ftp:// 844 845 Args: 846 data_dir: path where ftp files should be served 847 848 Returns: 849 handle to FTP Server, an instance of SpawnedTestServer 850 """ 851 ftp_server = pyautolib.SpawnedTestServer( 852 pyautolib.SpawnedTestServer.TYPE_FTP, 853 '127.0.0.1', 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) 857 return ftp_server 858 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.') 864 865 def StartHTTPServer(self, data_dir): 866 """Starts a local HTTP SpawnedTestServer serving files from |data_dir|. 867 868 Args: 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. 871 872 Returns: 873 handle to the HTTP SpawnedTestServer 874 """ 875 http_server = pyautolib.SpawnedTestServer( 876 pyautolib.SpawnedTestServer.TYPE_HTTP, 877 '127.0.0.1', 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) 881 return http_server 882 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.') 887 888 def StartHttpsServer(self, cert_type, data_dir): 889 """Starts a local HTTPS SpawnedTestServer serving files from |data_dir|. 890 891 Args: 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 896 document root. 897 898 Returns: 899 Handle to the HTTPS SpawnedTestServer 900 """ 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) 907 return https_server 908 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.') 913 914 class ActionTimeoutChanger(object): 915 """Facilitate temporary changes to PyAuto command timeout. 916 917 Automatically resets to original timeout when object is destroyed. 918 """ 919 _saved_timeout = -1 # Saved timeout value 920 921 def __init__(self, ui_test, new_timeout): 922 """Initialize. 923 924 Args: 925 ui_test: a PyUITest object 926 new_timeout: new timeout to use (in milli secs) 927 """ 928 self._saved_timeout = ui_test._automation_timeout 929 ui_test._automation_timeout = new_timeout 930 self._ui_test = ui_test 931 932 def __del__(self): 933 """Reset command_execution_timeout_ms to original value.""" 934 self._ui_test._automation_timeout = self._saved_timeout 935 936 class JavascriptExecutor(object): 937 """Abstract base class for JavaScript injection. 938 939 Derived classes should override Execute method.""" 940 def Execute(self, script): 941 pass 942 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=''): 946 """Initialize. 947 948 Refer to ExecuteJavascript() for the complete argument list 949 description. 950 951 Args: 952 ui_test: a PyUITest object 953 """ 954 self._ui_test = ui_test 955 self.windex = windex 956 self.tab_index = tab_index 957 self.frame_xpath = frame_xpath 958 959 def Execute(self, script): 960 """Execute script in the tab.""" 961 return self._ui_test.ExecuteJavascript(script, 962 self.tab_index, 963 self.windex, 964 self.frame_xpath) 965 966 class JavascriptExecutorInRenderView(JavascriptExecutor): 967 """Wrapper for injecting JavaScript in an extension view.""" 968 def __init__(self, ui_test, view, frame_xpath=''): 969 """Initialize. 970 971 Refer to ExecuteJavascriptInRenderView() for the complete argument list 972 description. 973 974 Args: 975 ui_test: a PyUITest object 976 """ 977 self._ui_test = ui_test 978 self.view = view 979 self.frame_xpath = frame_xpath 980 981 def Execute(self, script): 982 """Execute script in the render view.""" 983 return self._ui_test.ExecuteJavascriptInRenderView(script, 984 self.view, 985 self.frame_xpath) 986 987 def _GetResultFromJSONRequestDiagnostics(self): 988 """Same as _GetResultFromJSONRequest without throwing a timeout exception. 989 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 992 only. 993 994 Returns: 995 True if the request returned; False if it timed out. 996 """ 997 result = self._SendJSONRequest(-1, 998 json.dumps({'command': 'GetBrowserInfo',}), 999 self._automation_timeout) 1000 if not result: 1001 # The diagnostic command did not complete, Chrome is probably in a bad 1002 # state 1003 return False 1004 return True 1005 1006 def _GetResultFromJSONRequest(self, cmd_dict, windex=0, timeout=-1): 1007 """Issue call over the JSON automation channel and fetch output. 1008 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. 1012 1013 Args: 1014 cmd_dict: the command dictionary. It must have a 'command' key 1015 Sample: 1016 { 1017 'command': 'SetOmniboxText', 1018 'text': text, 1019 } 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 1023 1024 timeout: request timeout (in milliseconds) 1025 1026 Returns: 1027 a dictionary for the output returned by the automation channel. 1028 1029 Raises: 1030 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1031 """ 1032 if timeout == -1: # Default 1033 timeout = self._automation_timeout 1034 if windex is None: # Do not target any window 1035 windex = -1 1036 result = self._SendJSONRequest(windex, json.dumps(cmd_dict), timeout) 1037 if not result: 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? 1043 try: 1044 # Does not actually kill the process 1045 os.kill(int(_BROWSER_PID), 0) 1046 except OSError: 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.' 1054 % _BROWSER_PID) 1055 else: 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.' 1059 % _BROWSER_PID) 1060 else: 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, 1074 additional_info)) 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']) 1081 else: 1082 raise AutomationCommandFail(ret_dict['error']) 1083 return ret_dict 1084 1085 def NavigateToURL(self, url, windex=0, tab_index=None, navigation_count=1): 1086 """Navigate the given tab to the given URL. 1087 1088 Note that this method also activates the corresponding tab/window if it's 1089 not active already. Blocks until |navigation_count| navigations have 1090 completed. 1091 1092 Args: 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 1095 window. 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. 1098 1099 Raises: 1100 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1101 """ 1102 if isinstance(url, GURL): 1103 url = url.spec() 1104 if tab_index is None: 1105 tab_index = self.GetActiveTabIndex(windex) 1106 cmd_dict = { 1107 'command': 'NavigateToURL', 1108 'url': url, 1109 'windex': windex, 1110 'tab_index': tab_index, 1111 'navigation_count': navigation_count, 1112 } 1113 self._GetResultFromJSONRequest(cmd_dict, windex=None) 1114 1115 def NavigateToURLAsync(self, url, windex=0, tab_index=None): 1116 """Initiate a URL navigation. 1117 1118 A wrapper for NavigateToURL with navigation_count set to 0. 1119 """ 1120 self.NavigateToURL(url, windex, tab_index, 0) 1121 1122 def ApplyAccelerator(self, accelerator, windex=0): 1123 """Apply the accelerator with the given id. 1124 1125 Note that this method schedules the accelerator, but does not wait for it to 1126 actually finish doing anything. 1127 1128 Args: 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 1132 window. 1133 1134 Raises: 1135 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1136 """ 1137 1138 cmd_dict = { 1139 'command': 'ApplyAccelerator', 1140 'accelerator': accelerator, 1141 'windex': windex, 1142 } 1143 self._GetResultFromJSONRequest(cmd_dict, windex=None) 1144 1145 def RunCommand(self, accelerator, windex=0): 1146 """Apply the accelerator with the given id and wait for it to finish. 1147 1148 This is like ApplyAccelerator except that it waits for the command to finish 1149 executing. 1150 1151 Args: 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 1155 window. 1156 1157 Raises: 1158 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1159 """ 1160 cmd_dict = { 1161 'command': 'RunCommand', 1162 'accelerator': accelerator, 1163 'windex': windex, 1164 } 1165 self._GetResultFromJSONRequest(cmd_dict, windex=None) 1166 1167 def IsMenuCommandEnabled(self, accelerator, windex=0): 1168 """Check if a command is enabled for a window. 1169 1170 Returns true if the command with the given accelerator id is enabled on the 1171 given window. 1172 1173 Args: 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 1177 window. 1178 1179 Returns: 1180 True if the command is enabled for the given window. 1181 """ 1182 cmd_dict = { 1183 'command': 'IsMenuCommandEnabled', 1184 'accelerator': accelerator, 1185 'windex': windex, 1186 } 1187 return self._GetResultFromJSONRequest(cmd_dict, windex=None).get('enabled') 1188 1189 def TabGoForward(self, tab_index=0, windex=0): 1190 """Navigate a tab forward in history. 1191 1192 Equivalent to clicking the Forward button in the UI. Activates the tab as a 1193 side effect. 1194 1195 Raises: 1196 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1197 """ 1198 self.ActivateTab(tab_index, windex) 1199 self.RunCommand(IDC_FORWARD, windex) 1200 1201 def TabGoBack(self, tab_index=0, windex=0): 1202 """Navigate a tab backwards in history. 1203 1204 Equivalent to clicking the Back button in the UI. Activates the tab as a 1205 side effect. 1206 1207 Raises: 1208 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1209 """ 1210 self.ActivateTab(tab_index, windex) 1211 self.RunCommand(IDC_BACK, windex) 1212 1213 def ReloadTab(self, tab_index=0, windex=0): 1214 """Reload the given tab. 1215 1216 Blocks until the page has reloaded. 1217 1218 Args: 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 1221 window. 1222 1223 Raises: 1224 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1225 """ 1226 self.ActivateTab(tab_index, windex) 1227 self.RunCommand(IDC_RELOAD, windex) 1228 1229 def CloseTab(self, tab_index=0, windex=0, wait_until_closed=True): 1230 """Close the given tab. 1231 1232 Note: Be careful closing the last tab in a window as it may close the 1233 browser. 1234 1235 Args: 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 1238 window. 1239 wait_until_closed: Whether to block until the tab finishes closing. 1240 1241 Raises: 1242 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1243 """ 1244 cmd_dict = { 1245 'command': 'CloseTab', 1246 'tab_index': tab_index, 1247 'windex': windex, 1248 'wait_until_closed': wait_until_closed, 1249 } 1250 self._GetResultFromJSONRequest(cmd_dict, windex=None) 1251 1252 def WaitForTabToBeRestored(self, tab_index=0, windex=0, timeout=-1): 1253 """Wait for the given tab to be restored. 1254 1255 Args: 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 1258 window. 1259 timeout: Timeout in milliseconds. 1260 1261 Raises: 1262 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1263 """ 1264 cmd_dict = { 1265 'command': 'CloseTab', 1266 'tab_index': tab_index, 1267 'windex': windex, 1268 } 1269 self._GetResultFromJSONRequest(cmd_dict, windex=None, timeout=timeout) 1270 1271 def ReloadActiveTab(self, windex=0): 1272 """Reload an active tab. 1273 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. 1276 1277 Args: 1278 windex: The index of the browser window to work on. Defaults to the first 1279 window. 1280 1281 Raises: 1282 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1283 """ 1284 self.ReloadTab(self.GetActiveTabIndex(windex), windex) 1285 1286 def GetActiveTabIndex(self, windex=0): 1287 """Get the index of the currently active tab in the given browser window. 1288 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. 1291 1292 Args: 1293 windex: The index of the browser window to work on. Defaults to the first 1294 window. 1295 1296 Returns: 1297 An integer index for the currently active tab. 1298 """ 1299 cmd_dict = { 1300 'command': 'GetActiveTabIndex', 1301 'windex': windex, 1302 } 1303 return self._GetResultFromJSONRequest(cmd_dict, 1304 windex=None).get('tab_index') 1305 1306 def ActivateTab(self, tab_index=0, windex=0): 1307 """Activates the given tab in the specified window. 1308 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 1311 explicitly. 1312 1313 Args: 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 1316 window. 1317 1318 Raises: 1319 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1320 """ 1321 cmd_dict = { 1322 'command': 'ActivateTab', 1323 'tab_index': tab_index, 1324 'windex': windex, 1325 } 1326 self.BringBrowserToFront(windex) 1327 self._GetResultFromJSONRequest(cmd_dict, windex=None) 1328 1329 def BringBrowserToFront(self, windex=0): 1330 """Activate the browser's window and bring it to front. 1331 1332 Args: 1333 windex: Integer index of the browser window to use; defaults to the first 1334 window. 1335 1336 Raises: 1337 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1338 """ 1339 cmd_dict = { 1340 'command': 'BringBrowserToFront', 1341 'windex': windex, 1342 } 1343 self._GetResultFromJSONRequest(cmd_dict, windex=None) 1344 1345 def GetBrowserWindowCount(self): 1346 """Get the browser window count. 1347 1348 Args: 1349 None. 1350 1351 Returns: 1352 Integer count of the number of browser windows. Includes popups. 1353 1354 Raises: 1355 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1356 """ 1357 cmd_dict = {'command': 'GetBrowserWindowCount'} 1358 return self._GetResultFromJSONRequest(cmd_dict, windex=None)['count'] 1359 1360 def OpenNewBrowserWindow(self, show): 1361 """Create a new browser window. 1362 1363 Args: 1364 show: Boolean indicating whether to show the window. 1365 1366 Raises: 1367 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1368 """ 1369 cmd_dict = { 1370 'command': 'OpenNewBrowserWindow', 1371 'show': show, 1372 } 1373 self._GetResultFromJSONRequest(cmd_dict, windex=None) 1374 1375 def CloseBrowserWindow(self, windex=0): 1376 """Create a new browser window. 1377 1378 Args: 1379 windex: Index of the browser window to close; defaults to 0. 1380 1381 Raises: 1382 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1383 """ 1384 cmd_dict = { 1385 'command': 'CloseBrowserWindow', 1386 'windex': windex, 1387 } 1388 self._GetResultFromJSONRequest(cmd_dict, windex=None) 1389 1390 def AppendTab(self, url, windex=0): 1391 """Append a new tab. 1392 1393 Creates a new tab at the end of given browser window and activates 1394 it. Blocks until the specified |url| is loaded. 1395 1396 Args: 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 1399 window. 1400 1401 Returns: 1402 True if the url loads successfully in the new tab. False otherwise. 1403 1404 Raises: 1405 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1406 """ 1407 if isinstance(url, GURL): 1408 url = url.spec() 1409 cmd_dict = { 1410 'command': 'AppendTab', 1411 'url': url, 1412 'windex': windex, 1413 } 1414 return self._GetResultFromJSONRequest(cmd_dict, windex=None).get('result') 1415 1416 def GetTabCount(self, windex=0): 1417 """Gets the number of tab in the given browser window. 1418 1419 Args: 1420 windex: Integer index of the browser window to use; defaults to the first 1421 window. 1422 1423 Returns: 1424 The tab count. 1425 1426 Raises: 1427 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1428 """ 1429 cmd_dict = { 1430 'command': 'GetTabCount', 1431 'windex': windex, 1432 } 1433 return self._GetResultFromJSONRequest(cmd_dict, windex=None)['tab_count'] 1434 1435 def GetTabInfo(self, tab_index=0, windex=0): 1436 """Gets information about the specified tab. 1437 1438 Args: 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 1441 window. 1442 1443 Returns: 1444 A dictionary containing information about the tab. 1445 Example: 1446 { u'title': "Hello World", 1447 u'url': "http://foo.bar", } 1448 1449 Raises: 1450 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1451 """ 1452 cmd_dict = { 1453 'command': 'GetTabInfo', 1454 'tab_index': tab_index, 1455 'windex': windex, 1456 } 1457 return self._GetResultFromJSONRequest(cmd_dict, windex=None) 1458 1459 def GetActiveTabTitle(self, windex=0): 1460 """Gets the title of the active tab. 1461 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. 1464 1465 Args: 1466 windex: Integer index of the browser window to use; defaults to the first 1467 window. 1468 1469 Returns: 1470 The tab title as a string. 1471 1472 Raises: 1473 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1474 """ 1475 return self.GetTabInfo(self.GetActiveTabIndex(windex), windex)['title'] 1476 1477 def GetActiveTabURL(self, windex=0): 1478 """Gets the URL of the active tab. 1479 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. 1482 1483 Args: 1484 windex: Integer index of the browser window to use; defaults to the first 1485 window. 1486 1487 Returns: 1488 The tab URL as a GURL object. 1489 1490 Raises: 1491 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1492 """ 1493 return GURL(str(self.GetTabInfo(self.GetActiveTabIndex(windex), 1494 windex)['url'])) 1495 1496 def ActionOnSSLBlockingPage(self, tab_index=0, windex=0, proceed=True): 1497 """Take action on an interstitial page. 1498 1499 Calling this when an interstitial page is not showing is an error. 1500 1501 Args: 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 1504 window. 1505 proceed: Whether to proceed to the URL or not. 1506 1507 Raises: 1508 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1509 """ 1510 cmd_dict = { 1511 'command': 'ActionOnSSLBlockingPage', 1512 'tab_index': tab_index, 1513 'windex': windex, 1514 'proceed': proceed, 1515 } 1516 return self._GetResultFromJSONRequest(cmd_dict, windex=None) 1517 1518 def GetBookmarkModel(self, windex=0): 1519 """Return the bookmark model as a BookmarkModel object. 1520 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. 1523 """ 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) 1528 1529 def _GetBookmarksAsJSON(self, windex=0): 1530 """Get bookmarks as a JSON dictionary; used by GetBookmarkModel().""" 1531 cmd_dict = { 1532 'command': 'GetBookmarksAsJSON', 1533 'windex': windex, 1534 } 1535 self.WaitForBookmarkModelToLoad(windex) 1536 return self._GetResultFromJSONRequest(cmd_dict, 1537 windex=None)['bookmarks_as_json'] 1538 1539 def WaitForBookmarkModelToLoad(self, windex=0): 1540 """Gets the status of the bookmark bar as a dictionary. 1541 1542 Args: 1543 windex: Integer index of the browser window to use; defaults to the first 1544 window. 1545 1546 Raises: 1547 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1548 """ 1549 cmd_dict = { 1550 'command': 'WaitForBookmarkModelToLoad', 1551 'windex': windex, 1552 } 1553 return self._GetResultFromJSONRequest(cmd_dict, windex=None) 1554 1555 def GetBookmarkBarStatus(self, windex=0): 1556 """Gets the status of the bookmark bar as a dictionary. 1557 1558 Args: 1559 windex: Integer index of the browser window to use; defaults to the first 1560 window. 1561 1562 Returns: 1563 A dictionary. 1564 Example: 1565 { u'visible': True, 1566 u'animating': False, 1567 u'detached': False, } 1568 1569 Raises: 1570 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1571 """ 1572 cmd_dict = { 1573 'command': 'GetBookmarkBarStatus', 1574 'windex': windex, 1575 } 1576 return self._GetResultFromJSONRequest(cmd_dict, windex=None) 1577 1578 def GetBookmarkBarStatus(self, windex=0): 1579 """Gets the status of the bookmark bar as a dictionary. 1580 1581 Args: 1582 windex: Integer index of the browser window to use; defaults to the first 1583 window. 1584 1585 Returns: 1586 A dictionary. 1587 Example: 1588 { u'visible': True, 1589 u'animating': False, 1590 u'detached': False, } 1591 1592 Raises: 1593 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1594 """ 1595 cmd_dict = { 1596 'command': 'GetBookmarkBarStatus', 1597 'windex': windex, 1598 } 1599 return self._GetResultFromJSONRequest(cmd_dict, windex=None) 1600 1601 def GetBookmarkBarStatus(self, windex=0): 1602 """Gets the status of the bookmark bar as a dictionary. 1603 1604 Args: 1605 windex: Integer index of the browser window to use; defaults to the first 1606 window. 1607 1608 Returns: 1609 A dictionary. 1610 Example: 1611 { u'visible': True, 1612 u'animating': False, 1613 u'detached': False, } 1614 1615 Raises: 1616 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1617 """ 1618 cmd_dict = { 1619 'command': 'GetBookmarkBarStatus', 1620 'windex': windex, 1621 } 1622 return self._GetResultFromJSONRequest(cmd_dict, windex=None) 1623 1624 def GetBookmarkBarVisibility(self, windex=0): 1625 """Returns the visibility of the bookmark bar. 1626 1627 Args: 1628 windex: Integer index of the browser window to use; defaults to the first 1629 window. 1630 1631 Returns: 1632 True if the bookmark bar is visible, false otherwise. 1633 1634 Raises: 1635 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1636 """ 1637 return self.GetBookmarkBarStatus(windex)['visible'] 1638 1639 def AddBookmarkGroup(self, parent_id, index, title, windex=0): 1640 """Adds a bookmark folder. 1641 1642 Args: 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 1647 window. 1648 1649 Returns: 1650 True if the bookmark bar is detached, false otherwise. 1651 1652 Raises: 1653 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1654 """ 1655 if isinstance(parent_id, basestring): 1656 parent_id = int(parent_id) 1657 cmd_dict = { 1658 'command': 'AddBookmark', 1659 'parent_id': parent_id, 1660 'index': index, 1661 'title': title, 1662 'is_folder': True, 1663 'windex': windex, 1664 } 1665 self.WaitForBookmarkModelToLoad(windex) 1666 self._GetResultFromJSONRequest(cmd_dict, windex=None) 1667 1668 def AddBookmarkURL(self, parent_id, index, title, url, windex=0): 1669 """Add a bookmark URL. 1670 1671 Args: 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 1677 window. 1678 1679 Raises: 1680 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1681 """ 1682 if isinstance(parent_id, basestring): 1683 parent_id = int(parent_id) 1684 cmd_dict = { 1685 'command': 'AddBookmark', 1686 'parent_id': parent_id, 1687 'index': index, 1688 'title': title, 1689 'url': url, 1690 'is_folder': False, 1691 'windex': windex, 1692 } 1693 self.WaitForBookmarkModelToLoad(windex) 1694 self._GetResultFromJSONRequest(cmd_dict, windex=None) 1695 1696 def ReparentBookmark(self, id, new_parent_id, index, windex=0): 1697 """Move a bookmark. 1698 1699 Args: 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 1704 window. 1705 1706 Raises: 1707 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1708 """ 1709 if isinstance(id, basestring): 1710 id = int(id) 1711 if isinstance(new_parent_id, basestring): 1712 new_parent_id = int(new_parent_id) 1713 cmd_dict = { 1714 'command': 'ReparentBookmark', 1715 'id': id, 1716 'new_parent_id': new_parent_id, 1717 'index': index, 1718 'windex': windex, 1719 } 1720 self.WaitForBookmarkModelToLoad(windex) 1721 self._GetResultFromJSONRequest(cmd_dict, windex=None) 1722 1723 def SetBookmarkTitle(self, id, title, windex=0): 1724 """Change the title of a bookmark. 1725 1726 Args: 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 1730 window. 1731 1732 Raises: 1733 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1734 """ 1735 if isinstance(id, basestring): 1736 id = int(id) 1737 cmd_dict = { 1738 'command': 'SetBookmarkTitle', 1739 'id': id, 1740 'title': title, 1741 'windex': windex, 1742 } 1743 self.WaitForBookmarkModelToLoad(windex) 1744 self._GetResultFromJSONRequest(cmd_dict, windex=None) 1745 1746 def SetBookmarkURL(self, id, url, windex=0): 1747 """Change the URL of a bookmark. 1748 1749 Args: 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 1753 window. 1754 1755 Raises: 1756 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1757 """ 1758 if isinstance(id, basestring): 1759 id = int(id) 1760 cmd_dict = { 1761 'command': 'SetBookmarkURL', 1762 'id': id, 1763 'url': url, 1764 'windex': windex, 1765 } 1766 self.WaitForBookmarkModelToLoad(windex) 1767 self._GetResultFromJSONRequest(cmd_dict, windex=None) 1768 1769 def RemoveBookmark(self, id, windex=0): 1770 """Remove a bookmark. 1771 1772 Args: 1773 id: The bookmark to remove. 1774 windex: Integer index of the browser window to use; defaults to the first 1775 window. 1776 1777 Raises: 1778 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1779 """ 1780 if isinstance(id, basestring): 1781 id = int(id) 1782 cmd_dict = { 1783 'command': 'RemoveBookmark', 1784 'id': id, 1785 'windex': windex, 1786 } 1787 self.WaitForBookmarkModelToLoad(windex) 1788 self._GetResultFromJSONRequest(cmd_dict, windex=None) 1789 1790 def GetDownloadsInfo(self, windex=0): 1791 """Return info about downloads. 1792 1793 This includes all the downloads recognized by the history system. 1794 1795 Returns: 1796 an instance of downloads_info.DownloadInfo 1797 """ 1798 return download_info.DownloadInfo( 1799 self._GetResultFromJSONRequest({'command': 'GetDownloadsInfo'}, 1800 windex=windex)) 1801 1802 def GetOmniboxInfo(self, windex=0): 1803 """Return info about Omnibox. 1804 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. 1809 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. 1813 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 1816 THIS METHOD. 1817 1818 Args: 1819 windex: the index of the browser window to work on. 1820 Default: 0 (first window) 1821 1822 Returns: 1823 an instance of omnibox_info.OmniboxInfo 1824 """ 1825 return omnibox_info.OmniboxInfo( 1826 self._GetResultFromJSONRequest({'command': 'GetOmniboxInfo'}, 1827 windex=windex)) 1828 1829 def SetOmniboxText(self, text, windex=0): 1830 """Enter text into the omnibox. This shifts focus to the omnibox. 1831 1832 Args: 1833 text: the text to be set. 1834 windex: the index of the browser window to work on. 1835 Default: 0 (first window) 1836 """ 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'}) 1840 cmd_dict = { 1841 'command': 'SetOmniboxText', 1842 'text': text, 1843 } 1844 self._GetResultFromJSONRequest(cmd_dict, windex=windex) 1845 1846 # TODO(ace): Remove this hack, update bug 62783. 1847 def WaitUntilOmniboxReadyHack(self, windex=0): 1848 """Wait until the omnibox is ready for input. 1849 1850 This is a hack workaround for linux platform, which returns from 1851 synchronous window creation methods before the omnibox is fully functional. 1852 1853 No-op on non-linux platforms. 1854 1855 Args: 1856 windex: the index of the browser to work on. 1857 """ 1858 if self.IsLinux(): 1859 return self.WaitUntil( 1860 lambda : self.GetOmniboxInfo(windex).Properties('has_focus')) 1861 1862 def WaitUntilOmniboxQueryDone(self, windex=0): 1863 """Wait until omnibox has finished populating results. 1864 1865 Uses WaitUntil() so the wait duration is capped by the timeout values 1866 used by automation, which WaitUntil() uses. 1867 1868 Args: 1869 windex: the index of the browser window to work on. 1870 Default: 0 (first window) 1871 """ 1872 return self.WaitUntil( 1873 lambda : not self.GetOmniboxInfo(windex).IsQueryInProgress()) 1874 1875 def OmniboxMovePopupSelection(self, count, windex=0): 1876 """Move omnibox popup selection up or down. 1877 1878 Args: 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) 1883 """ 1884 cmd_dict = { 1885 'command': 'OmniboxMovePopupSelection', 1886 'count': count, 1887 } 1888 self._GetResultFromJSONRequest(cmd_dict, windex=windex) 1889 1890 def OmniboxAcceptInput(self, windex=0): 1891 """Accepts the current string of text in the omnibox. 1892 1893 This is equivalent to clicking or hiting enter on a popup selection. 1894 Blocks until the page loads. 1895 1896 Args: 1897 windex: the index of the browser window to work on. 1898 Default: 0 (first window) 1899 """ 1900 cmd_dict = { 1901 'command': 'OmniboxAcceptInput', 1902 } 1903 self._GetResultFromJSONRequest(cmd_dict, windex=windex) 1904 1905 def GetCookie(self, url, windex=0): 1906 """Get the value of the cookie at url in context of the specified browser. 1907 1908 Args: 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 1911 window. 1912 1913 Raises: 1914 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1915 """ 1916 if isinstance(url, GURL): 1917 url = url.spec() 1918 cmd_dict = { 1919 'command': 'GetCookiesInBrowserContext', 1920 'url': url, 1921 'windex': windex, 1922 } 1923 return self._GetResultFromJSONRequest(cmd_dict, windex=None)['cookies'] 1924 1925 def DeleteCookie(self, url, cookie_name, windex=0): 1926 """Delete the cookie at url with name cookie_name. 1927 1928 Args: 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 1932 window. 1933 1934 Raises: 1935 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1936 """ 1937 if isinstance(url, GURL): 1938 url = url.spec() 1939 cmd_dict = { 1940 'command': 'DeleteCookieInBrowserContext', 1941 'url': url, 1942 'cookie_name': cookie_name, 1943 'windex': windex, 1944 } 1945 self._GetResultFromJSONRequest(cmd_dict, windex=None) 1946 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. 1949 1950 Args: 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 1954 window. 1955 1956 Raises: 1957 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1958 """ 1959 if isinstance(url, GURL): 1960 url = url.spec() 1961 cmd_dict = { 1962 'command': 'SetCookieInBrowserContext', 1963 'url': url, 1964 'value': value, 1965 'windex': windex, 1966 } 1967 self._GetResultFromJSONRequest(cmd_dict, windex=None) 1968 1969 def GetSearchEngineInfo(self, windex=0): 1970 """Return info about search engines. 1971 1972 Args: 1973 windex: The window index, default is 0. 1974 1975 Returns: 1976 An ordered list of dictionaries describing info about each search engine. 1977 1978 Example: 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, 1983 u'is_valid': 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, 1993 u'is_valid': True, 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}'}, 1999 """ 2000 # Ensure that the search engine profile is loaded into data model. 2001 self._GetResultFromJSONRequest({'command': 'LoadSearchEngineInfo'}, 2002 windex=windex) 2003 cmd_dict = {'command': 'GetSearchEngineInfo'} 2004 return self._GetResultFromJSONRequest( 2005 cmd_dict, windex=windex)['search_engines'] 2006 2007 def AddSearchEngine(self, title, keyword, url, windex=0): 2008 """Add a search engine, as done through the search engines UI. 2009 2010 Args: 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. 2016 """ 2017 # Ensure that the search engine profile is loaded into data model. 2018 self._GetResultFromJSONRequest({'command': 'LoadSearchEngineInfo'}, 2019 windex=windex) 2020 cmd_dict = {'command': 'AddOrEditSearchEngine', 2021 'new_title': title, 2022 'new_keyword': keyword, 2023 'new_url': url} 2024 self._GetResultFromJSONRequest(cmd_dict, windex=windex) 2025 2026 def EditSearchEngine(self, keyword, new_title, new_keyword, new_url, 2027 windex=0): 2028 """Edit info for existing search engine. 2029 2030 Args: 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. 2036 """ 2037 # Ensure that the search engine profile is loaded into data model. 2038 self._GetResultFromJSONRequest({'command': 'LoadSearchEngineInfo'}, 2039 windex=windex) 2040 cmd_dict = {'command': 'AddOrEditSearchEngine', 2041 'keyword': keyword, 2042 'new_title': new_title, 2043 'new_keyword': new_keyword, 2044 'new_url': new_url} 2045 self._GetResultFromJSONRequest(cmd_dict, windex=windex) 2046 2047 def DeleteSearchEngine(self, keyword, windex=0): 2048 """Delete search engine with given keyword. 2049 2050 Args: 2051 keyword: the keyword string of the search engine to delete. 2052 windex: The window index, default is 0. 2053 """ 2054 # Ensure that the search engine profile is loaded into data model. 2055 self._GetResultFromJSONRequest({'command': 'LoadSearchEngineInfo'}, 2056 windex=windex) 2057 cmd_dict = {'command': 'PerformActionOnSearchEngine', 'keyword': keyword, 2058 'action': 'delete'} 2059 self._GetResultFromJSONRequest(cmd_dict, windex=windex) 2060 2061 def MakeSearchEngineDefault(self, keyword, windex=0): 2062 """Make search engine with given keyword the default search. 2063 2064 Args: 2065 keyword: the keyword string of the search engine to make default. 2066 windex: The window index, default is 0. 2067 """ 2068 # Ensure that the search engine profile is loaded into data model. 2069 self._GetResultFromJSONRequest({'command': 'LoadSearchEngineInfo'}, 2070 windex=windex) 2071 cmd_dict = {'command': 'PerformActionOnSearchEngine', 'keyword': keyword, 2072 'action': 'default'} 2073 self._GetResultFromJSONRequest(cmd_dict, windex=windex) 2074 2075 def GetLocalStatePrefsInfo(self): 2076 """Return info about preferences. 2077 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. 2081 2082 Returns: 2083 an instance of prefs_info.PrefsInfo 2084 """ 2085 return prefs_info.PrefsInfo( 2086 self._GetResultFromJSONRequest({'command': 'GetLocalStatePrefsInfo'}, 2087 windex=None)) 2088 2089 def SetLocalStatePrefs(self, path, value): 2090 """Set local state preference for the given path. 2091 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" 2095 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. 2099 2100 Args: 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 2104 also be used. 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. 2110 """ 2111 cmd_dict = { 2112 'command': 'SetLocalStatePrefs', 2113 'windex': 0, 2114 'path': path, 2115 'value': value, 2116 } 2117 self._GetResultFromJSONRequest(cmd_dict, windex=None) 2118 2119 def GetPrefsInfo(self, windex=0): 2120 """Return info about preferences. 2121 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 2124 snapshot. 2125 2126 Args: 2127 windex: The window index, default is 0. 2128 Returns: 2129 an instance of prefs_info.PrefsInfo 2130 """ 2131 cmd_dict = { 2132 'command': 'GetPrefsInfo', 2133 'windex': windex, 2134 } 2135 return prefs_info.PrefsInfo( 2136 self._GetResultFromJSONRequest(cmd_dict, windex=None)) 2137 2138 def SetPrefs(self, path, value, windex=0): 2139 """Set preference for the given path. 2140 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" 2144 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. 2148 2149 Args: 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 2153 also be used. 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). 2160 """ 2161 cmd_dict = { 2162 'command': 'SetPrefs', 2163 'windex': windex, 2164 'path': path, 2165 'value': value, 2166 } 2167 self._GetResultFromJSONRequest(cmd_dict, windex=None) 2168 2169 def SendWebkitKeyEvent(self, key_type, key_code, tab_index=0, windex=0): 2170 """Send a webkit key event to the browser. 2171 2172 Args: 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). 2177 """ 2178 cmd_dict = { 2179 'command': 'SendWebkitKeyEvent', 2180 'type': key_type, 2181 'text': '', 2182 'isSystemKey': False, 2183 'unmodifiedText': '', 2184 'nativeKeyCode': 0, 2185 'windowsKeyCode': key_code, 2186 'modifiers': 0, 2187 'windex': windex, 2188 'tab_index': tab_index, 2189 } 2190 # Sending request for key event. 2191 self._GetResultFromJSONRequest(cmd_dict, windex=None) 2192 2193 def SendWebkitCharEvent(self, char, tab_index=0, windex=0): 2194 """Send a webkit char to the browser. 2195 2196 Args: 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). 2200 """ 2201 cmd_dict = { 2202 'command': 'SendWebkitKeyEvent', 2203 'type': 2, # kCharType 2204 'text': char, 2205 'isSystemKey': False, 2206 'unmodifiedText': char, 2207 'nativeKeyCode': 0, 2208 'windowsKeyCode': ord((char).upper()), 2209 'modifiers': 0, 2210 'windex': windex, 2211 'tab_index': tab_index, 2212 } 2213 # Sending request for a char. 2214 self._GetResultFromJSONRequest(cmd_dict, windex=None) 2215 2216 def SetDownloadShelfVisible(self, is_visible, windex=0): 2217 """Set download shelf visibility for the specified browser window. 2218 2219 Args: 2220 is_visible: A boolean indicating the desired shelf visibility. 2221 windex: The window index, defaults to 0 (the first window). 2222 2223 Raises: 2224 pyauto_errors.JSONInterfaceError if the automation call returns an error. 2225 """ 2226 cmd_dict = { 2227 'command': 'SetDownloadShelfVisible', 2228 'is_visible': is_visible, 2229 'windex': windex, 2230 } 2231 self._GetResultFromJSONRequest(cmd_dict, windex=None) 2232 2233 def IsDownloadShelfVisible(self, windex=0): 2234 """Determine whether the download shelf is visible in the given window. 2235 2236 Args: 2237 windex: The window index, defaults to 0 (the first window). 2238 2239 Returns: 2240 A boolean indicating the shelf visibility. 2241 2242 Raises: 2243 pyauto_errors.JSONInterfaceError if the automation call returns an error. 2244 """ 2245 cmd_dict = { 2246 'command': 'IsDownloadShelfVisible', 2247 'windex': windex, 2248 } 2249 return self._GetResultFromJSONRequest(cmd_dict, windex=None)['is_visible'] 2250 2251 def GetDownloadDirectory(self, tab_index=None, windex=0): 2252 """Get the path to the download directory. 2253 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. 2256 2257 Args: 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. 2260 2261 Returns: 2262 The path to the download directory as a FilePath object. 2263 2264 Raises: 2265 pyauto_errors.JSONInterfaceError if the automation call returns an error. 2266 """ 2267 if tab_index is None: 2268 tab_index = self.GetActiveTabIndex(windex) 2269 cmd_dict = { 2270 'command': 'GetDownloadDirectory', 2271 'tab_index': tab_index, 2272 'windex': windex, 2273 } 2274 return FilePath(str(self._GetResultFromJSONRequest(cmd_dict, 2275 windex=None)['path'])) 2276 2277 def WaitForAllDownloadsToComplete(self, pre_download_ids=[], windex=0, 2278 timeout=-1): 2279 """Wait for all pending downloads to complete. 2280 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). 2284 2285 Args: 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 2290 downloaded files). 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. 2294 """ 2295 cmd_dict = { 2296 'command': 'WaitForAllDownloadsToComplete', 2297 'pre_download_ids': pre_download_ids, 2298 } 2299 self._GetResultFromJSONRequest(cmd_dict, windex=windex, timeout=timeout) 2300 2301 def PerformActionOnDownload(self, id, action, window_index=0): 2302 """Perform the given action on the download with the given id. 2303 2304 Args: 2305 id: The id of the download. 2306 action: The action to perform on the download. 2307 Possible actions: 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. 2322 2323 Returns: 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). 2327 Example dictionary: 2328 { u'PercentComplete': 100, 2329 u'file_name': u'file.txt', 2330 u'full_path': u'/path/to/file.txt', 2331 u'id': 0, 2332 u'is_otr': False, 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' 2340 } 2341 """ 2342 cmd_dict = { # Prepare command for the json interface 2343 'command': 'PerformActionOnDownload', 2344 'id': id, 2345 'action': action 2346 } 2347 return self._GetResultFromJSONRequest(cmd_dict, windex=window_index) 2348 2349 def DownloadAndWaitForStart(self, file_url, windex=0): 2350 """Trigger download for the given url and wait for downloads to start. 2351 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 2357 completed. 2358 """ 2359 try: 2360 num_downloads = len(self.GetDownloadsInfo(windex).Downloads()) 2361 except JSONInterfaceError: 2362 num_downloads = 0 2363 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()) > 2368 num_downloads)) 2369 2370 def SetWindowDimensions( 2371 self, x=None, y=None, width=None, height=None, windex=0): 2372 """Set window dimensions. 2373 2374 All args are optional and current values will be preserved. 2375 Arbitrarily large values will be handled gracefully by the browser. 2376 2377 Args: 2378 x: window origin x 2379 y: window origin y 2380 width: window width 2381 height: window height 2382 windex: window index to work on. Defaults to 0 (first window) 2383 """ 2384 cmd_dict = { # Prepare command for the json interface 2385 'command': 'SetWindowDimensions', 2386 } 2387 if x: 2388 cmd_dict['x'] = x 2389 if y: 2390 cmd_dict['y'] = y 2391 if width: 2392 cmd_dict['width'] = width 2393 if height: 2394 cmd_dict['height'] = height 2395 self._GetResultFromJSONRequest(cmd_dict, windex=windex) 2396 2397 def WaitForInfobarCount(self, count, windex=0, tab_index=0): 2398 """Wait until infobar count becomes |count|. 2399 2400 Note: Wait duration is capped by the automation timeout. 2401 2402 Args: 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) 2406 2407 Raises: 2408 pyauto_errors.JSONInterfaceError if the automation call returns an error. 2409 """ 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 2415 return -1 2416 tabs = windows[windex]['tabs'] 2417 if tab_index >= len(tabs): # not enough tabs 2418 return -1 2419 return len(tabs[tab_index]['infobars']) 2420 2421 return self.WaitUntil(_InfobarCount, expect_retval=count) 2422 2423 def PerformActionOnInfobar( 2424 self, action, infobar_index, windex=0, tab_index=0): 2425 """Perform actions on an infobar. 2426 2427 Args: 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. 2431 Valid inputs are: 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) 2438 2439 Raises: 2440 pyauto_errors.JSONInterfaceError if the automation call returns an error. 2441 """ 2442 cmd_dict = { 2443 'command': 'PerformActionOnInfobar', 2444 'action': action, 2445 'infobar_index': infobar_index, 2446 'tab_index': tab_index, 2447 } 2448 if action not in ('dismiss', 'accept', 'allow', 'deny', 'cancel'): 2449 raise JSONInterfaceError('Invalid action %s' % action) 2450 self._GetResultFromJSONRequest(cmd_dict, windex=windex) 2451 2452 def GetBrowserInfo(self): 2453 """Return info about the browser. 2454 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) 2458 2459 For notification pid info, see 'GetActiveNotifications'. 2460 2461 Returns: 2462 a dictionary 2463 2464 Sample: 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', 2471 u'pid': 93766, 2472 u'type': u'Plug-in'}], 2473 u'extension_views': [ { 2474 u'name': u'Webpage Screenshot', 2475 u'pid': 93938, 2476 u'extension_id': u'dgcoklnmbeljaehamekjpeidmbicddfj', 2477 u'url': u'chrome-extension://dgcoklnmbeljaehamekjpeidmbicddfj/' 2478 'bg.html', 2479 u'loaded': True, 2480 u'view': { 2481 u'render_process_id': 2, 2482 u'render_view_id': 1}, 2483 u'view_type': u'EXTENSION_BACKGROUND_PAGE'}] 2484 u'properties': { 2485 u'BrowserProcessExecutableName': u'Chromium', 2486 u'BrowserProcessExecutablePath': u'Chromium.app/Contents/MacOS/' 2487 'Chromium', 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, 2498 u'height': 1134, 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'] 2505 u'selected_tab': 0, 2506 u'tabs': [ { 2507 u'index': 0, 2508 u'infobars': [], 2509 u'pinned': True, 2510 u'renderer_pid': 93747, 2511 u'url': u'http://www.google.com/' }, { 2512 u'index': 1, 2513 u'infobars': [], 2514 u'pinned': False, 2515 u'renderer_pid': 93919, 2516 u'url': u'https://chrome.google.com/'}, { 2517 u'index': 2, 2518 u'infobars': [ { 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'}], 2524 u'pinned': False, 2525 u'renderer_pid': 93929, 2526 u'url': u'http://slides.html5rocks.com/#slide14'}, 2527 ], 2528 u'type': u'tabbed', 2529 u'width': 925, 2530 u'x': 26, 2531 u'y': 44}]} 2532 2533 Raises: 2534 pyauto_errors.JSONInterfaceError if the automation call returns an error. 2535 """ 2536 cmd_dict = { # Prepare command for the json interface 2537 'command': 'GetBrowserInfo', 2538 } 2539 return self._GetResultFromJSONRequest(cmd_dict, windex=None) 2540 2541 def IsAura(self): 2542 """Is this Aura?""" 2543 return self.GetBrowserInfo()['properties']['aura'] 2544 2545 def GetProcessInfo(self): 2546 """Returns information about browser-related processes that currently exist. 2547 2548 This will also return information about other currently-running browsers 2549 besides just Chrome. 2550 2551 Returns: 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. 2559 2560 The memory values given in |committed_mem| and |working_set_mem| are in 2561 KBytes. 2562 2563 Sample: 2564 { 'browsers': [ { 'name': 'Chromium', 2565 'process_name': 'chrome', 2566 'processes': [ { 'child_process_type': 'Browser', 2567 'committed_mem': { 'image': 0, 2568 'mapped': 0, 2569 'priv': 0}, 2570 'is_diagnostics': False, 2571 'num_processes': 1, 2572 'pid': 7770, 2573 'product_name': '', 2574 'renderer_type': 'Unknown', 2575 'titles': [], 2576 'version': '', 2577 'working_set_mem': { 'priv': 43672, 2578 'shareable': 0, 2579 'shared': 59251}}, 2580 { 'child_process_type': 'Tab', 2581 'committed_mem': { 'image': 0, 2582 'mapped': 0, 2583 'priv': 0}, 2584 'is_diagnostics': False, 2585 'num_processes': 1, 2586 'pid': 7791, 2587 'product_name': '', 2588 'renderer_type': 'Tab', 2589 'titles': ['about:blank'], 2590 'version': '', 2591 'working_set_mem': { 'priv': 16768, 2592 'shareable': 0, 2593 'shared': 26256}}, 2594 ...<more processes>...]}]} 2595 2596 Raises: 2597 pyauto_errors.JSONInterfaceError if the automation call returns an error. 2598 """ 2599 cmd_dict = { # Prepare command for the json interface. 2600 'command': 'GetProcessInfo', 2601 } 2602 return self._GetResultFromJSONRequest(cmd_dict, windex=None) 2603 2604 def GetNavigationInfo(self, tab_index=0, windex=0): 2605 """Get info about the navigation state of a given tab. 2606 2607 Args: 2608 tab_index: The tab index, default is 0. 2609 window_index: The window index, default is 0. 2610 2611 Returns: 2612 a dictionary. 2613 Sample: 2614 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'}} 2620 2621 Values for security_style can be: 2622 SECURITY_STYLE_UNKNOWN 2623 SECURITY_STYLE_UNAUTHENTICATED 2624 SECURITY_STYLE_AUTHENTICATION_BROKEN 2625 SECURITY_STYLE_AUTHENTICATED 2626 2627 Values for page_type can be: 2628 NORMAL_PAGE 2629 ERROR_PAGE 2630 INTERSTITIAL_PAGE 2631 """ 2632 cmd_dict = { # Prepare command for the json interface 2633 'command': 'GetNavigationInfo', 2634 'tab_index': tab_index, 2635 } 2636 return self._GetResultFromJSONRequest(cmd_dict, windex=windex) 2637 2638 def GetSecurityState(self, tab_index=0, windex=0): 2639 """Get security details for a given tab. 2640 2641 Args: 2642 tab_index: The tab index, default is 0. 2643 window_index: The window index, default is 0. 2644 2645 Returns: 2646 a dictionary. 2647 Sample: 2648 { "security_style": SECURITY_STYLE_AUTHENTICATED, 2649 "ssl_cert_status": 3, // bitmask of status flags 2650 "insecure_content_status": 1, // bitmask of status flags 2651 } 2652 """ 2653 cmd_dict = { # Prepare command for the json interface 2654 'command': 'GetSecurityState', 2655 'tab_index': tab_index, 2656 'windex': windex, 2657 } 2658 return self._GetResultFromJSONRequest(cmd_dict, windex=None) 2659 2660 def GetHistoryInfo(self, search_text='', windex=0): 2661 """Return info about browsing history. 2662 2663 Args: 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. 2672 2673 Returns: 2674 an instance of history_info.HistoryInfo 2675 """ 2676 cmd_dict = { # Prepare command for the json interface 2677 'command': 'GetHistoryInfo', 2678 'search_text': search_text, 2679 } 2680 return history_info.HistoryInfo( 2681 self._GetResultFromJSONRequest(cmd_dict, windex=windex)) 2682 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. 2686 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. 2690 2691 Args: 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 2699 (first window). 2700 2701 Returns: 2702 The ID of the installed extension. 2703 2704 Raises: 2705 pyauto_errors.JSONInterfaceError if the automation call returns an error. 2706 """ 2707 cmd_dict = { 2708 'command': 'InstallExtension', 2709 'path': extension_path, 2710 'with_ui': with_ui, 2711 'windex': windex, 2712 'tab_index': tab_index, 2713 } 2714 2715 if from_webstore: 2716 cmd_dict['from_webstore'] = True 2717 return self._GetResultFromJSONRequest(cmd_dict, windex=None)['id'] 2718 2719 def GetExtensionsInfo(self, windex=0): 2720 """Returns information about all installed extensions. 2721 2722 Args: 2723 windex: Integer index of the browser window to use; defaults to 0 2724 (first window). 2725 2726 Returns: 2727 A list of dictionaries representing each of the installed extensions. 2728 Example: 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\ 2744 qhOtiQIDAQAB', 2745 u'version': u'0.1' }, 2746 { u'api_permissions': [...], 2747 u'background_url': u'chrome-extension://\ 2748 lkdedmbpkaiahjjibfdmpoefffnbdkli/\ 2749 background.html', 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} ] 2761 """ 2762 cmd_dict = { # Prepare command for the json interface 2763 'command': 'GetExtensionsInfo', 2764 'windex': windex, 2765 } 2766 return self._GetResultFromJSONRequest(cmd_dict, windex=None)['extensions'] 2767 2768 def UninstallExtensionById(self, id, windex=0): 2769 """Uninstall the extension with the given id. 2770 2771 Args: 2772 id: The string id of the extension. 2773 windex: Integer index of the browser window to use; defaults to 0 2774 (first window). 2775 2776 Returns: 2777 True, if the extension was successfully uninstalled, or 2778 False, otherwise. 2779 """ 2780 cmd_dict = { # Prepare command for the json interface 2781 'command': 'UninstallExtensionById', 2782 'id': id, 2783 'windex': windex, 2784 } 2785 return self._GetResultFromJSONRequest(cmd_dict, windex=None)['success'] 2786 2787 def SetExtensionStateById(self, id, enable, allow_in_incognito, windex=0): 2788 """Set extension state: enable/disable, allow/disallow in incognito mode. 2789 2790 Args: 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 2795 (first window). 2796 """ 2797 cmd_dict = { # Prepare command for the json interface 2798 'command': 'SetExtensionStateById', 2799 'id': id, 2800 'enable': enable, 2801 'allow_in_incognito': allow_in_incognito, 2802 'windex': windex, 2803 } 2804 self._GetResultFromJSONRequest(cmd_dict, windex=None) 2805 2806 def TriggerPageActionById(self, id, tab_index=0, windex=0): 2807 """Trigger page action asynchronously in the active tab. 2808 2809 The page action icon must be displayed before invoking this function. 2810 2811 Args: 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 2815 (first window). 2816 """ 2817 cmd_dict = { # Prepare command for the json interface 2818 'command': 'TriggerPageActionById', 2819 'id': id, 2820 'windex': windex, 2821 'tab_index': tab_index, 2822 } 2823 self._GetResultFromJSONRequest(cmd_dict, windex=None) 2824 2825 def TriggerBrowserActionById(self, id, tab_index=0, windex=0): 2826 """Trigger browser action asynchronously in the active tab. 2827 2828 Args: 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 2832 (first window). 2833 """ 2834 cmd_dict = { # Prepare command for the json interface 2835 'command': 'TriggerBrowserActionById', 2836 'id': id, 2837 'windex': windex, 2838 'tab_index': tab_index, 2839 } 2840 self._GetResultFromJSONRequest(cmd_dict, windex=None) 2841 2842 def UpdateExtensionsNow(self, windex=0): 2843 """Auto-updates installed extensions. 2844 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. 2848 2849 Args: 2850 windex: Integer index of the browser window to use; defaults to 0 2851 (first window). 2852 2853 Raises: 2854 pyauto_errors.JSONInterfaceError if the automation returns an error. 2855 """ 2856 cmd_dict = { # Prepare command for the json interface. 2857 'command': 'UpdateExtensionsNow', 2858 'windex': windex, 2859 } 2860 self._GetResultFromJSONRequest(cmd_dict, windex=None) 2861 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. 2865 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. 2871 2872 Args: 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'| 2878 'EXTENSION_DIALOG'] 2879 2880 Returns: 2881 The 'view' property of the extension view. 2882 None, if no view loaded. 2883 2884 Raises: 2885 pyauto_errors.JSONInterfaceError if the automation returns an error. 2886 """ 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'])): 2894 continue 2895 if extension_view['loaded']: 2896 return extension_view['view'] 2897 return False 2898 2899 if self.WaitUntil(lambda: _GetExtensionViewLoaded()): 2900 return _GetExtensionViewLoaded() 2901 return None 2902 2903 def WaitUntilExtensionViewClosed(self, view): 2904 """Wait for the given extension view to to be closed. 2905 2906 Uses WaitUntil so timeout is capped by automation timeout. 2907 Refer to extension_view dictionary returned by GetBrowserInfo() 2908 for sample input value. 2909 2910 Args: 2911 view: 'view' property of extension view. 2912 2913 Raises: 2914 pyauto_errors.JSONInterfaceError if the automation returns an error. 2915 """ 2916 def _IsExtensionViewClosed(): 2917 extension_views = self.GetBrowserInfo()['extension_views'] 2918 for extension_view in extension_views: 2919 if view == extension_view['view']: 2920 return False 2921 return True 2922 2923 return self.WaitUntil(lambda: _IsExtensionViewClosed()) 2924 2925 def GetPluginsInfo(self, windex=0): 2926 """Return info about plugins. 2927 2928 This is the info available from about:plugins 2929 2930 Returns: 2931 an instance of plugins_info.PluginsInfo 2932 """ 2933 return plugins_info.PluginsInfo( 2934 self._GetResultFromJSONRequest({'command': 'GetPluginsInfo'}, 2935 windex=windex)) 2936 2937 def EnablePlugin(self, path): 2938 """Enable the plugin at the given path. 2939 2940 Use GetPluginsInfo() to fetch path info about a plugin. 2941 2942 Raises: 2943 pyauto_errors.JSONInterfaceError if the automation call returns an error. 2944 """ 2945 cmd_dict = { 2946 'command': 'EnablePlugin', 2947 'path': path, 2948 } 2949 self._GetResultFromJSONRequest(cmd_dict) 2950 2951 def DisablePlugin(self, path): 2952 """Disable the plugin at the given path. 2953 2954 Use GetPluginsInfo() to fetch path info about a plugin. 2955 2956 Raises: 2957 pyauto_errors.JSONInterfaceError if the automation call returns an error. 2958 """ 2959 cmd_dict = { 2960 'command': 'DisablePlugin', 2961 'path': path, 2962 } 2963 self._GetResultFromJSONRequest(cmd_dict) 2964 2965 def GetTabContents(self, tab_index=0, window_index=0): 2966 """Get the html contents of a tab (a la "view source"). 2967 2968 As an implementation detail, this saves the html in a file, reads 2969 the file into a buffer, then deletes it. 2970 2971 Args: 2972 tab_index: tab index, defaults to 0. 2973 window_index: window index, defaults to 0. 2974 Returns: 2975 html content of a page as a string. 2976 """ 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 2985 } 2986 self._GetResultFromJSONRequest(cmd_dict, windex=window_index) 2987 try: 2988 f = open(filename) 2989 all_data = f.read() 2990 f.close() 2991 return all_data 2992 finally: 2993 shutil.rmtree(tempdir, ignore_errors=True) 2994 2995 def AddSavedPassword(self, password_dict, windex=0): 2996 """Adds the given username-password combination to the saved passwords. 2997 2998 Args: 2999 password_dict: a dictionary that represents a password. Example: 3000 { 'username_value': 'user (at] 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). 3011 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. 3016 3017 Returns: 3018 True if adding the password succeeded, false otherwise. In incognito 3019 mode, adding the password should fail. 3020 3021 Raises: 3022 pyauto_errors.JSONInterfaceError if the automation call returns an error. 3023 """ 3024 cmd_dict = { # Prepare command for the json interface 3025 'command': 'AddSavedPassword', 3026 'password': password_dict 3027 } 3028 return self._GetResultFromJSONRequest( 3029 cmd_dict, windex=windex)['password_added'] 3030 3031 def RemoveSavedPassword(self, password_dict, windex=0): 3032 """Removes the password matching the provided password dictionary. 3033 3034 Args: 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). 3038 """ 3039 cmd_dict = { # Prepare command for the json interface 3040 'command': 'RemoveSavedPassword', 3041 'password': password_dict 3042 } 3043 self._GetResultFromJSONRequest(cmd_dict, windex=windex) 3044 3045 def GetSavedPasswords(self): 3046 """Return the passwords currently saved. 3047 3048 Returns: 3049 A list of dictionaries representing each password. For an example 3050 dictionary see AddSavedPassword documentation. The overall structure will 3051 be: 3052 [ {password1 dictionary}, {password2 dictionary} ] 3053 """ 3054 cmd_dict = { # Prepare command for the json interface 3055 'command': 'GetSavedPasswords' 3056 } 3057 return self._GetResultFromJSONRequest(cmd_dict)['passwords'] 3058 3059 def SetTheme(self, crx_file_path, windex=0): 3060 """Installs the given theme synchronously. 3061 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. 3066 3067 Uses InstallExtension(). 3068 3069 Returns: 3070 The ID of the installed theme. 3071 3072 Raises: 3073 pyauto_errors.JSONInterfaceError if the automation call returns an error. 3074 """ 3075 return self.InstallExtension(crx_file_path, True, windex) 3076 3077 def GetActiveNotifications(self): 3078 """Gets a list of the currently active/shown HTML5 notifications. 3079 3080 Returns: 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 3085 closing. 3086 3087 SAMPLE: 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/', 3091 u'pid': 8505}, 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/', 3095 u'pid': 9291}] 3096 3097 Raises: 3098 pyauto_errors.JSONInterfaceError if the automation call returns an error. 3099 """ 3100 return [x for x in self.GetAllNotifications() if 'pid' in x] 3101 3102 def GetAllNotifications(self): 3103 """Gets a list of all active and queued HTML5 notifications. 3104 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. 3110 3111 Returns: 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 3116 notifications. 3117 3118 SAMPLE: 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/', 3122 u'pid': 8505}, 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/'}] 3126 3127 Raises: 3128 pyauto_errors.JSONInterfaceError if the automation call returns an error. 3129 """ 3130 cmd_dict = { 3131 'command': 'GetAllNotifications', 3132 } 3133 return self._GetResultFromJSONRequest(cmd_dict)['notifications'] 3134 3135 def CloseNotification(self, index): 3136 """Closes the active HTML5 notification at the given index. 3137 3138 Args: 3139 index: the index of the notification to close. 0 refers to the 3140 notification on the bottom of the notification stack. 3141 3142 Raises: 3143 pyauto_errors.JSONInterfaceError if the automation call returns an error. 3144 """ 3145 cmd_dict = { 3146 'command': 'CloseNotification', 3147 'index': index, 3148 } 3149 return self._GetResultFromJSONRequest(cmd_dict) 3150 3151 def WaitForNotificationCount(self, count): 3152 """Waits for the number of active HTML5 notifications to reach the given 3153 count. 3154 3155 Raises: 3156 pyauto_errors.JSONInterfaceError if the automation call returns an error. 3157 """ 3158 cmd_dict = { 3159 'command': 'WaitForNotificationCount', 3160 'count': count, 3161 } 3162 self._GetResultFromJSONRequest(cmd_dict) 3163 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. 3169 3170 Args: 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. 3178 3179 Returns: 3180 number of matches found for the given search string and parameters 3181 SAMPLE: 3182 { u'match_count': 10, 3183 u'match_left': 100, 3184 u'match_top': 100, 3185 u'match_right': 200, 3186 u'match_bottom': 200} 3187 3188 Raises: 3189 pyauto_errors.JSONInterfaceError if the automation call returns an error. 3190 """ 3191 cmd_dict = { 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, 3198 } 3199 return self._GetResultFromJSONRequest(cmd_dict, windex=windex, 3200 timeout=timeout) 3201 3202 def OpenFindInPage(self, windex=0): 3203 """Opens the "Find in Page" box. 3204 3205 Args: 3206 windex: Index of the window; defaults to 0. 3207 3208 Raises: 3209 pyauto_errors.JSONInterfaceError if the automation call returns an error. 3210 """ 3211 cmd_dict = { 3212 'command': 'OpenFindInPage', 3213 'windex' : windex, 3214 } 3215 self._GetResultFromJSONRequest(cmd_dict, windex=None) 3216 3217 def IsFindInPageVisible(self, windex=0): 3218 """Returns the visibility of the "Find in Page" box. 3219 3220 Args: 3221 windex: Index of the window; defaults to 0. 3222 3223 Returns: 3224 A boolean indicating the visibility state of the "Find in Page" box. 3225 3226 Raises: 3227 pyauto_errors.JSONInterfaceError if the automation call returns an error. 3228 """ 3229 cmd_dict = { 3230 'command': 'IsFindInPageVisible', 3231 'windex' : windex, 3232 } 3233 return self._GetResultFromJSONRequest(cmd_dict, windex=None)['is_visible'] 3234 3235 3236 def AddDomEventObserver(self, event_name='', automation_id=-1, 3237 recurring=False): 3238 """Adds a DomEventObserver associated with the AutomationEventQueue. 3239 3240 An app raises a matching event in Javascript by calling: 3241 window.domAutomationController.sendWithId(automation_id, event_name) 3242 3243 Args: 3244 event_name: The event name to watch for. By default an event is raised 3245 for any message. 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). 3254 3255 Returns: 3256 The id of the created observer, which can be used with GetNextEvent(id) 3257 and RemoveEventObserver(id). 3258 3259 Raises: 3260 pyauto_errors.JSONInterfaceError if the automation call returns an error. 3261 """ 3262 cmd_dict = { 3263 'command': 'AddDomEventObserver', 3264 'event_name': event_name, 3265 'automation_id': automation_id, 3266 'recurring': recurring, 3267 } 3268 return self._GetResultFromJSONRequest(cmd_dict, windex=None)['observer_id'] 3269 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. 3275 3276 Creates an observer that raises an event when a mutation of the given type 3277 occurs on a DOM node specified by |selector|. 3278 3279 Args: 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 3284 to 'textContent'. 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. 3292 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. 3295 3296 Returns: 3297 The id of the created observer, which can be used with GetNextEvent(id) 3298 and RemoveEventObserver(id). 3299 3300 Raises: 3301 pyauto_errors.JSONInterfaceError if the automation call returns an error. 3302 pyauto_errors.JavascriptRuntimeError if the injected javascript 3303 MutationObserver returns an error. 3304 """ 3305 assert mutation_type in ('add', 'remove', 'change', 'exists'), \ 3306 'Unexpected value "%s" for mutation_type.' % mutation_type 3307 cmd_dict = { 3308 'command': 'AddDomEventObserver', 3309 'event_name': '__dom_mutation_observer__:$(id)', 3310 'automation_id': automation_id, 3311 'recurring': False, 3312 } 3313 observer_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 3324 try: 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) 3331 return observer_id 3332 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. 3337 3338 NOTE: This does NOT poll. It returns as soon as the node appears, or 3339 immediately if the node already exists. 3340 3341 Args: 3342 xpath: An xpath specifying the DOM node to watch. 3343 attribute: Attribute to match |expected_value| against, if given. Defaults 3344 to 'textContent'. 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. 3354 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. 3357 3358 Raises: 3359 pyauto_errors.JSONInterfaceError if the automation call returns an error. 3360 pyauto_errors.JavascriptRuntimeError if the injected javascript 3361 MutationObserver returns an error. 3362 """ 3363 observer_id = self.AddDomMutationObserver('exists', xpath, attribute, 3364 expected_value, exec_js=exec_js, 3365 **kwargs) 3366 try: 3367 self.GetNextEvent(observer_id, timeout=timeout) 3368 except JSONInterfaceError: 3369 raise JSONInterfaceError(msg) 3370 3371 def GetNextEvent(self, observer_id=-1, blocking=True, timeout=-1): 3372 """Waits for an observed event to occur. 3373 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. 3378 3379 Args: 3380 observer_id: The id of the observer to wait for, matches any event by 3381 default. 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 3384 immediately. 3385 timeout: Time to wait for a matching event, defaults to the default 3386 automation timeout. 3387 3388 Returns: 3389 Event response dictionary, or None if blocking is disabled and there is no 3390 matching event in the queue. 3391 SAMPLE: 3392 { 'observer_id': 1, 3393 'name': 'login completed', 3394 'type': 'raised_event'} 3395 3396 Raises: 3397 pyauto_errors.JSONInterfaceError if the automation call returns an error. 3398 """ 3399 cmd_dict = { 3400 'command': 'GetNextEvent', 3401 'observer_id' : observer_id, 3402 'blocking' : blocking, 3403 } 3404 return self._GetResultFromJSONRequest(cmd_dict, windex=None, 3405 timeout=timeout) 3406 3407 def RemoveEventObserver(self, observer_id): 3408 """Removes an Event Observer from the AutomationEventQueue. 3409 3410 Expects a valid observer_id. 3411 3412 Args: 3413 observer_id: The id of the observer to remove. 3414 3415 Raises: 3416 pyauto_errors.JSONInterfaceError if the automation call returns an error. 3417 """ 3418 cmd_dict = { 3419 'command': 'RemoveEventObserver', 3420 'observer_id' : observer_id, 3421 } 3422 return self._GetResultFromJSONRequest(cmd_dict, windex=None) 3423 3424 def ClearEventQueue(self): 3425 """Removes all events currently in the AutomationEventQueue. 3426 3427 Raises: 3428 pyauto_errors.JSONInterfaceError if the automation call returns an error. 3429 """ 3430 cmd_dict = { 3431 'command': 'ClearEventQueue', 3432 } 3433 return self._GetResultFromJSONRequest(cmd_dict, windex=None) 3434 3435 def WaitUntilNavigationCompletes(self, tab_index=0, windex=0): 3436 """Wait until the specified tab is done navigating. 3437 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. 3440 3441 Args: 3442 tab_index: index of the tab. 3443 windex: index of the window. 3444 3445 Raises: 3446 pyauto_errors.JSONInterfaceError if the automation call returns an error. 3447 """ 3448 cmd_dict = { 3449 'command': 'WaitUntilNavigationCompletes', 3450 'tab_index': tab_index, 3451 'windex': windex, 3452 } 3453 return self._GetResultFromJSONRequest(cmd_dict) 3454 3455 def ExecuteJavascript(self, js, tab_index=0, windex=0, frame_xpath=''): 3456 """Executes a script in the specified frame of a tab. 3457 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 3461 return. 3462 3463 Args: 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]'. 3469 3470 Returns: 3471 a value that was sent back via the domAutomationController.send method 3472 3473 Raises: 3474 pyauto_errors.JSONInterfaceError if the automation call returns an error. 3475 """ 3476 cmd_dict = { 3477 'command': 'ExecuteJavascript', 3478 'javascript' : js, 3479 'windex' : windex, 3480 'tab_index' : tab_index, 3481 'frame_xpath' : frame_xpath, 3482 } 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] 3488 3489 def ExecuteJavascriptInRenderView(self, js, view, frame_xpath=''): 3490 """Executes a script in the specified frame of an render view. 3491 3492 The invoked javascript function must send a result back via the 3493 domAutomationController.send function, or this function will never return. 3494 3495 Args: 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']. 3500 Example: 3501 { 'render_process_id': 1, 3502 'render_view_id' : 2} 3503 3504 frame_xpath: XPath of the frame to execute the script. Default is no 3505 frame. Example: 3506 '//frames[1]' 3507 3508 Returns: 3509 a value that was sent back via the domAutomationController.send method 3510 3511 Raises: 3512 pyauto_errors.JSONInterfaceError if the automation call returns an error. 3513 """ 3514 cmd_dict = { 3515 'command': 'ExecuteJavascriptInRenderView', 3516 'javascript' : js, 3517 'view' : view, 3518 'frame_xpath' : frame_xpath, 3519 } 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] 3525 3526 def ExecuteJavascriptInOOBEWebUI(self, js, frame_xpath=''): 3527 """Executes a script in the specified frame of the OOBE WebUI. 3528 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 3533 never return. 3534 3535 Args: 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]' 3539 3540 Returns: 3541 A value that was sent back via the domAutomationController.send method. 3542 3543 Raises: 3544 pyauto_errors.JSONInterfaceError if the automation call returns an error. 3545 """ 3546 cmd_dict = { 3547 'command': 'ExecuteJavascriptInOOBEWebUI', 3548 3549 'javascript': js, 3550 'frame_xpath': frame_xpath, 3551 } 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] 3556 3557 3558 def GetDOMValue(self, expr, tab_index=0, windex=0, frame_xpath=''): 3559 """Executes a Javascript expression and returns the value. 3560 3561 This is a wrapper for ExecuteJavascript, eliminating the need to 3562 explicitly call domAutomationController.send function. 3563 3564 Args: 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]'. 3570 3571 Returns: 3572 a string that was sent back via the domAutomationController.send method. 3573 """ 3574 js = 'window.domAutomationController.send(%s);' % expr 3575 return self.ExecuteJavascript(js, tab_index, windex, frame_xpath) 3576 3577 def CallJavascriptFunc(self, function, args=[], tab_index=0, windex=0): 3578 """Executes a script which calls a given javascript function. 3579 3580 The invoked javascript function must send a result back via the 3581 domAutomationController.send function, or this function will never return. 3582 3583 Defaults to first tab in first window. 3584 3585 Args: 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. 3591 3592 Returns: 3593 a string that was sent back via the domAutomationController.send method 3594 """ 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) 3599 3600 def HeapProfilerDump(self, process_type, reason, tab_index=0, windex=0): 3601 """Dumps a heap profile. It works only on Linux and ChromeOS. 3602 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. 3608 3609 Also, this requires the --enable-memory-benchmarking command line flag. 3610 3611 Args: 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. 3615 Examples: 3616 'To check memory leaking' 3617 'For PyAuto tests' 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). 3622 3623 Raises: 3624 pyauto_errors.JSONInterfaceError if the automation call returns an error. 3625 """ 3626 assert process_type in ('browser', 'renderer') 3627 if self.IsLinux(): # IsLinux() also implies IsChromeOS(). 3628 js = """ 3629 if (!chrome.memoryBenchmarking || 3630 !chrome.memoryBenchmarking.isHeapProfilerRunning()) { 3631 domAutomationController.send('memory benchmarking disabled'); 3632 } else { 3633 chrome.memoryBenchmarking.heapProfilerDump("%s", "%s"); 3634 domAutomationController.send('success'); 3635 } 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) 3640 else: 3641 logging.warn('Heap-profiling is not supported in this OS.') 3642 3643 def GetNTPThumbnails(self): 3644 """Return a list of info about the sites in the NTP most visited section. 3645 SAMPLE: 3646 [{ u'title': u'Google', 3647 u'url': u'http://www.google.com'}, 3648 { 3649 u'title': u'Yahoo', 3650 u'url': u'http://www.yahoo.com'}] 3651 """ 3652 return self._GetNTPInfo()['most_visited'] 3653 3654 def GetNTPThumbnailIndex(self, thumbnail): 3655 """Returns the index of the given NTP thumbnail, or -1 if it is not shown. 3656 3657 Args: 3658 thumbnail: a thumbnail dict received from |GetNTPThumbnails| 3659 """ 3660 thumbnails = self.GetNTPThumbnails() 3661 for i in range(len(thumbnails)): 3662 if thumbnails[i]['url'] == thumbnail['url']: 3663 return i 3664 return -1 3665 3666 def RemoveNTPThumbnail(self, thumbnail): 3667 """Removes the NTP thumbnail and returns true on success. 3668 3669 Args: 3670 thumbnail: a thumbnail dict received from |GetNTPThumbnails| 3671 """ 3672 self._CheckNTPThumbnailShown(thumbnail) 3673 cmd_dict = { 3674 'command': 'RemoveNTPMostVisitedThumbnail', 3675 'url': thumbnail['url'] 3676 } 3677 self._GetResultFromJSONRequest(cmd_dict) 3678 3679 def RestoreAllNTPThumbnails(self): 3680 """Restores all the removed NTP thumbnails. 3681 Note: 3682 the default thumbnails may come back into the Most Visited sites 3683 section after doing this 3684 """ 3685 cmd_dict = { 3686 'command': 'RestoreAllNTPMostVisitedThumbnails' 3687 } 3688 self._GetResultFromJSONRequest(cmd_dict) 3689 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. 3693 3694 These sites are the ones present in the NTP on a fresh install of Chrome. 3695 """ 3696 return self._GetNTPInfo()['default_sites'] 3697 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) 3705 3706 def GetNTPRecentlyClosed(self): 3707 """Return a list of info about the items in the NTP recently closed section. 3708 SAMPLE: 3709 [{ 3710 u'type': u'tab', 3711 u'url': u'http://www.bing.com', 3712 u'title': u'Bing', 3713 u'timestamp': 2139082.03912, # Seconds since epoch (Jan 1, 1970) 3714 u'direction': u'ltr'}, 3715 { 3716 u'type': u'window', 3717 u'timestamp': 2130821.90812, 3718 u'tabs': [ 3719 { 3720 u'type': u'tab', 3721 u'url': u'http://www.cnn.com', 3722 u'title': u'CNN', 3723 u'timestamp': 2129082.12098, 3724 u'direction': u'ltr'}]}, 3725 { 3726 u'type': u'tab', 3727 u'url': u'http://www.altavista.com', 3728 u'title': u'Altavista', 3729 u'timestamp': 21390820.12903, 3730 u'direction': u'rtl'}] 3731 """ 3732 return self._GetNTPInfo()['recently_closed'] 3733 3734 def GetNTPApps(self): 3735 """Retrieves information about the apps listed on the NTP. 3736 3737 In the sample data below, the "launch_type" will be one of the following 3738 strings: "pinned", "regular", "fullscreen", "window", or "unknown". 3739 3740 SAMPLE: 3741 [ 3742 { 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'', 3755 }, 3756 { 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'', 3773 } 3774 ] 3775 3776 Returns: 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. 3779 """ 3780 return self._GetNTPInfo()['apps'] 3781 3782 def _GetNTPInfo(self): 3783 """Get info about the New Tab Page (NTP). 3784 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. 3789 3790 SAMPLE: 3791 { 3792 u'apps': [ ... ], 3793 u'most_visited': [ ... ], 3794 u'recently_closed': [ ... ], 3795 u'default_sites': [ ... ] 3796 } 3797 3798 Returns: 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(). 3802 3803 Raises: 3804 pyauto_errors.JSONInterfaceError if the automation call returns an error. 3805 """ 3806 cmd_dict = { 3807 'command': 'GetNTPInfo', 3808 } 3809 return self._GetResultFromJSONRequest(cmd_dict) 3810 3811 def _CheckNTPThumbnailShown(self, thumbnail): 3812 if self.GetNTPThumbnailIndex(thumbnail) == -1: 3813 raise NTPThumbnailNotShownError() 3814 3815 def LaunchApp(self, app_id, windex=0): 3816 """Opens the New Tab Page and launches the specified app from it. 3817 3818 This method will not return until after the contents of a new tab for the 3819 launched app have stopped loading. 3820 3821 Args: 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 3824 first window). 3825 3826 Raises: 3827 pyauto_errors.JSONInterfaceError if the automation call returns an error. 3828 """ 3829 self.AppendTab(GURL('chrome://newtab'), windex) # Also activates this tab. 3830 cmd_dict = { 3831 'command': 'LaunchApp', 3832 'id': app_id, 3833 } 3834 return self._GetResultFromJSONRequest(cmd_dict, windex=windex) 3835 3836 def SetAppLaunchType(self, app_id, launch_type, windex=0): 3837 """Sets the launch type for the specified app. 3838 3839 Args: 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 3847 first window). 3848 3849 Raises: 3850 pyauto_errors.JSONInterfaceError if the automation call returns an error. 3851 """ 3852 self.assertTrue(launch_type in ('pinned', 'regular', 'fullscreen', 3853 'window'), 3854 msg='Unexpected launch type value: "%s"' % launch_type) 3855 cmd_dict = { 3856 'command': 'SetAppLaunchType', 3857 'id': app_id, 3858 'launch_type': launch_type, 3859 } 3860 return self._GetResultFromJSONRequest(cmd_dict, windex=windex) 3861 3862 def GetV8HeapStats(self, tab_index=0, windex=0): 3863 """Returns statistics about the v8 heap in the renderer process for a tab. 3864 3865 Args: 3866 tab_index: The tab index, default is 0. 3867 window_index: The window index, default is 0. 3868 3869 Returns: 3870 A dictionary containing v8 heap statistics. Memory values are in bytes. 3871 Example: 3872 { 'renderer_id': 6223, 3873 'v8_memory_allocated': 21803776, 3874 'v8_memory_used': 10565392 } 3875 """ 3876 cmd_dict = { # Prepare command for the json interface. 3877 'command': 'GetV8HeapStats', 3878 'tab_index': tab_index, 3879 } 3880 return self._GetResultFromJSONRequest(cmd_dict, windex=windex) 3881 3882 def GetFPS(self, tab_index=0, windex=0): 3883 """Returns the current FPS associated with the renderer process for a tab. 3884 3885 FPS is the rendered frames per second. 3886 3887 Args: 3888 tab_index: The tab index, default is 0. 3889 window_index: The window index, default is 0. 3890 3891 Returns: 3892 A dictionary containing FPS info. 3893 Example: 3894 { 'renderer_id': 23567, 3895 'routing_id': 1, 3896 'fps': 29.404298782348633 } 3897 """ 3898 cmd_dict = { # Prepare command for the json interface. 3899 'command': 'GetFPS', 3900 'tab_index': tab_index, 3901 } 3902 return self._GetResultFromJSONRequest(cmd_dict, windex=windex) 3903 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') 3910 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') 3916 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') 3922 3923 def IsMouseLockPermissionRequested(self, windex=0): 3924 """Returns true if the user is currently prompted to give permision for 3925 mouse lock.""" 3926 return self._GetResultFromJSONRequest( 3927 { 'command': 'IsMouseLockPermissionRequested' }, 3928 windex=windex).get('result') 3929 3930 def IsFullscreenPermissionRequested(self, windex=0): 3931 """Returns true if the user is currently prompted to give permision for 3932 fullscreen.""" 3933 return self._GetResultFromJSONRequest( 3934 { 'command': 'IsFullscreenPermissionRequested' }, 3935 windex=windex).get('result') 3936 3937 def IsFullscreenBubbleDisplayed(self, windex=0): 3938 """Returns true if the fullscreen and mouse lock bubble is currently 3939 displayed.""" 3940 return self._GetResultFromJSONRequest( 3941 { 'command': 'IsFullscreenBubbleDisplayed' }, 3942 windex=windex).get('result') 3943 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') 3950 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' }, 3955 windex=windex) 3956 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' }, 3961 windex=windex) 3962 3963 def KillRendererProcess(self, pid): 3964 """Kills the given renderer process. 3965 3966 This will return only after the browser has received notice of the renderer 3967 close. 3968 3969 Args: 3970 pid: the process id of the renderer to kill 3971 3972 Raises: 3973 pyauto_errors.JSONInterfaceError if the automation call returns an error. 3974 """ 3975 cmd_dict = { 3976 'command': 'KillRendererProcess', 3977 'pid': pid 3978 } 3979 return self._GetResultFromJSONRequest(cmd_dict) 3980 3981 def NewWebDriver(self, port=0): 3982 """Returns a new remote WebDriver instance. 3983 3984 Args: 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. 3988 3989 Returns: 3990 selenium.webdriver.remote.webdriver.WebDriver instance 3991 """ 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) 4000 4001 def CreateNewAutomationProvider(self, channel_id): 4002 """Creates a new automation provider. 4003 4004 The provider will open a named channel in server mode. 4005 Args: 4006 channel_id: the channel_id to open the server channel with 4007 """ 4008 cmd_dict = { 4009 'command': 'CreateNewAutomationProvider', 4010 'channel_id': channel_id 4011 } 4012 self._GetResultFromJSONRequest(cmd_dict) 4013 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. 4017 4018 This is equivalent to 'Add new user' action with multi-profiles. 4019 4020 To account for crbug.com/108761 on Win XP, this call polls until the 4021 profile count increments by 1. 4022 4023 Raises: 4024 pyauto_errors.JSONInterfaceError if the automation call returns an error. 4025 """ 4026 num_profiles = len(self.GetMultiProfileInfo()['profiles']) 4027 cmd_dict = { # Prepare command for the json interface 4028 'command': 'OpenNewBrowserWindowWithNewProfile' 4029 } 4030 self._GetResultFromJSONRequest(cmd_dict, windex=None) 4031 # TODO(nirnimesh): Remove when crbug.com/108761 is fixed 4032 self.WaitUntil( 4033 lambda: len(self.GetMultiProfileInfo()['profiles']), 4034 expect_retval=(num_profiles + 1)) 4035 4036 def OpenProfileWindow(self, path, num_loads=1): 4037 """Open browser window for an existing profile. 4038 4039 This is equivalent to picking a profile from the multi-profile menu. 4040 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. 4044 4045 Args: 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. 4049 """ 4050 cmd_dict = { # Prepare command for the json interface 4051 'command': 'OpenProfileWindow', 4052 'path': path, 4053 'num_loads': num_loads, 4054 } 4055 return self._GetResultFromJSONRequest(cmd_dict, windex=None) 4056 4057 def GetMultiProfileInfo(self): 4058 """Fetch info about all multi-profile users. 4059 4060 Returns: 4061 A dictionary. 4062 Sample: 4063 { 4064 'enabled': True, 4065 'profiles': [{'name': 'First user', 4066 'path': '/tmp/.org.chromium.Chromium.Tyx17X/Default'}, 4067 {'name': 'User 1', 4068 'path': '/tmp/.org.chromium.Chromium.Tyx17X/profile_1'}], 4069 } 4070 4071 Profiles will be listed in the same order as visible in preferences. 4072 4073 Raises: 4074 pyauto_errors.JSONInterfaceError if the automation call returns an error. 4075 """ 4076 cmd_dict = { # Prepare command for the json interface 4077 'command': 'GetMultiProfileInfo' 4078 } 4079 return self._GetResultFromJSONRequest(cmd_dict, windex=None) 4080 4081 def RefreshPolicies(self): 4082 """Refreshes all the available policy providers. 4083 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 4087 returns. 4088 """ 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) 4094 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. 4097 4098 Args: 4099 form_id: the id attribute of the form to submit. 4100 4101 Returns: true on success. 4102 """ 4103 js = """ 4104 document.getElementById("%s").submit(); 4105 window.addEventListener("unload", function() { 4106 window.domAutomationController.send("done"); 4107 }); 4108 """ % form_id 4109 if self.ExecuteJavascript(js, tab_index, windex, frame_xpath) != 'done': 4110 return False 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') 4116 4117 def SimulateAsanMemoryBug(self): 4118 """Simulates a memory bug for Address Sanitizer to catch. 4119 4120 Address Sanitizer (if it was built it) will catch the bug and abort 4121 the process. 4122 This method returns immediately before it actually causes a crash. 4123 """ 4124 cmd_dict = { 'command': 'SimulateAsanMemoryBug' } 4125 self._GetResultFromJSONRequest(cmd_dict, windex=None) 4126 4127 ## ChromeOS section 4128 4129 def GetLoginInfo(self): 4130 """Returns information about login and screen locker state. 4131 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. 4134 4135 Returns: 4136 A dictionary. 4137 Sample: 4138 { u'is_guest': False, 4139 u'is_owner': True, 4140 u'email': u'example (at] 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} 4145 4146 Raises: 4147 pyauto_errors.JSONInterfaceError if the automation call returns an error. 4148 """ 4149 cmd_dict = { 'command': 'GetLoginInfo' } 4150 return self._GetResultFromJSONRequest(cmd_dict, windex=None) 4151 4152 def WaitForSessionManagerRestart(self, function): 4153 """Call a function and wait for the ChromeOS session_manager to restart. 4154 4155 Args: 4156 function: The function to call. 4157 """ 4158 assert callable(function) 4159 pgrep_process = subprocess.Popen(['pgrep', 'session_manager'], 4160 stdout=subprocess.PIPE) 4161 old_pid = pgrep_process.communicate()[0].strip() 4162 function() 4163 return self.WaitUntil(lambda: self._IsSessionManagerReady(old_pid)) 4164 4165 def _WaitForInodeChange(self, path, function): 4166 """Call a function and wait for the specified file path to change. 4167 4168 Args: 4169 path: The file path to check for changes. 4170 function: The function to call. 4171 """ 4172 assert callable(function) 4173 old_inode = os.stat(path).st_ino 4174 function() 4175 return self.WaitUntil(lambda: self._IsInodeNew(path, old_inode)) 4176 4177 def ShowCreateAccountUI(self): 4178 """Go to the account creation page. 4179 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. 4183 4184 Raises: 4185 pyauto_errors.JSONInterfaceError if the automation call returns an error. 4186 """ 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.' 4194 self.SetUp() 4195 4196 def SkipToLogin(self, skip_image_selection=True): 4197 """Skips OOBE to the login screen. 4198 4199 Assumes that we're at the beginning of OOBE. 4200 4201 Args: 4202 skip_image_selection: Boolean indicating whether the user image selection 4203 screen should also be skipped. 4204 4205 Raises: 4206 pyauto_errors.JSONInterfaceError if the automation call returns an error. 4207 """ 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' 4212 4213 def GetOOBEScreenInfo(self): 4214 """Queries info about the current OOBE screen. 4215 4216 Returns: 4217 A dictionary with the following keys: 4218 4219 'screen_name': The title of the current OOBE screen as a string. 4220 4221 Raises: 4222 pyauto_errors.JSONInterfaceError if the automation call returns an error. 4223 """ 4224 cmd_dict = { 'command': 'GetOOBEScreenInfo' } 4225 return self._GetResultFromJSONRequest(cmd_dict, windex=None) 4226 4227 def AcceptOOBENetworkScreen(self): 4228 """Accepts OOBE network screen and advances to the next one. 4229 4230 Assumes that we're already at the OOBE network screen. 4231 4232 Returns: 4233 A dictionary with the following keys: 4234 4235 'next_screen': The title of the next OOBE screen as a string. 4236 4237 Raises: 4238 pyauto_errors.JSONInterfaceError if the automation call returns an error. 4239 """ 4240 cmd_dict = { 'command': 'AcceptOOBENetworkScreen' } 4241 return self._GetResultFromJSONRequest(cmd_dict, windex=None) 4242 4243 def AcceptOOBEEula(self, accepted, usage_stats_reporting=False): 4244 """Accepts OOBE EULA and advances to the next screen. 4245 4246 Assumes that we're already at the OOBE EULA screen. 4247 4248 Args: 4249 accepted: Boolean indicating whether the EULA should be accepted. 4250 usage_stats_reporting: Boolean indicating whether UMA should be enabled. 4251 4252 Returns: 4253 A dictionary with the following keys: 4254 4255 'next_screen': The title of the next OOBE screen as a string. 4256 4257 Raises: 4258 pyauto_errors.JSONInterfaceError if the automation call returns an error. 4259 """ 4260 cmd_dict = { 'command': 'AcceptOOBEEula', 4261 'accepted': accepted, 4262 'usage_stats_reporting': usage_stats_reporting } 4263 return self._GetResultFromJSONRequest(cmd_dict, windex=None) 4264 4265 def CancelOOBEUpdate(self): 4266 """Skips update on OOBE and advances to the next screen. 4267 4268 Returns: 4269 A dictionary with the following keys: 4270 4271 'next_screen': The title of the next OOBE screen as a string. 4272 4273 Raises: 4274 pyauto_errors.JSONInterfaceError if the automation call returns an error. 4275 """ 4276 cmd_dict = { 'command': 'CancelOOBEUpdate' } 4277 return self._GetResultFromJSONRequest(cmd_dict, windex=None) 4278 4279 def PickUserImage(self, image): 4280 """Chooses image for the newly created user. 4281 4282 Should be called immediately after login. 4283 4284 Args: 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 4288 4289 Returns: 4290 A dictionary with the following keys: 4291 4292 'next_screen': The title of the next OOBE screen as a string. 4293 4294 Raises: 4295 pyauto_errors.JSONInterfaceError if the automation call returns an error. 4296 """ 4297 cmd_dict = { 'command': 'PickUserImage', 4298 'image': image } 4299 return self._GetResultFromJSONRequest(cmd_dict, windex=None) 4300 4301 def LoginAsGuest(self): 4302 """Login to chromeos as a guest user. 4303 4304 Waits until logged in. 4305 Should be displaying the login screen to work. 4306 4307 Raises: 4308 pyauto_errors.JSONInterfaceError if the automation call returns an error. 4309 """ 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.' 4318 self.SetUp() 4319 4320 def Login(self, username, password, timeout=120 * 1000): 4321 """Login to chromeos. 4322 4323 Waits until logged in and browser is ready. 4324 Should be displaying the login screen to work. 4325 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. 4329 4330 Args: 4331 username: the username to log in as. 4332 password: the user's password. 4333 timeout: timeout in ms; defaults to two minutes. 4334 4335 Returns: 4336 An error string if an error occured. 4337 None otherwise. 4338 4339 Raises: 4340 pyauto_errors.JSONInterfaceError if the automation call returns an error. 4341 """ 4342 self._GetResultFromJSONRequest({'command': 'AddLoginEventObserver'}, 4343 windex=None) 4344 cmd_dict = { 4345 'command': 'SubmitLoginForm', 4346 'username': username, 4347 'password': password, 4348 } 4349 self._GetResultFromJSONRequest(cmd_dict, windex=None) 4350 self.AddDomEventObserver('loginfail', automation_id=4444) 4351 try: 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)) 4358 4359 def Logout(self): 4360 """Log out from ChromeOS and wait for session_manager to come up. 4361 4362 This is equivalent to pressing the 'Sign out' button from the 4363 aura shell tray when logged in. 4364 4365 Should be logged in to work. Re-initializes the automation channel 4366 after logout. 4367 """ 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.' 4372 def _SignOut(): 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.' 4377 self.__SetUp() 4378 self.set_clear_profile(clear_profile_orig) 4379 4380 def LockScreen(self): 4381 """Locks the screen on chromeos. 4382 4383 Waits until screen is locked. 4384 Should be logged in and screen should not be locked to work. 4385 4386 Raises: 4387 pyauto_errors.JSONInterfaceError if the automation call returns an error. 4388 """ 4389 cmd_dict = { 'command': 'LockScreen' } 4390 self._GetResultFromJSONRequest(cmd_dict, windex=None) 4391 4392 def UnlockScreen(self, password): 4393 """Unlocks the screen on chromeos, authenticating the user's password first. 4394 4395 Waits until screen is unlocked. 4396 Screen locker should be active for this to work. 4397 4398 Returns: 4399 An error string if an error occured. 4400 None otherwise. 4401 4402 Raises: 4403 pyauto_errors.JSONInterfaceError if the automation call returns an error. 4404 """ 4405 cmd_dict = { 4406 'command': 'UnlockScreen', 4407 'password': password, 4408 } 4409 result = self._GetResultFromJSONRequest(cmd_dict, windex=None) 4410 return result.get('error_string') 4411 4412 def SignoutInScreenLocker(self): 4413 """Signs out of chromeos using the screen locker's "Sign out" feature. 4414 4415 Effectively the same as clicking the "Sign out" link on the screen locker. 4416 Screen should be locked for this to work. 4417 4418 Raises: 4419 pyauto_errors.JSONInterfaceError if the automation call returns an error. 4420 """ 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.' 4425 self.__SetUp() 4426 4427 def GetBatteryInfo(self): 4428 """Get details about battery state. 4429 4430 Returns: 4431 A dictionary with the following keys: 4432 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 4438 if 'line_power_on': 4439 'battery_time_to_full': int (seconds) 4440 else: 4441 'battery_time_to_empty': int (seconds) 4442 4443 If it is still calculating the time left, 'battery_time_to_full' 4444 and 'battery_time_to_empty' will be absent. 4445 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. 4449 4450 Sample: 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 } 4456 4457 Raises: 4458 pyauto_errors.JSONInterfaceError if the automation call returns an error. 4459 """ 4460 cmd_dict = { 'command': 'GetBatteryInfo' } 4461 return self._GetResultFromJSONRequest(cmd_dict, windex=None) 4462 4463 def GetPanelInfo(self): 4464 """Get details about open ChromeOS panels. 4465 4466 A panel is actually a type of browser window, so all of 4467 this information is also available using GetBrowserInfo(). 4468 4469 Returns: 4470 A dictionary. 4471 Sample: 4472 [{ 'incognito': False, 4473 'renderer_pid': 4820, 4474 'title': u'Downloads', 4475 'url': u'chrome://active-downloads/'}] 4476 4477 Raises: 4478 pyauto_errors.JSONInterfaceError if the automation call returns an error. 4479 """ 4480 panels = [] 4481 for browser in self.GetBrowserInfo()['windows']: 4482 if browser['type'] != 'panel': 4483 continue 4484 4485 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'] 4492 4493 return panels 4494 4495 def EnableSpokenFeedback(self, enabled): 4496 """Enables or disables spoken feedback accessibility mode. 4497 4498 Args: 4499 enabled: Boolean value indicating the desired state of spoken feedback. 4500 4501 Raises: 4502 pyauto_errors.JSONInterfaceError if the automation call returns an error. 4503 """ 4504 cmd_dict = { 4505 'command': 'EnableSpokenFeedback', 4506 'enabled': enabled, 4507 } 4508 return self._GetResultFromJSONRequest(cmd_dict, windex=None) 4509 4510 def IsSpokenFeedbackEnabled(self): 4511 """Check whether spoken feedback accessibility mode is enabled. 4512 4513 Returns: 4514 True if spoken feedback is enabled, False otherwise. 4515 4516 Raises: 4517 pyauto_errors.JSONInterfaceError if the automation call returns an error. 4518 """ 4519 cmd_dict = { 'command': 'IsSpokenFeedbackEnabled', } 4520 result = self._GetResultFromJSONRequest(cmd_dict, windex=None) 4521 return result.get('spoken_feedback') 4522 4523 def GetTimeInfo(self, windex=0): 4524 """Gets info about the ChromeOS status bar clock. 4525 4526 Set the 24-hour clock by using: 4527 self.SetPrefs('settings.clock.use_24hour_clock', True) 4528 4529 Returns: 4530 a dictionary. 4531 Sample: 4532 {u'display_date': u'Tuesday, July 26, 2011', 4533 u'display_time': u'4:30', 4534 u'timezone': u'America/Los_Angeles'} 4535 4536 Raises: 4537 pyauto_errors.JSONInterfaceError if the automation call returns an error. 4538 """ 4539 cmd_dict = { 'command': 'GetTimeInfo' } 4540 if self.GetLoginInfo()['is_logged_in']: 4541 return self._GetResultFromJSONRequest(cmd_dict, windex=windex) 4542 else: 4543 return self._GetResultFromJSONRequest(cmd_dict, windex=None) 4544 4545 def SetTimezone(self, timezone): 4546 """Sets the timezone on ChromeOS. A user must be logged in. 4547 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'. 4552 4553 This method does not return indication of success or failure. 4554 If the timezone is it falls back to a valid timezone. 4555 4556 Raises: 4557 pyauto_errors.JSONInterfaceError if the automation call returns an error. 4558 """ 4559 cmd_dict = { 4560 'command': 'SetTimezone', 4561 'timezone': timezone, 4562 } 4563 self._GetResultFromJSONRequest(cmd_dict, windex=None) 4564 4565 def UpdateCheck(self): 4566 """Checks for a ChromeOS update. Blocks until finished updating. 4567 4568 Raises: 4569 pyauto_errors.JSONInterfaceError if the automation call returns an error. 4570 """ 4571 cmd_dict = { 'command': 'UpdateCheck' } 4572 self._GetResultFromJSONRequest(cmd_dict, windex=None) 4573 4574 def GetVolumeInfo(self): 4575 """Gets the volume and whether the device is muted. 4576 4577 Returns: 4578 a tuple. 4579 Sample: 4580 (47.763456790123456, False) 4581 4582 Raises: 4583 pyauto_errors.JSONInterfaceError if the automation call returns an error. 4584 """ 4585 cmd_dict = { 'command': 'GetVolumeInfo' } 4586 return self._GetResultFromJSONRequest(cmd_dict, windex=None) 4587 4588 def SetVolume(self, volume): 4589 """Sets the volume on ChromeOS. Only valid if not muted. 4590 4591 Args: 4592 volume: The desired volume level as a percent from 0 to 100. 4593 4594 Raises: 4595 pyauto_errors.JSONInterfaceError if the automation call returns an error. 4596 """ 4597 assert volume >= 0 and volume <= 100 4598 cmd_dict = { 4599 'command': 'SetVolume', 4600 'volume': float(volume), 4601 } 4602 return self._GetResultFromJSONRequest(cmd_dict, windex=None) 4603 4604 def SetMute(self, mute): 4605 """Sets whether ChromeOS is muted or not. 4606 4607 Args: 4608 mute: True to mute, False to unmute. 4609 4610 Raises: 4611 pyauto_errors.JSONInterfaceError if the automation call returns an error. 4612 """ 4613 cmd_dict = { 'command': 'SetMute' } 4614 cmd_dict = { 4615 'command': 'SetMute', 4616 'mute': mute, 4617 } 4618 return self._GetResultFromJSONRequest(cmd_dict, windex=None) 4619 4620 # HTML Terminal 4621 4622 def OpenCrosh(self): 4623 """Open crosh. 4624 4625 Equivalent to pressing Ctrl-Alt-t. 4626 Opens in the last active (non-incognito) window. 4627 4628 Waits long enough for crosh to load, but does not wait for the crosh 4629 prompt. Use WaitForHtermText() for that. 4630 """ 4631 cmd_dict = { 'command': 'OpenCrosh' } 4632 self._GetResultFromJSONRequest(cmd_dict, windex=None) 4633 4634 def WaitForHtermText(self, text, msg=None, tab_index=0, windex=0): 4635 """Waits for the given text in a hterm tab. 4636 4637 Can be used to wait for the crosh> prompt or ssh prompt. 4638 4639 This does not poll. It uses dom mutation observers to wait 4640 for the given text to show up. 4641 4642 Args: 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. 4647 """ 4648 self.WaitForDomNode( 4649 xpath='//*[contains(text(), "%s")]' % text, frame_xpath='//iframe', 4650 msg=msg, tab_index=tab_index, windex=windex) 4651 4652 def GetHtermRowsText(self, start, end, tab_index=0, windex=0): 4653 """Fetch rows from a html terminal tab. 4654 4655 Works for both crosh and ssh tab. 4656 Uses term_.getRowsText(start, end) javascript call. 4657 4658 Args: 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. 4663 """ 4664 return self.ExecuteJavascript( 4665 'domAutomationController.send(term_.getRowsText(%d, %d))' % ( 4666 start, end), 4667 tab_index=tab_index, windex=windex) 4668 4669 def SendKeysToHterm(self, text, tab_index=0, windex=0): 4670 """Send keys to a html terminal tab. 4671 4672 Works for both crosh and ssh tab. 4673 Uses term_.onVTKeystroke(str) javascript call. 4674 4675 Args: 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. 4679 """ 4680 return self.ExecuteJavascript( 4681 'term_.onVTKeystroke("%s");' 4682 'domAutomationController.send("done")' % text, 4683 tab_index=tab_index, windex=windex) 4684 4685 4686 def GetMemoryStatsChromeOS(self, duration): 4687 """Identifies and returns different kinds of current memory usage stats. 4688 4689 This function samples values each second for |duration| seconds, then 4690 outputs the min, max, and ending values for each measurement type. 4691 4692 Args: 4693 duration: The number of seconds to sample data before outputting the 4694 minimum, maximum, and ending values for each measurement type. 4695 4696 Returns: 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. 4700 { 4701 'gem_obj': { # GPU memory usage. 4702 'min': ..., 4703 'max': ..., 4704 'end': ..., 4705 }, 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. 4720 } 4721 """ 4722 logging.debug('Sampling memory information for %d seconds...' % duration) 4723 stats = {} 4724 4725 for _ in xrange(duration): 4726 # GPU memory. 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] 4732 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) 4738 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] 4744 4745 gtt = re.search( 4746 'Total [\d]+ objects, ([\d]+) bytes', stdout).group(1) 4747 if 'gtt' not in stats: 4748 stats['gtt'] = [] 4749 stats['gtt'].append(int(gtt) / 1024.0) 4750 4751 # CPU memory. 4752 stdout = '' 4753 with open('/proc/meminfo') as f: 4754 stdout = f.read() 4755 mem_free = re.search('MemFree:\s*([\d]+) kB', stdout).group(1) 4756 4757 if 'mem_free' not in stats: 4758 stats['mem_free'] = [] 4759 stats['mem_free'].append(int(mem_free)) 4760 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) 4766 4767 with open('/proc/sys/vm/min_filelist_kbytes') as f: 4768 mem_min_file = f.read() 4769 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)) 4777 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)) 4782 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)) 4787 4788 mem_anon_active = re.search('Active\(anon\):\s*([\d]+) kB', 4789 stdout).group(1) 4790 mem_anon_inactive = re.search('Inactive\(anon\):\s*([\d]+) kB', 4791 stdout).group(1) 4792 if 'mem_anon' not in stats: 4793 stats['mem_anon'] = [] 4794 stats['mem_anon'].append(int(mem_anon_active) + int(mem_anon_inactive)) 4795 4796 mem_file_active = re.search('Active\(file\):\s*([\d]+) kB', 4797 stdout).group(1) 4798 mem_file_inactive = re.search('Inactive\(file\):\s*([\d]+) kB', 4799 stdout).group(1) 4800 if 'mem_file' not in stats: 4801 stats['mem_file'] = [] 4802 stats['mem_file'].append(int(mem_file_active) + int(mem_file_inactive)) 4803 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)) 4808 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)) 4840 4841 time.sleep(1) 4842 4843 # Compute min, max, and ending values to return. 4844 result = {} 4845 for measurement_type in stats: 4846 values = stats[measurement_type] 4847 result[measurement_type] = { 4848 'min': min(values), 4849 'max': max(values), 4850 'end': values[-1], 4851 } 4852 4853 return result 4854 4855 ## ChromeOS section -- end 4856 4857 4858 class ExtraBrowser(PyUITest): 4859 """Launches a new browser with some extra flags. 4860 4861 The new browser is launched with its own fresh profile. 4862 This class does not apply to ChromeOS. 4863 """ 4864 def __init__(self, chrome_flags=[], methodName='runTest', **kwargs): 4865 """Accepts extra chrome flags for launching a new browser instance. 4866 4867 Args: 4868 chrome_flags: list of extra flags when launching a new browser. 4869 """ 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) 4875 4876 def __del__(self): 4877 """Tears down the browser and then calls super class's destructor""" 4878 PyUITest.tearDown(self) 4879 PyUITest.__del__(self) 4880 4881 def ExtraChromeFlags(self): 4882 """Prepares the browser to launch with specified Chrome flags.""" 4883 return PyUITest.ExtraChromeFlags(self) + self._chrome_flags 4884 4885 4886 class _RemoteProxy(): 4887 """Class for PyAuto remote method calls. 4888 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. 4892 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. 4896 4897 The remote host should be running the same version of PyAuto as the proxy. 4898 A mismatch could lead to undefined behavior. 4899 4900 Example usage: 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') 4907 """ 4908 class RemoteException(Exception): 4909 pass 4910 4911 def __init__(self, host): 4912 self.RemoteConnect(host) 4913 4914 def RemoteConnect(self, host): 4915 begin = time.time() 4916 while time.time() - begin < 50: 4917 self._socket = socket.socket() 4918 if not self._socket.connect_ex(host): 4919 break 4920 time.sleep(0.25) 4921 else: 4922 # Make one last attempt, but raise a socket error on failure. 4923 self._socket = socket.socket() 4924 self._socket.connect(host) 4925 4926 def RemoteDisconnect(self): 4927 if self._socket: 4928 self._socket.shutdown(socket.SHUT_RDWR) 4929 self._socket.close() 4930 self._socket = None 4931 4932 def CreateTarget(self, target): 4933 """Registers the methods and creates a remote instance of a target. 4934 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 4938 takes no arguments. 4939 """ 4940 self._Call('CreateTarget', target.__class__) 4941 self._RegisterClassMethods(target) 4942 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)) 4950 4951 def _Call(self, method_name, *args, **kwargs): 4952 # Send request. 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.') 4956 4957 # Receive response. 4958 response = self._socket.recv(4096) 4959 if not response: 4960 raise self.RemoteException('Client disconnected during method call.') 4961 result, stdout, stderr, exception = pickle.loads(response) 4962 4963 # Print any output the client captured, throw any exceptions, and return. 4964 sys.stdout.write(stdout) 4965 sys.stderr.write(stderr) 4966 if exception: 4967 raise self.RemoteException('%s raised by remote client: %s' % 4968 (exception[0], exception[1])) 4969 return result 4970 4971 4972 class PyUITestSuite(pyautolib.PyUITestSuiteBase, unittest.TestSuite): 4973 """Base TestSuite for PyAuto UI tests.""" 4974 4975 def __init__(self, args): 4976 pyautolib.PyUITestSuiteBase.__init__(self, args) 4977 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'] 4983 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)) 4988 4989 # Start http server, if needed. 4990 global _OPTIONS 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(',')) 4995 4996 def __del__(self): 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. 5001 del self._tests 5002 pyautolib.PyUITestSuiteBase.__del__(self) 5003 5004 global _HTTP_SERVER 5005 if _HTTP_SERVER: 5006 self._StopHTTPServer() 5007 5008 global _CHROME_DRIVER_FACTORY 5009 if _CHROME_DRIVER_FACTORY is not None: 5010 _CHROME_DRIVER_FACTORY.Stop() 5011 5012 def _StartHTTPServer(self): 5013 """Start a local file server hosting data files over http://""" 5014 global _HTTP_SERVER 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, 5019 '127.0.0.1', 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) 5024 5025 def _StopHTTPServer(self): 5026 """Stop the local http server.""" 5027 global _HTTP_SERVER 5028 assert _HTTP_SERVER, 'HTTP Server not yet started' 5029 assert _HTTP_SERVER.Stop(), 'Could not stop http server' 5030 _HTTP_SERVER = None 5031 logging.debug('Stopped http server.') 5032 5033 def _ConnectToRemoteHosts(self, addresses): 5034 """Connect to remote PyAuto instances using a RemoteProxy. 5035 5036 The RemoteHost instances must already be running.""" 5037 global _REMOTE_PROXY 5038 assert not _REMOTE_PROXY, 'Already connected to a remote host.' 5039 _REMOTE_PROXY = [] 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))) 5044 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. 5049 import __main__ 5050 main_path = os.path.dirname(__main__.__file__) 5051 env = os.environ 5052 if env.get('PYTHONPATH', None): 5053 env['PYTHONPATH'] += ':' + main_path 5054 else: 5055 env['PYTHONPATH'] = main_path 5056 5057 # Run it! 5058 subprocess.Popen([sys.executable, os.path.join(os.path.dirname(__file__), 5059 'remote_host.py')], env=env) 5060 5061 5062 class _GTestTextTestResult(unittest._TextTestResult): 5063 """A test result class that can print formatted text results to a stream. 5064 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 5072 """ 5073 def __init__(self, stream, descriptions, verbosity): 5074 unittest._TextTestResult.__init__(self, stream, descriptions, verbosity) 5075 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) 5083 5084 def getDescription(self, test): 5085 return '%s: "%s"' % (self._GetTestURI(test), test.shortDescription()) 5086 5087 def startTest(self, test): 5088 unittest.TestResult.startTest(self, test) 5089 self.stream.writeln('[ RUN ] %s' % self.getDescription(test)) 5090 5091 def addSuccess(self, test): 5092 unittest.TestResult.addSuccess(self, test) 5093 self.stream.writeln('[ OK ] %s' % self._GetTestURI(test)) 5094 5095 def addError(self, test, err): 5096 unittest.TestResult.addError(self, test, err) 5097 self.stream.writeln('[ ERROR ] %s' % self._GetTestURI(test)) 5098 5099 def addFailure(self, test, err): 5100 unittest.TestResult.addFailure(self, test, err) 5101 self.stream.writeln('[ FAILED ] %s' % self._GetTestURI(test)) 5102 5103 5104 class PyAutoTextTestRunner(unittest.TextTestRunner): 5105 """Test Runner for PyAuto tests that displays results in textual format. 5106 5107 Results are displayed in conformance with gtest output. 5108 """ 5109 def __init__(self, verbosity=1): 5110 unittest.TextTestRunner.__init__(self, 5111 stream=sys.stderr, 5112 verbosity=verbosity) 5113 5114 def _makeResult(self): 5115 return _GTestTextTestResult(self.stream, self.descriptions, self.verbosity) 5116 5117 5118 # Implementation inspired from unittest.main() 5119 class Main(object): 5120 """Main program for running PyAuto tests.""" 5121 5122 _options, _args = None, None 5123 _tests_filename = 'PYAUTO_TESTS' 5124 _platform_map = { 5125 'win32': 'win', 5126 'darwin': 'mac', 5127 'linux2': 'linux', 5128 'linux3': 'linux', 5129 'chromeos': 'chromeos', 5130 } 5131 5132 def __init__(self): 5133 self._ParseArgs() 5134 self._Run() 5135 5136 def _ParseArgs(self): 5137 """Parse command line args.""" 5138 parser = optparse.OptionParser() 5139 parser.add_option( 5140 '', '--channel-id', type='string', default='', 5141 help='Name of channel id, if using named interface.') 5142 parser.add_option( 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.') 5148 parser.add_option( 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.') 5152 parser.add_option( 5153 '-L', '--list-tests', action='store_true', default=False, 5154 help='List all tests, and exit.') 5155 parser.add_option( 5156 '--shard', 5157 help='Specify sharding params. Example: 1/3 implies split the list of ' 5158 'tests into 3 groups of which this is the 1st.') 5159 parser.add_option( 5160 '', '--log-file', type='string', default=None, 5161 help='Provide a path to a file to which the logger will log') 5162 parser.add_option( 5163 '', '--no-http-server', action='store_true', default=False, 5164 help='Do not start an http server to serve files in data dir.') 5165 parser.add_option( 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.') 5170 parser.add_option( 5171 '', '--repeat', type='int', default=1, 5172 help='Number of times to repeat the tests. Useful to determine ' 5173 'flakiness. Defaults to 1.') 5174 parser.add_option( 5175 '-S', '--suite', type='string', default='FULL', 5176 help='Name of the suite to load. Defaults to "FULL".') 5177 parser.add_option( 5178 '-v', '--verbose', action='store_true', default=False, 5179 help='Make PyAuto verbose.') 5180 parser.add_option( 5181 '-D', '--wait-for-debugger', action='store_true', default=False, 5182 help='Block PyAuto on startup for attaching debugger.') 5183 5184 self._options, self._args = parser.parse_args() 5185 global _OPTIONS 5186 _OPTIONS = self._options # Export options so other classes can access. 5187 5188 # Set up logging. All log messages will be prepended with a timestamp. 5189 format = '%(asctime)s %(levelname)-8s %(message)s' 5190 5191 level = logging.INFO 5192 if self._options.verbose: 5193 level=logging.DEBUG 5194 5195 logging.basicConfig(level=level, format=format, 5196 filename=self._options.log_file) 5197 5198 def TestsDir(self): 5199 """Returns the path to dir containing tests. 5200 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 5203 if needed. 5204 """ 5205 return os.path.dirname(__file__) 5206 5207 @staticmethod 5208 def _ImportTestsFromName(name): 5209 """Get a list of all test names from the given string. 5210 5211 Args: 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) 5216 5217 Returns: 5218 [omnibox.OmniboxTest.testA, omnibox.OmniboxTest.testB, ...] 5219 """ 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')] 5224 5225 def _GetTestsFromModule(module): 5226 """Return all test method names from the given module object.""" 5227 tests = [] 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)]) 5234 return tests 5235 5236 module = None 5237 # Locate the module 5238 parts = name.split('.') 5239 parts_copy = parts[:] 5240 while parts_copy: 5241 try: 5242 module = __import__('.'.join(parts_copy)) 5243 break 5244 except ImportError: 5245 del parts_copy[-1] 5246 if not parts_copy: raise 5247 # We have the module. Pick the exact test method or class asked for. 5248 parts = parts[1:] 5249 obj = module 5250 for part in parts: 5251 obj = getattr(obj, part) 5252 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: 5259 return [name] 5260 else: 5261 logging.warn('No tests in "%s"', name) 5262 return [] 5263 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)): 5272 return True 5273 return False 5274 5275 def _ExpandTestNames(self, args): 5276 """Returns a list of tests loaded from the given args. 5277 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 5281 modules are loaded. 5282 5283 Args: 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 5287 5288 Returns: 5289 a list of expanded test names. Example: 5290 [ 5291 'module1.TestCase1.testA', 5292 'module1.TestCase1.testB', 5293 'module2.TestCase2.testX', 5294 'module3.testcase.testY', 5295 'module4.testcase.testX' 5296 ] 5297 """ 5298 5299 def _TestsFromDescriptionFile(suite): 5300 pyauto_tests_file = os.path.join(self.TestsDir(), self._tests_filename) 5301 if suite: 5302 logging.debug("Reading %s (@%s)", pyauto_tests_file, suite) 5303 else: 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) 5307 return [] 5308 else: 5309 return self._ExpandTestNamesFrom(pyauto_tests_file, suite) 5310 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 5318 out_args = [] 5319 for arg in args: 5320 if arg.startswith('@'): 5321 suite = arg[1:] 5322 out_args += _TestsFromDescriptionFile(suite) 5323 else: 5324 out_args.append(arg) 5325 args = out_args 5326 return args 5327 5328 def _ExpandTestNamesFrom(self, filename, suite): 5329 """Load test names from the given file. 5330 5331 Args: 5332 filename: the file to read the tests from 5333 suite: the name of the suite to load from |filename|. 5334 5335 Returns: 5336 a list of test names 5337 [module.testcase.testX, module.testcase.testY, ..] 5338 """ 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], []) 5349 ret = [] 5350 # Recursively include suites if any. Suites begin with @. 5351 for name in names: 5352 if name.startswith('@'): # Include another suite 5353 ret.extend(_NamesInSuite(name[1:])) 5354 else: 5355 ret.append(name) 5356 return ret 5357 5358 assert suite in suites, '%s: No such suite in %s' % (suite, filename) 5359 all_names = _NamesInSuite(suite) 5360 args = [] 5361 excluded = [] 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:])) 5366 else: 5367 args.extend(self._ImportTestsFromName(name)) 5368 for name in excluded: 5369 if name in args: 5370 args.remove(name) 5371 else: 5372 logging.warn('Cannot exclude %s. Not included. Ignoring', name) 5373 if excluded: 5374 logging.debug('Excluded %d test(s): %s', len(excluded), excluded) 5375 return args 5376 5377 def _Run(self): 5378 """Run the tests.""" 5379 if self._options.wait_for_debugger: 5380 raw_input('Attach debugger to process %s and hit <enter> ' % os.getpid()) 5381 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) 5388 5389 # Shard, if requested (--shard). 5390 if self._options.shard: 5391 matched = re.match('(\d+)/(\d+)', self._options.shard) 5392 if not matched: 5393 print >>sys.stderr, 'Invalid sharding params: %s' % self._options.shard 5394 sys.exit(1) 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 5399 sys.exit(1) 5400 test_names = pyauto_utils.Shard(test_names, shard_index, num_shards) 5401 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: 5406 print name 5407 sys.exit(0) 5408 pyauto_suite = PyUITestSuite(suite_args) 5409 loaded_tests = unittest.defaultTestLoader.loadTestsFromNames(test_names) 5410 pyauto_suite.addTests(loaded_tests) 5411 verbosity = 1 5412 if self._options.verbose: 5413 verbosity = 2 5414 result = PyAutoTextTestRunner(verbosity=verbosity).run(pyauto_suite) 5415 del loaded_tests # Need to destroy test cases before the suite 5416 del pyauto_suite 5417 successful = result.wasSuccessful() 5418 if not successful: 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) 5423 5424 5425 if __name__ == '__main__': 5426 Main() 5427