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 # TODO(dtu): Remove this after crosbug.com/4558 is fixed. 212 if self.IsChromeOS(): 213 self.WaitUntil(lambda: not self.GetNetworkInfo()['offline_mode']) 214 215 if (self.IsChromeOS() and not self.GetLoginInfo()['is_logged_in'] and 216 self.ShouldOOBESkipToLogin()): 217 if self.GetOOBEScreenInfo()['screen_name'] != 'login': 218 self.SkipToLogin() 219 if self.ShouldAutoLogin(): 220 # Login with default creds. 221 sys.path.append('/usr/local') # to import autotest libs 222 from autotest.cros import constants 223 creds = constants.CREDENTIALS['$default'] 224 self.Login(creds[0], creds[1]) 225 assert self.GetLoginInfo()['is_logged_in'] 226 logging.info('Logged in as %s.' % creds[0]) 227 228 # If we are connected to any RemoteHosts, create PyAuto 229 # instances on the remote sides and set them up too. 230 for remote in self.remotes: 231 remote.CreateTarget(self) 232 remote.setUp() 233 234 def setUp(self): 235 """Override this method to launch browser differently. 236 237 Can be used to prevent launching the browser window by default in case a 238 test wants to do some additional setup before firing browser. 239 240 When using the named interface, it connects to an existing browser 241 instance. 242 243 On ChromeOS, a browser showing the login window is started. Tests can 244 initiate a user session by calling Login() or LoginAsGuest(). Cryptohome 245 vaults or flimflam profiles left over by previous tests can be cleared by 246 calling RemoveAllCryptohomeVaults() respectively CleanFlimflamDirs() before 247 logging in to improve isolation. Note that clearing flimflam profiles 248 requires a flimflam restart, briefly taking down network connectivity and 249 slowing down the test. This should be done for tests that use flimflam only. 250 """ 251 self.__SetUp() 252 253 def tearDown(self): 254 for remote in self.remotes: 255 remote.tearDown() 256 257 self.TearDown() # Destroy browser 258 259 # Method required by the Python standard library unittest.TestCase. 260 def runTest(self): 261 pass 262 263 @staticmethod 264 def BrowserPath(): 265 """Returns the path to Chromium binaries. 266 267 Expects the browser binaries to be in the 268 same location as the pyautolib binaries. 269 """ 270 return os.path.normpath(os.path.dirname(pyautolib.__file__)) 271 272 def ExtraChromeFlags(self): 273 """Return a list of extra chrome flags to use with Chrome for testing. 274 275 These are flags needed to facilitate testing. Override this function to 276 use a custom set of Chrome flags. 277 """ 278 auth_ext_path = ('/usr/local/autotest/deps/pyauto_dep/' + 279 'test_src/chrome/browser/resources/gaia_auth') 280 if self.IsChromeOS(): 281 return [ 282 '--homepage=about:blank', 283 '--allow-file-access', 284 '--allow-file-access-from-files', 285 '--enable-file-cookies', 286 '--disable-default-apps', 287 '--dom-automation', 288 '--skip-oauth-login', 289 # Enables injection of test content script for webui login automation 290 '--auth-ext-path=%s' % auth_ext_path, 291 # Enable automation provider, chromeos net and chromeos login logs 292 '--vmodule=*/browser/automation/*=2,*/chromeos/net/*=2,' + 293 '*/chromeos/login/*=2', 294 ] 295 else: 296 return [] 297 298 def ShouldOOBESkipToLogin(self): 299 """Determine if we should skip the OOBE flow on ChromeOS. 300 301 This makes automation skip the OOBE flow during setUp() and land directly 302 to the login screen. Applies only if not logged in already. 303 304 Override and return False if OOBE flow is required, for OOBE tests, for 305 example. Calling this function directly will have no effect. 306 307 Returns: 308 True, if the OOBE should be skipped and automation should 309 go to the 'Add user' login screen directly 310 False, if the OOBE should not be skipped. 311 """ 312 assert self.IsChromeOS() 313 return True 314 315 def ShouldAutoLogin(self): 316 """Determine if we should auto-login on ChromeOS at browser startup. 317 318 To be used for tests that expect user to be logged in before running test, 319 without caring which user. ShouldOOBESkipToLogin() should return True 320 for this to take effect. 321 322 Override and return False to not auto login, for tests where login is part 323 of the use case. 324 325 Returns: 326 True, if chrome should auto login after startup. 327 False, otherwise. 328 """ 329 assert self.IsChromeOS() 330 return True 331 332 def CloseChromeOnChromeOS(self): 333 """Gracefully exit chrome on ChromeOS.""" 334 335 def _GetListOfChromePids(): 336 """Retrieves the list of currently-running Chrome process IDs. 337 338 Returns: 339 A list of strings, where each string represents a currently-running 340 'chrome' process ID. 341 """ 342 proc = subprocess.Popen(['pgrep', '^chrome$'], stdout=subprocess.PIPE) 343 proc.wait() 344 return [x.strip() for x in proc.stdout.readlines()] 345 346 orig_pids = _GetListOfChromePids() 347 subprocess.call(['pkill', '^chrome$']) 348 349 def _AreOrigPidsDead(orig_pids): 350 """Determines whether all originally-running 'chrome' processes are dead. 351 352 Args: 353 orig_pids: A list of strings, where each string represents the PID for 354 an originally-running 'chrome' process. 355 356 Returns: 357 True, if all originally-running 'chrome' processes have been killed, or 358 False otherwise. 359 """ 360 for new_pid in _GetListOfChromePids(): 361 if new_pid in orig_pids: 362 return False 363 return True 364 365 self.WaitUntil(lambda: _AreOrigPidsDead(orig_pids)) 366 367 @staticmethod 368 def _IsRootSuid(path): 369 """Determine if |path| is a suid-root file.""" 370 return os.path.isfile(path) and (os.stat(path).st_mode & stat.S_ISUID) 371 372 @staticmethod 373 def SuidPythonPath(): 374 """Path to suid_python binary on ChromeOS. 375 376 This is typically in the same directory as pyautolib.py 377 """ 378 return os.path.join(PyUITest.BrowserPath(), 'suid-python') 379 380 @staticmethod 381 def RunSuperuserActionOnChromeOS(action): 382 """Run the given action with superuser privs (on ChromeOS). 383 384 Uses the suid_actions.py script. 385 386 Args: 387 action: An action to perform. 388 See suid_actions.py for available options. 389 390 Returns: 391 (stdout, stderr) 392 """ 393 assert PyUITest._IsRootSuid(PyUITest.SuidPythonPath()), \ 394 'Did not find suid-root python at %s' % PyUITest.SuidPythonPath() 395 file_path = os.path.join(os.path.dirname(__file__), 'chromeos', 396 'suid_actions.py') 397 args = [PyUITest.SuidPythonPath(), file_path, '--action=%s' % action] 398 proc = subprocess.Popen( 399 args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 400 stdout, stderr = proc.communicate() 401 return (stdout, stderr) 402 403 def EnableChromeTestingOnChromeOS(self): 404 """Enables the named automation interface on chromeos. 405 406 Restarts chrome so that you get a fresh instance. 407 Also sets some testing-friendly flags for chrome. 408 409 Expects suid python to be present in the same dir as pyautolib.py 410 """ 411 assert PyUITest._IsRootSuid(self.SuidPythonPath()), \ 412 'Did not find suid-root python at %s' % self.SuidPythonPath() 413 file_path = os.path.join(os.path.dirname(__file__), 'chromeos', 414 'enable_testing.py') 415 args = [self.SuidPythonPath(), file_path] 416 # Pass extra chrome flags for testing 417 for flag in self.ExtraChromeFlags(): 418 args.append('--extra-chrome-flags=%s' % flag) 419 assert self.WaitUntil(lambda: self._IsSessionManagerReady(0)) 420 proc = subprocess.Popen(args, stdout=subprocess.PIPE) 421 automation_channel_path = proc.communicate()[0].strip() 422 assert len(automation_channel_path), 'Could not enable testing interface' 423 return automation_channel_path 424 425 @staticmethod 426 def EnableCrashReportingOnChromeOS(): 427 """Enables crash reporting on ChromeOS. 428 429 Writes the "/home/chronos/Consent To Send Stats" file with a 32-char 430 readable string. See comment in session_manager_setup.sh which does this 431 too. 432 433 Note that crash reporting will work only if breakpad is built in, ie in a 434 'Google Chrome' build (not Chromium). 435 """ 436 consent_file = '/home/chronos/Consent To Send Stats' 437 def _HasValidConsentFile(): 438 if not os.path.isfile(consent_file): 439 return False 440 stat = os.stat(consent_file) 441 return (len(open(consent_file).read()) and 442 (1000, 1000) == (stat.st_uid, stat.st_gid)) 443 if not _HasValidConsentFile(): 444 client_id = hashlib.md5('abcdefgh').hexdigest() 445 # Consent file creation and chown to chronos needs to be atomic 446 # to avoid races with the session_manager. crosbug.com/18413 447 # Therefore, create a temp file, chown, then rename it as consent file. 448 temp_file = consent_file + '.tmp' 449 open(temp_file, 'w').write(client_id) 450 # This file must be owned by chronos:chronos! 451 os.chown(temp_file, 1000, 1000); 452 shutil.move(temp_file, consent_file) 453 assert _HasValidConsentFile(), 'Could not create %s' % consent_file 454 455 @staticmethod 456 def _IsSessionManagerReady(old_pid): 457 """Is the ChromeOS session_manager running and ready to accept DBus calls? 458 459 Called after session_manager is killed to know when it has restarted. 460 461 Args: 462 old_pid: The pid that session_manager had before it was killed, 463 to ensure that we don't look at the DBus interface 464 of an old session_manager process. 465 """ 466 pgrep_process = subprocess.Popen(['pgrep', 'session_manager'], 467 stdout=subprocess.PIPE) 468 new_pid = pgrep_process.communicate()[0].strip() 469 if not new_pid or old_pid == new_pid: 470 return False 471 472 import dbus 473 try: 474 bus = dbus.SystemBus() 475 proxy = bus.get_object('org.chromium.SessionManager', 476 '/org/chromium/SessionManager') 477 dbus.Interface(proxy, 'org.chromium.SessionManagerInterface') 478 except dbus.DBusException: 479 return False 480 return True 481 482 @staticmethod 483 def CleanupBrowserProfileOnChromeOS(): 484 """Cleanup browser profile dir on ChromeOS. 485 486 This does not clear cryptohome. 487 488 Browser should not be running, or else there will be locked files. 489 """ 490 profile_dir = '/home/chronos/user' 491 for item in os.listdir(profile_dir): 492 # Deleting .pki causes stateful partition to get erased. 493 if item not in ['log', 'flimflam'] and not item.startswith('.'): 494 pyauto_utils.RemovePath(os.path.join(profile_dir, item)) 495 496 chronos_dir = '/home/chronos' 497 for item in os.listdir(chronos_dir): 498 if item != 'user' and not item.startswith('.'): 499 pyauto_utils.RemovePath(os.path.join(chronos_dir, item)) 500 501 @staticmethod 502 def CleanupFlimflamDirsOnChromeOS(): 503 """Clean the contents of flimflam profiles and restart flimflam.""" 504 PyUITest.RunSuperuserActionOnChromeOS('CleanFlimflamDirs') 505 506 @staticmethod 507 def RemoveAllCryptohomeVaultsOnChromeOS(): 508 """Remove any existing cryptohome vaults.""" 509 PyUITest.RunSuperuserActionOnChromeOS('RemoveAllCryptohomeVaults') 510 511 @staticmethod 512 def _IsInodeNew(path, old_inode): 513 """Determine whether an inode has changed. POSIX only. 514 515 Args: 516 path: The file path to check for changes. 517 old_inode: The old inode number. 518 519 Returns: 520 True if the path exists and its inode number is different from old_inode. 521 False otherwise. 522 """ 523 try: 524 stat_result = os.stat(path) 525 except OSError: 526 return False 527 if not stat_result: 528 return False 529 return stat_result.st_ino != old_inode 530 531 def RestartBrowser(self, clear_profile=True, pre_launch_hook=None): 532 """Restart the browser. 533 534 For use with tests that require to restart the browser. 535 536 Args: 537 clear_profile: If True, the browser profile is cleared before restart. 538 Defaults to True, that is restarts browser with a clean 539 profile. 540 pre_launch_hook: If specified, must be a callable that is invoked before 541 the browser is started again. Not supported in ChromeOS. 542 """ 543 if self.IsChromeOS(): 544 assert pre_launch_hook is None, 'Not supported in ChromeOS' 545 self.TearDown() 546 if clear_profile: 547 self.CleanupBrowserProfileOnChromeOS() 548 self.CloseChromeOnChromeOS() 549 self.EnableChromeTestingOnChromeOS() 550 self.SetUp() 551 return 552 # Not chromeos 553 orig_clear_state = self.get_clear_profile() 554 self.CloseBrowserAndServer() 555 self.set_clear_profile(clear_profile) 556 if pre_launch_hook: 557 pre_launch_hook() 558 logging.debug('Restarting browser with clear_profile=%s', 559 self.get_clear_profile()) 560 self.LaunchBrowserAndServer() 561 self.set_clear_profile(orig_clear_state) # Reset to original state. 562 563 @staticmethod 564 def DataDir(): 565 """Returns the path to the data dir chrome/test/data.""" 566 return os.path.normpath( 567 os.path.join(os.path.dirname(__file__), os.pardir, "data")) 568 569 @staticmethod 570 def ChromeOSDataDir(): 571 """Returns the path to the data dir chromeos/test/data.""" 572 return os.path.normpath( 573 os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, os.pardir, 574 "chromeos", "test", "data")) 575 576 @staticmethod 577 def GetFileURLForPath(*path): 578 """Get file:// url for the given path. 579 580 Also quotes the url using urllib.quote(). 581 582 Args: 583 path: Variable number of strings that can be joined. 584 """ 585 path_str = os.path.join(*path) 586 abs_path = os.path.abspath(path_str) 587 if sys.platform == 'win32': 588 # Don't quote the ':' in drive letter ( say, C: ) on win. 589 # Also, replace '\' with '/' as expected in a file:/// url. 590 drive, rest = os.path.splitdrive(abs_path) 591 quoted_path = drive.upper() + urllib.quote((rest.replace('\\', '/'))) 592 return 'file:///' + quoted_path 593 else: 594 quoted_path = urllib.quote(abs_path) 595 return 'file://' + quoted_path 596 597 @staticmethod 598 def GetFileURLForDataPath(*relative_path): 599 """Get file:// url for the given path relative to the chrome test data dir. 600 601 Also quotes the url using urllib.quote(). 602 603 Args: 604 relative_path: Variable number of strings that can be joined. 605 """ 606 return PyUITest.GetFileURLForPath(PyUITest.DataDir(), *relative_path) 607 608 @staticmethod 609 def GetHttpURLForDataPath(*relative_path): 610 """Get http:// url for the given path in the data dir. 611 612 The URL will be usable only after starting the http server. 613 """ 614 global _HTTP_SERVER 615 assert _HTTP_SERVER, 'HTTP Server not yet started' 616 return _HTTP_SERVER.GetURL(os.path.join('files', *relative_path)).spec() 617 618 @staticmethod 619 def ContentDataDir(): 620 """Get path to content/test/data.""" 621 return os.path.join(PyUITest.DataDir(), os.pardir, os.pardir, os.pardir, 622 'content', 'test', 'data') 623 624 @staticmethod 625 def GetFileURLForContentDataPath(*relative_path): 626 """Get file:// url for the given path relative to content test data dir. 627 628 Also quotes the url using urllib.quote(). 629 630 Args: 631 relative_path: Variable number of strings that can be joined. 632 """ 633 return PyUITest.GetFileURLForPath(PyUITest.ContentDataDir(), *relative_path) 634 635 @staticmethod 636 def GetFtpURLForDataPath(ftp_server, *relative_path): 637 """Get ftp:// url for the given path in the data dir. 638 639 Args: 640 ftp_server: handle to ftp server, an instance of SpawnedTestServer 641 relative_path: any number of path elements 642 643 The URL will be usable only after starting the ftp server. 644 """ 645 assert ftp_server, 'FTP Server not yet started' 646 return ftp_server.GetURL(os.path.join(*relative_path)).spec() 647 648 @staticmethod 649 def IsMac(): 650 """Are we on Mac?""" 651 return 'darwin' == sys.platform 652 653 @staticmethod 654 def IsLinux(): 655 """Are we on Linux? ChromeOS is linux too.""" 656 return sys.platform.startswith('linux') 657 658 @staticmethod 659 def IsWin(): 660 """Are we on Win?""" 661 return 'win32' == sys.platform 662 663 @staticmethod 664 def IsWin7(): 665 """Are we on Windows 7?""" 666 if not PyUITest.IsWin(): 667 return False 668 ver = sys.getwindowsversion() 669 return (ver[3], ver[0], ver[1]) == (2, 6, 1) 670 671 @staticmethod 672 def IsWinVista(): 673 """Are we on Windows Vista?""" 674 if not PyUITest.IsWin(): 675 return False 676 ver = sys.getwindowsversion() 677 return (ver[3], ver[0], ver[1]) == (2, 6, 0) 678 679 @staticmethod 680 def IsWinXP(): 681 """Are we on Windows XP?""" 682 if not PyUITest.IsWin(): 683 return False 684 ver = sys.getwindowsversion() 685 return (ver[3], ver[0], ver[1]) == (2, 5, 1) 686 687 @staticmethod 688 def IsChromeOS(): 689 """Are we on ChromeOS (or Chromium OS)? 690 691 Checks for "CHROMEOS_RELEASE_NAME=" in /etc/lsb-release. 692 """ 693 lsb_release = '/etc/lsb-release' 694 if not PyUITest.IsLinux() or not os.path.isfile(lsb_release): 695 return False 696 for line in open(lsb_release).readlines(): 697 if line.startswith('CHROMEOS_RELEASE_NAME='): 698 return True 699 return False 700 701 @staticmethod 702 def IsPosix(): 703 """Are we on Mac/Linux?""" 704 return PyUITest.IsMac() or PyUITest.IsLinux() 705 706 @staticmethod 707 def IsEnUS(): 708 """Are we en-US?""" 709 # TODO: figure out the machine's langugage. 710 return True 711 712 @staticmethod 713 def GetPlatform(): 714 """Return the platform name.""" 715 # Since ChromeOS is also Linux, we check for it first. 716 if PyUITest.IsChromeOS(): 717 return 'chromeos' 718 elif PyUITest.IsLinux(): 719 return 'linux' 720 elif PyUITest.IsMac(): 721 return 'mac' 722 elif PyUITest.IsWin(): 723 return 'win' 724 else: 725 return 'unknown' 726 727 @staticmethod 728 def EvalDataFrom(filename): 729 """Return eval of python code from given file. 730 731 The datastructure used in the file will be preserved. 732 """ 733 data_file = os.path.join(filename) 734 contents = open(data_file).read() 735 try: 736 ret = eval(contents) 737 except: 738 print >>sys.stderr, '%s is an invalid data file.' % data_file 739 raise 740 return ret 741 742 @staticmethod 743 def ChromeOSBoard(): 744 """What is the ChromeOS board name""" 745 if PyUITest.IsChromeOS(): 746 for line in open('/etc/lsb-release'): 747 line = line.strip() 748 if line.startswith('CHROMEOS_RELEASE_BOARD='): 749 return line.split('=')[1] 750 return None 751 752 @staticmethod 753 def Kill(pid): 754 """Terminate the given pid. 755 756 If the pid refers to a renderer, use KillRendererProcess instead. 757 """ 758 if PyUITest.IsWin(): 759 subprocess.call(['taskkill.exe', '/T', '/F', '/PID', str(pid)]) 760 else: 761 os.kill(pid, signal.SIGTERM) 762 763 @staticmethod 764 def GetPrivateInfo(): 765 """Fetch info from private_tests_info.txt in private dir. 766 767 Returns: 768 a dictionary of items from private_tests_info.txt 769 """ 770 private_file = os.path.join( 771 PyUITest.DataDir(), 'pyauto_private', 'private_tests_info.txt') 772 assert os.path.exists(private_file), '%s missing' % private_file 773 return PyUITest.EvalDataFrom(private_file) 774 775 def WaitUntil(self, function, timeout=-1, retry_sleep=0.25, args=[], 776 expect_retval=None, return_retval=False, debug=True): 777 """Poll on a condition until timeout. 778 779 Waits until the |function| evalues to |expect_retval| or until |timeout| 780 secs, whichever occurs earlier. 781 782 This is better than using a sleep, since it waits (almost) only as much 783 as needed. 784 785 WARNING: This method call should be avoided as far as possible in favor 786 of a real wait from chromium (like wait-until-page-loaded). 787 Only use in case there's really no better option. 788 789 EXAMPLES:- 790 Wait for "file.txt" to get created: 791 WaitUntil(os.path.exists, args=["file.txt"]) 792 793 Same as above, but using lambda: 794 WaitUntil(lambda: os.path.exists("file.txt")) 795 796 Args: 797 function: the function whose truth value is to be evaluated 798 timeout: the max timeout (in secs) for which to wait. The default 799 action is to wait for kWaitForActionMaxMsec, as set in 800 ui_test.cc 801 Use None to wait indefinitely. 802 retry_sleep: the sleep interval (in secs) before retrying |function|. 803 Defaults to 0.25 secs. 804 args: the args to pass to |function| 805 expect_retval: the expected return value for |function|. This forms the 806 exit criteria. In case this is None (the default), 807 |function|'s return value is checked for truth, 808 so 'non-empty-string' should match with True 809 return_retval: If True, return the value returned by the last call to 810 |function()| 811 debug: if True, displays debug info at each retry. 812 813 Returns: 814 The return value of the |function| (when return_retval == True) 815 True, if returning when |function| evaluated to True (when 816 return_retval == False) 817 False, when returning due to timeout 818 """ 819 if timeout == -1: # Default 820 timeout = self._automation_timeout / 1000.0 821 assert callable(function), "function should be a callable" 822 begin = time.time() 823 debug_begin = begin 824 retval = None 825 while timeout is None or time.time() - begin <= timeout: 826 retval = function(*args) 827 if (expect_retval is None and retval) or \ 828 (expect_retval is not None and expect_retval == retval): 829 return retval if return_retval else True 830 if debug and time.time() - debug_begin > 5: 831 debug_begin += 5 832 if function.func_name == (lambda: True).func_name: 833 function_info = inspect.getsource(function).strip() 834 else: 835 function_info = '%s()' % function.func_name 836 logging.debug('WaitUntil(%s:%d %s) still waiting. ' 837 'Expecting %s. Last returned %s.', 838 os.path.basename(inspect.getsourcefile(function)), 839 inspect.getsourcelines(function)[1], 840 function_info, 841 True if expect_retval is None else expect_retval, 842 retval) 843 time.sleep(retry_sleep) 844 return retval if return_retval else False 845 846 def StartFTPServer(self, data_dir): 847 """Start a local file server hosting data files over ftp:// 848 849 Args: 850 data_dir: path where ftp files should be served 851 852 Returns: 853 handle to FTP Server, an instance of SpawnedTestServer 854 """ 855 ftp_server = pyautolib.SpawnedTestServer( 856 pyautolib.SpawnedTestServer.TYPE_FTP, 857 '127.0.0.1', 858 pyautolib.FilePath(data_dir)) 859 assert ftp_server.Start(), 'Could not start ftp server' 860 logging.debug('Started ftp server at "%s".', data_dir) 861 return ftp_server 862 863 def StopFTPServer(self, ftp_server): 864 """Stop the local ftp server.""" 865 assert ftp_server, 'FTP Server not yet started' 866 assert ftp_server.Stop(), 'Could not stop ftp server' 867 logging.debug('Stopped ftp server.') 868 869 def StartHTTPServer(self, data_dir): 870 """Starts a local HTTP SpawnedTestServer serving files from |data_dir|. 871 872 Args: 873 data_dir: path where the SpawnedTestServer should serve files from. 874 This will be appended to the source dir to get the final document root. 875 876 Returns: 877 handle to the HTTP SpawnedTestServer 878 """ 879 http_server = pyautolib.SpawnedTestServer( 880 pyautolib.SpawnedTestServer.TYPE_HTTP, 881 '127.0.0.1', 882 pyautolib.FilePath(data_dir)) 883 assert http_server.Start(), 'Could not start HTTP server' 884 logging.debug('Started HTTP server at "%s".', data_dir) 885 return http_server 886 887 def StopHTTPServer(self, http_server): 888 assert http_server, 'HTTP server not yet started' 889 assert http_server.Stop(), 'Cloud not stop the HTTP server' 890 logging.debug('Stopped HTTP server.') 891 892 def StartHttpsServer(self, cert_type, data_dir): 893 """Starts a local HTTPS SpawnedTestServer serving files from |data_dir|. 894 895 Args: 896 cert_type: An instance of SSLOptions.ServerCertificate for three 897 certificate types: ok, expired, or mismatch. 898 data_dir: The path where SpawnedTestServer should serve files from. 899 This is appended to the source dir to get the final 900 document root. 901 902 Returns: 903 Handle to the HTTPS SpawnedTestServer 904 """ 905 https_server = pyautolib.SpawnedTestServer( 906 pyautolib.SpawnedTestServer.TYPE_HTTPS, 907 pyautolib.SSLOptions(cert_type), 908 pyautolib.FilePath(data_dir)) 909 assert https_server.Start(), 'Could not start HTTPS server.' 910 logging.debug('Start HTTPS server at "%s".' % data_dir) 911 return https_server 912 913 def StopHttpsServer(self, https_server): 914 assert https_server, 'HTTPS server not yet started.' 915 assert https_server.Stop(), 'Could not stop the HTTPS server.' 916 logging.debug('Stopped HTTPS server.') 917 918 class ActionTimeoutChanger(object): 919 """Facilitate temporary changes to PyAuto command timeout. 920 921 Automatically resets to original timeout when object is destroyed. 922 """ 923 _saved_timeout = -1 # Saved timeout value 924 925 def __init__(self, ui_test, new_timeout): 926 """Initialize. 927 928 Args: 929 ui_test: a PyUITest object 930 new_timeout: new timeout to use (in milli secs) 931 """ 932 self._saved_timeout = ui_test._automation_timeout 933 ui_test._automation_timeout = new_timeout 934 self._ui_test = ui_test 935 936 def __del__(self): 937 """Reset command_execution_timeout_ms to original value.""" 938 self._ui_test._automation_timeout = self._saved_timeout 939 940 class JavascriptExecutor(object): 941 """Abstract base class for JavaScript injection. 942 943 Derived classes should override Execute method.""" 944 def Execute(self, script): 945 pass 946 947 class JavascriptExecutorInTab(JavascriptExecutor): 948 """Wrapper for injecting JavaScript in a tab.""" 949 def __init__(self, ui_test, tab_index=0, windex=0, frame_xpath=''): 950 """Initialize. 951 952 Refer to ExecuteJavascript() for the complete argument list 953 description. 954 955 Args: 956 ui_test: a PyUITest object 957 """ 958 self._ui_test = ui_test 959 self.windex = windex 960 self.tab_index = tab_index 961 self.frame_xpath = frame_xpath 962 963 def Execute(self, script): 964 """Execute script in the tab.""" 965 return self._ui_test.ExecuteJavascript(script, 966 self.tab_index, 967 self.windex, 968 self.frame_xpath) 969 970 class JavascriptExecutorInRenderView(JavascriptExecutor): 971 """Wrapper for injecting JavaScript in an extension view.""" 972 def __init__(self, ui_test, view, frame_xpath=''): 973 """Initialize. 974 975 Refer to ExecuteJavascriptInRenderView() for the complete argument list 976 description. 977 978 Args: 979 ui_test: a PyUITest object 980 """ 981 self._ui_test = ui_test 982 self.view = view 983 self.frame_xpath = frame_xpath 984 985 def Execute(self, script): 986 """Execute script in the render view.""" 987 return self._ui_test.ExecuteJavascriptInRenderView(script, 988 self.view, 989 self.frame_xpath) 990 991 def _GetResultFromJSONRequestDiagnostics(self): 992 """Same as _GetResultFromJSONRequest without throwing a timeout exception. 993 994 This method is used to diagnose if a command returns without causing a 995 timout exception to be thrown. This should be used for debugging purposes 996 only. 997 998 Returns: 999 True if the request returned; False if it timed out. 1000 """ 1001 result = self._SendJSONRequest(-1, 1002 json.dumps({'command': 'GetBrowserInfo',}), 1003 self._automation_timeout) 1004 if not result: 1005 # The diagnostic command did not complete, Chrome is probably in a bad 1006 # state 1007 return False 1008 return True 1009 1010 def _GetResultFromJSONRequest(self, cmd_dict, windex=0, timeout=-1): 1011 """Issue call over the JSON automation channel and fetch output. 1012 1013 This method packages the given dictionary into a json string, sends it 1014 over the JSON automation channel, loads the json output string returned, 1015 and returns it back as a dictionary. 1016 1017 Args: 1018 cmd_dict: the command dictionary. It must have a 'command' key 1019 Sample: 1020 { 1021 'command': 'SetOmniboxText', 1022 'text': text, 1023 } 1024 windex: 0-based window index on which to work. Default: 0 (first window) 1025 Use -ve windex or None if the automation command does not apply 1026 to a browser window. Example: for chromeos login 1027 1028 timeout: request timeout (in milliseconds) 1029 1030 Returns: 1031 a dictionary for the output returned by the automation channel. 1032 1033 Raises: 1034 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1035 """ 1036 if timeout == -1: # Default 1037 timeout = self._automation_timeout 1038 if windex is None: # Do not target any window 1039 windex = -1 1040 result = self._SendJSONRequest(windex, json.dumps(cmd_dict), timeout) 1041 if not result: 1042 additional_info = 'No information available.' 1043 # Windows does not support os.kill until Python 2.7. 1044 if not self.IsWin() and _BROWSER_PID: 1045 browser_pid_exists = True 1046 # Does the browser PID exist? 1047 try: 1048 # Does not actually kill the process 1049 os.kill(int(_BROWSER_PID), 0) 1050 except OSError: 1051 browser_pid_exists = False 1052 if browser_pid_exists: 1053 if self._GetResultFromJSONRequestDiagnostics(): 1054 # Browser info, worked, that means this hook had a problem 1055 additional_info = ('The browser process ID %d still exists. ' 1056 'PyAuto was able to obtain browser info. It ' 1057 'is possible this hook is broken.' 1058 % _BROWSER_PID) 1059 else: 1060 additional_info = ('The browser process ID %d still exists. ' 1061 'PyAuto was not able to obtain browser info. ' 1062 'It is possible the browser is hung.' 1063 % _BROWSER_PID) 1064 else: 1065 additional_info = ('The browser process ID %d no longer exists. ' 1066 'Perhaps the browser crashed.' % _BROWSER_PID) 1067 elif not _BROWSER_PID: 1068 additional_info = ('The browser PID was not obtained. Does this test ' 1069 'have a unique startup configuration?') 1070 # Mask private data if it is in the JSON dictionary 1071 cmd_dict_copy = copy.copy(cmd_dict) 1072 if 'password' in cmd_dict_copy.keys(): 1073 cmd_dict_copy['password'] = '**********' 1074 if 'username' in cmd_dict_copy.keys(): 1075 cmd_dict_copy['username'] = 'removed_username' 1076 raise JSONInterfaceError('Automation call %s received empty response. ' 1077 'Additional information:\n%s' % (cmd_dict_copy, 1078 additional_info)) 1079 ret_dict = json.loads(result) 1080 if ret_dict.has_key('error'): 1081 if ret_dict.get('is_interface_timeout'): 1082 raise AutomationCommandTimeout(ret_dict['error']) 1083 elif ret_dict.get('is_interface_error'): 1084 raise JSONInterfaceError(ret_dict['error']) 1085 else: 1086 raise AutomationCommandFail(ret_dict['error']) 1087 return ret_dict 1088 1089 def NavigateToURL(self, url, windex=0, tab_index=None, navigation_count=1): 1090 """Navigate the given tab to the given URL. 1091 1092 Note that this method also activates the corresponding tab/window if it's 1093 not active already. Blocks until |navigation_count| navigations have 1094 completed. 1095 1096 Args: 1097 url: The URL to which to navigate, can be a string or GURL object. 1098 windex: The index of the browser window to work on. Defaults to the first 1099 window. 1100 tab_index: The index of the tab to work on. Defaults to the active tab. 1101 navigation_count: the number of navigations to wait for. Defaults to 1. 1102 1103 Raises: 1104 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1105 """ 1106 if isinstance(url, GURL): 1107 url = url.spec() 1108 if tab_index is None: 1109 tab_index = self.GetActiveTabIndex(windex) 1110 cmd_dict = { 1111 'command': 'NavigateToURL', 1112 'url': url, 1113 'windex': windex, 1114 'tab_index': tab_index, 1115 'navigation_count': navigation_count, 1116 } 1117 self._GetResultFromJSONRequest(cmd_dict, windex=None) 1118 1119 def NavigateToURLAsync(self, url, windex=0, tab_index=None): 1120 """Initiate a URL navigation. 1121 1122 A wrapper for NavigateToURL with navigation_count set to 0. 1123 """ 1124 self.NavigateToURL(url, windex, tab_index, 0) 1125 1126 def ApplyAccelerator(self, accelerator, windex=0): 1127 """Apply the accelerator with the given id. 1128 1129 Note that this method schedules the accelerator, but does not wait for it to 1130 actually finish doing anything. 1131 1132 Args: 1133 accelerator: The accelerator id, IDC_BACK, IDC_NEWTAB, etc. The list of 1134 ids can be found at chrome/app/chrome_command_ids.h. 1135 windex: The index of the browser window to work on. Defaults to the first 1136 window. 1137 1138 Raises: 1139 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1140 """ 1141 1142 cmd_dict = { 1143 'command': 'ApplyAccelerator', 1144 'accelerator': accelerator, 1145 'windex': windex, 1146 } 1147 self._GetResultFromJSONRequest(cmd_dict, windex=None) 1148 1149 def RunCommand(self, accelerator, windex=0): 1150 """Apply the accelerator with the given id and wait for it to finish. 1151 1152 This is like ApplyAccelerator except that it waits for the command to finish 1153 executing. 1154 1155 Args: 1156 accelerator: The accelerator id. The list of ids can be found at 1157 chrome/app/chrome_command_ids.h. 1158 windex: The index of the browser window to work on. Defaults to the first 1159 window. 1160 1161 Raises: 1162 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1163 """ 1164 cmd_dict = { 1165 'command': 'RunCommand', 1166 'accelerator': accelerator, 1167 'windex': windex, 1168 } 1169 self._GetResultFromJSONRequest(cmd_dict, windex=None) 1170 1171 def IsMenuCommandEnabled(self, accelerator, windex=0): 1172 """Check if a command is enabled for a window. 1173 1174 Returns true if the command with the given accelerator id is enabled on the 1175 given window. 1176 1177 Args: 1178 accelerator: The accelerator id. The list of ids can be found at 1179 chrome/app/chrome_command_ids.h. 1180 windex: The index of the browser window to work on. Defaults to the first 1181 window. 1182 1183 Returns: 1184 True if the command is enabled for the given window. 1185 """ 1186 cmd_dict = { 1187 'command': 'IsMenuCommandEnabled', 1188 'accelerator': accelerator, 1189 'windex': windex, 1190 } 1191 return self._GetResultFromJSONRequest(cmd_dict, windex=None).get('enabled') 1192 1193 def TabGoForward(self, tab_index=0, windex=0): 1194 """Navigate a tab forward in history. 1195 1196 Equivalent to clicking the Forward button in the UI. Activates the tab as a 1197 side effect. 1198 1199 Raises: 1200 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1201 """ 1202 self.ActivateTab(tab_index, windex) 1203 self.RunCommand(IDC_FORWARD, windex) 1204 1205 def TabGoBack(self, tab_index=0, windex=0): 1206 """Navigate a tab backwards in history. 1207 1208 Equivalent to clicking the Back button in the UI. Activates the tab as a 1209 side effect. 1210 1211 Raises: 1212 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1213 """ 1214 self.ActivateTab(tab_index, windex) 1215 self.RunCommand(IDC_BACK, windex) 1216 1217 def ReloadTab(self, tab_index=0, windex=0): 1218 """Reload the given tab. 1219 1220 Blocks until the page has reloaded. 1221 1222 Args: 1223 tab_index: The index of the tab to reload. Defaults to 0. 1224 windex: The index of the browser window to work on. Defaults to the first 1225 window. 1226 1227 Raises: 1228 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1229 """ 1230 self.ActivateTab(tab_index, windex) 1231 self.RunCommand(IDC_RELOAD, windex) 1232 1233 def CloseTab(self, tab_index=0, windex=0, wait_until_closed=True): 1234 """Close the given tab. 1235 1236 Note: Be careful closing the last tab in a window as it may close the 1237 browser. 1238 1239 Args: 1240 tab_index: The index of the tab to reload. Defaults to 0. 1241 windex: The index of the browser window to work on. Defaults to the first 1242 window. 1243 wait_until_closed: Whether to block until the tab finishes closing. 1244 1245 Raises: 1246 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1247 """ 1248 cmd_dict = { 1249 'command': 'CloseTab', 1250 'tab_index': tab_index, 1251 'windex': windex, 1252 'wait_until_closed': wait_until_closed, 1253 } 1254 self._GetResultFromJSONRequest(cmd_dict, windex=None) 1255 1256 def WaitForTabToBeRestored(self, tab_index=0, windex=0, timeout=-1): 1257 """Wait for the given tab to be restored. 1258 1259 Args: 1260 tab_index: The index of the tab to reload. Defaults to 0. 1261 windex: The index of the browser window to work on. Defaults to the first 1262 window. 1263 timeout: Timeout in milliseconds. 1264 1265 Raises: 1266 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1267 """ 1268 cmd_dict = { 1269 'command': 'CloseTab', 1270 'tab_index': tab_index, 1271 'windex': windex, 1272 } 1273 self._GetResultFromJSONRequest(cmd_dict, windex=None, timeout=timeout) 1274 1275 def ReloadActiveTab(self, windex=0): 1276 """Reload an active tab. 1277 1278 Warning: Depending on the concept of an active tab is dangerous as it can 1279 change during the test. Use ReloadTab and supply a tab_index explicitly. 1280 1281 Args: 1282 windex: The index of the browser window to work on. Defaults to the first 1283 window. 1284 1285 Raises: 1286 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1287 """ 1288 self.ReloadTab(self.GetActiveTabIndex(windex), windex) 1289 1290 def GetActiveTabIndex(self, windex=0): 1291 """Get the index of the currently active tab in the given browser window. 1292 1293 Warning: Depending on the concept of an active tab is dangerous as it can 1294 change during the test. Supply the tab_index explicitly, if possible. 1295 1296 Args: 1297 windex: The index of the browser window to work on. Defaults to the first 1298 window. 1299 1300 Returns: 1301 An integer index for the currently active tab. 1302 """ 1303 cmd_dict = { 1304 'command': 'GetActiveTabIndex', 1305 'windex': windex, 1306 } 1307 return self._GetResultFromJSONRequest(cmd_dict, 1308 windex=None).get('tab_index') 1309 1310 def ActivateTab(self, tab_index=0, windex=0): 1311 """Activates the given tab in the specified window. 1312 1313 Warning: Depending on the concept of an active tab is dangerous as it can 1314 change during the test. Instead use functions that accept a tab_index 1315 explicitly. 1316 1317 Args: 1318 tab_index: Integer index of the tab to activate; defaults to 0. 1319 windex: Integer index of the browser window to use; defaults to the first 1320 window. 1321 1322 Raises: 1323 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1324 """ 1325 cmd_dict = { 1326 'command': 'ActivateTab', 1327 'tab_index': tab_index, 1328 'windex': windex, 1329 } 1330 self.BringBrowserToFront(windex) 1331 self._GetResultFromJSONRequest(cmd_dict, windex=None) 1332 1333 def BringBrowserToFront(self, windex=0): 1334 """Activate the browser's window and bring it to front. 1335 1336 Args: 1337 windex: Integer index of the browser window to use; defaults to the first 1338 window. 1339 1340 Raises: 1341 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1342 """ 1343 cmd_dict = { 1344 'command': 'BringBrowserToFront', 1345 'windex': windex, 1346 } 1347 self._GetResultFromJSONRequest(cmd_dict, windex=None) 1348 1349 def GetBrowserWindowCount(self): 1350 """Get the browser window count. 1351 1352 Args: 1353 None. 1354 1355 Returns: 1356 Integer count of the number of browser windows. Includes popups. 1357 1358 Raises: 1359 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1360 """ 1361 cmd_dict = {'command': 'GetBrowserWindowCount'} 1362 return self._GetResultFromJSONRequest(cmd_dict, windex=None)['count'] 1363 1364 def OpenNewBrowserWindow(self, show): 1365 """Create a new browser window. 1366 1367 Args: 1368 show: Boolean indicating whether to show the window. 1369 1370 Raises: 1371 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1372 """ 1373 cmd_dict = { 1374 'command': 'OpenNewBrowserWindow', 1375 'show': show, 1376 } 1377 self._GetResultFromJSONRequest(cmd_dict, windex=None) 1378 1379 def CloseBrowserWindow(self, windex=0): 1380 """Create a new browser window. 1381 1382 Args: 1383 windex: Index of the browser window to close; defaults to 0. 1384 1385 Raises: 1386 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1387 """ 1388 cmd_dict = { 1389 'command': 'CloseBrowserWindow', 1390 'windex': windex, 1391 } 1392 self._GetResultFromJSONRequest(cmd_dict, windex=None) 1393 1394 def AppendTab(self, url, windex=0): 1395 """Append a new tab. 1396 1397 Creates a new tab at the end of given browser window and activates 1398 it. Blocks until the specified |url| is loaded. 1399 1400 Args: 1401 url: The url to load, can be string or a GURL object. 1402 windex: The index of the browser window to work on. Defaults to the first 1403 window. 1404 1405 Returns: 1406 True if the url loads successfully in the new tab. False otherwise. 1407 1408 Raises: 1409 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1410 """ 1411 if isinstance(url, GURL): 1412 url = url.spec() 1413 cmd_dict = { 1414 'command': 'AppendTab', 1415 'url': url, 1416 'windex': windex, 1417 } 1418 return self._GetResultFromJSONRequest(cmd_dict, windex=None).get('result') 1419 1420 def GetTabCount(self, windex=0): 1421 """Gets the number of tab in the given browser window. 1422 1423 Args: 1424 windex: Integer index of the browser window to use; defaults to the first 1425 window. 1426 1427 Returns: 1428 The tab count. 1429 1430 Raises: 1431 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1432 """ 1433 cmd_dict = { 1434 'command': 'GetTabCount', 1435 'windex': windex, 1436 } 1437 return self._GetResultFromJSONRequest(cmd_dict, windex=None)['tab_count'] 1438 1439 def GetTabInfo(self, tab_index=0, windex=0): 1440 """Gets information about the specified tab. 1441 1442 Args: 1443 tab_index: Integer index of the tab to activate; defaults to 0. 1444 windex: Integer index of the browser window to use; defaults to the first 1445 window. 1446 1447 Returns: 1448 A dictionary containing information about the tab. 1449 Example: 1450 { u'title': "Hello World", 1451 u'url': "http://foo.bar", } 1452 1453 Raises: 1454 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1455 """ 1456 cmd_dict = { 1457 'command': 'GetTabInfo', 1458 'tab_index': tab_index, 1459 'windex': windex, 1460 } 1461 return self._GetResultFromJSONRequest(cmd_dict, windex=None) 1462 1463 def GetActiveTabTitle(self, windex=0): 1464 """Gets the title of the active tab. 1465 1466 Warning: Depending on the concept of an active tab is dangerous as it can 1467 change during the test. Use GetTabInfo and supply a tab_index explicitly. 1468 1469 Args: 1470 windex: Integer index of the browser window to use; defaults to the first 1471 window. 1472 1473 Returns: 1474 The tab title as a string. 1475 1476 Raises: 1477 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1478 """ 1479 return self.GetTabInfo(self.GetActiveTabIndex(windex), windex)['title'] 1480 1481 def GetActiveTabURL(self, windex=0): 1482 """Gets the URL of the active tab. 1483 1484 Warning: Depending on the concept of an active tab is dangerous as it can 1485 change during the test. Use GetTabInfo and supply a tab_index explicitly. 1486 1487 Args: 1488 windex: Integer index of the browser window to use; defaults to the first 1489 window. 1490 1491 Returns: 1492 The tab URL as a GURL object. 1493 1494 Raises: 1495 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1496 """ 1497 return GURL(str(self.GetTabInfo(self.GetActiveTabIndex(windex), 1498 windex)['url'])) 1499 1500 def ActionOnSSLBlockingPage(self, tab_index=0, windex=0, proceed=True): 1501 """Take action on an interstitial page. 1502 1503 Calling this when an interstitial page is not showing is an error. 1504 1505 Args: 1506 tab_index: Integer index of the tab to activate; defaults to 0. 1507 windex: Integer index of the browser window to use; defaults to the first 1508 window. 1509 proceed: Whether to proceed to the URL or not. 1510 1511 Raises: 1512 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1513 """ 1514 cmd_dict = { 1515 'command': 'ActionOnSSLBlockingPage', 1516 'tab_index': tab_index, 1517 'windex': windex, 1518 'proceed': proceed, 1519 } 1520 return self._GetResultFromJSONRequest(cmd_dict, windex=None) 1521 1522 def GetBookmarkModel(self, windex=0): 1523 """Return the bookmark model as a BookmarkModel object. 1524 1525 This is a snapshot of the bookmark model; it is not a proxy and 1526 does not get updated as the bookmark model changes. 1527 """ 1528 bookmarks_as_json = self._GetBookmarksAsJSON(windex) 1529 if not bookmarks_as_json: 1530 raise JSONInterfaceError('Could not resolve browser proxy.') 1531 return bookmark_model.BookmarkModel(bookmarks_as_json) 1532 1533 def _GetBookmarksAsJSON(self, windex=0): 1534 """Get bookmarks as a JSON dictionary; used by GetBookmarkModel().""" 1535 cmd_dict = { 1536 'command': 'GetBookmarksAsJSON', 1537 'windex': windex, 1538 } 1539 self.WaitForBookmarkModelToLoad(windex) 1540 return self._GetResultFromJSONRequest(cmd_dict, 1541 windex=None)['bookmarks_as_json'] 1542 1543 def WaitForBookmarkModelToLoad(self, windex=0): 1544 """Gets the status of the bookmark bar as a dictionary. 1545 1546 Args: 1547 windex: Integer index of the browser window to use; defaults to the first 1548 window. 1549 1550 Raises: 1551 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1552 """ 1553 cmd_dict = { 1554 'command': 'WaitForBookmarkModelToLoad', 1555 'windex': windex, 1556 } 1557 return self._GetResultFromJSONRequest(cmd_dict, windex=None) 1558 1559 def GetBookmarkBarStatus(self, windex=0): 1560 """Gets the status of the bookmark bar as a dictionary. 1561 1562 Args: 1563 windex: Integer index of the browser window to use; defaults to the first 1564 window. 1565 1566 Returns: 1567 A dictionary. 1568 Example: 1569 { u'visible': True, 1570 u'animating': False, 1571 u'detached': False, } 1572 1573 Raises: 1574 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1575 """ 1576 cmd_dict = { 1577 'command': 'GetBookmarkBarStatus', 1578 'windex': windex, 1579 } 1580 return self._GetResultFromJSONRequest(cmd_dict, windex=None) 1581 1582 def GetBookmarkBarStatus(self, windex=0): 1583 """Gets the status of the bookmark bar as a dictionary. 1584 1585 Args: 1586 windex: Integer index of the browser window to use; defaults to the first 1587 window. 1588 1589 Returns: 1590 A dictionary. 1591 Example: 1592 { u'visible': True, 1593 u'animating': False, 1594 u'detached': False, } 1595 1596 Raises: 1597 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1598 """ 1599 cmd_dict = { 1600 'command': 'GetBookmarkBarStatus', 1601 'windex': windex, 1602 } 1603 return self._GetResultFromJSONRequest(cmd_dict, windex=None) 1604 1605 def GetBookmarkBarStatus(self, windex=0): 1606 """Gets the status of the bookmark bar as a dictionary. 1607 1608 Args: 1609 windex: Integer index of the browser window to use; defaults to the first 1610 window. 1611 1612 Returns: 1613 A dictionary. 1614 Example: 1615 { u'visible': True, 1616 u'animating': False, 1617 u'detached': False, } 1618 1619 Raises: 1620 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1621 """ 1622 cmd_dict = { 1623 'command': 'GetBookmarkBarStatus', 1624 'windex': windex, 1625 } 1626 return self._GetResultFromJSONRequest(cmd_dict, windex=None) 1627 1628 def GetBookmarkBarVisibility(self, windex=0): 1629 """Returns the visibility of the bookmark bar. 1630 1631 Args: 1632 windex: Integer index of the browser window to use; defaults to the first 1633 window. 1634 1635 Returns: 1636 True if the bookmark bar is visible, false otherwise. 1637 1638 Raises: 1639 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1640 """ 1641 return self.GetBookmarkBarStatus(windex)['visible'] 1642 1643 def AddBookmarkGroup(self, parent_id, index, title, windex=0): 1644 """Adds a bookmark folder. 1645 1646 Args: 1647 parent_id: The parent bookmark folder. 1648 index: The location in the parent's list to insert this bookmark folder. 1649 title: The name of the bookmark folder. 1650 windex: Integer index of the browser window to use; defaults to the first 1651 window. 1652 1653 Returns: 1654 True if the bookmark bar is detached, false otherwise. 1655 1656 Raises: 1657 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1658 """ 1659 if isinstance(parent_id, basestring): 1660 parent_id = int(parent_id) 1661 cmd_dict = { 1662 'command': 'AddBookmark', 1663 'parent_id': parent_id, 1664 'index': index, 1665 'title': title, 1666 'is_folder': True, 1667 'windex': windex, 1668 } 1669 self.WaitForBookmarkModelToLoad(windex) 1670 self._GetResultFromJSONRequest(cmd_dict, windex=None) 1671 1672 def AddBookmarkURL(self, parent_id, index, title, url, windex=0): 1673 """Add a bookmark URL. 1674 1675 Args: 1676 parent_id: The parent bookmark folder. 1677 index: The location in the parent's list to insert this bookmark. 1678 title: The name of the bookmark. 1679 url: The url of the bookmark. 1680 windex: Integer index of the browser window to use; defaults to the first 1681 window. 1682 1683 Raises: 1684 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1685 """ 1686 if isinstance(parent_id, basestring): 1687 parent_id = int(parent_id) 1688 cmd_dict = { 1689 'command': 'AddBookmark', 1690 'parent_id': parent_id, 1691 'index': index, 1692 'title': title, 1693 'url': url, 1694 'is_folder': False, 1695 'windex': windex, 1696 } 1697 self.WaitForBookmarkModelToLoad(windex) 1698 self._GetResultFromJSONRequest(cmd_dict, windex=None) 1699 1700 def ReparentBookmark(self, id, new_parent_id, index, windex=0): 1701 """Move a bookmark. 1702 1703 Args: 1704 id: The bookmark to move. 1705 new_parent_id: The new parent bookmark folder. 1706 index: The location in the parent's list to insert this bookmark. 1707 windex: Integer index of the browser window to use; defaults to the first 1708 window. 1709 1710 Raises: 1711 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1712 """ 1713 if isinstance(id, basestring): 1714 id = int(id) 1715 if isinstance(new_parent_id, basestring): 1716 new_parent_id = int(new_parent_id) 1717 cmd_dict = { 1718 'command': 'ReparentBookmark', 1719 'id': id, 1720 'new_parent_id': new_parent_id, 1721 'index': index, 1722 'windex': windex, 1723 } 1724 self.WaitForBookmarkModelToLoad(windex) 1725 self._GetResultFromJSONRequest(cmd_dict, windex=None) 1726 1727 def SetBookmarkTitle(self, id, title, windex=0): 1728 """Change the title of a bookmark. 1729 1730 Args: 1731 id: The bookmark to rename. 1732 title: The new title for the bookmark. 1733 windex: Integer index of the browser window to use; defaults to the first 1734 window. 1735 1736 Raises: 1737 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1738 """ 1739 if isinstance(id, basestring): 1740 id = int(id) 1741 cmd_dict = { 1742 'command': 'SetBookmarkTitle', 1743 'id': id, 1744 'title': title, 1745 'windex': windex, 1746 } 1747 self.WaitForBookmarkModelToLoad(windex) 1748 self._GetResultFromJSONRequest(cmd_dict, windex=None) 1749 1750 def SetBookmarkURL(self, id, url, windex=0): 1751 """Change the URL of a bookmark. 1752 1753 Args: 1754 id: The bookmark to change. 1755 url: The new url for the bookmark. 1756 windex: Integer index of the browser window to use; defaults to the first 1757 window. 1758 1759 Raises: 1760 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1761 """ 1762 if isinstance(id, basestring): 1763 id = int(id) 1764 cmd_dict = { 1765 'command': 'SetBookmarkURL', 1766 'id': id, 1767 'url': url, 1768 'windex': windex, 1769 } 1770 self.WaitForBookmarkModelToLoad(windex) 1771 self._GetResultFromJSONRequest(cmd_dict, windex=None) 1772 1773 def RemoveBookmark(self, id, windex=0): 1774 """Remove a bookmark. 1775 1776 Args: 1777 id: The bookmark to remove. 1778 windex: Integer index of the browser window to use; defaults to the first 1779 window. 1780 1781 Raises: 1782 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1783 """ 1784 if isinstance(id, basestring): 1785 id = int(id) 1786 cmd_dict = { 1787 'command': 'RemoveBookmark', 1788 'id': id, 1789 'windex': windex, 1790 } 1791 self.WaitForBookmarkModelToLoad(windex) 1792 self._GetResultFromJSONRequest(cmd_dict, windex=None) 1793 1794 def GetDownloadsInfo(self, windex=0): 1795 """Return info about downloads. 1796 1797 This includes all the downloads recognized by the history system. 1798 1799 Returns: 1800 an instance of downloads_info.DownloadInfo 1801 """ 1802 return download_info.DownloadInfo( 1803 self._GetResultFromJSONRequest({'command': 'GetDownloadsInfo'}, 1804 windex=windex)) 1805 1806 def GetOmniboxInfo(self, windex=0): 1807 """Return info about Omnibox. 1808 1809 This represents a snapshot of the omnibox. If you expect changes 1810 you need to call this method again to get a fresh snapshot. 1811 Note that this DOES NOT shift focus to the omnibox; you've to ensure that 1812 the omnibox is in focus or else you won't get any interesting info. 1813 1814 It's OK to call this even when the omnibox popup is not showing. In this 1815 case however, there won't be any matches, but other properties (like the 1816 current text in the omnibox) will still be fetched. 1817 1818 Due to the nature of the omnibox, this function is sensitive to mouse 1819 focus. DO NOT HOVER MOUSE OVER OMNIBOX OR CHANGE WINDOW FOCUS WHEN USING 1820 THIS METHOD. 1821 1822 Args: 1823 windex: the index of the browser window to work on. 1824 Default: 0 (first window) 1825 1826 Returns: 1827 an instance of omnibox_info.OmniboxInfo 1828 """ 1829 return omnibox_info.OmniboxInfo( 1830 self._GetResultFromJSONRequest({'command': 'GetOmniboxInfo'}, 1831 windex=windex)) 1832 1833 def SetOmniboxText(self, text, windex=0): 1834 """Enter text into the omnibox. This shifts focus to the omnibox. 1835 1836 Args: 1837 text: the text to be set. 1838 windex: the index of the browser window to work on. 1839 Default: 0 (first window) 1840 """ 1841 # Ensure that keyword data is loaded from the profile. 1842 # This would normally be triggered by the user inputting this text. 1843 self._GetResultFromJSONRequest({'command': 'LoadSearchEngineInfo'}) 1844 cmd_dict = { 1845 'command': 'SetOmniboxText', 1846 'text': text, 1847 } 1848 self._GetResultFromJSONRequest(cmd_dict, windex=windex) 1849 1850 # TODO(ace): Remove this hack, update bug 62783. 1851 def WaitUntilOmniboxReadyHack(self, windex=0): 1852 """Wait until the omnibox is ready for input. 1853 1854 This is a hack workaround for linux platform, which returns from 1855 synchronous window creation methods before the omnibox is fully functional. 1856 1857 No-op on non-linux platforms. 1858 1859 Args: 1860 windex: the index of the browser to work on. 1861 """ 1862 if self.IsLinux(): 1863 return self.WaitUntil( 1864 lambda : self.GetOmniboxInfo(windex).Properties('has_focus')) 1865 1866 def WaitUntilOmniboxQueryDone(self, windex=0): 1867 """Wait until omnibox has finished populating results. 1868 1869 Uses WaitUntil() so the wait duration is capped by the timeout values 1870 used by automation, which WaitUntil() uses. 1871 1872 Args: 1873 windex: the index of the browser window to work on. 1874 Default: 0 (first window) 1875 """ 1876 return self.WaitUntil( 1877 lambda : not self.GetOmniboxInfo(windex).IsQueryInProgress()) 1878 1879 def OmniboxMovePopupSelection(self, count, windex=0): 1880 """Move omnibox popup selection up or down. 1881 1882 Args: 1883 count: number of rows by which to move. 1884 -ve implies down, +ve implies up 1885 windex: the index of the browser window to work on. 1886 Default: 0 (first window) 1887 """ 1888 cmd_dict = { 1889 'command': 'OmniboxMovePopupSelection', 1890 'count': count, 1891 } 1892 self._GetResultFromJSONRequest(cmd_dict, windex=windex) 1893 1894 def OmniboxAcceptInput(self, windex=0): 1895 """Accepts the current string of text in the omnibox. 1896 1897 This is equivalent to clicking or hiting enter on a popup selection. 1898 Blocks until the page loads. 1899 1900 Args: 1901 windex: the index of the browser window to work on. 1902 Default: 0 (first window) 1903 """ 1904 cmd_dict = { 1905 'command': 'OmniboxAcceptInput', 1906 } 1907 self._GetResultFromJSONRequest(cmd_dict, windex=windex) 1908 1909 def GetCookie(self, url, windex=0): 1910 """Get the value of the cookie at url in context of the specified browser. 1911 1912 Args: 1913 url: Either a GURL object or url string specifing the cookie url. 1914 windex: The index of the browser window to work on. Defaults to the first 1915 window. 1916 1917 Raises: 1918 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1919 """ 1920 if isinstance(url, GURL): 1921 url = url.spec() 1922 cmd_dict = { 1923 'command': 'GetCookiesInBrowserContext', 1924 'url': url, 1925 'windex': windex, 1926 } 1927 return self._GetResultFromJSONRequest(cmd_dict, windex=None)['cookies'] 1928 1929 def DeleteCookie(self, url, cookie_name, windex=0): 1930 """Delete the cookie at url with name cookie_name. 1931 1932 Args: 1933 url: Either a GURL object or url string specifing the cookie url. 1934 cookie_name: The name of the cookie to delete as a string. 1935 windex: The index of the browser window to work on. Defaults to the first 1936 window. 1937 1938 Raises: 1939 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1940 """ 1941 if isinstance(url, GURL): 1942 url = url.spec() 1943 cmd_dict = { 1944 'command': 'DeleteCookieInBrowserContext', 1945 'url': url, 1946 'cookie_name': cookie_name, 1947 'windex': windex, 1948 } 1949 self._GetResultFromJSONRequest(cmd_dict, windex=None) 1950 1951 def SetCookie(self, url, value, windex=0): 1952 """Set the value of the cookie at url to value in the context of a browser. 1953 1954 Args: 1955 url: Either a GURL object or url string specifing the cookie url. 1956 value: A string to set as the cookie's value. 1957 windex: The index of the browser window to work on. Defaults to the first 1958 window. 1959 1960 Raises: 1961 pyauto_errors.JSONInterfaceError if the automation call returns an error. 1962 """ 1963 if isinstance(url, GURL): 1964 url = url.spec() 1965 cmd_dict = { 1966 'command': 'SetCookieInBrowserContext', 1967 'url': url, 1968 'value': value, 1969 'windex': windex, 1970 } 1971 self._GetResultFromJSONRequest(cmd_dict, windex=None) 1972 1973 def GetSearchEngineInfo(self, windex=0): 1974 """Return info about search engines. 1975 1976 Args: 1977 windex: The window index, default is 0. 1978 1979 Returns: 1980 An ordered list of dictionaries describing info about each search engine. 1981 1982 Example: 1983 [ { u'display_url': u'{google:baseURL}search?q=%s', 1984 u'host': u'www.google.com', 1985 u'in_default_list': True, 1986 u'is_default': True, 1987 u'is_valid': True, 1988 u'keyword': u'google.com', 1989 u'path': u'/search', 1990 u'short_name': u'Google', 1991 u'supports_replacement': True, 1992 u'url': u'{google:baseURL}search?q={searchTerms}'}, 1993 { u'display_url': u'http://search.yahoo.com/search?p=%s', 1994 u'host': u'search.yahoo.com', 1995 u'in_default_list': True, 1996 u'is_default': False, 1997 u'is_valid': True, 1998 u'keyword': u'yahoo.com', 1999 u'path': u'/search', 2000 u'short_name': u'Yahoo!', 2001 u'supports_replacement': True, 2002 u'url': u'http://search.yahoo.com/search?p={searchTerms}'}, 2003 """ 2004 # Ensure that the search engine profile is loaded into data model. 2005 self._GetResultFromJSONRequest({'command': 'LoadSearchEngineInfo'}, 2006 windex=windex) 2007 cmd_dict = {'command': 'GetSearchEngineInfo'} 2008 return self._GetResultFromJSONRequest( 2009 cmd_dict, windex=windex)['search_engines'] 2010 2011 def AddSearchEngine(self, title, keyword, url, windex=0): 2012 """Add a search engine, as done through the search engines UI. 2013 2014 Args: 2015 title: name for search engine. 2016 keyword: keyword, used to initiate a custom search from omnibox. 2017 url: url template for this search engine's query. 2018 '%s' is replaced by search query string when used to search. 2019 windex: The window index, default is 0. 2020 """ 2021 # Ensure that the search engine profile is loaded into data model. 2022 self._GetResultFromJSONRequest({'command': 'LoadSearchEngineInfo'}, 2023 windex=windex) 2024 cmd_dict = {'command': 'AddOrEditSearchEngine', 2025 'new_title': title, 2026 'new_keyword': keyword, 2027 'new_url': url} 2028 self._GetResultFromJSONRequest(cmd_dict, windex=windex) 2029 2030 def EditSearchEngine(self, keyword, new_title, new_keyword, new_url, 2031 windex=0): 2032 """Edit info for existing search engine. 2033 2034 Args: 2035 keyword: existing search engine keyword. 2036 new_title: new name for this search engine. 2037 new_keyword: new keyword for this search engine. 2038 new_url: new url for this search engine. 2039 windex: The window index, default is 0. 2040 """ 2041 # Ensure that the search engine profile is loaded into data model. 2042 self._GetResultFromJSONRequest({'command': 'LoadSearchEngineInfo'}, 2043 windex=windex) 2044 cmd_dict = {'command': 'AddOrEditSearchEngine', 2045 'keyword': keyword, 2046 'new_title': new_title, 2047 'new_keyword': new_keyword, 2048 'new_url': new_url} 2049 self._GetResultFromJSONRequest(cmd_dict, windex=windex) 2050 2051 def DeleteSearchEngine(self, keyword, windex=0): 2052 """Delete search engine with given keyword. 2053 2054 Args: 2055 keyword: the keyword string of the search engine to delete. 2056 windex: The window index, default is 0. 2057 """ 2058 # Ensure that the search engine profile is loaded into data model. 2059 self._GetResultFromJSONRequest({'command': 'LoadSearchEngineInfo'}, 2060 windex=windex) 2061 cmd_dict = {'command': 'PerformActionOnSearchEngine', 'keyword': keyword, 2062 'action': 'delete'} 2063 self._GetResultFromJSONRequest(cmd_dict, windex=windex) 2064 2065 def MakeSearchEngineDefault(self, keyword, windex=0): 2066 """Make search engine with given keyword the default search. 2067 2068 Args: 2069 keyword: the keyword string of the search engine to make default. 2070 windex: The window index, default is 0. 2071 """ 2072 # Ensure that the search engine profile is loaded into data model. 2073 self._GetResultFromJSONRequest({'command': 'LoadSearchEngineInfo'}, 2074 windex=windex) 2075 cmd_dict = {'command': 'PerformActionOnSearchEngine', 'keyword': keyword, 2076 'action': 'default'} 2077 self._GetResultFromJSONRequest(cmd_dict, windex=windex) 2078 2079 def GetLocalStatePrefsInfo(self): 2080 """Return info about preferences. 2081 2082 This represents a snapshot of the local state preferences. If you expect 2083 local state preferences to have changed, you need to call this method again 2084 to get a fresh snapshot. 2085 2086 Returns: 2087 an instance of prefs_info.PrefsInfo 2088 """ 2089 return prefs_info.PrefsInfo( 2090 self._GetResultFromJSONRequest({'command': 'GetLocalStatePrefsInfo'}, 2091 windex=None)) 2092 2093 def SetLocalStatePrefs(self, path, value): 2094 """Set local state preference for the given path. 2095 2096 Preferences are stored by Chromium as a hierarchical dictionary. 2097 dot-separated paths can be used to refer to a particular preference. 2098 example: "session.restore_on_startup" 2099 2100 Some preferences are managed, that is, they cannot be changed by the 2101 user. It's up to the user to know which ones can be changed. Typically, 2102 the options available via Chromium preferences can be changed. 2103 2104 Args: 2105 path: the path the preference key that needs to be changed 2106 example: "session.restore_on_startup" 2107 One of the equivalent names in chrome/common/pref_names.h could 2108 also be used. 2109 value: the value to be set. It could be plain values like int, bool, 2110 string or complex ones like list. 2111 The user has to ensure that the right value is specified for the 2112 right key. It's useful to dump the preferences first to determine 2113 what type is expected for a particular preference path. 2114 """ 2115 cmd_dict = { 2116 'command': 'SetLocalStatePrefs', 2117 'windex': 0, 2118 'path': path, 2119 'value': value, 2120 } 2121 self._GetResultFromJSONRequest(cmd_dict, windex=None) 2122 2123 def GetPrefsInfo(self, windex=0): 2124 """Return info about preferences. 2125 2126 This represents a snapshot of the preferences. If you expect preferences 2127 to have changed, you need to call this method again to get a fresh 2128 snapshot. 2129 2130 Args: 2131 windex: The window index, default is 0. 2132 Returns: 2133 an instance of prefs_info.PrefsInfo 2134 """ 2135 cmd_dict = { 2136 'command': 'GetPrefsInfo', 2137 'windex': windex, 2138 } 2139 return prefs_info.PrefsInfo( 2140 self._GetResultFromJSONRequest(cmd_dict, windex=None)) 2141 2142 def SetPrefs(self, path, value, windex=0): 2143 """Set preference for the given path. 2144 2145 Preferences are stored by Chromium as a hierarchical dictionary. 2146 dot-separated paths can be used to refer to a particular preference. 2147 example: "session.restore_on_startup" 2148 2149 Some preferences are managed, that is, they cannot be changed by the 2150 user. It's up to the user to know which ones can be changed. Typically, 2151 the options available via Chromium preferences can be changed. 2152 2153 Args: 2154 path: the path the preference key that needs to be changed 2155 example: "session.restore_on_startup" 2156 One of the equivalent names in chrome/common/pref_names.h could 2157 also be used. 2158 value: the value to be set. It could be plain values like int, bool, 2159 string or complex ones like list. 2160 The user has to ensure that the right value is specified for the 2161 right key. It's useful to dump the preferences first to determine 2162 what type is expected for a particular preference path. 2163 windex: window index to work on. Defaults to 0 (first window). 2164 """ 2165 cmd_dict = { 2166 'command': 'SetPrefs', 2167 'windex': windex, 2168 'path': path, 2169 'value': value, 2170 } 2171 self._GetResultFromJSONRequest(cmd_dict, windex=None) 2172 2173 def SendWebkitKeyEvent(self, key_type, key_code, tab_index=0, windex=0): 2174 """Send a webkit key event to the browser. 2175 2176 Args: 2177 key_type: the raw key type such as 0 for up and 3 for down. 2178 key_code: the hex value associated with the keypress (virtual key code). 2179 tab_index: tab index to work on. Defaults to 0 (first tab). 2180 windex: window index to work on. Defaults to 0 (first window). 2181 """ 2182 cmd_dict = { 2183 'command': 'SendWebkitKeyEvent', 2184 'type': key_type, 2185 'text': '', 2186 'isSystemKey': False, 2187 'unmodifiedText': '', 2188 'nativeKeyCode': 0, 2189 'windowsKeyCode': key_code, 2190 'modifiers': 0, 2191 'windex': windex, 2192 'tab_index': tab_index, 2193 } 2194 # Sending request for key event. 2195 self._GetResultFromJSONRequest(cmd_dict, windex=None) 2196 2197 def SendWebkitCharEvent(self, char, tab_index=0, windex=0): 2198 """Send a webkit char to the browser. 2199 2200 Args: 2201 char: the char value to be sent to the browser. 2202 tab_index: tab index to work on. Defaults to 0 (first tab). 2203 windex: window index to work on. Defaults to 0 (first window). 2204 """ 2205 cmd_dict = { 2206 'command': 'SendWebkitKeyEvent', 2207 'type': 2, # kCharType 2208 'text': char, 2209 'isSystemKey': False, 2210 'unmodifiedText': char, 2211 'nativeKeyCode': 0, 2212 'windowsKeyCode': ord((char).upper()), 2213 'modifiers': 0, 2214 'windex': windex, 2215 'tab_index': tab_index, 2216 } 2217 # Sending request for a char. 2218 self._GetResultFromJSONRequest(cmd_dict, windex=None) 2219 2220 def SetDownloadShelfVisible(self, is_visible, windex=0): 2221 """Set download shelf visibility for the specified browser window. 2222 2223 Args: 2224 is_visible: A boolean indicating the desired shelf visibility. 2225 windex: The window index, defaults to 0 (the first window). 2226 2227 Raises: 2228 pyauto_errors.JSONInterfaceError if the automation call returns an error. 2229 """ 2230 cmd_dict = { 2231 'command': 'SetDownloadShelfVisible', 2232 'is_visible': is_visible, 2233 'windex': windex, 2234 } 2235 self._GetResultFromJSONRequest(cmd_dict, windex=None) 2236 2237 def IsDownloadShelfVisible(self, windex=0): 2238 """Determine whether the download shelf is visible in the given window. 2239 2240 Args: 2241 windex: The window index, defaults to 0 (the first window). 2242 2243 Returns: 2244 A boolean indicating the shelf visibility. 2245 2246 Raises: 2247 pyauto_errors.JSONInterfaceError if the automation call returns an error. 2248 """ 2249 cmd_dict = { 2250 'command': 'IsDownloadShelfVisible', 2251 'windex': windex, 2252 } 2253 return self._GetResultFromJSONRequest(cmd_dict, windex=None)['is_visible'] 2254 2255 def GetDownloadDirectory(self, tab_index=None, windex=0): 2256 """Get the path to the download directory. 2257 2258 Warning: Depending on the concept of an active tab is dangerous as it can 2259 change during the test. Always supply a tab_index explicitly. 2260 2261 Args: 2262 tab_index: The index of the tab to work on. Defaults to the active tab. 2263 windex: The index of the browser window to work on. Defaults to 0. 2264 2265 Returns: 2266 The path to the download directory as a FilePath object. 2267 2268 Raises: 2269 pyauto_errors.JSONInterfaceError if the automation call returns an error. 2270 """ 2271 if tab_index is None: 2272 tab_index = self.GetActiveTabIndex(windex) 2273 cmd_dict = { 2274 'command': 'GetDownloadDirectory', 2275 'tab_index': tab_index, 2276 'windex': windex, 2277 } 2278 return FilePath(str(self._GetResultFromJSONRequest(cmd_dict, 2279 windex=None)['path'])) 2280 2281 def WaitForAllDownloadsToComplete(self, pre_download_ids=[], windex=0, 2282 timeout=-1): 2283 """Wait for all pending downloads to complete. 2284 2285 This function assumes that any downloads to wait for have already been 2286 triggered and have started (it is ok if those downloads complete before this 2287 function is called). 2288 2289 Args: 2290 pre_download_ids: A list of numbers representing the IDs of downloads that 2291 exist *before* downloads to wait for have been 2292 triggered. Defaults to []; use GetDownloadsInfo() to get 2293 these IDs (only necessary if a test previously 2294 downloaded files). 2295 windex: The window index, defaults to 0 (the first window). 2296 timeout: The maximum amount of time (in milliseconds) to wait for 2297 downloads to complete. 2298 """ 2299 cmd_dict = { 2300 'command': 'WaitForAllDownloadsToComplete', 2301 'pre_download_ids': pre_download_ids, 2302 } 2303 self._GetResultFromJSONRequest(cmd_dict, windex=windex, timeout=timeout) 2304 2305 def PerformActionOnDownload(self, id, action, window_index=0): 2306 """Perform the given action on the download with the given id. 2307 2308 Args: 2309 id: The id of the download. 2310 action: The action to perform on the download. 2311 Possible actions: 2312 'open': Opens the download (waits until it has completed first). 2313 'toggle_open_files_like_this': Toggles the 'Always Open Files 2314 Of This Type' option. 2315 'remove': Removes the file from downloads (not from disk). 2316 'decline_dangerous_download': Equivalent to 'Discard' option 2317 after downloading a dangerous download (ex. an executable). 2318 'save_dangerous_download': Equivalent to 'Save' option after 2319 downloading a dangerous file. 2320 'pause': Pause the download. If the download completed before 2321 this call or is already paused, it's a no-op. 2322 'resume': Resume the download. If the download completed before 2323 this call or was not paused, it's a no-op. 2324 'cancel': Cancel the download. 2325 window_index: The window index, default is 0. 2326 2327 Returns: 2328 A dictionary representing the updated download item (except in the case 2329 of 'decline_dangerous_download', 'toggle_open_files_like_this', and 2330 'remove', which return an empty dict). 2331 Example dictionary: 2332 { u'PercentComplete': 100, 2333 u'file_name': u'file.txt', 2334 u'full_path': u'/path/to/file.txt', 2335 u'id': 0, 2336 u'is_otr': False, 2337 u'is_paused': False, 2338 u'is_temporary': False, 2339 u'open_when_complete': False, 2340 u'referrer_url': u'', 2341 u'state': u'COMPLETE', 2342 u'danger_type': u'DANGEROUS_FILE', 2343 u'url': u'file://url/to/file.txt' 2344 } 2345 """ 2346 cmd_dict = { # Prepare command for the json interface 2347 'command': 'PerformActionOnDownload', 2348 'id': id, 2349 'action': action 2350 } 2351 return self._GetResultFromJSONRequest(cmd_dict, windex=window_index) 2352 2353 def DownloadAndWaitForStart(self, file_url, windex=0): 2354 """Trigger download for the given url and wait for downloads to start. 2355 2356 It waits for download by looking at the download info from Chrome, so 2357 anything which isn't registered by the history service won't be noticed. 2358 This is not thread-safe, but it's fine to call this method to start 2359 downloading multiple files in parallel. That is after starting a 2360 download, it's fine to start another one even if the first one hasn't 2361 completed. 2362 """ 2363 try: 2364 num_downloads = len(self.GetDownloadsInfo(windex).Downloads()) 2365 except JSONInterfaceError: 2366 num_downloads = 0 2367 2368 self.NavigateToURL(file_url, windex) # Trigger download. 2369 # It might take a while for the download to kick in, hold on until then. 2370 self.assertTrue(self.WaitUntil( 2371 lambda: len(self.GetDownloadsInfo(windex).Downloads()) > 2372 num_downloads)) 2373 2374 def SetWindowDimensions( 2375 self, x=None, y=None, width=None, height=None, windex=0): 2376 """Set window dimensions. 2377 2378 All args are optional and current values will be preserved. 2379 Arbitrarily large values will be handled gracefully by the browser. 2380 2381 Args: 2382 x: window origin x 2383 y: window origin y 2384 width: window width 2385 height: window height 2386 windex: window index to work on. Defaults to 0 (first window) 2387 """ 2388 cmd_dict = { # Prepare command for the json interface 2389 'command': 'SetWindowDimensions', 2390 } 2391 if x: 2392 cmd_dict['x'] = x 2393 if y: 2394 cmd_dict['y'] = y 2395 if width: 2396 cmd_dict['width'] = width 2397 if height: 2398 cmd_dict['height'] = height 2399 self._GetResultFromJSONRequest(cmd_dict, windex=windex) 2400 2401 def WaitForInfobarCount(self, count, windex=0, tab_index=0): 2402 """Wait until infobar count becomes |count|. 2403 2404 Note: Wait duration is capped by the automation timeout. 2405 2406 Args: 2407 count: requested number of infobars 2408 windex: window index. Defaults to 0 (first window) 2409 tab_index: tab index Defaults to 0 (first tab) 2410 2411 Raises: 2412 pyauto_errors.JSONInterfaceError if the automation call returns an error. 2413 """ 2414 # TODO(phajdan.jr): We need a solid automation infrastructure to handle 2415 # these cases. See crbug.com/53647. 2416 def _InfobarCount(): 2417 windows = self.GetBrowserInfo()['windows'] 2418 if windex >= len(windows): # not enough windows 2419 return -1 2420 tabs = windows[windex]['tabs'] 2421 if tab_index >= len(tabs): # not enough tabs 2422 return -1 2423 return len(tabs[tab_index]['infobars']) 2424 2425 return self.WaitUntil(_InfobarCount, expect_retval=count) 2426 2427 def PerformActionOnInfobar( 2428 self, action, infobar_index, windex=0, tab_index=0): 2429 """Perform actions on an infobar. 2430 2431 Args: 2432 action: the action to be performed. 2433 Actions depend on the type of the infobar. The user needs to 2434 call the right action for the right infobar. 2435 Valid inputs are: 2436 - "dismiss": closes the infobar (for all infobars) 2437 - "accept", "cancel": click accept / cancel (for confirm infobars) 2438 - "allow", "deny": click allow / deny (for media stream infobars) 2439 infobar_index: 0-based index of the infobar on which to perform the action 2440 windex: 0-based window index Defaults to 0 (first window) 2441 tab_index: 0-based tab index. Defaults to 0 (first tab) 2442 2443 Raises: 2444 pyauto_errors.JSONInterfaceError if the automation call returns an error. 2445 """ 2446 cmd_dict = { 2447 'command': 'PerformActionOnInfobar', 2448 'action': action, 2449 'infobar_index': infobar_index, 2450 'tab_index': tab_index, 2451 } 2452 if action not in ('dismiss', 'accept', 'allow', 'deny', 'cancel'): 2453 raise JSONInterfaceError('Invalid action %s' % action) 2454 self._GetResultFromJSONRequest(cmd_dict, windex=windex) 2455 2456 def GetBrowserInfo(self): 2457 """Return info about the browser. 2458 2459 This includes things like the version number, the executable name, 2460 executable path, pid info about the renderer/plugin/extension processes, 2461 window dimensions. (See sample below) 2462 2463 For notification pid info, see 'GetActiveNotifications'. 2464 2465 Returns: 2466 a dictionary 2467 2468 Sample: 2469 { u'browser_pid': 93737, 2470 # Child processes are the processes for plugins and other workers. 2471 u'child_process_path': u'.../Chromium.app/Contents/' 2472 'Versions/6.0.412.0/Chromium Helper.app/' 2473 'Contents/MacOS/Chromium Helper', 2474 u'child_processes': [ { u'name': u'Shockwave Flash', 2475 u'pid': 93766, 2476 u'type': u'Plug-in'}], 2477 u'extension_views': [ { 2478 u'name': u'Webpage Screenshot', 2479 u'pid': 93938, 2480 u'extension_id': u'dgcoklnmbeljaehamekjpeidmbicddfj', 2481 u'url': u'chrome-extension://dgcoklnmbeljaehamekjpeidmbicddfj/' 2482 'bg.html', 2483 u'loaded': True, 2484 u'view': { 2485 u'render_process_id': 2, 2486 u'render_view_id': 1}, 2487 u'view_type': u'EXTENSION_BACKGROUND_PAGE'}] 2488 u'properties': { 2489 u'BrowserProcessExecutableName': u'Chromium', 2490 u'BrowserProcessExecutablePath': u'Chromium.app/Contents/MacOS/' 2491 'Chromium', 2492 u'ChromeVersion': u'6.0.412.0', 2493 u'HelperProcessExecutableName': u'Chromium Helper', 2494 u'HelperProcessExecutablePath': u'Chromium Helper.app/Contents/' 2495 'MacOS/Chromium Helper', 2496 u'command_line_string': "COMMAND_LINE_STRING --WITH-FLAGS", 2497 u'branding': 'Chromium', 2498 u'is_official': False,} 2499 # The order of the windows and tabs listed here will be the same as 2500 # what shows up on screen. 2501 u'windows': [ { u'index': 0, 2502 u'height': 1134, 2503 u'incognito': False, 2504 u'profile_path': u'Default', 2505 u'fullscreen': False, 2506 u'visible_page_actions': 2507 [u'dgcoklnmbeljaehamekjpeidmbicddfj', 2508 u'osfcklnfasdofpcldmalwpicslasdfgd'] 2509 u'selected_tab': 0, 2510 u'tabs': [ { 2511 u'index': 0, 2512 u'infobars': [], 2513 u'pinned': True, 2514 u'renderer_pid': 93747, 2515 u'url': u'http://www.google.com/' }, { 2516 u'index': 1, 2517 u'infobars': [], 2518 u'pinned': False, 2519 u'renderer_pid': 93919, 2520 u'url': u'https://chrome.google.com/'}, { 2521 u'index': 2, 2522 u'infobars': [ { 2523 u'buttons': [u'Allow', u'Deny'], 2524 u'link_text': u'Learn more', 2525 u'text': u'slides.html5rocks.com wants to track ' 2526 'your physical location', 2527 u'type': u'confirm_infobar'}], 2528 u'pinned': False, 2529 u'renderer_pid': 93929, 2530 u'url': u'http://slides.html5rocks.com/#slide14'}, 2531 ], 2532 u'type': u'tabbed', 2533 u'width': 925, 2534 u'x': 26, 2535 u'y': 44}]} 2536 2537 Raises: 2538 pyauto_errors.JSONInterfaceError if the automation call returns an error. 2539 """ 2540 cmd_dict = { # Prepare command for the json interface 2541 'command': 'GetBrowserInfo', 2542 } 2543 return self._GetResultFromJSONRequest(cmd_dict, windex=None) 2544 2545 def IsAura(self): 2546 """Is this Aura?""" 2547 return self.GetBrowserInfo()['properties']['aura'] 2548 2549 def GetProcessInfo(self): 2550 """Returns information about browser-related processes that currently exist. 2551 2552 This will also return information about other currently-running browsers 2553 besides just Chrome. 2554 2555 Returns: 2556 A dictionary containing browser-related process information as identified 2557 by class MemoryDetails in src/chrome/browser/memory_details.h. The 2558 dictionary contains a single key 'browsers', mapped to a list of 2559 dictionaries containing information about each browser process name. 2560 Each of those dictionaries contains a key 'processes', mapped to a list 2561 of dictionaries containing the specific information for each process 2562 with the given process name. 2563 2564 The memory values given in |committed_mem| and |working_set_mem| are in 2565 KBytes. 2566 2567 Sample: 2568 { 'browsers': [ { 'name': 'Chromium', 2569 'process_name': 'chrome', 2570 'processes': [ { 'child_process_type': 'Browser', 2571 'committed_mem': { 'image': 0, 2572 'mapped': 0, 2573 'priv': 0}, 2574 'is_diagnostics': False, 2575 'num_processes': 1, 2576 'pid': 7770, 2577 'product_name': '', 2578 'renderer_type': 'Unknown', 2579 'titles': [], 2580 'version': '', 2581 'working_set_mem': { 'priv': 43672, 2582 'shareable': 0, 2583 'shared': 59251}}, 2584 { 'child_process_type': 'Tab', 2585 'committed_mem': { 'image': 0, 2586 'mapped': 0, 2587 'priv': 0}, 2588 'is_diagnostics': False, 2589 'num_processes': 1, 2590 'pid': 7791, 2591 'product_name': '', 2592 'renderer_type': 'Tab', 2593 'titles': ['about:blank'], 2594 'version': '', 2595 'working_set_mem': { 'priv': 16768, 2596 'shareable': 0, 2597 'shared': 26256}}, 2598 ...<more processes>...]}]} 2599 2600 Raises: 2601 pyauto_errors.JSONInterfaceError if the automation call returns an error. 2602 """ 2603 cmd_dict = { # Prepare command for the json interface. 2604 'command': 'GetProcessInfo', 2605 } 2606 return self._GetResultFromJSONRequest(cmd_dict, windex=None) 2607 2608 def GetNavigationInfo(self, tab_index=0, windex=0): 2609 """Get info about the navigation state of a given tab. 2610 2611 Args: 2612 tab_index: The tab index, default is 0. 2613 window_index: The window index, default is 0. 2614 2615 Returns: 2616 a dictionary. 2617 Sample: 2618 2619 { u'favicon_url': u'https://www.google.com/favicon.ico', 2620 u'page_type': u'NORMAL_PAGE', 2621 u'ssl': { u'displayed_insecure_content': False, 2622 u'ran_insecure_content': False, 2623 u'security_style': u'SECURITY_STYLE_AUTHENTICATED'}} 2624 2625 Values for security_style can be: 2626 SECURITY_STYLE_UNKNOWN 2627 SECURITY_STYLE_UNAUTHENTICATED 2628 SECURITY_STYLE_AUTHENTICATION_BROKEN 2629 SECURITY_STYLE_AUTHENTICATED 2630 2631 Values for page_type can be: 2632 NORMAL_PAGE 2633 ERROR_PAGE 2634 INTERSTITIAL_PAGE 2635 """ 2636 cmd_dict = { # Prepare command for the json interface 2637 'command': 'GetNavigationInfo', 2638 'tab_index': tab_index, 2639 } 2640 return self._GetResultFromJSONRequest(cmd_dict, windex=windex) 2641 2642 def GetSecurityState(self, tab_index=0, windex=0): 2643 """Get security details for a given tab. 2644 2645 Args: 2646 tab_index: The tab index, default is 0. 2647 window_index: The window index, default is 0. 2648 2649 Returns: 2650 a dictionary. 2651 Sample: 2652 { "security_style": SECURITY_STYLE_AUTHENTICATED, 2653 "ssl_cert_status": 3, // bitmask of status flags 2654 "insecure_content_status": 1, // bitmask of status flags 2655 } 2656 """ 2657 cmd_dict = { # Prepare command for the json interface 2658 'command': 'GetSecurityState', 2659 'tab_index': tab_index, 2660 'windex': windex, 2661 } 2662 return self._GetResultFromJSONRequest(cmd_dict, windex=None) 2663 2664 def GetHistoryInfo(self, search_text='', windex=0): 2665 """Return info about browsing history. 2666 2667 Args: 2668 search_text: the string to search in history. Defaults to empty string 2669 which means that all history would be returned. This is 2670 functionally equivalent to searching for a text in the 2671 chrome://history UI. So partial matches work too. 2672 When non-empty, the history items returned will contain a 2673 "snippet" field corresponding to the snippet visible in 2674 the chrome://history/ UI. 2675 windex: index of the browser window, defaults to 0. 2676 2677 Returns: 2678 an instance of history_info.HistoryInfo 2679 """ 2680 cmd_dict = { # Prepare command for the json interface 2681 'command': 'GetHistoryInfo', 2682 'search_text': search_text, 2683 } 2684 return history_info.HistoryInfo( 2685 self._GetResultFromJSONRequest(cmd_dict, windex=windex)) 2686 2687 def InstallExtension(self, extension_path, with_ui=False, from_webstore=None, 2688 windex=0, tab_index=0): 2689 """Installs an extension from the given path. 2690 2691 The path must be absolute and may be a crx file or an unpacked extension 2692 directory. Returns the extension ID if successfully installed and loaded. 2693 Otherwise, throws an exception. The extension must not already be installed. 2694 2695 Args: 2696 extension_path: The absolute path to the extension to install. If the 2697 extension is packed, it must have a .crx extension. 2698 with_ui: Whether the extension install confirmation UI should be shown. 2699 from_webstore: If True, forces a .crx extension to be recognized as one 2700 from the webstore. Can be used to force install an extension with 2701 'experimental' permissions. 2702 windex: Integer index of the browser window to use; defaults to 0 2703 (first window). 2704 2705 Returns: 2706 The ID of the installed extension. 2707 2708 Raises: 2709 pyauto_errors.JSONInterfaceError if the automation call returns an error. 2710 """ 2711 cmd_dict = { 2712 'command': 'InstallExtension', 2713 'path': extension_path, 2714 'with_ui': with_ui, 2715 'windex': windex, 2716 'tab_index': tab_index, 2717 } 2718 2719 if from_webstore: 2720 cmd_dict['from_webstore'] = True 2721 return self._GetResultFromJSONRequest(cmd_dict, windex=None)['id'] 2722 2723 def GetExtensionsInfo(self, windex=0): 2724 """Returns information about all installed extensions. 2725 2726 Args: 2727 windex: Integer index of the browser window to use; defaults to 0 2728 (first window). 2729 2730 Returns: 2731 A list of dictionaries representing each of the installed extensions. 2732 Example: 2733 [ { u'api_permissions': [u'bookmarks', u'experimental', u'tabs'], 2734 u'background_url': u'', 2735 u'description': u'Bookmark Manager', 2736 u'effective_host_permissions': [u'chrome://favicon/*', 2737 u'chrome://resources/*'], 2738 u'host_permissions': [u'chrome://favicon/*', u'chrome://resources/*'], 2739 u'id': u'eemcgdkfndhakfknompkggombfjjjeno', 2740 u'is_component': True, 2741 u'is_internal': False, 2742 u'name': u'Bookmark Manager', 2743 u'options_url': u'', 2744 u'public_key': u'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDQcByy+eN9jza\ 2745 zWF/DPn7NW47sW7lgmpk6eKc0BQM18q8hvEM3zNm2n7HkJv/R6f\ 2746 U+X5mtqkDuKvq5skF6qqUF4oEyaleWDFhd1xFwV7JV+/DU7bZ00\ 2747 w2+6gzqsabkerFpoP33ZRIw7OviJenP0c0uWqDWF8EGSyMhB3tx\ 2748 qhOtiQIDAQAB', 2749 u'version': u'0.1' }, 2750 { u'api_permissions': [...], 2751 u'background_url': u'chrome-extension://\ 2752 lkdedmbpkaiahjjibfdmpoefffnbdkli/\ 2753 background.html', 2754 u'description': u'Extension which lets you read your Facebook news \ 2755 feed and wall. You can also post status updates.', 2756 u'effective_host_permissions': [...], 2757 u'host_permissions': [...], 2758 u'id': u'lkdedmbpkaiahjjibfdmpoefffnbdkli', 2759 u'name': u'Facebook for Google Chrome', 2760 u'options_url': u'', 2761 u'public_key': u'...', 2762 u'version': u'2.0.9' 2763 u'is_enabled': True, 2764 u'allowed_in_incognito': True} ] 2765 """ 2766 cmd_dict = { # Prepare command for the json interface 2767 'command': 'GetExtensionsInfo', 2768 'windex': windex, 2769 } 2770 return self._GetResultFromJSONRequest(cmd_dict, windex=None)['extensions'] 2771 2772 def UninstallExtensionById(self, id, windex=0): 2773 """Uninstall the extension with the given id. 2774 2775 Args: 2776 id: The string id of the extension. 2777 windex: Integer index of the browser window to use; defaults to 0 2778 (first window). 2779 2780 Returns: 2781 True, if the extension was successfully uninstalled, or 2782 False, otherwise. 2783 """ 2784 cmd_dict = { # Prepare command for the json interface 2785 'command': 'UninstallExtensionById', 2786 'id': id, 2787 'windex': windex, 2788 } 2789 return self._GetResultFromJSONRequest(cmd_dict, windex=None)['success'] 2790 2791 def SetExtensionStateById(self, id, enable, allow_in_incognito, windex=0): 2792 """Set extension state: enable/disable, allow/disallow in incognito mode. 2793 2794 Args: 2795 id: The string id of the extension. 2796 enable: A boolean, enable extension. 2797 allow_in_incognito: A boolean, allow extension in incognito. 2798 windex: Integer index of the browser window to use; defaults to 0 2799 (first window). 2800 """ 2801 cmd_dict = { # Prepare command for the json interface 2802 'command': 'SetExtensionStateById', 2803 'id': id, 2804 'enable': enable, 2805 'allow_in_incognito': allow_in_incognito, 2806 'windex': windex, 2807 } 2808 self._GetResultFromJSONRequest(cmd_dict, windex=None) 2809 2810 def TriggerPageActionById(self, id, tab_index=0, windex=0): 2811 """Trigger page action asynchronously in the active tab. 2812 2813 The page action icon must be displayed before invoking this function. 2814 2815 Args: 2816 id: The string id of the extension. 2817 tab_index: Integer index of the tab to use; defaults to 0 (first tab). 2818 windex: Integer index of the browser window to use; defaults to 0 2819 (first window). 2820 """ 2821 cmd_dict = { # Prepare command for the json interface 2822 'command': 'TriggerPageActionById', 2823 'id': id, 2824 'windex': windex, 2825 'tab_index': tab_index, 2826 } 2827 self._GetResultFromJSONRequest(cmd_dict, windex=None) 2828 2829 def TriggerBrowserActionById(self, id, tab_index=0, windex=0): 2830 """Trigger browser action asynchronously in the active tab. 2831 2832 Args: 2833 id: The string id of the extension. 2834 tab_index: Integer index of the tab to use; defaults to 0 (first tab). 2835 windex: Integer index of the browser window to use; defaults to 0 2836 (first window). 2837 """ 2838 cmd_dict = { # Prepare command for the json interface 2839 'command': 'TriggerBrowserActionById', 2840 'id': id, 2841 'windex': windex, 2842 'tab_index': tab_index, 2843 } 2844 self._GetResultFromJSONRequest(cmd_dict, windex=None) 2845 2846 def UpdateExtensionsNow(self, windex=0): 2847 """Auto-updates installed extensions. 2848 2849 Waits until all extensions are updated, loaded, and ready for use. 2850 This is equivalent to clicking the "Update extensions now" button on the 2851 chrome://extensions page. 2852 2853 Args: 2854 windex: Integer index of the browser window to use; defaults to 0 2855 (first window). 2856 2857 Raises: 2858 pyauto_errors.JSONInterfaceError if the automation returns an error. 2859 """ 2860 cmd_dict = { # Prepare command for the json interface. 2861 'command': 'UpdateExtensionsNow', 2862 'windex': windex, 2863 } 2864 self._GetResultFromJSONRequest(cmd_dict, windex=None) 2865 2866 def WaitUntilExtensionViewLoaded(self, name=None, extension_id=None, 2867 url=None, view_type=None): 2868 """Wait for a loaded extension view matching all the given properties. 2869 2870 If no matching extension views are found, wait for one to be loaded. 2871 If there are more than one matching extension view, return one at random. 2872 Uses WaitUntil so timeout is capped by automation timeout. 2873 Refer to extension_view dictionary returned in GetBrowserInfo() 2874 for sample input/output values. 2875 2876 Args: 2877 name: (optional) Name of the extension. 2878 extension_id: (optional) ID of the extension. 2879 url: (optional) URL of the extension view. 2880 view_type: (optional) Type of the extension view. 2881 ['EXTENSION_BACKGROUND_PAGE'|'EXTENSION_POPUP'|'EXTENSION_INFOBAR'| 2882 'EXTENSION_DIALOG'] 2883 2884 Returns: 2885 The 'view' property of the extension view. 2886 None, if no view loaded. 2887 2888 Raises: 2889 pyauto_errors.JSONInterfaceError if the automation returns an error. 2890 """ 2891 def _GetExtensionViewLoaded(): 2892 extension_views = self.GetBrowserInfo()['extension_views'] 2893 for extension_view in extension_views: 2894 if ((name and name != extension_view['name']) or 2895 (extension_id and extension_id != extension_view['extension_id']) or 2896 (url and url != extension_view['url']) or 2897 (view_type and view_type != extension_view['view_type'])): 2898 continue 2899 if extension_view['loaded']: 2900 return extension_view['view'] 2901 return False 2902 2903 if self.WaitUntil(lambda: _GetExtensionViewLoaded()): 2904 return _GetExtensionViewLoaded() 2905 return None 2906 2907 def WaitUntilExtensionViewClosed(self, view): 2908 """Wait for the given extension view to to be closed. 2909 2910 Uses WaitUntil so timeout is capped by automation timeout. 2911 Refer to extension_view dictionary returned by GetBrowserInfo() 2912 for sample input value. 2913 2914 Args: 2915 view: 'view' property of extension view. 2916 2917 Raises: 2918 pyauto_errors.JSONInterfaceError if the automation returns an error. 2919 """ 2920 def _IsExtensionViewClosed(): 2921 extension_views = self.GetBrowserInfo()['extension_views'] 2922 for extension_view in extension_views: 2923 if view == extension_view['view']: 2924 return False 2925 return True 2926 2927 return self.WaitUntil(lambda: _IsExtensionViewClosed()) 2928 2929 def GetPluginsInfo(self, windex=0): 2930 """Return info about plugins. 2931 2932 This is the info available from about:plugins 2933 2934 Returns: 2935 an instance of plugins_info.PluginsInfo 2936 """ 2937 return plugins_info.PluginsInfo( 2938 self._GetResultFromJSONRequest({'command': 'GetPluginsInfo'}, 2939 windex=windex)) 2940 2941 def EnablePlugin(self, path): 2942 """Enable the plugin at the given path. 2943 2944 Use GetPluginsInfo() to fetch path info about a plugin. 2945 2946 Raises: 2947 pyauto_errors.JSONInterfaceError if the automation call returns an error. 2948 """ 2949 cmd_dict = { 2950 'command': 'EnablePlugin', 2951 'path': path, 2952 } 2953 self._GetResultFromJSONRequest(cmd_dict) 2954 2955 def DisablePlugin(self, path): 2956 """Disable the plugin at the given path. 2957 2958 Use GetPluginsInfo() to fetch path info about a plugin. 2959 2960 Raises: 2961 pyauto_errors.JSONInterfaceError if the automation call returns an error. 2962 """ 2963 cmd_dict = { 2964 'command': 'DisablePlugin', 2965 'path': path, 2966 } 2967 self._GetResultFromJSONRequest(cmd_dict) 2968 2969 def GetTabContents(self, tab_index=0, window_index=0): 2970 """Get the html contents of a tab (a la "view source"). 2971 2972 As an implementation detail, this saves the html in a file, reads 2973 the file into a buffer, then deletes it. 2974 2975 Args: 2976 tab_index: tab index, defaults to 0. 2977 window_index: window index, defaults to 0. 2978 Returns: 2979 html content of a page as a string. 2980 """ 2981 tempdir = tempfile.mkdtemp() 2982 # Make it writable by chronos on chromeos 2983 os.chmod(tempdir, 0777) 2984 filename = os.path.join(tempdir, 'content.html') 2985 cmd_dict = { # Prepare command for the json interface 2986 'command': 'SaveTabContents', 2987 'tab_index': tab_index, 2988 'filename': filename 2989 } 2990 self._GetResultFromJSONRequest(cmd_dict, windex=window_index) 2991 try: 2992 f = open(filename) 2993 all_data = f.read() 2994 f.close() 2995 return all_data 2996 finally: 2997 shutil.rmtree(tempdir, ignore_errors=True) 2998 2999 def AddSavedPassword(self, password_dict, windex=0): 3000 """Adds the given username-password combination to the saved passwords. 3001 3002 Args: 3003 password_dict: a dictionary that represents a password. Example: 3004 { 'username_value': 'user (at] example.com', # Required 3005 'password_value': 'test.password', # Required 3006 'signon_realm': 'https://www.example.com/', # Required 3007 'time': 1279317810.0, # Can get from time.time() 3008 'origin_url': 'https://www.example.com/login', 3009 'username_element': 'username', # The HTML element 3010 'password_element': 'password', # The HTML element 3011 'submit_element': 'submit', # The HTML element 3012 'action_target': 'https://www.example.com/login/', 3013 'blacklist': False } 3014 windex: window index; defaults to 0 (first window). 3015 3016 *Blacklist notes* To blacklist a site, add a blacklist password with the 3017 following dictionary items: origin_url, signon_realm, username_element, 3018 password_element, action_target, and 'blacklist': True. Then all sites that 3019 have password forms matching those are blacklisted. 3020 3021 Returns: 3022 True if adding the password succeeded, false otherwise. In incognito 3023 mode, adding the password should fail. 3024 3025 Raises: 3026 pyauto_errors.JSONInterfaceError if the automation call returns an error. 3027 """ 3028 cmd_dict = { # Prepare command for the json interface 3029 'command': 'AddSavedPassword', 3030 'password': password_dict 3031 } 3032 return self._GetResultFromJSONRequest( 3033 cmd_dict, windex=windex)['password_added'] 3034 3035 def RemoveSavedPassword(self, password_dict, windex=0): 3036 """Removes the password matching the provided password dictionary. 3037 3038 Args: 3039 password_dict: A dictionary that represents a password. 3040 For an example, see the dictionary in AddSavedPassword. 3041 windex: The window index, default is 0 (first window). 3042 """ 3043 cmd_dict = { # Prepare command for the json interface 3044 'command': 'RemoveSavedPassword', 3045 'password': password_dict 3046 } 3047 self._GetResultFromJSONRequest(cmd_dict, windex=windex) 3048 3049 def GetSavedPasswords(self): 3050 """Return the passwords currently saved. 3051 3052 Returns: 3053 A list of dictionaries representing each password. For an example 3054 dictionary see AddSavedPassword documentation. The overall structure will 3055 be: 3056 [ {password1 dictionary}, {password2 dictionary} ] 3057 """ 3058 cmd_dict = { # Prepare command for the json interface 3059 'command': 'GetSavedPasswords' 3060 } 3061 return self._GetResultFromJSONRequest(cmd_dict)['passwords'] 3062 3063 def SetTheme(self, crx_file_path, windex=0): 3064 """Installs the given theme synchronously. 3065 3066 A theme file is a file with a .crx suffix, like an extension. The theme 3067 file must be specified with an absolute path. This method call waits until 3068 the theme is installed and will trigger the "theme installed" infobar. 3069 If the install is unsuccessful, will throw an exception. 3070 3071 Uses InstallExtension(). 3072 3073 Returns: 3074 The ID of the installed theme. 3075 3076 Raises: 3077 pyauto_errors.JSONInterfaceError if the automation call returns an error. 3078 """ 3079 return self.InstallExtension(crx_file_path, True, windex) 3080 3081 def GetActiveNotifications(self): 3082 """Gets a list of the currently active/shown HTML5 notifications. 3083 3084 Returns: 3085 a list containing info about each active notification, with the 3086 first item in the list being the notification on the bottom of the 3087 notification stack. The 'content_url' key can refer to a URL or a data 3088 URI. The 'pid' key-value pair may be invalid if the notification is 3089 closing. 3090 3091 SAMPLE: 3092 [ { u'content_url': u'data:text/html;charset=utf-8,%3C!DOCTYPE%l%3E%0Atm...' 3093 u'display_source': 'www.corp.google.com', 3094 u'origin_url': 'http://www.corp.google.com/', 3095 u'pid': 8505}, 3096 { u'content_url': 'http://www.gmail.com/special_notification.html', 3097 u'display_source': 'www.gmail.com', 3098 u'origin_url': 'http://www.gmail.com/', 3099 u'pid': 9291}] 3100 3101 Raises: 3102 pyauto_errors.JSONInterfaceError if the automation call returns an error. 3103 """ 3104 return [x for x in self.GetAllNotifications() if 'pid' in x] 3105 3106 def GetAllNotifications(self): 3107 """Gets a list of all active and queued HTML5 notifications. 3108 3109 An active notification is one that is currently shown to the user. Chrome's 3110 notification system will limit the number of notifications shown (currently 3111 by only allowing a certain percentage of the screen to be taken up by them). 3112 A notification will be queued if there are too many active notifications. 3113 Once other notifications are closed, another will be shown from the queue. 3114 3115 Returns: 3116 a list containing info about each notification, with the first 3117 item in the list being the notification on the bottom of the 3118 notification stack. The 'content_url' key can refer to a URL or a data 3119 URI. The 'pid' key-value pair will only be present for active 3120 notifications. 3121 3122 SAMPLE: 3123 [ { u'content_url': u'data:text/html;charset=utf-8,%3C!DOCTYPE%l%3E%0Atm...' 3124 u'display_source': 'www.corp.google.com', 3125 u'origin_url': 'http://www.corp.google.com/', 3126 u'pid': 8505}, 3127 { u'content_url': 'http://www.gmail.com/special_notification.html', 3128 u'display_source': 'www.gmail.com', 3129 u'origin_url': 'http://www.gmail.com/'}] 3130 3131 Raises: 3132 pyauto_errors.JSONInterfaceError if the automation call returns an error. 3133 """ 3134 cmd_dict = { 3135 'command': 'GetAllNotifications', 3136 } 3137 return self._GetResultFromJSONRequest(cmd_dict)['notifications'] 3138 3139 def CloseNotification(self, index): 3140 """Closes the active HTML5 notification at the given index. 3141 3142 Args: 3143 index: the index of the notification to close. 0 refers to the 3144 notification on the bottom of the notification stack. 3145 3146 Raises: 3147 pyauto_errors.JSONInterfaceError if the automation call returns an error. 3148 """ 3149 cmd_dict = { 3150 'command': 'CloseNotification', 3151 'index': index, 3152 } 3153 return self._GetResultFromJSONRequest(cmd_dict) 3154 3155 def WaitForNotificationCount(self, count): 3156 """Waits for the number of active HTML5 notifications to reach the given 3157 count. 3158 3159 Raises: 3160 pyauto_errors.JSONInterfaceError if the automation call returns an error. 3161 """ 3162 cmd_dict = { 3163 'command': 'WaitForNotificationCount', 3164 'count': count, 3165 } 3166 self._GetResultFromJSONRequest(cmd_dict) 3167 3168 def FindInPage(self, search_string, forward=True, 3169 match_case=False, find_next=False, 3170 tab_index=0, windex=0, timeout=-1): 3171 """Find the match count for the given search string and search parameters. 3172 This is equivalent to using the find box. 3173 3174 Args: 3175 search_string: The string to find on the page. 3176 forward: Boolean to set if the search direction is forward or backwards 3177 match_case: Boolean to set for case sensitive search. 3178 find_next: Boolean to set to continue the search or start from beginning. 3179 tab_index: The tab index, default is 0. 3180 windex: The window index, default is 0. 3181 timeout: request timeout (in milliseconds), default is -1. 3182 3183 Returns: 3184 number of matches found for the given search string and parameters 3185 SAMPLE: 3186 { u'match_count': 10, 3187 u'match_left': 100, 3188 u'match_top': 100, 3189 u'match_right': 200, 3190 u'match_bottom': 200} 3191 3192 Raises: 3193 pyauto_errors.JSONInterfaceError if the automation call returns an error. 3194 """ 3195 cmd_dict = { 3196 'command': 'FindInPage', 3197 'tab_index' : tab_index, 3198 'search_string' : search_string, 3199 'forward' : forward, 3200 'match_case' : match_case, 3201 'find_next' : find_next, 3202 } 3203 return self._GetResultFromJSONRequest(cmd_dict, windex=windex, 3204 timeout=timeout) 3205 3206 def OpenFindInPage(self, windex=0): 3207 """Opens the "Find in Page" box. 3208 3209 Args: 3210 windex: Index of the window; defaults to 0. 3211 3212 Raises: 3213 pyauto_errors.JSONInterfaceError if the automation call returns an error. 3214 """ 3215 cmd_dict = { 3216 'command': 'OpenFindInPage', 3217 'windex' : windex, 3218 } 3219 self._GetResultFromJSONRequest(cmd_dict, windex=None) 3220 3221 def IsFindInPageVisible(self, windex=0): 3222 """Returns the visibility of the "Find in Page" box. 3223 3224 Args: 3225 windex: Index of the window; defaults to 0. 3226 3227 Returns: 3228 A boolean indicating the visibility state of the "Find in Page" box. 3229 3230 Raises: 3231 pyauto_errors.JSONInterfaceError if the automation call returns an error. 3232 """ 3233 cmd_dict = { 3234 'command': 'IsFindInPageVisible', 3235 'windex' : windex, 3236 } 3237 return self._GetResultFromJSONRequest(cmd_dict, windex=None)['is_visible'] 3238 3239 3240 def AddDomEventObserver(self, event_name='', automation_id=-1, 3241 recurring=False): 3242 """Adds a DomEventObserver associated with the AutomationEventQueue. 3243 3244 An app raises a matching event in Javascript by calling: 3245 window.domAutomationController.sendWithId(automation_id, event_name) 3246 3247 Args: 3248 event_name: The event name to watch for. By default an event is raised 3249 for any message. 3250 automation_id: The Automation Id of the sent message. By default all 3251 messages sent from the window.domAutomationController are 3252 observed. Note that other PyAuto functions also send 3253 messages through window.domAutomationController with 3254 arbirary Automation Ids and they will be observed. 3255 recurring: If False the observer will be removed after it generates one 3256 event, otherwise it will continue observing and generating 3257 events until explicity removed with RemoveEventObserver(id). 3258 3259 Returns: 3260 The id of the created observer, which can be used with GetNextEvent(id) 3261 and RemoveEventObserver(id). 3262 3263 Raises: 3264 pyauto_errors.JSONInterfaceError if the automation call returns an error. 3265 """ 3266 cmd_dict = { 3267 'command': 'AddDomEventObserver', 3268 'event_name': event_name, 3269 'automation_id': automation_id, 3270 'recurring': recurring, 3271 } 3272 return self._GetResultFromJSONRequest(cmd_dict, windex=None)['observer_id'] 3273 3274 def AddDomMutationObserver(self, mutation_type, xpath, 3275 attribute='textContent', expected_value=None, 3276 automation_id=44444, 3277 exec_js=None, **kwargs): 3278 """Sets up an event observer watching for a specific DOM mutation. 3279 3280 Creates an observer that raises an event when a mutation of the given type 3281 occurs on a DOM node specified by |selector|. 3282 3283 Args: 3284 mutation_type: One of 'add', 'remove', 'change', or 'exists'. 3285 xpath: An xpath specifying the DOM node to watch. The node must already 3286 exist if |mutation_type| is 'change'. 3287 attribute: Attribute to match |expected_value| against, if given. Defaults 3288 to 'textContent'. 3289 expected_value: Optional regular expression to match against the node's 3290 textContent attribute after the mutation. Defaults to None. 3291 automation_id: The automation_id used to route the observer javascript 3292 messages. Defaults to 44444. 3293 exec_js: A callable of the form f(self, js, **kwargs) used to inject the 3294 MutationObserver javascript. Defaults to None, which uses 3295 PyUITest.ExecuteJavascript. 3296 3297 Any additional keyword arguments are passed on to ExecuteJavascript and 3298 can be used to select the tab where the DOM MutationObserver is created. 3299 3300 Returns: 3301 The id of the created observer, which can be used with GetNextEvent(id) 3302 and RemoveEventObserver(id). 3303 3304 Raises: 3305 pyauto_errors.JSONInterfaceError if the automation call returns an error. 3306 pyauto_errors.JavascriptRuntimeError if the injected javascript 3307 MutationObserver returns an error. 3308 """ 3309 assert mutation_type in ('add', 'remove', 'change', 'exists'), \ 3310 'Unexpected value "%s" for mutation_type.' % mutation_type 3311 cmd_dict = { 3312 'command': 'AddDomEventObserver', 3313 'event_name': '__dom_mutation_observer__:$(id)', 3314 'automation_id': automation_id, 3315 'recurring': False, 3316 } 3317 observer_id = ( 3318 self._GetResultFromJSONRequest(cmd_dict, windex=None)['observer_id']) 3319 expected_string = ('null' if expected_value is None else '"%s"' % 3320 expected_value.replace('"', r'\"')) 3321 jsfile = os.path.join(os.path.abspath(os.path.dirname(__file__)), 3322 'dom_mutation_observer.js') 3323 with open(jsfile, 'r') as f: 3324 js = ('(' + f.read() + ')(%d, %d, "%s", "%s", "%s", %s);' % 3325 (automation_id, observer_id, mutation_type, 3326 xpath.replace('"', r'\"'), attribute, expected_string)) 3327 exec_js = exec_js or PyUITest.ExecuteJavascript 3328 try: 3329 jsreturn = exec_js(self, js, **kwargs) 3330 except JSONInterfaceError: 3331 raise JSONInterfaceError('Failed to inject DOM mutation observer.') 3332 if jsreturn != 'success': 3333 self.RemoveEventObserver(observer_id) 3334 raise JavascriptRuntimeError(jsreturn) 3335 return observer_id 3336 3337 def WaitForDomNode(self, xpath, attribute='textContent', 3338 expected_value=None, exec_js=None, timeout=-1, 3339 msg='Expected DOM node failed to appear.', **kwargs): 3340 """Waits until a node specified by an xpath exists in the DOM. 3341 3342 NOTE: This does NOT poll. It returns as soon as the node appears, or 3343 immediately if the node already exists. 3344 3345 Args: 3346 xpath: An xpath specifying the DOM node to watch. 3347 attribute: Attribute to match |expected_value| against, if given. Defaults 3348 to 'textContent'. 3349 expected_value: Optional regular expression to match against the node's 3350 textContent attribute. Defaults to None. 3351 exec_js: A callable of the form f(self, js, **kwargs) used to inject the 3352 MutationObserver javascript. Defaults to None, which uses 3353 PyUITest.ExecuteJavascript. 3354 msg: An optional error message used if a JSONInterfaceError is caught 3355 while waiting for the DOM node to appear. 3356 timeout: Time to wait for the node to exist before raising an exception, 3357 defaults to the default automation timeout. 3358 3359 Any additional keyword arguments are passed on to ExecuteJavascript and 3360 can be used to select the tab where the DOM MutationObserver is created. 3361 3362 Raises: 3363 pyauto_errors.JSONInterfaceError if the automation call returns an error. 3364 pyauto_errors.JavascriptRuntimeError if the injected javascript 3365 MutationObserver returns an error. 3366 """ 3367 observer_id = self.AddDomMutationObserver('exists', xpath, attribute, 3368 expected_value, exec_js=exec_js, 3369 **kwargs) 3370 try: 3371 self.GetNextEvent(observer_id, timeout=timeout) 3372 except JSONInterfaceError: 3373 raise JSONInterfaceError(msg) 3374 3375 def GetNextEvent(self, observer_id=-1, blocking=True, timeout=-1): 3376 """Waits for an observed event to occur. 3377 3378 The returned event is removed from the Event Queue. If there is already a 3379 matching event in the queue it is returned immediately, otherwise the call 3380 blocks until a matching event occurs. If blocking is disabled and no 3381 matching event is in the queue this function will immediately return None. 3382 3383 Args: 3384 observer_id: The id of the observer to wait for, matches any event by 3385 default. 3386 blocking: If True waits until there is a matching event in the queue, 3387 if False and there is no event waiting in the queue returns None 3388 immediately. 3389 timeout: Time to wait for a matching event, defaults to the default 3390 automation timeout. 3391 3392 Returns: 3393 Event response dictionary, or None if blocking is disabled and there is no 3394 matching event in the queue. 3395 SAMPLE: 3396 { 'observer_id': 1, 3397 'name': 'login completed', 3398 'type': 'raised_event'} 3399 3400 Raises: 3401 pyauto_errors.JSONInterfaceError if the automation call returns an error. 3402 """ 3403 cmd_dict = { 3404 'command': 'GetNextEvent', 3405 'observer_id' : observer_id, 3406 'blocking' : blocking, 3407 } 3408 return self._GetResultFromJSONRequest(cmd_dict, windex=None, 3409 timeout=timeout) 3410 3411 def RemoveEventObserver(self, observer_id): 3412 """Removes an Event Observer from the AutomationEventQueue. 3413 3414 Expects a valid observer_id. 3415 3416 Args: 3417 observer_id: The id of the observer to remove. 3418 3419 Raises: 3420 pyauto_errors.JSONInterfaceError if the automation call returns an error. 3421 """ 3422 cmd_dict = { 3423 'command': 'RemoveEventObserver', 3424 'observer_id' : observer_id, 3425 } 3426 return self._GetResultFromJSONRequest(cmd_dict, windex=None) 3427 3428 def ClearEventQueue(self): 3429 """Removes all events currently in the AutomationEventQueue. 3430 3431 Raises: 3432 pyauto_errors.JSONInterfaceError if the automation call returns an error. 3433 """ 3434 cmd_dict = { 3435 'command': 'ClearEventQueue', 3436 } 3437 return self._GetResultFromJSONRequest(cmd_dict, windex=None) 3438 3439 def WaitUntilNavigationCompletes(self, tab_index=0, windex=0): 3440 """Wait until the specified tab is done navigating. 3441 3442 It is safe to call ExecuteJavascript() as soon as the call returns. If 3443 there is no outstanding navigation the call will return immediately. 3444 3445 Args: 3446 tab_index: index of the tab. 3447 windex: index of the window. 3448 3449 Raises: 3450 pyauto_errors.JSONInterfaceError if the automation call returns an error. 3451 """ 3452 cmd_dict = { 3453 'command': 'WaitUntilNavigationCompletes', 3454 'tab_index': tab_index, 3455 'windex': windex, 3456 } 3457 return self._GetResultFromJSONRequest(cmd_dict) 3458 3459 def ExecuteJavascript(self, js, tab_index=0, windex=0, frame_xpath=''): 3460 """Executes a script in the specified frame of a tab. 3461 3462 By default, execute the script in the top frame of the first tab in the 3463 first window. The invoked javascript function must send a result back via 3464 the domAutomationController.send function, or this function will never 3465 return. 3466 3467 Args: 3468 js: script to be executed. 3469 windex: index of the window. 3470 tab_index: index of the tab. 3471 frame_xpath: XPath of the frame to execute the script. Default is no 3472 frame. Example: '//frames[1]'. 3473 3474 Returns: 3475 a value that was sent back via the domAutomationController.send method 3476 3477 Raises: 3478 pyauto_errors.JSONInterfaceError if the automation call returns an error. 3479 """ 3480 cmd_dict = { 3481 'command': 'ExecuteJavascript', 3482 'javascript' : js, 3483 'windex' : windex, 3484 'tab_index' : tab_index, 3485 'frame_xpath' : frame_xpath, 3486 } 3487 result = self._GetResultFromJSONRequest(cmd_dict)['result'] 3488 # Wrap result in an array before deserializing because valid JSON has an 3489 # array or an object as the root. 3490 json_string = '[' + result + ']' 3491 return json.loads(json_string)[0] 3492 3493 def ExecuteJavascriptInRenderView(self, js, view, frame_xpath=''): 3494 """Executes a script in the specified frame of an render view. 3495 3496 The invoked javascript function must send a result back via the 3497 domAutomationController.send function, or this function will never return. 3498 3499 Args: 3500 js: script to be executed. 3501 view: A dictionary representing a unique id for the render view as 3502 returned for example by. 3503 self.GetBrowserInfo()['extension_views'][]['view']. 3504 Example: 3505 { 'render_process_id': 1, 3506 'render_view_id' : 2} 3507 3508 frame_xpath: XPath of the frame to execute the script. Default is no 3509 frame. Example: 3510 '//frames[1]' 3511 3512 Returns: 3513 a value that was sent back via the domAutomationController.send method 3514 3515 Raises: 3516 pyauto_errors.JSONInterfaceError if the automation call returns an error. 3517 """ 3518 cmd_dict = { 3519 'command': 'ExecuteJavascriptInRenderView', 3520 'javascript' : js, 3521 'view' : view, 3522 'frame_xpath' : frame_xpath, 3523 } 3524 result = self._GetResultFromJSONRequest(cmd_dict, windex=None)['result'] 3525 # Wrap result in an array before deserializing because valid JSON has an 3526 # array or an object as the root. 3527 json_string = '[' + result + ']' 3528 return json.loads(json_string)[0] 3529 3530 def ExecuteJavascriptInOOBEWebUI(self, js, frame_xpath=''): 3531 """Executes a script in the specified frame of the OOBE WebUI. 3532 3533 By default, execute the script in the top frame of the OOBE window. This 3534 also works for all OOBE pages, including the enterprise enrollment 3535 screen and login page. The invoked javascript function must send a result 3536 back via the domAutomationController.send function, or this function will 3537 never return. 3538 3539 Args: 3540 js: Script to be executed. 3541 frame_xpath: XPath of the frame to execute the script. Default is no 3542 frame. Example: '//frames[1]' 3543 3544 Returns: 3545 A value that was sent back via the domAutomationController.send method. 3546 3547 Raises: 3548 pyauto_errors.JSONInterfaceError if the automation call returns an error. 3549 """ 3550 cmd_dict = { 3551 'command': 'ExecuteJavascriptInOOBEWebUI', 3552 3553 'javascript': js, 3554 'frame_xpath': frame_xpath, 3555 } 3556 result = self._GetResultFromJSONRequest(cmd_dict, windex=None)['result'] 3557 # Wrap result in an array before deserializing because valid JSON has an 3558 # array or an object as the root. 3559 return json.loads('[' + result + ']')[0] 3560 3561 3562 def GetDOMValue(self, expr, tab_index=0, windex=0, frame_xpath=''): 3563 """Executes a Javascript expression and returns the value. 3564 3565 This is a wrapper for ExecuteJavascript, eliminating the need to 3566 explicitly call domAutomationController.send function. 3567 3568 Args: 3569 expr: expression value to be returned. 3570 tab_index: index of the tab. 3571 windex: index of the window. 3572 frame_xpath: XPath of the frame to execute the script. Default is no 3573 frame. Example: '//frames[1]'. 3574 3575 Returns: 3576 a string that was sent back via the domAutomationController.send method. 3577 """ 3578 js = 'window.domAutomationController.send(%s);' % expr 3579 return self.ExecuteJavascript(js, tab_index, windex, frame_xpath) 3580 3581 def CallJavascriptFunc(self, function, args=[], tab_index=0, windex=0): 3582 """Executes a script which calls a given javascript function. 3583 3584 The invoked javascript function must send a result back via the 3585 domAutomationController.send function, or this function will never return. 3586 3587 Defaults to first tab in first window. 3588 3589 Args: 3590 function: name of the function. 3591 args: list of all the arguments to pass into the called function. These 3592 should be able to be converted to a string using the |str| function. 3593 tab_index: index of the tab within the given window. 3594 windex: index of the window. 3595 3596 Returns: 3597 a string that was sent back via the domAutomationController.send method 3598 """ 3599 converted_args = map(lambda arg: json.dumps(arg), args) 3600 js = '%s(%s)' % (function, ', '.join(converted_args)) 3601 logging.debug('Executing javascript: %s', js) 3602 return self.ExecuteJavascript(js, tab_index, windex) 3603 3604 def HeapProfilerDump(self, process_type, reason, tab_index=0, windex=0): 3605 """Dumps a heap profile. It works only on Linux and ChromeOS. 3606 3607 We need an environment variable "HEAPPROFILE" set to a directory and a 3608 filename prefix, for example, "/tmp/prof". In a case of this example, 3609 heap profiles will be dumped into "/tmp/prof.(pid).0002.heap", 3610 "/tmp/prof.(pid).0003.heap", and so on. Nothing happens when this 3611 function is called without the env. 3612 3613 Also, this requires the --enable-memory-benchmarking command line flag. 3614 3615 Args: 3616 process_type: A string which is one of 'browser' or 'renderer'. 3617 reason: A string which describes the reason for dumping a heap profile. 3618 The reason will be included in the logged message. 3619 Examples: 3620 'To check memory leaking' 3621 'For PyAuto tests' 3622 tab_index: tab index to work on if 'process_type' == 'renderer'. 3623 Defaults to 0 (first tab). 3624 windex: window index to work on if 'process_type' == 'renderer'. 3625 Defaults to 0 (first window). 3626 3627 Raises: 3628 pyauto_errors.JSONInterfaceError if the automation call returns an error. 3629 """ 3630 assert process_type in ('browser', 'renderer') 3631 if self.IsLinux(): # IsLinux() also implies IsChromeOS(). 3632 js = """ 3633 if (!chrome.memoryBenchmarking || 3634 !chrome.memoryBenchmarking.isHeapProfilerRunning()) { 3635 domAutomationController.send('memory benchmarking disabled'); 3636 } else { 3637 chrome.memoryBenchmarking.heapProfilerDump("%s", "%s"); 3638 domAutomationController.send('success'); 3639 } 3640 """ % (process_type, reason.replace('"', '\\"')) 3641 result = self.ExecuteJavascript(js, tab_index, windex) 3642 if result != 'success': 3643 raise JSONInterfaceError('Heap profiler dump failed: ' + result) 3644 else: 3645 logging.warn('Heap-profiling is not supported in this OS.') 3646 3647 def GetNTPThumbnails(self): 3648 """Return a list of info about the sites in the NTP most visited section. 3649 SAMPLE: 3650 [{ u'title': u'Google', 3651 u'url': u'http://www.google.com'}, 3652 { 3653 u'title': u'Yahoo', 3654 u'url': u'http://www.yahoo.com'}] 3655 """ 3656 return self._GetNTPInfo()['most_visited'] 3657 3658 def GetNTPThumbnailIndex(self, thumbnail): 3659 """Returns the index of the given NTP thumbnail, or -1 if it is not shown. 3660 3661 Args: 3662 thumbnail: a thumbnail dict received from |GetNTPThumbnails| 3663 """ 3664 thumbnails = self.GetNTPThumbnails() 3665 for i in range(len(thumbnails)): 3666 if thumbnails[i]['url'] == thumbnail['url']: 3667 return i 3668 return -1 3669 3670 def RemoveNTPThumbnail(self, thumbnail): 3671 """Removes the NTP thumbnail and returns true on success. 3672 3673 Args: 3674 thumbnail: a thumbnail dict received from |GetNTPThumbnails| 3675 """ 3676 self._CheckNTPThumbnailShown(thumbnail) 3677 cmd_dict = { 3678 'command': 'RemoveNTPMostVisitedThumbnail', 3679 'url': thumbnail['url'] 3680 } 3681 self._GetResultFromJSONRequest(cmd_dict) 3682 3683 def RestoreAllNTPThumbnails(self): 3684 """Restores all the removed NTP thumbnails. 3685 Note: 3686 the default thumbnails may come back into the Most Visited sites 3687 section after doing this 3688 """ 3689 cmd_dict = { 3690 'command': 'RestoreAllNTPMostVisitedThumbnails' 3691 } 3692 self._GetResultFromJSONRequest(cmd_dict) 3693 3694 def GetNTPDefaultSites(self): 3695 """Returns a list of URLs for all the default NTP sites, regardless of 3696 whether they are showing or not. 3697 3698 These sites are the ones present in the NTP on a fresh install of Chrome. 3699 """ 3700 return self._GetNTPInfo()['default_sites'] 3701 3702 def RemoveNTPDefaultThumbnails(self): 3703 """Removes all thumbnails for default NTP sites, regardless of whether they 3704 are showing or not.""" 3705 cmd_dict = { 'command': 'RemoveNTPMostVisitedThumbnail' } 3706 for site in self.GetNTPDefaultSites(): 3707 cmd_dict['url'] = site 3708 self._GetResultFromJSONRequest(cmd_dict) 3709 3710 def GetNTPRecentlyClosed(self): 3711 """Return a list of info about the items in the NTP recently closed section. 3712 SAMPLE: 3713 [{ 3714 u'type': u'tab', 3715 u'url': u'http://www.bing.com', 3716 u'title': u'Bing', 3717 u'timestamp': 2139082.03912, # Seconds since epoch (Jan 1, 1970) 3718 u'direction': u'ltr'}, 3719 { 3720 u'type': u'window', 3721 u'timestamp': 2130821.90812, 3722 u'tabs': [ 3723 { 3724 u'type': u'tab', 3725 u'url': u'http://www.cnn.com', 3726 u'title': u'CNN', 3727 u'timestamp': 2129082.12098, 3728 u'direction': u'ltr'}]}, 3729 { 3730 u'type': u'tab', 3731 u'url': u'http://www.altavista.com', 3732 u'title': u'Altavista', 3733 u'timestamp': 21390820.12903, 3734 u'direction': u'rtl'}] 3735 """ 3736 return self._GetNTPInfo()['recently_closed'] 3737 3738 def GetNTPApps(self): 3739 """Retrieves information about the apps listed on the NTP. 3740 3741 In the sample data below, the "launch_type" will be one of the following 3742 strings: "pinned", "regular", "fullscreen", "window", or "unknown". 3743 3744 SAMPLE: 3745 [ 3746 { 3747 u'app_launch_index': 2, 3748 u'description': u'Web Store', 3749 u'icon_big': u'chrome://theme/IDR_APP_DEFAULT_ICON', 3750 u'icon_small': u'chrome://favicon/https://chrome.google.com/webstore', 3751 u'id': u'ahfgeienlihckogmohjhadlkjgocpleb', 3752 u'is_component_extension': True, 3753 u'is_disabled': False, 3754 u'launch_container': 2, 3755 u'launch_type': u'regular', 3756 u'launch_url': u'https://chrome.google.com/webstore', 3757 u'name': u'Chrome Web Store', 3758 u'options_url': u'', 3759 }, 3760 { 3761 u'app_launch_index': 1, 3762 u'description': u'A countdown app', 3763 u'icon_big': (u'chrome-extension://aeabikdlfbfeihglecobdkdflahfgcpd/' 3764 u'countdown128.png'), 3765 u'icon_small': (u'chrome://favicon/chrome-extension://' 3766 u'aeabikdlfbfeihglecobdkdflahfgcpd/' 3767 u'launchLocalPath.html'), 3768 u'id': u'aeabikdlfbfeihglecobdkdflahfgcpd', 3769 u'is_component_extension': False, 3770 u'is_disabled': False, 3771 u'launch_container': 2, 3772 u'launch_type': u'regular', 3773 u'launch_url': (u'chrome-extension://aeabikdlfbfeihglecobdkdflahfgcpd/' 3774 u'launchLocalPath.html'), 3775 u'name': u'Countdown', 3776 u'options_url': u'', 3777 } 3778 ] 3779 3780 Returns: 3781 A list of dictionaries in which each dictionary contains the information 3782 for a single app that appears in the "Apps" section of the NTP. 3783 """ 3784 return self._GetNTPInfo()['apps'] 3785 3786 def _GetNTPInfo(self): 3787 """Get info about the New Tab Page (NTP). 3788 3789 This does not retrieve the actual info displayed in a particular NTP; it 3790 retrieves the current state of internal data that would be used to display 3791 an NTP. This includes info about the apps, the most visited sites, 3792 the recently closed tabs and windows, and the default NTP sites. 3793 3794 SAMPLE: 3795 { 3796 u'apps': [ ... ], 3797 u'most_visited': [ ... ], 3798 u'recently_closed': [ ... ], 3799 u'default_sites': [ ... ] 3800 } 3801 3802 Returns: 3803 A dictionary containing all the NTP info. See details about the different 3804 sections in their respective methods: GetNTPApps(), GetNTPThumbnails(), 3805 GetNTPRecentlyClosed(), and GetNTPDefaultSites(). 3806 3807 Raises: 3808 pyauto_errors.JSONInterfaceError if the automation call returns an error. 3809 """ 3810 cmd_dict = { 3811 'command': 'GetNTPInfo', 3812 } 3813 return self._GetResultFromJSONRequest(cmd_dict) 3814 3815 def _CheckNTPThumbnailShown(self, thumbnail): 3816 if self.GetNTPThumbnailIndex(thumbnail) == -1: 3817 raise NTPThumbnailNotShownError() 3818 3819 def LaunchApp(self, app_id, windex=0): 3820 """Opens the New Tab Page and launches the specified app from it. 3821 3822 This method will not return until after the contents of a new tab for the 3823 launched app have stopped loading. 3824 3825 Args: 3826 app_id: The string ID of the app to launch. 3827 windex: The index of the browser window to work on. Defaults to 0 (the 3828 first window). 3829 3830 Raises: 3831 pyauto_errors.JSONInterfaceError if the automation call returns an error. 3832 """ 3833 self.AppendTab(GURL('chrome://newtab'), windex) # Also activates this tab. 3834 cmd_dict = { 3835 'command': 'LaunchApp', 3836 'id': app_id, 3837 } 3838 return self._GetResultFromJSONRequest(cmd_dict, windex=windex) 3839 3840 def SetAppLaunchType(self, app_id, launch_type, windex=0): 3841 """Sets the launch type for the specified app. 3842 3843 Args: 3844 app_id: The string ID of the app whose launch type should be set. 3845 launch_type: The string launch type, which must be one of the following: 3846 'pinned': Launch in a pinned tab. 3847 'regular': Launch in a regular tab. 3848 'fullscreen': Launch in a fullscreen tab. 3849 'window': Launch in a new browser window. 3850 windex: The index of the browser window to work on. Defaults to 0 (the 3851 first window). 3852 3853 Raises: 3854 pyauto_errors.JSONInterfaceError if the automation call returns an error. 3855 """ 3856 self.assertTrue(launch_type in ('pinned', 'regular', 'fullscreen', 3857 'window'), 3858 msg='Unexpected launch type value: "%s"' % launch_type) 3859 cmd_dict = { 3860 'command': 'SetAppLaunchType', 3861 'id': app_id, 3862 'launch_type': launch_type, 3863 } 3864 return self._GetResultFromJSONRequest(cmd_dict, windex=windex) 3865 3866 def GetV8HeapStats(self, tab_index=0, windex=0): 3867 """Returns statistics about the v8 heap in the renderer process for a tab. 3868 3869 Args: 3870 tab_index: The tab index, default is 0. 3871 window_index: The window index, default is 0. 3872 3873 Returns: 3874 A dictionary containing v8 heap statistics. Memory values are in bytes. 3875 Example: 3876 { 'renderer_id': 6223, 3877 'v8_memory_allocated': 21803776, 3878 'v8_memory_used': 10565392 } 3879 """ 3880 cmd_dict = { # Prepare command for the json interface. 3881 'command': 'GetV8HeapStats', 3882 'tab_index': tab_index, 3883 } 3884 return self._GetResultFromJSONRequest(cmd_dict, windex=windex) 3885 3886 def GetFPS(self, tab_index=0, windex=0): 3887 """Returns the current FPS associated with the renderer process for a tab. 3888 3889 FPS is the rendered frames per second. 3890 3891 Args: 3892 tab_index: The tab index, default is 0. 3893 window_index: The window index, default is 0. 3894 3895 Returns: 3896 A dictionary containing FPS info. 3897 Example: 3898 { 'renderer_id': 23567, 3899 'routing_id': 1, 3900 'fps': 29.404298782348633 } 3901 """ 3902 cmd_dict = { # Prepare command for the json interface. 3903 'command': 'GetFPS', 3904 'tab_index': tab_index, 3905 } 3906 return self._GetResultFromJSONRequest(cmd_dict, windex=windex) 3907 3908 def IsFullscreenForBrowser(self, windex=0): 3909 """Returns true if the window is currently fullscreen and was initially 3910 transitioned to fullscreen by a browser (vs tab) mode transition.""" 3911 return self._GetResultFromJSONRequest( 3912 { 'command': 'IsFullscreenForBrowser' }, 3913 windex=windex).get('result') 3914 3915 def IsFullscreenForTab(self, windex=0): 3916 """Returns true if fullscreen has been caused by a tab.""" 3917 return self._GetResultFromJSONRequest( 3918 { 'command': 'IsFullscreenForTab' }, 3919 windex=windex).get('result') 3920 3921 def IsMouseLocked(self, windex=0): 3922 """Returns true if the mouse is currently locked.""" 3923 return self._GetResultFromJSONRequest( 3924 { 'command': 'IsMouseLocked' }, 3925 windex=windex).get('result') 3926 3927 def IsMouseLockPermissionRequested(self, windex=0): 3928 """Returns true if the user is currently prompted to give permision for 3929 mouse lock.""" 3930 return self._GetResultFromJSONRequest( 3931 { 'command': 'IsMouseLockPermissionRequested' }, 3932 windex=windex).get('result') 3933 3934 def IsFullscreenPermissionRequested(self, windex=0): 3935 """Returns true if the user is currently prompted to give permision for 3936 fullscreen.""" 3937 return self._GetResultFromJSONRequest( 3938 { 'command': 'IsFullscreenPermissionRequested' }, 3939 windex=windex).get('result') 3940 3941 def IsFullscreenBubbleDisplayed(self, windex=0): 3942 """Returns true if the fullscreen and mouse lock bubble is currently 3943 displayed.""" 3944 return self._GetResultFromJSONRequest( 3945 { 'command': 'IsFullscreenBubbleDisplayed' }, 3946 windex=windex).get('result') 3947 3948 def IsFullscreenBubbleDisplayingButtons(self, windex=0): 3949 """Returns true if the fullscreen and mouse lock bubble is currently 3950 displayed and presenting buttons.""" 3951 return self._GetResultFromJSONRequest( 3952 { 'command': 'IsFullscreenBubbleDisplayingButtons' }, 3953 windex=windex).get('result') 3954 3955 def AcceptCurrentFullscreenOrMouseLockRequest(self, windex=0): 3956 """Activate the accept button on the fullscreen and mouse lock bubble.""" 3957 return self._GetResultFromJSONRequest( 3958 { 'command': 'AcceptCurrentFullscreenOrMouseLockRequest' }, 3959 windex=windex) 3960 3961 def DenyCurrentFullscreenOrMouseLockRequest(self, windex=0): 3962 """Activate the deny button on the fullscreen and mouse lock bubble.""" 3963 return self._GetResultFromJSONRequest( 3964 { 'command': 'DenyCurrentFullscreenOrMouseLockRequest' }, 3965 windex=windex) 3966 3967 def KillRendererProcess(self, pid): 3968 """Kills the given renderer process. 3969 3970 This will return only after the browser has received notice of the renderer 3971 close. 3972 3973 Args: 3974 pid: the process id of the renderer to kill 3975 3976 Raises: 3977 pyauto_errors.JSONInterfaceError if the automation call returns an error. 3978 """ 3979 cmd_dict = { 3980 'command': 'KillRendererProcess', 3981 'pid': pid 3982 } 3983 return self._GetResultFromJSONRequest(cmd_dict) 3984 3985 def NewWebDriver(self, port=0): 3986 """Returns a new remote WebDriver instance. 3987 3988 Args: 3989 port: The port to start WebDriver on; by default the service selects an 3990 open port. It is an error to request a port number and request a 3991 different port later. 3992 3993 Returns: 3994 selenium.webdriver.remote.webdriver.WebDriver instance 3995 """ 3996 from chrome_driver_factory import ChromeDriverFactory 3997 global _CHROME_DRIVER_FACTORY 3998 if _CHROME_DRIVER_FACTORY is None: 3999 _CHROME_DRIVER_FACTORY = ChromeDriverFactory(port=port) 4000 self.assertTrue(_CHROME_DRIVER_FACTORY.GetPort() == port or port == 0, 4001 msg='Requested a WebDriver on a specific port while already' 4002 ' running on a different port.') 4003 return _CHROME_DRIVER_FACTORY.NewChromeDriver(self) 4004 4005 def CreateNewAutomationProvider(self, channel_id): 4006 """Creates a new automation provider. 4007 4008 The provider will open a named channel in server mode. 4009 Args: 4010 channel_id: the channel_id to open the server channel with 4011 """ 4012 cmd_dict = { 4013 'command': 'CreateNewAutomationProvider', 4014 'channel_id': channel_id 4015 } 4016 self._GetResultFromJSONRequest(cmd_dict) 4017 4018 def OpenNewBrowserWindowWithNewProfile(self): 4019 """Creates a new multi-profiles user, and then opens and shows a new 4020 tabbed browser window with the new profile. 4021 4022 This is equivalent to 'Add new user' action with multi-profiles. 4023 4024 To account for crbug.com/108761 on Win XP, this call polls until the 4025 profile count increments by 1. 4026 4027 Raises: 4028 pyauto_errors.JSONInterfaceError if the automation call returns an error. 4029 """ 4030 num_profiles = len(self.GetMultiProfileInfo()['profiles']) 4031 cmd_dict = { # Prepare command for the json interface 4032 'command': 'OpenNewBrowserWindowWithNewProfile' 4033 } 4034 self._GetResultFromJSONRequest(cmd_dict, windex=None) 4035 # TODO(nirnimesh): Remove when crbug.com/108761 is fixed 4036 self.WaitUntil( 4037 lambda: len(self.GetMultiProfileInfo()['profiles']), 4038 expect_retval=(num_profiles + 1)) 4039 4040 def OpenProfileWindow(self, path, num_loads=1): 4041 """Open browser window for an existing profile. 4042 4043 This is equivalent to picking a profile from the multi-profile menu. 4044 4045 Multi-profile should be enabled and the requested profile should already 4046 exist. Creates a new window for the given profile. Use 4047 OpenNewBrowserWindowWithNewProfile() to create a new profile. 4048 4049 Args: 4050 path: profile path of the profile to be opened. 4051 num_loads: the number of loads to wait for, when a new browser window 4052 is created. Useful when restoring a window with many tabs. 4053 """ 4054 cmd_dict = { # Prepare command for the json interface 4055 'command': 'OpenProfileWindow', 4056 'path': path, 4057 'num_loads': num_loads, 4058 } 4059 return self._GetResultFromJSONRequest(cmd_dict, windex=None) 4060 4061 def GetMultiProfileInfo(self): 4062 """Fetch info about all multi-profile users. 4063 4064 Returns: 4065 A dictionary. 4066 Sample: 4067 { 4068 'enabled': True, 4069 'profiles': [{'name': 'First user', 4070 'path': '/tmp/.org.chromium.Chromium.Tyx17X/Default'}, 4071 {'name': 'User 1', 4072 'path': '/tmp/.org.chromium.Chromium.Tyx17X/profile_1'}], 4073 } 4074 4075 Profiles will be listed in the same order as visible in preferences. 4076 4077 Raises: 4078 pyauto_errors.JSONInterfaceError if the automation call returns an error. 4079 """ 4080 cmd_dict = { # Prepare command for the json interface 4081 'command': 'GetMultiProfileInfo' 4082 } 4083 return self._GetResultFromJSONRequest(cmd_dict, windex=None) 4084 4085 def RefreshPolicies(self): 4086 """Refreshes all the available policy providers. 4087 4088 Each policy provider will reload its policy source and push the updated 4089 policies. This call waits for the new policies to be applied; any policies 4090 installed before this call is issued are guaranteed to be ready after it 4091 returns. 4092 """ 4093 # TODO(craigdh): Determine the root cause of RefreshPolicies' flakiness. 4094 # See crosbug.com/30221 4095 timeout = PyUITest.ActionTimeoutChanger(self, 3 * 60 * 1000) 4096 cmd_dict = { 'command': 'RefreshPolicies' } 4097 self._GetResultFromJSONRequest(cmd_dict, windex=None) 4098 4099 def SubmitForm(self, form_id, tab_index=0, windex=0, frame_xpath=''): 4100 """Submits the given form ID, and returns after it has been submitted. 4101 4102 Args: 4103 form_id: the id attribute of the form to submit. 4104 4105 Returns: true on success. 4106 """ 4107 js = """ 4108 document.getElementById("%s").submit(); 4109 window.addEventListener("unload", function() { 4110 window.domAutomationController.send("done"); 4111 }); 4112 """ % form_id 4113 if self.ExecuteJavascript(js, tab_index, windex, frame_xpath) != 'done': 4114 return False 4115 # Wait until the form is submitted and the page completes loading. 4116 return self.WaitUntil( 4117 lambda: self.GetDOMValue('document.readyState', 4118 tab_index, windex, frame_xpath), 4119 expect_retval='complete') 4120 4121 def SimulateAsanMemoryBug(self): 4122 """Simulates a memory bug for Address Sanitizer to catch. 4123 4124 Address Sanitizer (if it was built it) will catch the bug and abort 4125 the process. 4126 This method returns immediately before it actually causes a crash. 4127 """ 4128 cmd_dict = { 'command': 'SimulateAsanMemoryBug' } 4129 self._GetResultFromJSONRequest(cmd_dict, windex=None) 4130 4131 ## ChromeOS section 4132 4133 def GetLoginInfo(self): 4134 """Returns information about login and screen locker state. 4135 4136 This includes things like whether a user is logged in, the username 4137 of the logged in user, and whether the screen is locked. 4138 4139 Returns: 4140 A dictionary. 4141 Sample: 4142 { u'is_guest': False, 4143 u'is_owner': True, 4144 u'email': u'example (at] gmail.com', 4145 u'user_image': 2, # non-negative int, 'profile', 'file' 4146 u'is_screen_locked': False, 4147 u'login_ui_type': 'nativeui', # or 'webui' 4148 u'is_logged_in': True} 4149 4150 Raises: 4151 pyauto_errors.JSONInterfaceError if the automation call returns an error. 4152 """ 4153 cmd_dict = { 'command': 'GetLoginInfo' } 4154 return self._GetResultFromJSONRequest(cmd_dict, windex=None) 4155 4156 def WaitForSessionManagerRestart(self, function): 4157 """Call a function and wait for the ChromeOS session_manager to restart. 4158 4159 Args: 4160 function: The function to call. 4161 """ 4162 assert callable(function) 4163 pgrep_process = subprocess.Popen(['pgrep', 'session_manager'], 4164 stdout=subprocess.PIPE) 4165 old_pid = pgrep_process.communicate()[0].strip() 4166 function() 4167 return self.WaitUntil(lambda: self._IsSessionManagerReady(old_pid)) 4168 4169 def _WaitForInodeChange(self, path, function): 4170 """Call a function and wait for the specified file path to change. 4171 4172 Args: 4173 path: The file path to check for changes. 4174 function: The function to call. 4175 """ 4176 assert callable(function) 4177 old_inode = os.stat(path).st_ino 4178 function() 4179 return self.WaitUntil(lambda: self._IsInodeNew(path, old_inode)) 4180 4181 def ShowCreateAccountUI(self): 4182 """Go to the account creation page. 4183 4184 This is the same as clicking the "Create Account" link on the 4185 ChromeOS login screen. Does not actually create a new account. 4186 Should be displaying the login screen to work. 4187 4188 Raises: 4189 pyauto_errors.JSONInterfaceError if the automation call returns an error. 4190 """ 4191 cmd_dict = { 'command': 'ShowCreateAccountUI' } 4192 # See note below under LoginAsGuest(). ShowCreateAccountUI() logs 4193 # the user in as guest in order to access the account creation page. 4194 assert self._WaitForInodeChange( 4195 self._named_channel_id, 4196 lambda: self._GetResultFromJSONRequest(cmd_dict, windex=None)), \ 4197 'Chrome did not reopen the testing channel after login as guest.' 4198 self.SetUp() 4199 4200 def SkipToLogin(self, skip_image_selection=True): 4201 """Skips OOBE to the login screen. 4202 4203 Assumes that we're at the beginning of OOBE. 4204 4205 Args: 4206 skip_image_selection: Boolean indicating whether the user image selection 4207 screen should also be skipped. 4208 4209 Raises: 4210 pyauto_errors.JSONInterfaceError if the automation call returns an error. 4211 """ 4212 cmd_dict = { 'command': 'SkipToLogin', 4213 'skip_image_selection': skip_image_selection } 4214 result = self._GetResultFromJSONRequest(cmd_dict, windex=None) 4215 assert result['next_screen'] == 'login', 'Unexpected wizard transition' 4216 4217 def GetOOBEScreenInfo(self): 4218 """Queries info about the current OOBE screen. 4219 4220 Returns: 4221 A dictionary with the following keys: 4222 4223 'screen_name': The title of the current OOBE screen as a string. 4224 4225 Raises: 4226 pyauto_errors.JSONInterfaceError if the automation call returns an error. 4227 """ 4228 cmd_dict = { 'command': 'GetOOBEScreenInfo' } 4229 return self._GetResultFromJSONRequest(cmd_dict, windex=None) 4230 4231 def AcceptOOBENetworkScreen(self): 4232 """Accepts OOBE network screen and advances to the next one. 4233 4234 Assumes that we're already at the OOBE network screen. 4235 4236 Returns: 4237 A dictionary with the following keys: 4238 4239 'next_screen': The title of the next OOBE screen as a string. 4240 4241 Raises: 4242 pyauto_errors.JSONInterfaceError if the automation call returns an error. 4243 """ 4244 cmd_dict = { 'command': 'AcceptOOBENetworkScreen' } 4245 return self._GetResultFromJSONRequest(cmd_dict, windex=None) 4246 4247 def AcceptOOBEEula(self, accepted, usage_stats_reporting=False): 4248 """Accepts OOBE EULA and advances to the next screen. 4249 4250 Assumes that we're already at the OOBE EULA screen. 4251 4252 Args: 4253 accepted: Boolean indicating whether the EULA should be accepted. 4254 usage_stats_reporting: Boolean indicating whether UMA should be enabled. 4255 4256 Returns: 4257 A dictionary with the following keys: 4258 4259 'next_screen': The title of the next OOBE screen as a string. 4260 4261 Raises: 4262 pyauto_errors.JSONInterfaceError if the automation call returns an error. 4263 """ 4264 cmd_dict = { 'command': 'AcceptOOBEEula', 4265 'accepted': accepted, 4266 'usage_stats_reporting': usage_stats_reporting } 4267 return self._GetResultFromJSONRequest(cmd_dict, windex=None) 4268 4269 def CancelOOBEUpdate(self): 4270 """Skips update on OOBE and advances to the next screen. 4271 4272 Returns: 4273 A dictionary with the following keys: 4274 4275 'next_screen': The title of the next OOBE screen as a string. 4276 4277 Raises: 4278 pyauto_errors.JSONInterfaceError if the automation call returns an error. 4279 """ 4280 cmd_dict = { 'command': 'CancelOOBEUpdate' } 4281 return self._GetResultFromJSONRequest(cmd_dict, windex=None) 4282 4283 def PickUserImage(self, image): 4284 """Chooses image for the newly created user. 4285 4286 Should be called immediately after login. 4287 4288 Args: 4289 image_type: type of user image to choose. Possible values: 4290 - "profile": Google profile image 4291 - non-negative int: one of the default images 4292 4293 Returns: 4294 A dictionary with the following keys: 4295 4296 'next_screen': The title of the next OOBE screen as a string. 4297 4298 Raises: 4299 pyauto_errors.JSONInterfaceError if the automation call returns an error. 4300 """ 4301 cmd_dict = { 'command': 'PickUserImage', 4302 'image': image } 4303 return self._GetResultFromJSONRequest(cmd_dict, windex=None) 4304 4305 def LoginAsGuest(self): 4306 """Login to chromeos as a guest user. 4307 4308 Waits until logged in. 4309 Should be displaying the login screen to work. 4310 4311 Raises: 4312 pyauto_errors.JSONInterfaceError if the automation call returns an error. 4313 """ 4314 cmd_dict = { 'command': 'LoginAsGuest' } 4315 # Currently, logging in as guest causes session_manager to 4316 # restart Chrome, which will close the testing channel. 4317 # We need to call SetUp() again to reconnect to the new channel. 4318 assert self._WaitForInodeChange( 4319 self._named_channel_id, 4320 lambda: self._GetResultFromJSONRequest(cmd_dict, windex=None)), \ 4321 'Chrome did not reopen the testing channel after login as guest.' 4322 self.SetUp() 4323 4324 def Login(self, username, password, timeout=120 * 1000): 4325 """Login to chromeos. 4326 4327 Waits until logged in and browser is ready. 4328 Should be displaying the login screen to work. 4329 4330 Note that in case of webui auth-extension-based login, gaia auth errors 4331 will not be noticed here, because the browser has no knowledge of it. In 4332 this case the GetNextEvent automation command will always time out. 4333 4334 Args: 4335 username: the username to log in as. 4336 password: the user's password. 4337 timeout: timeout in ms; defaults to two minutes. 4338 4339 Returns: 4340 An error string if an error occured. 4341 None otherwise. 4342 4343 Raises: 4344 pyauto_errors.JSONInterfaceError if the automation call returns an error. 4345 """ 4346 self._GetResultFromJSONRequest({'command': 'AddLoginEventObserver'}, 4347 windex=None) 4348 cmd_dict = { 4349 'command': 'SubmitLoginForm', 4350 'username': username, 4351 'password': password, 4352 } 4353 self._GetResultFromJSONRequest(cmd_dict, windex=None) 4354 self.AddDomEventObserver('loginfail', automation_id=4444) 4355 try: 4356 if self.GetNextEvent(timeout=timeout).get('name') == 'loginfail': 4357 raise JSONInterfaceError('Login denied by auth server.') 4358 except JSONInterfaceError as e: 4359 raise JSONInterfaceError('Login failed. Perhaps Chrome crashed, ' 4360 'failed to start, or the login flow is ' 4361 'broken? Error message: %s' % str(e)) 4362 4363 def Logout(self): 4364 """Log out from ChromeOS and wait for session_manager to come up. 4365 4366 This is equivalent to pressing the 'Sign out' button from the 4367 aura shell tray when logged in. 4368 4369 Should be logged in to work. Re-initializes the automation channel 4370 after logout. 4371 """ 4372 clear_profile_orig = self.get_clear_profile() 4373 self.set_clear_profile(False) 4374 assert self.GetLoginInfo()['is_logged_in'], \ 4375 'Trying to log out when already logged out.' 4376 def _SignOut(): 4377 cmd_dict = { 'command': 'SignOut' } 4378 self._GetResultFromJSONRequest(cmd_dict, windex=None) 4379 assert self.WaitForSessionManagerRestart(_SignOut), \ 4380 'Session manager did not restart after logout.' 4381 self.__SetUp() 4382 self.set_clear_profile(clear_profile_orig) 4383 4384 def LockScreen(self): 4385 """Locks the screen on chromeos. 4386 4387 Waits until screen is locked. 4388 Should be logged in and screen should not be locked to work. 4389 4390 Raises: 4391 pyauto_errors.JSONInterfaceError if the automation call returns an error. 4392 """ 4393 cmd_dict = { 'command': 'LockScreen' } 4394 self._GetResultFromJSONRequest(cmd_dict, windex=None) 4395 4396 def UnlockScreen(self, password): 4397 """Unlocks the screen on chromeos, authenticating the user's password first. 4398 4399 Waits until screen is unlocked. 4400 Screen locker should be active for this to work. 4401 4402 Returns: 4403 An error string if an error occured. 4404 None otherwise. 4405 4406 Raises: 4407 pyauto_errors.JSONInterfaceError if the automation call returns an error. 4408 """ 4409 cmd_dict = { 4410 'command': 'UnlockScreen', 4411 'password': password, 4412 } 4413 result = self._GetResultFromJSONRequest(cmd_dict, windex=None) 4414 return result.get('error_string') 4415 4416 def SignoutInScreenLocker(self): 4417 """Signs out of chromeos using the screen locker's "Sign out" feature. 4418 4419 Effectively the same as clicking the "Sign out" link on the screen locker. 4420 Screen should be locked for this to work. 4421 4422 Raises: 4423 pyauto_errors.JSONInterfaceError if the automation call returns an error. 4424 """ 4425 cmd_dict = { 'command': 'SignoutInScreenLocker' } 4426 assert self.WaitForSessionManagerRestart( 4427 lambda: self._GetResultFromJSONRequest(cmd_dict, windex=None)), \ 4428 'Session manager did not restart after logout.' 4429 self.__SetUp() 4430 4431 def GetBatteryInfo(self): 4432 """Get details about battery state. 4433 4434 Returns: 4435 A dictionary with the following keys: 4436 4437 'battery_is_present': bool 4438 'line_power_on': bool 4439 if 'battery_is_present': 4440 'battery_percentage': float (0 ~ 100) 4441 'battery_fully_charged': bool 4442 if 'line_power_on': 4443 'battery_time_to_full': int (seconds) 4444 else: 4445 'battery_time_to_empty': int (seconds) 4446 4447 If it is still calculating the time left, 'battery_time_to_full' 4448 and 'battery_time_to_empty' will be absent. 4449 4450 Use 'battery_fully_charged' instead of 'battery_percentage' 4451 or 'battery_time_to_full' to determine whether the battery 4452 is fully charged, since the percentage is only approximate. 4453 4454 Sample: 4455 { u'battery_is_present': True, 4456 u'line_power_on': False, 4457 u'battery_time_to_empty': 29617, 4458 u'battery_percentage': 100.0, 4459 u'battery_fully_charged': False } 4460 4461 Raises: 4462 pyauto_errors.JSONInterfaceError if the automation call returns an error. 4463 """ 4464 cmd_dict = { 'command': 'GetBatteryInfo' } 4465 return self._GetResultFromJSONRequest(cmd_dict, windex=None) 4466 4467 def GetPanelInfo(self): 4468 """Get details about open ChromeOS panels. 4469 4470 A panel is actually a type of browser window, so all of 4471 this information is also available using GetBrowserInfo(). 4472 4473 Returns: 4474 A dictionary. 4475 Sample: 4476 [{ 'incognito': False, 4477 'renderer_pid': 4820, 4478 'title': u'Downloads', 4479 'url': u'chrome://active-downloads/'}] 4480 4481 Raises: 4482 pyauto_errors.JSONInterfaceError if the automation call returns an error. 4483 """ 4484 panels = [] 4485 for browser in self.GetBrowserInfo()['windows']: 4486 if browser['type'] != 'panel': 4487 continue 4488 4489 panel = {} 4490 panels.append(panel) 4491 tab = browser['tabs'][0] 4492 panel['incognito'] = browser['incognito'] 4493 panel['renderer_pid'] = tab['renderer_pid'] 4494 panel['title'] = self.GetActiveTabTitle(browser['index']) 4495 panel['url'] = tab['url'] 4496 4497 return panels 4498 4499 def RestoreOnline(self): 4500 """Returns the device from offline mode if GoOffline was used.""" 4501 4502 assert PyUITest.IsChromeOS() 4503 4504 # Restores etherent connection 4505 stdout, stderr = self.RunSuperuserActionOnChromeOS('TeardownBackchannel') 4506 4507 if hasattr(self, 'bc_cellular_enabled') and self.bc_cellular_enabled: 4508 self.ToggleNetworkDevice('cellular', True) 4509 if hasattr(self, 'bc_wifi_enabled') and self.bc_wifi_enabled: 4510 self.ToggleNetworkDevice('wifi', True) 4511 4512 assert 'RuntimeError' not in stderr, stderr 4513 4514 def GoOffline(self): 4515 """Puts device in offline mode. 4516 4517 The device is put into offline mode by disabling all network interfaces 4518 but keeping the the wired ethernet interface up and faking shill/flimflam 4519 into thinking there is no ethernet interface by renaming the interface. 4520 This is so we can keep ssh connections over the wired connection alive. 4521 """ 4522 assert PyUITest.IsChromeOS() 4523 net_info = self.GetNetworkInfo() 4524 self.bc_wifi_enabled = net_info.get('wifi_enabled') 4525 self.bc_cellular_enabled = net_info.get('cellular_enabled') 4526 4527 if self.bc_cellular_enabled: 4528 self.ToggleNetworkDevice('cellular', False) 4529 if self.bc_wifi_enabled: 4530 self.ToggleNetworkDevice('wifi', False) 4531 4532 stdout, stderr = self.RunSuperuserActionOnChromeOS('SetupBackchannel') 4533 assert 'RuntimeError' not in stderr, stderr 4534 4535 def GetNetworkInfo(self): 4536 """Get details about ethernet, wifi, and cellular networks on chromeos. 4537 4538 Returns: 4539 A dictionary. 4540 Sample: 4541 { u'cellular_available': True, 4542 u'cellular_enabled': False, 4543 u'connected_ethernet': u'/service/ethernet_abcd', 4544 u'connected_wifi': u'/service/wifi_abcd_1234_managed_none', 4545 u'ethernet_available': True, 4546 u'ethernet_enabled': True, 4547 u'ethernet_networks': 4548 { u'/service/ethernet_abcd': 4549 { u'device_path': u'/device/abcdeth', 4550 u'name': u'', 4551 u'service_path': 4552 u'/profile/default/ethernet_abcd', 4553 u'status': u'Connected'} 4554 u'network_type': pyautolib.TYPE_ETHERNET }, 4555 u'remembered_wifi': 4556 { u'/service/wifi_abcd_1234_managed_none': 4557 { u'device_path': u'', 4558 u'encrypted': False, 4559 u'encryption': u'', 4560 u'name': u'WifiNetworkName1', 4561 u'status': u'Unknown', 4562 u'strength': 0}, 4563 u'network_type': pyautolib.TYPE_WIFI 4564 }, 4565 u'wifi_available': True, 4566 u'wifi_enabled': True, 4567 u'wifi_networks': 4568 { u'/service/wifi_abcd_1234_managed_none': 4569 { u'device_path': u'/device/abcdwifi', 4570 u'encrypted': False, 4571 u'encryption': u'', 4572 u'name': u'WifiNetworkName1', 4573 u'status': u'Connected', 4574 u'strength': 76}, 4575 u'/service/wifi_abcd_1234_managed_802_1x': 4576 { u'encrypted': True, 4577 u'encryption': u'8021X', 4578 u'name': u'WifiNetworkName2', 4579 u'status': u'Idle', 4580 u'strength': 79} 4581 u'network_type': pyautolib.TYPE_WIFI }} 4582 4583 4584 Raises: 4585 pyauto_errors.JSONInterfaceError if the automation call returns an error. 4586 """ 4587 cmd_dict = { 'command': 'GetNetworkInfo' } 4588 network_info = self._GetResultFromJSONRequest(cmd_dict, windex=None) 4589 4590 # Remembered networks do not have /service/ prepended to the service path 4591 # even though wifi_networks does. We want this prepended to allow for 4592 # consistency and easy string comparison with wifi_networks. 4593 remembered_wifi = {} 4594 network_info['remembered_wifi'] = dict([('/service/' + k, v) for k, v in 4595 network_info['remembered_wifi'].iteritems()]) 4596 4597 return network_info 4598 4599 def GetConnectedWifi(self): 4600 """Returns the SSID of the currently connected wifi network. 4601 4602 Returns: 4603 The SSID of the connected network or None if we're not connected. 4604 """ 4605 service_list = self.GetNetworkInfo() 4606 connected_service_path = service_list.get('connected_wifi') 4607 if 'wifi_networks' in service_list and \ 4608 connected_service_path in service_list['wifi_networks']: 4609 return service_list['wifi_networks'][connected_service_path]['name'] 4610 4611 def GetServicePath(self, ssid, encryption=None, timeout=30): 4612 """Waits until the SSID is observed and returns its service path. 4613 4614 Args: 4615 ssid: String defining the SSID we are searching for. 4616 encryption: Encryption type of the network; either None to return the 4617 first instance of network that matches the ssid, '' for 4618 an empty network, 'PSK', 'WEP' or '8021X'. 4619 timeout: Duration to wait for ssid to appear. 4620 4621 Returns: 4622 The service path or None if SSID does not exist after timeout period. 4623 """ 4624 def _GetServicePath(): 4625 service_list = self.GetNetworkInfo().get('wifi_networks', []) 4626 for service_path, service_obj in service_list.iteritems(): 4627 if not (isinstance(service_obj, dict) and 4628 'encryption' in service_obj and 4629 'name' in service_obj): 4630 continue 4631 4632 service_encr = 'PSK' if service_obj['encryption'] in ['WPA', 'RSN']\ 4633 else service_obj['encryption'] 4634 4635 if service_obj['name'] == ssid and \ 4636 (encryption == None or service_encr == encryption): 4637 return service_path 4638 self.NetworkScan() 4639 return None 4640 4641 service_path = self.WaitUntil(_GetServicePath, timeout=timeout, 4642 retry_sleep=1, return_retval=True) 4643 return service_path or None 4644 4645 def NetworkScan(self): 4646 """Causes ChromeOS to scan for available wifi networks. 4647 4648 Blocks until scanning is complete. 4649 4650 Returns: 4651 The new list of networks obtained from GetNetworkInfo(). 4652 4653 Raises: 4654 pyauto_errors.JSONInterfaceError if the automation call returns an error. 4655 """ 4656 cmd_dict = { 'command': 'NetworkScan' } 4657 self._GetResultFromJSONRequest(cmd_dict, windex=None) 4658 return self.GetNetworkInfo() 4659 4660 def ToggleNetworkDevice(self, device, enable): 4661 """Enable or disable a network device on ChromeOS. 4662 4663 Valid device names are ethernet, wifi, cellular. 4664 4665 Raises: 4666 pyauto_errors.JSONInterfaceError if the automation call returns an error. 4667 """ 4668 cmd_dict = { 4669 'command': 'ToggleNetworkDevice', 4670 'device': device, 4671 'enable': enable, 4672 } 4673 return self._GetResultFromJSONRequest(cmd_dict, windex=None) 4674 4675 PROXY_TYPE_DIRECT = 1 4676 PROXY_TYPE_MANUAL = 2 4677 PROXY_TYPE_PAC = 3 4678 4679 def WaitUntilWifiNetworkAvailable(self, ssid, timeout=60, is_hidden=False): 4680 """Waits until the given network is available. 4681 4682 Routers that are just turned on may take up to 1 minute upon turning them 4683 on to broadcast their SSID. 4684 4685 Args: 4686 ssid: SSID of the service we want to connect to. 4687 timeout: timeout (in seconds) 4688 4689 Raises: 4690 Exception if timeout duration has been hit before wifi router is seen. 4691 4692 Returns: 4693 True, when the wifi network is seen within the timout period. 4694 False, otherwise. 4695 """ 4696 def _GotWifiNetwork(): 4697 # Returns non-empty array if desired SSID is available. 4698 try: 4699 return [wifi for wifi in 4700 self.NetworkScan().get('wifi_networks', {}).values() 4701 if wifi.get('name') == ssid] 4702 except pyauto_errors.JSONInterfaceError: 4703 # Temporary fix until crosbug.com/14174 is fixed. 4704 # NetworkScan is only used in updating the list of networks so errors 4705 # thrown by it are not critical to the results of wifi tests that use 4706 # this method. 4707 return False 4708 4709 # The hidden AP's will always be on, thus we will assume it is ready to 4710 # connect to. 4711 if is_hidden: 4712 return bool(_GotWifiNetwork()) 4713 4714 return self.WaitUntil(_GotWifiNetwork, timeout=timeout, retry_sleep=1) 4715 4716 def ResetProxySettingsOnChromeOS(self): 4717 """Public wrapper around proxysettings teardown functions.""" 4718 self.SetSharedProxies(False) 4719 proxy_dict = { 4720 'mode': 'direct' 4721 } 4722 self.SetProxySettingOnChromeOS(proxy_dict) 4723 4724 def SetProxySettingOnChromeOS(self, proxy_config): 4725 """Set the proxy config of the current network. 4726 4727 Owner must be logged in for these to persist. 4728 If user is not logged in or is logged in as non-owner or guest, 4729 proxy settings do not persist across browser restarts or login/logout. 4730 4731 Args: 4732 proxy_config: A dictionary following the format described in 4733 prefs/proxy_config_dictionary.h. 4734 4735 Raises: 4736 pyauto_errors.JSONInterfaceError if the automation call returns an error. 4737 """ 4738 cmd_dict = { 4739 'command': 'SetProxySettings', 4740 'proxy_config': json.dumps(proxy_config) 4741 } 4742 return self._GetResultFromJSONRequest(cmd_dict, windex=None) 4743 4744 def SetSharedProxies(self, value): 4745 """Allows proxies on the shared networks. 4746 4747 Args: 4748 value: True/False to set and clear respectively. 4749 4750 Raises: 4751 pyauto_errors.JSONInterfaceError if the automation call returns an error. 4752 """ 4753 cmd_dict = { 4754 'command': 'SetSharedProxies', 4755 'value': value, 4756 } 4757 return self._GetResultFromJSONRequest(cmd_dict, windex=None) 4758 4759 def ForgetAllRememberedNetworks(self): 4760 """Forgets all networks that the device has marked as remembered.""" 4761 for service in self.GetNetworkInfo()['remembered_wifi']: 4762 self.ForgetWifiNetwork(service) 4763 4764 def ForgetWifiNetwork(self, service_path): 4765 """Forget a remembered network by its service path. 4766 4767 This function is equivalent to clicking the 'Forget Network' button in the 4768 chrome://settings/internet page. This function does not indicate whether 4769 or not forget succeeded or failed. It is up to the caller to call 4770 GetNetworkInfo to check the updated remembered_wifi list to verify the 4771 service has been removed. 4772 4773 Args: 4774 service_path: Flimflam path that defines the remembered network. 4775 4776 Raises: 4777 pyauto_errors.JSONInterfaceError if the automation call returns an error. 4778 """ 4779 # Usually the service_path is prepended with '/service/', such as when the 4780 # service path is retrieved from GetNetworkInfo. ForgetWifiNetwork works 4781 # only for service paths where this has already been stripped. 4782 service_path = service_path.split('/service/')[-1] 4783 cmd_dict = { 4784 'command': 'ForgetWifiNetwork', 4785 'service_path': service_path, 4786 } 4787 self._GetResultFromJSONRequest(cmd_dict, windex=None, timeout=50000) 4788 4789 def ConnectToCellularNetwork(self): 4790 """Connects to the available cellular network. 4791 4792 Blocks until connection succeeds or fails. 4793 4794 Returns: 4795 An error string if an error occured. 4796 None otherwise. 4797 4798 Raises: 4799 pyauto_errors.JSONInterfaceError if the automation call returns an error. 4800 """ 4801 # Every device should only have one cellular network present, so we can 4802 # scan for it. 4803 cellular_networks = self.NetworkScan().get('cellular_networks', {}).keys() 4804 self.assertTrue(cellular_networks, 'Could not find cellular service.') 4805 service_path = cellular_networks[0] 4806 4807 cmd_dict = { 4808 'command': 'ConnectToCellularNetwork', 4809 'service_path': service_path, 4810 } 4811 result = self._GetResultFromJSONRequest( 4812 cmd_dict, windex=None, timeout=50000) 4813 return result.get('error_string') 4814 4815 def DisconnectFromCellularNetwork(self): 4816 """Disconnect from the connected cellular network. 4817 4818 Blocks until disconnect is complete. 4819 4820 Raises: 4821 pyauto_errors.JSONInterfaceError if the automation call returns an error. 4822 """ 4823 cmd_dict = { 4824 'command': 'DisconnectFromCellularNetwork', 4825 } 4826 self._GetResultFromJSONRequest(cmd_dict, windex=None) 4827 4828 def ConnectToWifiNetwork(self, service_path, password='', shared=True): 4829 """Connect to a wifi network by its service path. 4830 4831 Blocks until connection succeeds or fails. 4832 4833 Args: 4834 service_path: Flimflam path that defines the wifi network. 4835 password: Passphrase for connecting to the wifi network. 4836 shared: Boolean value specifying whether the network should be shared. 4837 4838 Returns: 4839 An error string if an error occured. 4840 None otherwise. 4841 4842 Raises: 4843 pyauto_errors.JSONInterfaceError if the automation call returns an error. 4844 """ 4845 cmd_dict = { 4846 'command': 'ConnectToWifiNetwork', 4847 'service_path': service_path, 4848 'password': password, 4849 'shared': shared, 4850 } 4851 result = self._GetResultFromJSONRequest( 4852 cmd_dict, windex=None, timeout=50000) 4853 return result.get('error_string') 4854 4855 def ConnectToHiddenWifiNetwork(self, ssid, security, password='', 4856 shared=True, save_credentials=False): 4857 """Connect to a wifi network by its service path. 4858 4859 Blocks until connection succeeds or fails. 4860 4861 Args: 4862 ssid: The SSID of the network to connect to. 4863 security: The network's security type. One of: 'SECURITY_NONE', 4864 'SECURITY_WEP', 'SECURITY_WPA', 'SECURITY_RSN', 'SECURITY_8021X' 4865 password: Passphrase for connecting to the wifi network. 4866 shared: Boolean value specifying whether the network should be shared. 4867 save_credentials: Boolean value specifying whether 802.1x credentials are 4868 saved. 4869 4870 Returns: 4871 An error string if an error occured. 4872 None otherwise. 4873 4874 Raises: 4875 pyauto_errors.JSONInterfaceError if the automation call returns an error. 4876 """ 4877 assert security in ('SECURITY_NONE', 'SECURITY_WEP', 'SECURITY_WPA', 4878 'SECURITY_RSN', 'SECURITY_8021X') 4879 cmd_dict = { 4880 'command': 'ConnectToHiddenWifiNetwork', 4881 'ssid': ssid, 4882 'security': security, 4883 'password': password, 4884 'shared': shared, 4885 'save_credentials': save_credentials, 4886 } 4887 result = self._GetResultFromJSONRequest( 4888 cmd_dict, windex=None, timeout=50000) 4889 return result.get('error_string') 4890 4891 def DisconnectFromWifiNetwork(self): 4892 """Disconnect from the connected wifi network. 4893 4894 Blocks until disconnect is complete. 4895 4896 Raises: 4897 pyauto_errors.JSONInterfaceError if the automation call returns an error. 4898 """ 4899 cmd_dict = { 4900 'command': 'DisconnectFromWifiNetwork', 4901 } 4902 self._GetResultFromJSONRequest(cmd_dict, windex=None) 4903 4904 def AddPrivateNetwork(self, 4905 hostname, 4906 service_name, 4907 provider_type, 4908 username, 4909 password, 4910 cert_id='', 4911 key=''): 4912 """Add and connect to a private network. 4913 4914 Blocks until connection succeeds or fails. This is equivalent to 4915 'Add Private Network' in the network menu UI. 4916 4917 Args: 4918 hostname: Server hostname for the private network. 4919 service_name: Service name that defines the private network. Do not 4920 add multiple services with the same name. 4921 provider_type: Types are L2TP_IPSEC_PSK and L2TP_IPSEC_USER_CERT. 4922 Provider type OPEN_VPN is not yet supported. 4923 Type names returned by GetPrivateNetworkInfo will 4924 also work. 4925 username: Username for connecting to the virtual network. 4926 password: Passphrase for connecting to the virtual network. 4927 cert_id: Certificate id for a L2TP_IPSEC_USER_CERT network. 4928 key: Pre-shared key for a L2TP_IPSEC_PSK network. 4929 4930 Returns: 4931 An error string if an error occured. 4932 None otherwise. 4933 4934 Raises: 4935 pyauto_errors.JSONInterfaceError if the automation call returns an error. 4936 """ 4937 cmd_dict = { 4938 'command': 'AddPrivateNetwork', 4939 'hostname': hostname, 4940 'service_name': service_name, 4941 'provider_type': provider_type, 4942 'username': username, 4943 'password': password, 4944 'cert_id': cert_id, 4945 'key': key, 4946 } 4947 result = self._GetResultFromJSONRequest( 4948 cmd_dict, windex=None, timeout=50000) 4949 return result.get('error_string') 4950 4951 def GetPrivateNetworkInfo(self): 4952 """Get details about private networks on chromeos. 4953 4954 Returns: 4955 A dictionary including information about all remembered virtual networks 4956 as well as the currently connected virtual network, if any. 4957 Sample: 4958 { u'connected': u'/service/vpn_123_45_67_89_test_vpn'} 4959 u'/service/vpn_123_45_67_89_test_vpn': 4960 { u'username': u'vpn_user', 4961 u'name': u'test_vpn', 4962 u'hostname': u'123.45.67.89', 4963 u'key': u'abcde', 4964 u'cert_id': u'', 4965 u'password': u'zyxw123', 4966 u'provider_type': u'L2TP_IPSEC_PSK'}, 4967 u'/service/vpn_111_11_11_11_test_vpn2': 4968 { u'username': u'testerman', 4969 u'name': u'test_vpn2', 4970 u'hostname': u'111.11.11.11', 4971 u'key': u'fghijklm', 4972 u'cert_id': u'', 4973 u'password': u'789mnop', 4974 u'provider_type': u'L2TP_IPSEC_PSK'}, 4975 4976 Raises: 4977 pyauto_errors.JSONInterfaceError if the automation call returns an error. 4978 """ 4979 cmd_dict = { 'command': 'GetPrivateNetworkInfo' } 4980 return self._GetResultFromJSONRequest(cmd_dict, windex=None) 4981 4982 def ConnectToPrivateNetwork(self, service_path): 4983 """Connect to a remembered private network by its service path. 4984 4985 Blocks until connection succeeds or fails. The network must have been 4986 previously added with all necessary connection details. 4987 4988 Args: 4989 service_path: Service name that defines the private network. 4990 4991 Returns: 4992 An error string if an error occured. 4993 None otherwise. 4994 4995 Raises: 4996 pyauto_errors.JSONInterfaceError if the automation call returns an error. 4997 """ 4998 cmd_dict = { 4999 'command': 'ConnectToPrivateNetwork', 5000 'service_path': service_path, 5001 } 5002 result = self._GetResultFromJSONRequest( 5003 cmd_dict, windex=None, timeout=50000) 5004 return result.get('error_string') 5005 5006 def DisconnectFromPrivateNetwork(self): 5007 """Disconnect from the active private network. 5008 5009 Expects a private network to be active. 5010 5011 Raises: 5012 pyauto_errors.JSONInterfaceError if the automation call returns an error. 5013 """ 5014 cmd_dict = { 5015 'command': 'DisconnectFromPrivateNetwork', 5016 } 5017 return self._GetResultFromJSONRequest(cmd_dict, windex=None) 5018 5019 def EnableSpokenFeedback(self, enabled): 5020 """Enables or disables spoken feedback accessibility mode. 5021 5022 Args: 5023 enabled: Boolean value indicating the desired state of spoken feedback. 5024 5025 Raises: 5026 pyauto_errors.JSONInterfaceError if the automation call returns an error. 5027 """ 5028 cmd_dict = { 5029 'command': 'EnableSpokenFeedback', 5030 'enabled': enabled, 5031 } 5032 return self._GetResultFromJSONRequest(cmd_dict, windex=None) 5033 5034 def IsSpokenFeedbackEnabled(self): 5035 """Check whether spoken feedback accessibility mode is enabled. 5036 5037 Returns: 5038 True if spoken feedback is enabled, False otherwise. 5039 5040 Raises: 5041 pyauto_errors.JSONInterfaceError if the automation call returns an error. 5042 """ 5043 cmd_dict = { 'command': 'IsSpokenFeedbackEnabled', } 5044 result = self._GetResultFromJSONRequest(cmd_dict, windex=None) 5045 return result.get('spoken_feedback') 5046 5047 def GetTimeInfo(self, windex=0): 5048 """Gets info about the ChromeOS status bar clock. 5049 5050 Set the 24-hour clock by using: 5051 self.SetPrefs('settings.clock.use_24hour_clock', True) 5052 5053 Returns: 5054 a dictionary. 5055 Sample: 5056 {u'display_date': u'Tuesday, July 26, 2011', 5057 u'display_time': u'4:30', 5058 u'timezone': u'America/Los_Angeles'} 5059 5060 Raises: 5061 pyauto_errors.JSONInterfaceError if the automation call returns an error. 5062 """ 5063 cmd_dict = { 'command': 'GetTimeInfo' } 5064 if self.GetLoginInfo()['is_logged_in']: 5065 return self._GetResultFromJSONRequest(cmd_dict, windex=windex) 5066 else: 5067 return self._GetResultFromJSONRequest(cmd_dict, windex=None) 5068 5069 def SetTimezone(self, timezone): 5070 """Sets the timezone on ChromeOS. A user must be logged in. 5071 5072 The timezone is the relative path to the timezone file in 5073 /usr/share/zoneinfo. For example, /usr/share/zoneinfo/America/Los_Angeles is 5074 'America/Los_Angeles'. For a list of valid timezones see 5075 'chrome/browser/chromeos/system/timezone_settings.cc'. 5076 5077 This method does not return indication of success or failure. 5078 If the timezone is it falls back to a valid timezone. 5079 5080 Raises: 5081 pyauto_errors.JSONInterfaceError if the automation call returns an error. 5082 """ 5083 cmd_dict = { 5084 'command': 'SetTimezone', 5085 'timezone': timezone, 5086 } 5087 self._GetResultFromJSONRequest(cmd_dict, windex=None) 5088 5089 def UpdateCheck(self): 5090 """Checks for a ChromeOS update. Blocks until finished updating. 5091 5092 Raises: 5093 pyauto_errors.JSONInterfaceError if the automation call returns an error. 5094 """ 5095 cmd_dict = { 'command': 'UpdateCheck' } 5096 self._GetResultFromJSONRequest(cmd_dict, windex=None) 5097 5098 def GetVolumeInfo(self): 5099 """Gets the volume and whether the device is muted. 5100 5101 Returns: 5102 a tuple. 5103 Sample: 5104 (47.763456790123456, False) 5105 5106 Raises: 5107 pyauto_errors.JSONInterfaceError if the automation call returns an error. 5108 """ 5109 cmd_dict = { 'command': 'GetVolumeInfo' } 5110 return self._GetResultFromJSONRequest(cmd_dict, windex=None) 5111 5112 def SetVolume(self, volume): 5113 """Sets the volume on ChromeOS. Only valid if not muted. 5114 5115 Args: 5116 volume: The desired volume level as a percent from 0 to 100. 5117 5118 Raises: 5119 pyauto_errors.JSONInterfaceError if the automation call returns an error. 5120 """ 5121 assert volume >= 0 and volume <= 100 5122 cmd_dict = { 5123 'command': 'SetVolume', 5124 'volume': float(volume), 5125 } 5126 return self._GetResultFromJSONRequest(cmd_dict, windex=None) 5127 5128 def SetMute(self, mute): 5129 """Sets whether ChromeOS is muted or not. 5130 5131 Args: 5132 mute: True to mute, False to unmute. 5133 5134 Raises: 5135 pyauto_errors.JSONInterfaceError if the automation call returns an error. 5136 """ 5137 cmd_dict = { 'command': 'SetMute' } 5138 cmd_dict = { 5139 'command': 'SetMute', 5140 'mute': mute, 5141 } 5142 return self._GetResultFromJSONRequest(cmd_dict, windex=None) 5143 5144 # HTML Terminal 5145 5146 def OpenCrosh(self): 5147 """Open crosh. 5148 5149 Equivalent to pressing Ctrl-Alt-t. 5150 Opens in the last active (non-incognito) window. 5151 5152 Waits long enough for crosh to load, but does not wait for the crosh 5153 prompt. Use WaitForHtermText() for that. 5154 """ 5155 cmd_dict = { 'command': 'OpenCrosh' } 5156 self._GetResultFromJSONRequest(cmd_dict, windex=None) 5157 5158 def WaitForHtermText(self, text, msg=None, tab_index=0, windex=0): 5159 """Waits for the given text in a hterm tab. 5160 5161 Can be used to wait for the crosh> prompt or ssh prompt. 5162 5163 This does not poll. It uses dom mutation observers to wait 5164 for the given text to show up. 5165 5166 Args: 5167 text: the text to wait for. Can be a regex. 5168 msg: the failure message to emit if the text could not be found. 5169 tab_index: the tab for the hterm tab. Default: 0. 5170 windex: the window index for the hterm tab. Default: 0. 5171 """ 5172 self.WaitForDomNode( 5173 xpath='//*[contains(text(), "%s")]' % text, frame_xpath='//iframe', 5174 msg=msg, tab_index=tab_index, windex=windex) 5175 5176 def GetHtermRowsText(self, start, end, tab_index=0, windex=0): 5177 """Fetch rows from a html terminal tab. 5178 5179 Works for both crosh and ssh tab. 5180 Uses term_.getRowsText(start, end) javascript call. 5181 5182 Args: 5183 start: start line number (0-based). 5184 end: the end line (one beyond the line of interest). 5185 tab_index: the tab for the hterm tab. Default: 0. 5186 windex: the window index for the hterm tab. Default: 0. 5187 """ 5188 return self.ExecuteJavascript( 5189 'domAutomationController.send(term_.getRowsText(%d, %d))' % ( 5190 start, end), 5191 tab_index=tab_index, windex=windex) 5192 5193 def SendKeysToHterm(self, text, tab_index=0, windex=0): 5194 """Send keys to a html terminal tab. 5195 5196 Works for both crosh and ssh tab. 5197 Uses term_.onVTKeystroke(str) javascript call. 5198 5199 Args: 5200 text: the text to send. 5201 tab_index: the tab for the hterm tab. Default: 0. 5202 windex: the window index for the hterm tab. Default: 0. 5203 """ 5204 return self.ExecuteJavascript( 5205 'term_.onVTKeystroke("%s");' 5206 'domAutomationController.send("done")' % text, 5207 tab_index=tab_index, windex=windex) 5208 5209 5210 def GetMemoryStatsChromeOS(self, duration): 5211 """Identifies and returns different kinds of current memory usage stats. 5212 5213 This function samples values each second for |duration| seconds, then 5214 outputs the min, max, and ending values for each measurement type. 5215 5216 Args: 5217 duration: The number of seconds to sample data before outputting the 5218 minimum, maximum, and ending values for each measurement type. 5219 5220 Returns: 5221 A dictionary containing memory usage information. Each measurement type 5222 is associated with the min, max, and ending values from among all 5223 sampled values. Values are specified in KB. 5224 { 5225 'gem_obj': { # GPU memory usage. 5226 'min': ..., 5227 'max': ..., 5228 'end': ..., 5229 }, 5230 'gtt': { ... }, # GPU memory usage (graphics translation table). 5231 'mem_free': { ... }, # CPU free memory. 5232 'mem_available': { ... }, # CPU available memory. 5233 'mem_shared': { ... }, # CPU shared memory. 5234 'mem_cached': { ... }, # CPU cached memory. 5235 'mem_anon': { ... }, # CPU anon memory (active + inactive). 5236 'mem_file': { ... }, # CPU file memory (active + inactive). 5237 'mem_slab': { ... }, # CPU slab memory. 5238 'browser_priv': { ... }, # Chrome browser private memory. 5239 'browser_shared': { ... }, # Chrome browser shared memory. 5240 'gpu_priv': { ... }, # Chrome GPU private memory. 5241 'gpu_shared': { ... }, # Chrome GPU shared memory. 5242 'renderer_priv': { ... }, # Total private memory of all renderers. 5243 'renderer_shared': { ... }, # Total shared memory of all renderers. 5244 } 5245 """ 5246 logging.debug('Sampling memory information for %d seconds...' % duration) 5247 stats = {} 5248 5249 for _ in xrange(duration): 5250 # GPU memory. 5251 gem_obj_path = '/sys/kernel/debug/dri/0/i915_gem_objects' 5252 if os.path.exists(gem_obj_path): 5253 p = subprocess.Popen('grep bytes %s' % gem_obj_path, 5254 stdout=subprocess.PIPE, shell=True) 5255 stdout = p.communicate()[0] 5256 5257 gem_obj = re.search( 5258 '\d+ objects, (\d+) bytes\n', stdout).group(1) 5259 if 'gem_obj' not in stats: 5260 stats['gem_obj'] = [] 5261 stats['gem_obj'].append(int(gem_obj) / 1024.0) 5262 5263 gtt_path = '/sys/kernel/debug/dri/0/i915_gem_gtt' 5264 if os.path.exists(gtt_path): 5265 p = subprocess.Popen('grep bytes %s' % gtt_path, 5266 stdout=subprocess.PIPE, shell=True) 5267 stdout = p.communicate()[0] 5268 5269 gtt = re.search( 5270 'Total [\d]+ objects, ([\d]+) bytes', stdout).group(1) 5271 if 'gtt' not in stats: 5272 stats['gtt'] = [] 5273 stats['gtt'].append(int(gtt) / 1024.0) 5274 5275 # CPU memory. 5276 stdout = '' 5277 with open('/proc/meminfo') as f: 5278 stdout = f.read() 5279 mem_free = re.search('MemFree:\s*([\d]+) kB', stdout).group(1) 5280 5281 if 'mem_free' not in stats: 5282 stats['mem_free'] = [] 5283 stats['mem_free'].append(int(mem_free)) 5284 5285 mem_dirty = re.search('Dirty:\s*([\d]+) kB', stdout).group(1) 5286 mem_active_file = re.search( 5287 'Active\(file\):\s*([\d]+) kB', stdout).group(1) 5288 mem_inactive_file = re.search( 5289 'Inactive\(file\):\s*([\d]+) kB', stdout).group(1) 5290 5291 with open('/proc/sys/vm/min_filelist_kbytes') as f: 5292 mem_min_file = f.read() 5293 5294 # Available memory = 5295 # MemFree + ActiveFile + InactiveFile - DirtyMem - MinFileMem 5296 if 'mem_available' not in stats: 5297 stats['mem_available'] = [] 5298 stats['mem_available'].append( 5299 int(mem_free) + int(mem_active_file) + int(mem_inactive_file) - 5300 int(mem_dirty) - int(mem_min_file)) 5301 5302 mem_shared = re.search('Shmem:\s*([\d]+) kB', stdout).group(1) 5303 if 'mem_shared' not in stats: 5304 stats['mem_shared'] = [] 5305 stats['mem_shared'].append(int(mem_shared)) 5306 5307 mem_cached = re.search('Cached:\s*([\d]+) kB', stdout).group(1) 5308 if 'mem_cached' not in stats: 5309 stats['mem_cached'] = [] 5310 stats['mem_cached'].append(int(mem_cached)) 5311 5312 mem_anon_active = re.search('Active\(anon\):\s*([\d]+) kB', 5313 stdout).group(1) 5314 mem_anon_inactive = re.search('Inactive\(anon\):\s*([\d]+) kB', 5315 stdout).group(1) 5316 if 'mem_anon' not in stats: 5317 stats['mem_anon'] = [] 5318 stats['mem_anon'].append(int(mem_anon_active) + int(mem_anon_inactive)) 5319 5320 mem_file_active = re.search('Active\(file\):\s*([\d]+) kB', 5321 stdout).group(1) 5322 mem_file_inactive = re.search('Inactive\(file\):\s*([\d]+) kB', 5323 stdout).group(1) 5324 if 'mem_file' not in stats: 5325 stats['mem_file'] = [] 5326 stats['mem_file'].append(int(mem_file_active) + int(mem_file_inactive)) 5327 5328 mem_slab = re.search('Slab:\s*([\d]+) kB', stdout).group(1) 5329 if 'mem_slab' not in stats: 5330 stats['mem_slab'] = [] 5331 stats['mem_slab'].append(int(mem_slab)) 5332 5333 # Chrome process memory. 5334 pinfo = self.GetProcessInfo()['browsers'][0]['processes'] 5335 total_renderer_priv = 0 5336 total_renderer_shared = 0 5337 for process in pinfo: 5338 mem_priv = process['working_set_mem']['priv'] 5339 mem_shared = process['working_set_mem']['shared'] 5340 if process['child_process_type'] == 'Browser': 5341 if 'browser_priv' not in stats: 5342 stats['browser_priv'] = [] 5343 stats['browser_priv'].append(int(mem_priv)) 5344 if 'browser_shared' not in stats: 5345 stats['browser_shared'] = [] 5346 stats['browser_shared'].append(int(mem_shared)) 5347 elif process['child_process_type'] == 'GPU': 5348 if 'gpu_priv' not in stats: 5349 stats['gpu_priv'] = [] 5350 stats['gpu_priv'].append(int(mem_priv)) 5351 if 'gpu_shared' not in stats: 5352 stats['gpu_shared'] = [] 5353 stats['gpu_shared'].append(int(mem_shared)) 5354 elif process['child_process_type'] == 'Tab': 5355 # Sum the memory of all renderer processes. 5356 total_renderer_priv += int(mem_priv) 5357 total_renderer_shared += int(mem_shared) 5358 if 'renderer_priv' not in stats: 5359 stats['renderer_priv'] = [] 5360 stats['renderer_priv'].append(int(total_renderer_priv)) 5361 if 'renderer_shared' not in stats: 5362 stats['renderer_shared'] = [] 5363 stats['renderer_shared'].append(int(total_renderer_shared)) 5364 5365 time.sleep(1) 5366 5367 # Compute min, max, and ending values to return. 5368 result = {} 5369 for measurement_type in stats: 5370 values = stats[measurement_type] 5371 result[measurement_type] = { 5372 'min': min(values), 5373 'max': max(values), 5374 'end': values[-1], 5375 } 5376 5377 return result 5378 5379 ## ChromeOS section -- end 5380 5381 5382 class ExtraBrowser(PyUITest): 5383 """Launches a new browser with some extra flags. 5384 5385 The new browser is launched with its own fresh profile. 5386 This class does not apply to ChromeOS. 5387 """ 5388 def __init__(self, chrome_flags=[], methodName='runTest', **kwargs): 5389 """Accepts extra chrome flags for launching a new browser instance. 5390 5391 Args: 5392 chrome_flags: list of extra flags when launching a new browser. 5393 """ 5394 assert not PyUITest.IsChromeOS(), \ 5395 'This function cannot be used to launch a new browser in ChromeOS.' 5396 PyUITest.__init__(self, methodName=methodName, **kwargs) 5397 self._chrome_flags = chrome_flags 5398 PyUITest.setUp(self) 5399 5400 def __del__(self): 5401 """Tears down the browser and then calls super class's destructor""" 5402 PyUITest.tearDown(self) 5403 PyUITest.__del__(self) 5404 5405 def ExtraChromeFlags(self): 5406 """Prepares the browser to launch with specified Chrome flags.""" 5407 return PyUITest.ExtraChromeFlags(self) + self._chrome_flags 5408 5409 5410 class _RemoteProxy(): 5411 """Class for PyAuto remote method calls. 5412 5413 Use this class along with RemoteHost.testRemoteHost to establish a PyAuto 5414 connection with another machine and make remote PyAuto calls. The RemoteProxy 5415 mimics a PyAuto object, so all json-style PyAuto calls can be made on it. 5416 5417 The remote host acts as a dumb executor that receives method call requests, 5418 executes them, and sends all of the results back to the RemoteProxy, including 5419 the return value, thrown exceptions, and console output. 5420 5421 The remote host should be running the same version of PyAuto as the proxy. 5422 A mismatch could lead to undefined behavior. 5423 5424 Example usage: 5425 class MyTest(pyauto.PyUITest): 5426 def testRemoteExample(self): 5427 remote = pyauto._RemoteProxy(('127.0.0.1', 7410)) 5428 remote.NavigateToURL('http://www.google.com') 5429 title = remote.GetActiveTabTitle() 5430 self.assertEqual(title, 'Google') 5431 """ 5432 class RemoteException(Exception): 5433 pass 5434 5435 def __init__(self, host): 5436 self.RemoteConnect(host) 5437 5438 def RemoteConnect(self, host): 5439 begin = time.time() 5440 while time.time() - begin < 50: 5441 self._socket = socket.socket() 5442 if not self._socket.connect_ex(host): 5443 break 5444 time.sleep(0.25) 5445 else: 5446 # Make one last attempt, but raise a socket error on failure. 5447 self._socket = socket.socket() 5448 self._socket.connect(host) 5449 5450 def RemoteDisconnect(self): 5451 if self._socket: 5452 self._socket.shutdown(socket.SHUT_RDWR) 5453 self._socket.close() 5454 self._socket = None 5455 5456 def CreateTarget(self, target): 5457 """Registers the methods and creates a remote instance of a target. 5458 5459 Any RPC calls will then be made on the remote target instance. Note that the 5460 remote instance will be a brand new instance and will have none of the state 5461 of the local instance. The target's class should have a constructor that 5462 takes no arguments. 5463 """ 5464 self._Call('CreateTarget', target.__class__) 5465 self._RegisterClassMethods(target) 5466 5467 def _RegisterClassMethods(self, remote_class): 5468 # Make remote-call versions of all remote_class methods. 5469 for method_name, _ in inspect.getmembers(remote_class, inspect.ismethod): 5470 # Ignore private methods and duplicates. 5471 if method_name[0] in string.letters and \ 5472 getattr(self, method_name, None) is None: 5473 setattr(self, method_name, functools.partial(self._Call, method_name)) 5474 5475 def _Call(self, method_name, *args, **kwargs): 5476 # Send request. 5477 request = pickle.dumps((method_name, args, kwargs)) 5478 if self._socket.send(request) != len(request): 5479 raise self.RemoteException('Error sending remote method call request.') 5480 5481 # Receive response. 5482 response = self._socket.recv(4096) 5483 if not response: 5484 raise self.RemoteException('Client disconnected during method call.') 5485 result, stdout, stderr, exception = pickle.loads(response) 5486 5487 # Print any output the client captured, throw any exceptions, and return. 5488 sys.stdout.write(stdout) 5489 sys.stderr.write(stderr) 5490 if exception: 5491 raise self.RemoteException('%s raised by remote client: %s' % 5492 (exception[0], exception[1])) 5493 return result 5494 5495 5496 class PyUITestSuite(pyautolib.PyUITestSuiteBase, unittest.TestSuite): 5497 """Base TestSuite for PyAuto UI tests.""" 5498 5499 def __init__(self, args): 5500 pyautolib.PyUITestSuiteBase.__init__(self, args) 5501 5502 # Figure out path to chromium binaries 5503 browser_dir = os.path.normpath(os.path.dirname(pyautolib.__file__)) 5504 logging.debug('Loading pyauto libs from %s', browser_dir) 5505 self.InitializeWithPath(pyautolib.FilePath(browser_dir)) 5506 os.environ['PATH'] = browser_dir + os.pathsep + os.environ['PATH'] 5507 5508 unittest.TestSuite.__init__(self) 5509 cr_source_root = os.path.normpath(os.path.join( 5510 os.path.dirname(__file__), os.pardir, os.pardir, os.pardir)) 5511 self.SetCrSourceRoot(pyautolib.FilePath(cr_source_root)) 5512 5513 # Start http server, if needed. 5514 global _OPTIONS 5515 if _OPTIONS and not _OPTIONS.no_http_server: 5516 self._StartHTTPServer() 5517 if _OPTIONS and _OPTIONS.remote_host: 5518 self._ConnectToRemoteHosts(_OPTIONS.remote_host.split(',')) 5519 5520 def __del__(self): 5521 # python unittest module is setup such that the suite gets deleted before 5522 # the test cases, which is odd because our test cases depend on 5523 # initializtions like exitmanager, autorelease pool provided by the 5524 # suite. Forcibly delete the test cases before the suite. 5525 del self._tests 5526 pyautolib.PyUITestSuiteBase.__del__(self) 5527 5528 global _HTTP_SERVER 5529 if _HTTP_SERVER: 5530 self._StopHTTPServer() 5531 5532 global _CHROME_DRIVER_FACTORY 5533 if _CHROME_DRIVER_FACTORY is not None: 5534 _CHROME_DRIVER_FACTORY.Stop() 5535 5536 def _StartHTTPServer(self): 5537 """Start a local file server hosting data files over http://""" 5538 global _HTTP_SERVER 5539 assert not _HTTP_SERVER, 'HTTP Server already started' 5540 http_data_dir = _OPTIONS.http_data_dir 5541 http_server = pyautolib.SpawnedTestServer( 5542 pyautolib.SpawnedTestServer.TYPE_HTTP, 5543 '127.0.0.1', 5544 pyautolib.FilePath(http_data_dir)) 5545 assert http_server.Start(), 'Could not start http server' 5546 _HTTP_SERVER = http_server 5547 logging.debug('Started http server at "%s".', http_data_dir) 5548 5549 def _StopHTTPServer(self): 5550 """Stop the local http server.""" 5551 global _HTTP_SERVER 5552 assert _HTTP_SERVER, 'HTTP Server not yet started' 5553 assert _HTTP_SERVER.Stop(), 'Could not stop http server' 5554 _HTTP_SERVER = None 5555 logging.debug('Stopped http server.') 5556 5557 def _ConnectToRemoteHosts(self, addresses): 5558 """Connect to remote PyAuto instances using a RemoteProxy. 5559 5560 The RemoteHost instances must already be running.""" 5561 global _REMOTE_PROXY 5562 assert not _REMOTE_PROXY, 'Already connected to a remote host.' 5563 _REMOTE_PROXY = [] 5564 for address in addresses: 5565 if address == 'localhost' or address == '127.0.0.1': 5566 self._StartLocalRemoteHost() 5567 _REMOTE_PROXY.append(_RemoteProxy((address, 7410))) 5568 5569 def _StartLocalRemoteHost(self): 5570 """Start a remote PyAuto instance on the local machine.""" 5571 # Add the path to our main class to the RemoteHost's 5572 # environment, so it can load that class at runtime. 5573 import __main__ 5574 main_path = os.path.dirname(__main__.__file__) 5575 env = os.environ 5576 if env.get('PYTHONPATH', None): 5577 env['PYTHONPATH'] += ':' + main_path 5578 else: 5579 env['PYTHONPATH'] = main_path 5580 5581 # Run it! 5582 subprocess.Popen([sys.executable, os.path.join(os.path.dirname(__file__), 5583 'remote_host.py')], env=env) 5584 5585 5586 class _GTestTextTestResult(unittest._TextTestResult): 5587 """A test result class that can print formatted text results to a stream. 5588 5589 Results printed in conformance with gtest output format, like: 5590 [ RUN ] autofill.AutofillTest.testAutofillInvalid: "test desc." 5591 [ OK ] autofill.AutofillTest.testAutofillInvalid 5592 [ RUN ] autofill.AutofillTest.testFillProfile: "test desc." 5593 [ OK ] autofill.AutofillTest.testFillProfile 5594 [ RUN ] autofill.AutofillTest.testFillProfileCrazyCharacters: "Test." 5595 [ OK ] autofill.AutofillTest.testFillProfileCrazyCharacters 5596 """ 5597 def __init__(self, stream, descriptions, verbosity): 5598 unittest._TextTestResult.__init__(self, stream, descriptions, verbosity) 5599 5600 def _GetTestURI(self, test): 5601 if sys.version_info[:2] <= (2, 4): 5602 return '%s.%s' % (unittest._strclass(test.__class__), 5603 test._TestCase__testMethodName) 5604 return '%s.%s.%s' % (test.__class__.__module__, 5605 test.__class__.__name__, 5606 test._testMethodName) 5607 5608 def getDescription(self, test): 5609 return '%s: "%s"' % (self._GetTestURI(test), test.shortDescription()) 5610 5611 def startTest(self, test): 5612 unittest.TestResult.startTest(self, test) 5613 self.stream.writeln('[ RUN ] %s' % self.getDescription(test)) 5614 5615 def addSuccess(self, test): 5616 unittest.TestResult.addSuccess(self, test) 5617 self.stream.writeln('[ OK ] %s' % self._GetTestURI(test)) 5618 5619 def addError(self, test, err): 5620 unittest.TestResult.addError(self, test, err) 5621 self.stream.writeln('[ ERROR ] %s' % self._GetTestURI(test)) 5622 5623 def addFailure(self, test, err): 5624 unittest.TestResult.addFailure(self, test, err) 5625 self.stream.writeln('[ FAILED ] %s' % self._GetTestURI(test)) 5626 5627 5628 class PyAutoTextTestRunner(unittest.TextTestRunner): 5629 """Test Runner for PyAuto tests that displays results in textual format. 5630 5631 Results are displayed in conformance with gtest output. 5632 """ 5633 def __init__(self, verbosity=1): 5634 unittest.TextTestRunner.__init__(self, 5635 stream=sys.stderr, 5636 verbosity=verbosity) 5637 5638 def _makeResult(self): 5639 return _GTestTextTestResult(self.stream, self.descriptions, self.verbosity) 5640 5641 5642 # Implementation inspired from unittest.main() 5643 class Main(object): 5644 """Main program for running PyAuto tests.""" 5645 5646 _options, _args = None, None 5647 _tests_filename = 'PYAUTO_TESTS' 5648 _platform_map = { 5649 'win32': 'win', 5650 'darwin': 'mac', 5651 'linux2': 'linux', 5652 'linux3': 'linux', 5653 'chromeos': 'chromeos', 5654 } 5655 5656 def __init__(self): 5657 self._ParseArgs() 5658 self._Run() 5659 5660 def _ParseArgs(self): 5661 """Parse command line args.""" 5662 parser = optparse.OptionParser() 5663 parser.add_option( 5664 '', '--channel-id', type='string', default='', 5665 help='Name of channel id, if using named interface.') 5666 parser.add_option( 5667 '', '--chrome-flags', type='string', default='', 5668 help='Flags passed to Chrome. This is in addition to the usual flags ' 5669 'like suppressing first-run dialogs, enabling automation. ' 5670 'See chrome/common/chrome_switches.cc for the list of flags ' 5671 'chrome understands.') 5672 parser.add_option( 5673 '', '--http-data-dir', type='string', 5674 default=os.path.join('chrome', 'test', 'data'), 5675 help='Relative path from which http server should serve files.') 5676 parser.add_option( 5677 '-L', '--list-tests', action='store_true', default=False, 5678 help='List all tests, and exit.') 5679 parser.add_option( 5680 '--shard', 5681 help='Specify sharding params. Example: 1/3 implies split the list of ' 5682 'tests into 3 groups of which this is the 1st.') 5683 parser.add_option( 5684 '', '--log-file', type='string', default=None, 5685 help='Provide a path to a file to which the logger will log') 5686 parser.add_option( 5687 '', '--no-http-server', action='store_true', default=False, 5688 help='Do not start an http server to serve files in data dir.') 5689 parser.add_option( 5690 '', '--remote-host', type='string', default=None, 5691 help='Connect to remote hosts for remote automation. If "localhost" ' 5692 '"127.0.0.1" is specified, a remote host will be launched ' 5693 'automatically on the local machine.') 5694 parser.add_option( 5695 '', '--repeat', type='int', default=1, 5696 help='Number of times to repeat the tests. Useful to determine ' 5697 'flakiness. Defaults to 1.') 5698 parser.add_option( 5699 '-S', '--suite', type='string', default='FULL', 5700 help='Name of the suite to load. Defaults to "FULL".') 5701 parser.add_option( 5702 '-v', '--verbose', action='store_true', default=False, 5703 help='Make PyAuto verbose.') 5704 parser.add_option( 5705 '-D', '--wait-for-debugger', action='store_true', default=False, 5706 help='Block PyAuto on startup for attaching debugger.') 5707 5708 self._options, self._args = parser.parse_args() 5709 global _OPTIONS 5710 _OPTIONS = self._options # Export options so other classes can access. 5711 5712 # Set up logging. All log messages will be prepended with a timestamp. 5713 format = '%(asctime)s %(levelname)-8s %(message)s' 5714 5715 level = logging.INFO 5716 if self._options.verbose: 5717 level=logging.DEBUG 5718 5719 logging.basicConfig(level=level, format=format, 5720 filename=self._options.log_file) 5721 5722 def TestsDir(self): 5723 """Returns the path to dir containing tests. 5724 5725 This is typically the dir containing the tests description file. 5726 This method should be overridden by derived class to point to other dirs 5727 if needed. 5728 """ 5729 return os.path.dirname(__file__) 5730 5731 @staticmethod 5732 def _ImportTestsFromName(name): 5733 """Get a list of all test names from the given string. 5734 5735 Args: 5736 name: dot-separated string for a module, a test case or a test method. 5737 Examples: omnibox (a module) 5738 omnibox.OmniboxTest (a test case) 5739 omnibox.OmniboxTest.testA (a test method) 5740 5741 Returns: 5742 [omnibox.OmniboxTest.testA, omnibox.OmniboxTest.testB, ...] 5743 """ 5744 def _GetTestsFromTestCase(class_obj): 5745 """Return all test method names from given class object.""" 5746 return [class_obj.__name__ + '.' + x for x in dir(class_obj) if 5747 x.startswith('test')] 5748 5749 def _GetTestsFromModule(module): 5750 """Return all test method names from the given module object.""" 5751 tests = [] 5752 for name in dir(module): 5753 obj = getattr(module, name) 5754 if (isinstance(obj, (type, types.ClassType)) and 5755 issubclass(obj, PyUITest) and obj != PyUITest): 5756 tests.extend([module.__name__ + '.' + x for x in 5757 _GetTestsFromTestCase(obj)]) 5758 return tests 5759 5760 module = None 5761 # Locate the module 5762 parts = name.split('.') 5763 parts_copy = parts[:] 5764 while parts_copy: 5765 try: 5766 module = __import__('.'.join(parts_copy)) 5767 break 5768 except ImportError: 5769 del parts_copy[-1] 5770 if not parts_copy: raise 5771 # We have the module. Pick the exact test method or class asked for. 5772 parts = parts[1:] 5773 obj = module 5774 for part in parts: 5775 obj = getattr(obj, part) 5776 5777 if type(obj) == types.ModuleType: 5778 return _GetTestsFromModule(obj) 5779 elif (isinstance(obj, (type, types.ClassType)) and 5780 issubclass(obj, PyUITest) and obj != PyUITest): 5781 return [module.__name__ + '.' + x for x in _GetTestsFromTestCase(obj)] 5782 elif type(obj) == types.UnboundMethodType: 5783 return [name] 5784 else: 5785 logging.warn('No tests in "%s"', name) 5786 return [] 5787 5788 def _HasTestCases(self, module_string): 5789 """Determines if we have any PyUITest test case classes in the module 5790 identified by |module_string|.""" 5791 module = __import__(module_string) 5792 for name in dir(module): 5793 obj = getattr(module, name) 5794 if (isinstance(obj, (type, types.ClassType)) and 5795 issubclass(obj, PyUITest)): 5796 return True 5797 return False 5798 5799 def _ExpandTestNames(self, args): 5800 """Returns a list of tests loaded from the given args. 5801 5802 The given args can be either a module (ex: module1) or a testcase 5803 (ex: module2.MyTestCase) or a test (ex: module1.MyTestCase.testX) 5804 or a suite name (ex: @FULL). If empty, the tests in the already imported 5805 modules are loaded. 5806 5807 Args: 5808 args: [module1, module2, module3.testcase, module4.testcase.testX] 5809 These modules or test cases or tests should be importable. 5810 Suites can be specified by prefixing @. Example: @FULL 5811 5812 Returns: 5813 a list of expanded test names. Example: 5814 [ 5815 'module1.TestCase1.testA', 5816 'module1.TestCase1.testB', 5817 'module2.TestCase2.testX', 5818 'module3.testcase.testY', 5819 'module4.testcase.testX' 5820 ] 5821 """ 5822 5823 def _TestsFromDescriptionFile(suite): 5824 pyauto_tests_file = os.path.join(self.TestsDir(), self._tests_filename) 5825 if suite: 5826 logging.debug("Reading %s (@%s)", pyauto_tests_file, suite) 5827 else: 5828 logging.debug("Reading %s", pyauto_tests_file) 5829 if not os.path.exists(pyauto_tests_file): 5830 logging.warn("%s missing. Cannot load tests.", pyauto_tests_file) 5831 return [] 5832 else: 5833 return self._ExpandTestNamesFrom(pyauto_tests_file, suite) 5834 5835 if not args: # Load tests ourselves 5836 if self._HasTestCases('__main__'): # we are running a test script 5837 module_name = os.path.splitext(os.path.basename(sys.argv[0]))[0] 5838 args.append(module_name) # run the test cases found in it 5839 else: # run tests from the test description file 5840 args = _TestsFromDescriptionFile(self._options.suite) 5841 else: # Check args with @ prefix for suites 5842 out_args = [] 5843 for arg in args: 5844 if arg.startswith('@'): 5845 suite = arg[1:] 5846 out_args += _TestsFromDescriptionFile(suite) 5847 else: 5848 out_args.append(arg) 5849 args = out_args 5850 return args 5851 5852 def _ExpandTestNamesFrom(self, filename, suite): 5853 """Load test names from the given file. 5854 5855 Args: 5856 filename: the file to read the tests from 5857 suite: the name of the suite to load from |filename|. 5858 5859 Returns: 5860 a list of test names 5861 [module.testcase.testX, module.testcase.testY, ..] 5862 """ 5863 suites = PyUITest.EvalDataFrom(filename) 5864 platform = sys.platform 5865 if PyUITest.IsChromeOS(): # check if it's chromeos 5866 platform = 'chromeos' 5867 assert platform in self._platform_map, '%s unsupported' % platform 5868 def _NamesInSuite(suite_name): 5869 logging.debug('Expanding suite %s', suite_name) 5870 platforms = suites.get(suite_name) 5871 names = platforms.get('all', []) + \ 5872 platforms.get(self._platform_map[platform], []) 5873 ret = [] 5874 # Recursively include suites if any. Suites begin with @. 5875 for name in names: 5876 if name.startswith('@'): # Include another suite 5877 ret.extend(_NamesInSuite(name[1:])) 5878 else: 5879 ret.append(name) 5880 return ret 5881 5882 assert suite in suites, '%s: No such suite in %s' % (suite, filename) 5883 all_names = _NamesInSuite(suite) 5884 args = [] 5885 excluded = [] 5886 # Find all excluded tests. Excluded tests begin with '-'. 5887 for name in all_names: 5888 if name.startswith('-'): # Exclude 5889 excluded.extend(self._ImportTestsFromName(name[1:])) 5890 else: 5891 args.extend(self._ImportTestsFromName(name)) 5892 for name in excluded: 5893 if name in args: 5894 args.remove(name) 5895 else: 5896 logging.warn('Cannot exclude %s. Not included. Ignoring', name) 5897 if excluded: 5898 logging.debug('Excluded %d test(s): %s', len(excluded), excluded) 5899 return args 5900 5901 def _Run(self): 5902 """Run the tests.""" 5903 if self._options.wait_for_debugger: 5904 raw_input('Attach debugger to process %s and hit <enter> ' % os.getpid()) 5905 5906 suite_args = [sys.argv[0]] 5907 chrome_flags = self._options.chrome_flags 5908 # Set CHROME_HEADLESS. It enables crash reporter on posix. 5909 os.environ['CHROME_HEADLESS'] = '1' 5910 os.environ['EXTRA_CHROME_FLAGS'] = chrome_flags 5911 test_names = self._ExpandTestNames(self._args) 5912 5913 # Shard, if requested (--shard). 5914 if self._options.shard: 5915 matched = re.match('(\d+)/(\d+)', self._options.shard) 5916 if not matched: 5917 print >>sys.stderr, 'Invalid sharding params: %s' % self._options.shard 5918 sys.exit(1) 5919 shard_index = int(matched.group(1)) - 1 5920 num_shards = int(matched.group(2)) 5921 if shard_index < 0 or shard_index >= num_shards: 5922 print >>sys.stderr, 'Invalid sharding params: %s' % self._options.shard 5923 sys.exit(1) 5924 test_names = pyauto_utils.Shard(test_names, shard_index, num_shards) 5925 5926 test_names *= self._options.repeat 5927 logging.debug("Loading %d tests from %s", len(test_names), test_names) 5928 if self._options.list_tests: # List tests and exit 5929 for name in test_names: 5930 print name 5931 sys.exit(0) 5932 pyauto_suite = PyUITestSuite(suite_args) 5933 loaded_tests = unittest.defaultTestLoader.loadTestsFromNames(test_names) 5934 pyauto_suite.addTests(loaded_tests) 5935 verbosity = 1 5936 if self._options.verbose: 5937 verbosity = 2 5938 result = PyAutoTextTestRunner(verbosity=verbosity).run(pyauto_suite) 5939 del loaded_tests # Need to destroy test cases before the suite 5940 del pyauto_suite 5941 successful = result.wasSuccessful() 5942 if not successful: 5943 pyauto_tests_file = os.path.join(self.TestsDir(), self._tests_filename) 5944 print >>sys.stderr, 'Tests can be disabled by editing %s. ' \ 5945 'Ref: %s' % (pyauto_tests_file, _PYAUTO_DOC_URL) 5946 sys.exit(not successful) 5947 5948 5949 if __name__ == '__main__': 5950 Main() 5951