1# Copyright 2013 The Chromium Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5import copy 6import ctypes 7import email 8import logging 9import os 10import shutil 11import smtplib 12import subprocess 13import sys 14import types 15 16import pyauto_functional 17import pyauto 18import pyauto_utils 19import pyauto_errors 20 21 22"""Commonly used functions for PyAuto tests.""" 23 24def CrashBrowser(test): 25 """Crashes the browser by navigating to special URL.""" 26 try: 27 test.NavigateToURL('chrome://inducebrowsercrashforrealz') 28 except pyauto_errors.JSONInterfaceError: 29 pass 30 else: 31 raise RuntimeError( 32 'Browser did not crash at chrome://inducebrowsercrashforrealz') 33 34 35def CopyFileFromDataDirToDownloadDir(test, file_path): 36 """Copy a file from data directory to downloads directory. 37 38 Args: 39 test: derived from pyauto.PyUITest - base class for UI test cases. 40 path: path of the file relative to the data directory 41 """ 42 data_file = os.path.join(test.DataDir(), file_path) 43 download_dir = test.GetDownloadDirectory().value() 44 shutil.copy(data_file, download_dir) 45 46 47def CopyFileFromContentDataDirToDownloadDir(test, file_path): 48 """Copy a file from content data directory to downloads directory. 49 50 Args: 51 test: derived from pyauto.PyUITest - base class for UI test cases. 52 path: path of the file relative to the data directory 53 """ 54 data_file = os.path.join(test.ContentDataDir(), file_path) 55 download_dir = test.GetDownloadDirectory().value() 56 shutil.copy(data_file, download_dir) 57 58 59def RemoveDownloadedTestFile(test, file_name): 60 """Delete a file from the downloads directory. 61 62 Arg: 63 test: derived from pyauto.PyUITest - base class for UI test cases 64 file_name: name of file to remove 65 """ 66 downloaded_pkg = os.path.join(test.GetDownloadDirectory().value(), 67 file_name) 68 pyauto_utils.RemovePath(downloaded_pkg) 69 pyauto_utils.RemovePath(downloaded_pkg + '.crdownload') 70 71 72def GoogleAccountsLogin(test, username, password, 73 tab_index=0, windex=0, url=None): 74 """Log into Google Accounts. 75 76 Attempts to login to Google by entering the username/password into the google 77 login page and click submit button. 78 79 Args: 80 test: derived from pyauto.PyUITest - base class for UI test cases. 81 username: users login input. 82 password: users login password input. 83 tab_index: The tab index, default is 0. 84 windex: The window index, default is 0. 85 url: an alternative url for login page, if None, original one will be used. 86 """ 87 url = url or 'https://accounts.google.com/' 88 test.NavigateToURL(url, windex, tab_index) 89 email_id = 'document.getElementById("Email").value = "%s"; ' \ 90 'window.domAutomationController.send("done")' % username 91 password = 'document.getElementById("Passwd").value = "%s"; ' \ 92 'window.domAutomationController.send("done")' % password 93 test.ExecuteJavascript(email_id, tab_index, windex) 94 test.ExecuteJavascript(password, tab_index, windex) 95 test.assertTrue(test.SubmitForm('gaia_loginform', tab_index, windex)) 96 97 98def VerifyGoogleAccountCredsFilled(test, username, password, tab_index=0, 99 windex=0): 100 """Verify stored/saved user and password values to the values in the field. 101 102 Args: 103 test: derived from pyauto.PyUITest - base class for UI test cases. 104 username: user log in input. 105 password: user log in password input. 106 tab_index: The tab index, default is 0. 107 windex: The window index, default is 0. 108 """ 109 email_value = test.GetDOMValue('document.getElementById("Email").value', 110 tab_index, windex) 111 passwd_value = test.GetDOMValue('document.getElementById("Passwd").value', 112 tab_index, windex) 113 test.assertEqual(email_value, username) 114 # Not using assertEqual because if it fails it would end up dumping the 115 # password (which is supposed to be private) 116 test.assertTrue(passwd_value == password) 117 118 119def Shell2(cmd_string, bg=False): 120 """Run a shell command. 121 122 Args: 123 cmd_string: command to run 124 bg: should the process be run in background? Default: False 125 126 Returns: 127 Output, return code 128 """ 129 if not cmd_string: return ('', 0) 130 if bg: 131 cmd_string += ' 1>/dev/null 2>&1 &' 132 proc = os.popen(cmd_string) 133 if bg: return ('Background process: %s' % cmd_string, 0) 134 out = proc.read() 135 retcode = proc.close() 136 if not retcode: # Success 137 retcode = 0 138 return (out, retcode) 139 140 141def SendMail(send_from, send_to, subject, text, smtp, file_to_send=None): 142 """Send mail to all the group to notify about the crash and uploaded data. 143 144 Args: 145 send_from: From mail id as a string. 146 send_to: To mail id. Can be a string representing a single address, or a 147 list of strings representing multiple addresses. 148 subject: Mail subject as a string. 149 text: Mail body as a string. 150 smtp: The smtp to use, as a string. 151 file_to_send: Attachments for the mail. 152 """ 153 msg = email.MIMEMultipart.MIMEMultipart() 154 msg['From'] = send_from 155 if isinstance(send_to, list): 156 msg['To'] = ','.join(send_to) 157 else: 158 msg['To'] = send_to 159 msg['Date'] = email.Utils.formatdate(localtime=True) 160 msg['Subject'] = subject 161 162 # To send multiple files in one message, introduce for loop here for files. 163 msg.attach(email.MIMEText.MIMEText(text)) 164 part = email.MIMEBase.MIMEBase('application', 'octet-stream') 165 if file_to_send is not None: 166 part.set_payload(open(file_to_send,'rb').read()) 167 email.Encoders.encode_base64(part) 168 part.add_header('Content-Disposition', 169 'attachment; filename="%s"' 170 % os.path.basename(file_to_send)) 171 msg.attach(part) 172 smtp_obj = smtplib.SMTP(smtp) 173 smtp_obj.sendmail(send_from, send_to, msg.as_string()) 174 smtp_obj.close() 175 176 177def GetFreeSpace(path): 178 """Returns the free space (in bytes) on the drive containing |path|.""" 179 if sys.platform == 'win32': 180 free_bytes = ctypes.c_ulonglong(0) 181 ctypes.windll.kernel32.GetDiskFreeSpaceExW( 182 ctypes.c_wchar_p(os.path.dirname(path)), None, None, 183 ctypes.pointer(free_bytes)) 184 return free_bytes.value 185 fs_stat = os.statvfs(path) 186 return fs_stat.f_bsize * fs_stat.f_bavail 187 188 189def StripUnmatchedKeys(item_to_strip, reference_item): 190 """Returns a copy of 'item_to_strip' where unmatched key-value pairs in 191 every dictionary are removed. 192 193 This will examine each dictionary in 'item_to_strip' recursively, and will 194 remove keys that are not found in the corresponding dictionary in 195 'reference_item'. This is useful for testing equality of a subset of data. 196 197 Items may contain dictionaries, lists, or primitives, but only corresponding 198 dictionaries will be stripped. A corresponding entry is one which is found 199 in the same index in the corresponding parent array or at the same key in the 200 corresponding parent dictionary. 201 202 Arg: 203 item_to_strip: item to copy and remove all unmatched key-value pairs 204 reference_item: item that serves as a reference for which keys-value pairs 205 to strip from 'item_to_strip' 206 207 Returns: 208 a copy of 'item_to_strip' where all key-value pairs that do not have a 209 matching key in 'reference_item' are removed 210 211 Example: 212 item_to_strip = {'tabs': 3, 213 'time': 5908} 214 reference_item = {'tabs': 2} 215 StripUnmatchedKeys(item_to_strip, reference_item) will return {'tabs': 3} 216 """ 217 def StripList(list1, list2): 218 return_list = copy.deepcopy(list2) 219 for i in range(min(len(list1), len(list2))): 220 return_list[i] = StripUnmatchedKeys(list1[i], list2[i]) 221 return return_list 222 223 def StripDict(dict1, dict2): 224 return_dict = {} 225 for key in dict1: 226 if key in dict2: 227 return_dict[key] = StripUnmatchedKeys(dict1[key], dict2[key]) 228 return return_dict 229 230 item_to_strip_type = type(item_to_strip) 231 if item_to_strip_type is type(reference_item): 232 if item_to_strip_type is types.ListType: 233 return StripList(item_to_strip, reference_item) 234 elif item_to_strip_type is types.DictType: 235 return StripDict(item_to_strip, reference_item) 236 return copy.deepcopy(item_to_strip) 237 238 239def StringContentCheck(test, content_string, have_list, nothave_list): 240 """Check for the presence or absence of strings within content. 241 242 Confirm all strings in |have_list| are found in |content_string|. 243 Confirm all strings in |nothave_list| are not found in |content_string|. 244 245 Args: 246 content_string: string containing the content to check. 247 have_list: list of strings expected to be found within the content. 248 nothave_list: list of strings expected to not be found within the content. 249 """ 250 for s in have_list: 251 test.assertTrue(s in content_string, 252 msg='"%s" missing from content.' % s) 253 for s in nothave_list: 254 test.assertTrue(s not in content_string, 255 msg='"%s" unexpectedly contained in content.' % s) 256 257 258def CallFunctionWithNewTimeout(self, new_timeout, function): 259 """Sets the timeout to |new_timeout| and calls |function|. 260 261 This method resets the timeout before returning. 262 """ 263 timeout_changer = pyauto.PyUITest.ActionTimeoutChanger( 264 self, new_timeout) 265 logging.info('Automation execution timeout has been changed to %d. ' 266 'If the timeout is large the test might appear to hang.' 267 % new_timeout) 268 function() 269 del timeout_changer 270 271 272def GetOmniboxMatchesFor(self, text, windex=0, attr_dict=None): 273 """Fetch omnibox matches with the given attributes for the given query. 274 275 Args: 276 text: the query text to use 277 windex: the window index to work on. Defaults to 0 (first window) 278 attr_dict: the dictionary of properties to be satisfied 279 280 Returns: 281 a list of match items 282 """ 283 self.SetOmniboxText(text, windex=windex) 284 self.WaitUntilOmniboxQueryDone(windex=windex) 285 if not attr_dict: 286 matches = self.GetOmniboxInfo(windex=windex).Matches() 287 else: 288 matches = self.GetOmniboxInfo(windex=windex).MatchesWithAttributes( 289 attr_dict=attr_dict) 290 return matches 291 292 293def GetMemoryUsageOfProcess(pid): 294 """Queries the system for the current memory usage of a specified process. 295 296 This function only works in Linux and ChromeOS. 297 298 Args: 299 pid: The integer process identifier for the process to use. 300 301 Returns: 302 The memory usage of the process in MB, given as a float. If the process 303 doesn't exist on the machine, then the value 0 is returned. 304 """ 305 assert pyauto.PyUITest.IsLinux() or pyauto.PyUITest.IsChromeOS() 306 process = subprocess.Popen('ps h -o rss -p %s' % pid, shell=True, 307 stdout=subprocess.PIPE, stderr=subprocess.PIPE) 308 stdout = process.communicate()[0] 309 if stdout: 310 return float(stdout.strip()) / 1024 311 else: 312 return 0 313 314 315def LoginToDevice(test, test_account='test_google_account'): 316 """Login to the Chromeos device using the given test account. 317 318 If no test account is specified, we use test_google_account as the default. 319 You can choose test accounts from - 320 chrome/test/data/pyauto_private/private_tests_info.txt 321 322 Args: 323 test_account: The account used to login to the Chromeos device. 324 """ 325 if not test.GetLoginInfo()['is_logged_in']: 326 credentials = test.GetPrivateInfo()[test_account] 327 test.Login(credentials['username'], credentials['password']) 328 login_info = test.GetLoginInfo() 329 test.assertTrue(login_info['is_logged_in'], msg='Login failed.') 330 else: 331 test.fail(msg='Another user is already logged in. Please logout first.') 332 333def GetInfobarIndexByType(test, infobar_type, windex=0, tab_index=0): 334 """Returns the index of the infobar of the given type. 335 336 Args: 337 test: Derived from pyauto.PyUITest - base class for UI test cases. 338 infobar_type: The infobar type to look for. 339 windex: Window index. 340 tab_index: Tab index. 341 342 Returns: 343 Index of infobar for infobar type, or None if not found. 344 """ 345 infobar_list = ( 346 test.GetBrowserInfo()['windows'][windex]['tabs'][tab_index] \ 347 ['infobars']) 348 for infobar in infobar_list: 349 if infobar_type == infobar['type']: 350 return infobar_list.index(infobar) 351 return None 352 353def WaitForInfobarTypeAndGetIndex(test, infobar_type, windex=0, tab_index=0): 354 """Wait for infobar type to appear and returns its index. 355 356 If the infobar never appears, an exception will be raised. 357 358 Args: 359 test: Derived from pyauto.PyUITest - base class for UI test cases. 360 infobar_type: The infobar type to look for. 361 windex: Window index. Defaults to 0 (first window). 362 tab_index: Tab index. Defaults to 0 (first tab). 363 364 Returns: 365 Index of infobar for infobar type. 366 """ 367 test.assertTrue( 368 test.WaitUntil(lambda: GetInfobarIndexByType( 369 test, infobar_type, windex, tab_index) is not None), 370 msg='Infobar type for %s did not appear.' % infobar_type) 371 # Return the infobar index. 372 return GetInfobarIndexByType(test, infobar_type, windex, tab_index) 373 374def AssertInfobarTypeDoesNotAppear(test, infobar_type, windex=0, tab_index=0): 375 """Check that the infobar type does not appear. 376 377 This function waits 20s to assert that the infobar does not appear. 378 379 Args: 380 test: Derived from pyauto.PyUITest - base class for UI test cases. 381 infobar_type: The infobar type to look for. 382 windex: Window index. Defaults to 0 (first window). 383 tab_index: Tab index. Defaults to 0 (first tab). 384 """ 385 test.assertFalse( 386 test.WaitUntil(lambda: GetInfobarIndexByType( 387 test, infobar_type, windex, tab_index) is not None, timeout=20), 388 msg=('Infobar type for %s appeared when it should be hidden.' 389 % infobar_type)) 390 391def OpenCroshVerification(self): 392 """This test opens crosh. 393 394 This function assumes that no browser windows are open. 395 """ 396 self.assertEqual(0, self.GetBrowserWindowCount()) 397 self.OpenCrosh() 398 self.assertEqual(1, self.GetBrowserWindowCount()) 399 self.assertEqual(1, self.GetTabCount(), 400 msg='Could not open crosh') 401 self.assertEqual('crosh', self.GetActiveTabTitle()) 402