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