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