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