1# Copyright (c) 2012 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 5"""Base GTalk tests. 6 7This module contains a set of common utilities for querying 8and manipulating the Google Talk Chrome Extension. 9""" 10 11import logging 12import re 13import os 14 15import pyauto_gtalk 16import pyauto 17import pyauto_errors 18 19 20class GTalkBaseTest(pyauto.PyUITest): 21 """Base test class for testing GTalk.""" 22 23 _injected_js = None 24 25 def Prompt(self, text): 26 """Pause execution with debug output. 27 28 Args: 29 text: The debug output. 30 """ 31 text = str(text) 32 raw_input('--------------------> ' + text) 33 34 def InstallGTalkExtension(self, gtalk_version): 35 """Download and install the GTalk extension.""" 36 extension_path = os.path.abspath( 37 os.path.join(self.DataDir(), 'extensions', 'gtalk', 38 gtalk_version + '.crx')) 39 self.assertTrue( 40 os.path.exists(extension_path), 41 msg='Failed to find GTalk extension: ' + extension_path) 42 43 extension = self.GetGTalkExtensionInfo() 44 if extension: 45 logging.info('Extension already installed. Skipping install...\n') 46 return 47 48 self.InstallExtension(extension_path, False) 49 extension = self.GetGTalkExtensionInfo() 50 self.assertTrue(extension, msg='Failed to install GTalk extension.') 51 self.assertTrue(extension['is_enabled'], msg='GTalk extension is disabled.') 52 53 def UninstallGTalkExtension(self): 54 """Uninstall the GTalk extension (if present)""" 55 extension = self.GetGTalkExtensionInfo() 56 if extension: 57 self.UninstallExtensionById(extension['id']) 58 59 def GetGTalkExtensionInfo(self): 60 """Get the data object about the GTalk extension.""" 61 extensions = [x for x in self.GetExtensionsInfo() 62 if x['name'] == 'Chat for Google'] 63 return extensions[0] if len(extensions) == 1 else None 64 65 def RunInMole(self, js, mole_index=0): 66 """Execute javascript in a chat mole. 67 68 Args: 69 js: The javascript to run. 70 mole_index: The index of the mole in which to run the JS. 71 72 Returns: 73 The resulting value from executing the javascript. 74 """ 75 return self._RunInRenderView(self.GetMoleInfo(mole_index), js, 76 '//iframe[1]') 77 78 def RunInAllMoles(self, js): 79 """Execute javascript in all chat moles. 80 81 Args: 82 js: The javascript to run. 83 """ 84 moles = self.GetMolesInfo() 85 for mole in moles: 86 self._RunInRenderView(mole, js, '//iframe[1]') 87 88 def RunInRoster(self, js): 89 """Execute javascript in the chat roster. 90 91 Args: 92 js: The javascript to run. 93 94 Returns: 95 The resulting value from executing the javascript. 96 """ 97 return self._RunInRenderView(self.GetViewerInfo(), js, 98 '//iframe[1]\n//iframe[1]') 99 100 def RunInLoginPage(self, js, xpath=''): 101 """Execute javascript in the gaia login popup. 102 103 Args: 104 js: The javascript to run. 105 xpath: The xpath to the frame in which to execute the javascript. 106 107 Returns: 108 The resulting value from executing the javascript. 109 """ 110 return self._RunInTab(self.GetLoginPageInfo(), js, xpath) 111 112 def RunInViewer(self, js, xpath=''): 113 """Execute javascript in the GTalk viewer window. 114 115 Args: 116 js: The javascript to run. 117 xpath: The xpath to the frame in which to execute the javascript. 118 119 Returns: 120 The resulting value from executing the javascript. 121 """ 122 return self._RunInRenderView(self.GetViewerInfo(), js, xpath) 123 124 def RunInBackground(self, js, xpath=''): 125 """Execute javascript in the GTalk viewer window. 126 127 Args: 128 js: The javascript to run. 129 xpath: The xpath to the frame in which to execute the javascript. 130 131 Returns: 132 The resulting value from executing the javascript. 133 """ 134 background_view = self.GetBackgroundInfo() 135 return self._RunInRenderView(background_view['view'], js, xpath) 136 137 def GetMoleInfo(self, mole_index=0): 138 """Get the data object about a given chat mole. 139 140 Args: 141 mole_index: The index of the mole to retrieve. 142 143 Returns: 144 Data object describing mole. 145 """ 146 extension = self.GetGTalkExtensionInfo() 147 return self._GetExtensionViewInfo( 148 'chrome-extension://%s/panel.html' % extension['id'], 149 mole_index) 150 151 def GetMolesInfo(self): 152 """Get the data objects for all of the chat moles. 153 154 Returns: 155 Set of data objects describing moles. 156 """ 157 extension = self.GetGTalkExtensionInfo() 158 return self._GetMatchingExtensionViews( 159 'chrome-extension://%s/panel.html' % extension['id']) 160 161 def GetViewerInfo(self): 162 """Get the data object about the GTalk viewer dialog.""" 163 extension = self.GetGTalkExtensionInfo() 164 return self._GetExtensionViewInfo( 165 'chrome-extension://%s/viewer.html' % extension['id']) 166 167 def GetLoginPageInfo(self): 168 """Get the data object about the gaia login popup.""" 169 return self._GetTabInfo('https://accounts.google.com/ServiceLogin?') 170 171 def GetBackgroundInfo(self): 172 """Get the data object about the GTalk background page.""" 173 extension_views = self.GetBrowserInfo()['extension_views'] 174 for extension_view in extension_views: 175 if 'Google Talk' in extension_view['name'] and \ 176 'EXTENSION_BACKGROUND_PAGE' == extension_view['view_type']: 177 return extension_view 178 return None 179 180 def WaitUntilResult(self, result, func, msg): 181 """Loop func until a condition matches is satified. 182 183 Args: 184 result: Value of func() at which to stop. 185 func: Function to run at each iteration. 186 msg: Error to print upon timing out. 187 """ 188 assert callable(func) 189 self.assertTrue(self.WaitUntil( 190 lambda: func(), expect_retval=result), msg=msg) 191 192 def WaitUntilCondition(self, func, matches, msg): 193 """Loop func until condition matches is satified. 194 195 Args: 196 func: Function to run at each iteration. 197 matches: Funtion to evalute output and determine whether to stop. 198 msg: Error to print upon timing out. 199 """ 200 assert callable(func) 201 assert callable(matches) 202 self.assertTrue(self.WaitUntil( 203 lambda: matches(func())), msg=msg) 204 205 def _WrapJs(self, statement): 206 """Wrap the javascript to be executed. 207 208 Args: 209 statement: The piece of javascript to wrap. 210 211 Returns: 212 The wrapped javascript. 213 """ 214 return """ 215 window.domAutomationController.send( 216 (function(){ 217 %s 218 try{return %s} 219 catch(e){return "JS_ERROR: " + e}})()) 220 """ % (self._GetInjectedJs(), statement) 221 222 def _RunInTab(self, tab, js, xpath=''): 223 """Execute javascript in a given tab. 224 225 Args: 226 tab: The data object for the Chrome window tab returned by 227 _GetTabInfo. 228 js: The javascript to run. 229 xpath: The xpath to the frame in which to execute the javascript. 230 231 Returns: 232 The resulting value from executing the javascript. 233 """ 234 if not tab: 235 logging.debug('Tab not found: %s' % tab) 236 return False 237 logging.info('Run in tab: %s' % js) 238 239 value = self.ExecuteJavascript( 240 self._WrapJs(js), 241 tab_index = tab['index'], 242 windex = tab['windex'], 243 frame_xpath = xpath) 244 self._LogRun(js, value) 245 return value 246 247 def _RunInRenderView(self, view, js, xpath=''): 248 """Execute javascript in a given render view. 249 250 Args: 251 view: The data object for the Chrome render view returned by 252 _GetExtensionViewInfo. 253 js: The javascript to run. 254 xpath: The xpath to the frame in which to execute the javascript. 255 256 Returns: 257 The resulting value from executing the javascript. 258 """ 259 if not view: 260 logging.debug('View not found: %s' % view) 261 return False 262 logging.info('Run in view: %s' % js) 263 264 value = self.ExecuteJavascriptInRenderView( 265 self._WrapJs(js), 266 view, 267 frame_xpath = xpath) 268 self._LogRun(js, value) 269 return value 270 271 def _LogRun(self, js, value): 272 """Log a particular run. 273 274 Args: 275 js: The javascript statement executed. 276 value: The return value for the execution. 277 """ 278 # works around UnicodeEncodeError: 'ascii' codec can't encode... 279 out = value 280 if not isinstance(value, basestring): 281 out = str(value) 282 out = re.sub('\s', ';', out[:300]) 283 logging.info(js + ' ===> ' + out.encode('utf-8')) 284 285 def _GetTabInfo(self, url_query, index=0): 286 """Get the data object for a given tab. 287 288 Args: 289 url_query: The substring of the URL to search for. 290 index: The index within the list of matches to return. 291 292 Returns: 293 The data object for the tab. 294 """ 295 windows = self.GetBrowserInfo()['windows'] 296 i = 0 297 for win in windows: 298 for tab in win['tabs']: 299 if tab['url'] and url_query in tab['url']: 300 # Store reference to windex used in _RunInTab. 301 tab['windex'] = win['index'] 302 if i == index: 303 return tab 304 i = i + 1 305 return None 306 307 def _GetExtensionViewInfo(self, url_query, index=0): 308 """Get the data object for a given extension view. 309 310 Args: 311 url_query: The substring of the URL to search for. 312 index: The index within the list of matches to return. 313 314 Returns: 315 The data object for the tab. 316 """ 317 318 candidate_views = self._GetMatchingExtensionViews(url_query) 319 if len(candidate_views) > index: 320 return candidate_views[index] 321 return None 322 323 def _GetMatchingExtensionViews(self, url_query): 324 """Gets the data objects for the extension views matching the url_query. 325 326 Args: 327 url_query: The substring of the URL to search for. 328 329 Returns: 330 An array of matching data objects. 331 """ 332 extension_views = self.GetBrowserInfo()['extension_views'] 333 candidate_views = list() 334 for extension_view in extension_views: 335 if extension_view['url'] and url_query in extension_view['url']: 336 candidate_views.append(extension_view['view']) 337 338 # No guarantee on view order, so sort the views to get the correct one for 339 # a given index. 340 candidate_views.sort() 341 return candidate_views 342 343 def _GetInjectedJs(self): 344 """Get the javascript to inject in the execution environment.""" 345 if self._injected_js is None: 346 self._injected_js = open( 347 os.path.join(os.path.dirname(__file__), 'jsutils.js')).read() 348 return self._injected_js 349