Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(919)

Side by Side Diff: chrome/test/pyautolib/pyauto.py

Issue 222873002: Remove pyauto tests. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/
Patch Set: sync Created 6 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « chrome/test/pyautolib/prefs_info.py ('k') | chrome/test/pyautolib/pyauto_errors.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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()
OLDNEW
« no previous file with comments | « chrome/test/pyautolib/prefs_info.py ('k') | chrome/test/pyautolib/pyauto_errors.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698