1 #!/usr/bin/env python 2 # Copyright (c) 2012 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 import copy 7 import os 8 9 import pyauto_functional # Must be imported before pyauto 10 import pyauto 11 import test_utils 12 13 14 class NTPTest(pyauto.PyUITest): 15 """Test of the NTP.""" 16 17 # Default apps are registered in ProfileImpl::RegisterComponentExtensions(). 18 _EXPECTED_DEFAULT_APPS = [ 19 {u'title': u'Chrome Web Store'}, 20 ] 21 if pyauto.PyUITest.IsChromeOS(): 22 _EXPECTED_DEFAULT_APPS.append({u'title': u'Files'}) 23 _EXPECTED_DEFAULT_APPS.append({u'title': u'Chrome'}) 24 else: 25 _EXPECTED_DEFAULT_APPS.append({u'title': u'Cloud Print'}) 26 27 # Default menu and thumbnail mode preferences are set in 28 # ShownSectionsHandler::RegisterUserPrefs. 29 if pyauto.PyUITest.IsChromeOS(): 30 _EXPECTED_DEFAULT_THUMB_INFO = { 31 u'apps': True, 32 u'most_visited': False 33 } 34 _EXPECTED_DEFAULT_MENU_INFO = { 35 u'apps': False, 36 u'most_visited': True, 37 u'recently_closed': True 38 } 39 else: 40 _EXPECTED_DEFAULT_THUMB_INFO = { 41 u'apps': False, 42 u'most_visited': True 43 } 44 _EXPECTED_DEFAULT_MENU_INFO = { 45 u'apps': False, 46 u'most_visited': False, 47 u'recently_closed': False 48 } 49 50 def Debug(self): 51 """Test method for experimentation. 52 53 This method is not run automatically. 54 """ 55 while True: 56 raw_input('Interact with the browser and hit <enter> to dump NTP info...') 57 print '*' * 20 58 self.pprint(self._GetNTPInfo()) 59 60 def __init__(self, methodName='runTest'): 61 super(NTPTest, self).__init__(methodName) 62 63 # Create some dummy file urls we can use in the tests. 64 filenames = ['title1.html', 'title2.html'] 65 titles = [u'', u'Title Of Awesomeness'] 66 urls = map(lambda name: self.GetFileURLForDataPath(name), filenames) 67 self.PAGES = map(lambda url, title: {'url': url, 'title': title}, 68 urls, titles) 69 70 def _NTPContainsThumbnail(self, check_thumbnail): 71 """Returns whether the NTP's Most Visited section contains the given 72 thumbnail.""" 73 for thumbnail in self.GetNTPThumbnails(): 74 if check_thumbnail['url'] == thumbnail['url']: 75 return True 76 return False 77 78 def testFreshProfile(self): 79 """Tests that the NTP with a fresh profile is correct""" 80 thumbnails = self.GetNTPThumbnails() 81 default_sites = self.GetNTPDefaultSites() 82 self.assertEqual(len(default_sites), len(thumbnails)) 83 for thumbnail, default_site in zip(thumbnails, default_sites): 84 self.assertEqual(thumbnail['url'], default_site) 85 self.assertEqual(0, len(self.GetNTPRecentlyClosed())) 86 87 def testRemoveDefaultThumbnails(self): 88 """Tests that the default thumbnails can be removed""" 89 self.RemoveNTPDefaultThumbnails() 90 self.assertFalse(self.GetNTPThumbnails()) 91 self.RestoreAllNTPThumbnails() 92 self.assertEqual(len(self.GetNTPDefaultSites()), 93 len(self.GetNTPThumbnails())) 94 self.RemoveNTPDefaultThumbnails() 95 self.assertFalse(self.GetNTPThumbnails()) 96 97 def testOneMostVisitedSite(self): 98 """Tests that a site is added to the most visited sites""" 99 self.RemoveNTPDefaultThumbnails() 100 self.NavigateToURL(self.PAGES[1]['url']) 101 thumbnail = self.GetNTPThumbnails()[0] 102 self.assertEqual(self.PAGES[1]['url'], thumbnail['url']) 103 self.assertEqual(self.PAGES[1]['title'], thumbnail['title']) 104 105 def testRemoveThumbnail(self): 106 """Tests removing a thumbnail works""" 107 self.RemoveNTPDefaultThumbnails() 108 for page in self.PAGES: 109 self.AppendTab(pyauto.GURL(page['url'])) 110 111 thumbnails = self.GetNTPThumbnails() 112 for thumbnail in thumbnails: 113 self.assertEquals(thumbnail, self.GetNTPThumbnails()[0]) 114 self.RemoveNTPThumbnail(thumbnail) 115 self.assertFalse(self._NTPContainsThumbnail(thumbnail)) 116 self.assertFalse(self.GetNTPThumbnails()) 117 118 def testIncognitoNotAppearInMostVisited(self): 119 """Tests that visiting a page in incognito mode does cause it to appear in 120 the Most Visited section""" 121 self.RemoveNTPDefaultThumbnails() 122 self.RunCommand(pyauto.IDC_NEW_INCOGNITO_WINDOW) 123 self.NavigateToURL(self.PAGES[0]['url'], 1, 0) 124 self.assertFalse(self.GetNTPThumbnails()) 125 126 def testDifferentProfileNotAppearInMostVisited(self): 127 """Tests that visiting a page in one profile does not cause it to appear in 128 the Most Visited section of another.""" 129 self.RemoveNTPDefaultThumbnails() 130 self.OpenNewBrowserWindowWithNewProfile() 131 self.NavigateToURL(self.PAGES[0]['url'], 1, 0) 132 self.assertFalse(self.GetNTPThumbnails()) 133 134 def testThumbnailPersistence(self): 135 """Tests that thumbnails persist across Chrome restarts""" 136 self.RemoveNTPDefaultThumbnails() 137 for page in self.PAGES: 138 self.AppendTab(pyauto.GURL(page['url'])) 139 thumbnails = self.GetNTPThumbnails() 140 self.RestartBrowser(clear_profile=False) 141 self.assertEqual(thumbnails, self.GetNTPThumbnails()) 142 143 def testRestoreAllRemovedThumbnails(self): 144 """Tests restoring all removed thumbnails""" 145 for page in self.PAGES: 146 self.AppendTab(pyauto.GURL(page['url'])) 147 148 thumbnails = self.GetNTPThumbnails() 149 for thumbnail in thumbnails: 150 self.RemoveNTPThumbnail(thumbnail) 151 152 self.RestoreAllNTPThumbnails() 153 self.assertEquals(thumbnails, self.GetNTPThumbnails()) 154 155 def testThumbnailRanking(self): 156 """Tests that the thumbnails are ordered according to visit count""" 157 self.RemoveNTPDefaultThumbnails() 158 for page in self.PAGES: 159 self.AppendTab(pyauto.GURL(page['url'])) 160 thumbnails = self.GetNTPThumbnails() 161 self.assertEqual(self.PAGES[0]['url'], self.GetNTPThumbnails()[0]['url']) 162 self.AppendTab(pyauto.GURL(self.PAGES[1]['url'])) 163 self.assertEqual(self.PAGES[1]['url'], self.GetNTPThumbnails()[0]['url']) 164 self.AppendTab(pyauto.GURL(self.PAGES[0]['url'])) 165 self.AppendTab(pyauto.GURL(self.PAGES[0]['url'])) 166 self.assertEqual(self.PAGES[0]['url'], self.GetNTPThumbnails()[0]['url']) 167 168 def testThumbnailTitleChangeAfterPageTitleChange(self): 169 """Tests that once a page title changes, the thumbnail title changes too""" 170 self.RemoveNTPDefaultThumbnails() 171 self.NavigateToURL(self.PAGES[0]['url']) 172 self.assertEqual(self.PAGES[0]['title'], 173 self.GetNTPThumbnails()[0]['title']) 174 self.ExecuteJavascript('window.domAutomationController.send(' + 175 'document.title = "new title")') 176 self.assertEqual('new title', self.GetNTPThumbnails()[0]['title']) 177 178 def testCloseOneTab(self): 179 """Tests that closing a tab populates the recently closed list""" 180 self.RemoveNTPDefaultThumbnails() 181 self.AppendTab(pyauto.GURL(self.PAGES[1]['url'])) 182 self.CloseTab(tab_index=1) 183 self.assertEqual(self.PAGES[1]['url'], 184 self.GetNTPRecentlyClosed()[0]['url']) 185 self.assertEqual(self.PAGES[1]['title'], 186 self.GetNTPRecentlyClosed()[0]['title']) 187 188 def testCloseOneWindow(self): 189 """Tests that closing a window populates the recently closed list""" 190 self.RemoveNTPDefaultThumbnails() 191 self.OpenNewBrowserWindow(True) 192 self.NavigateToURL(self.PAGES[0]['url'], 1, 0) 193 self.AppendTab(pyauto.GURL(self.PAGES[1]['url']), 1) 194 self.CloseBrowserWindow(1) 195 expected = [{ u'type': u'window', 196 u'tabs': [ 197 { u'type': u'tab', 198 u'url': self.PAGES[0]['url'], 199 u'direction': u'ltr' }, 200 { u'type': u'tab', 201 u'url': self.PAGES[1]['url']}] 202 }] 203 self.assertEquals(expected, test_utils.StripUnmatchedKeys( 204 self.GetNTPRecentlyClosed(), expected)) 205 206 def testCloseMultipleTabs(self): 207 """Tests closing multiple tabs populates the Recently Closed section in 208 order""" 209 self.RemoveNTPDefaultThumbnails() 210 self.AppendTab(pyauto.GURL(self.PAGES[0]['url'])) 211 self.AppendTab(pyauto.GURL(self.PAGES[1]['url'])) 212 self.CloseTab(tab_index=2) 213 self.CloseTab(tab_index=1) 214 expected = [{ u'type': u'tab', 215 u'url': self.PAGES[0]['url'] 216 }, 217 { u'type': u'tab', 218 u'url': self.PAGES[1]['url'] 219 }] 220 self.assertEquals(expected, test_utils.StripUnmatchedKeys( 221 self.GetNTPRecentlyClosed(), expected)) 222 223 def testCloseWindowWithOneTab(self): 224 """Tests that closing a window with only one tab only shows up as a tab in 225 the Recently Closed section""" 226 self.RemoveNTPDefaultThumbnails() 227 self.OpenNewBrowserWindow(True) 228 self.NavigateToURL(self.PAGES[0]['url'], 1, 0) 229 self.CloseBrowserWindow(1) 230 expected = [{ u'type': u'tab', 231 u'url': self.PAGES[0]['url'] 232 }] 233 self.assertEquals(expected, test_utils.StripUnmatchedKeys( 234 self.GetNTPRecentlyClosed(), expected)) 235 236 def testCloseMultipleWindows(self): 237 """Tests closing multiple windows populates the Recently Closed list""" 238 self.RemoveNTPDefaultThumbnails() 239 self.OpenNewBrowserWindow(True) 240 self.NavigateToURL(self.PAGES[0]['url'], 1, 0) 241 self.AppendTab(pyauto.GURL(self.PAGES[1]['url']), 1) 242 self.OpenNewBrowserWindow(True) 243 self.NavigateToURL(self.PAGES[1]['url'], 2, 0) 244 self.AppendTab(pyauto.GURL(self.PAGES[0]['url']), 2) 245 self.CloseBrowserWindow(2) 246 self.CloseBrowserWindow(1) 247 expected = [{ u'type': u'window', 248 u'tabs': [ 249 { u'type': u'tab', 250 u'url': self.PAGES[0]['url'], 251 u'direction': u'ltr' }, 252 { u'type': u'tab', 253 u'url': self.PAGES[1]['url']}] 254 }, 255 { u'type': u'window', 256 u'tabs': [ 257 { u'type': u'tab', 258 u'url': self.PAGES[1]['url'], 259 u'direction': u'ltr' }, 260 { u'type': u'tab', 261 u'url': self.PAGES[0]['url']}] 262 }] 263 self.assertEquals(expected, test_utils.StripUnmatchedKeys( 264 self.GetNTPRecentlyClosed(), expected)) 265 266 def testRecentlyClosedIncognito(self): 267 """Tests that we don't record closure of Incognito tabs or windows""" 268 #self.RemoveNTPDefaultThumbnails() 269 self.RunCommand(pyauto.IDC_NEW_INCOGNITO_WINDOW) 270 self.NavigateToURL(self.PAGES[0]['url'], 1, 0) 271 self.AppendTab(pyauto.GURL(self.PAGES[0]['url']), 1) 272 self.AppendTab(pyauto.GURL(self.PAGES[1]['url']), 1) 273 self.CloseTab(windex=1) 274 self.assertFalse(self.GetNTPRecentlyClosed()) 275 self.CloseBrowserWindow(1) 276 self.assertFalse(self.GetNTPRecentlyClosed()) 277 278 def _VerifyAppInfo(self, actual_info, expected_info): 279 """Ensures that the actual app info contains the expected app info. 280 281 This method assumes that both the actual and expected information for each 282 app contains at least the 'title' attribute. Both sets of info are 283 considered to match if the actual info contains at least the specified 284 expected info (if the actual info contains additional values that are not 285 specified in the expected info, that's ok). This function will fail the 286 current test if both sets of info don't match. 287 288 Args: 289 actual_info: A list of dictionaries representing the information from 290 all apps that would currently be displayed on the NTP. 291 expected_info: A corrresponding list of dictionaries representing the 292 information that is expected. 293 """ 294 # Ensure all app info dictionaries contain at least the 'title' attribute. 295 self.assertTrue(all(map(lambda app: 'title' in app, actual_info)) and 296 all(map(lambda app: 'title' in app, expected_info)), 297 msg='At least one app is missing the "title" attribute.') 298 299 # Sort both app lists by title to ensure they're in a known order. 300 actual_info = sorted(actual_info, key=lambda app: app['title']) 301 expected_info = sorted(expected_info, key=lambda app: app['title']) 302 303 # Ensure the expected info matches the actual info. 304 self.assertTrue(len(actual_info) == len(expected_info), 305 msg='Expected %d app(s) on NTP, but got %d instead.' % ( 306 len(expected_info), len(actual_info))) 307 for i, expected_app in enumerate(expected_info): 308 for attribute in expected_app: 309 self.assertTrue(attribute in actual_info[i], 310 msg='Expected attribute "%s" not found in app info.' % ( 311 attribute)) 312 self.assertTrue(expected_app[attribute] == actual_info[i][attribute], 313 msg='For attribute "%s", expected value "%s", but got ' 314 '"%s".' % (attribute, expected_app[attribute], 315 actual_info[i][attribute])) 316 317 def _InstallAndVerifySamplePackagedApp(self): 318 """Installs a sample packaged app and verifies the install is successful. 319 320 Returns: 321 The string ID of the installed app. 322 """ 323 app_crx_file = os.path.abspath(os.path.join( 324 self.DataDir(), 'pyauto_private', 'apps', 'countdown.crx')) 325 return self.InstallExtension(app_crx_file) 326 327 def testGetAppsInNewProfile(self): 328 """Ensures that the only app in a new profile is the Web Store app.""" 329 app_info = self.GetNTPApps() 330 self._VerifyAppInfo(app_info, self._EXPECTED_DEFAULT_APPS) 331 332 def testGetAppsWhenInstallApp(self): 333 """Ensures that an installed app is reflected in the app info in the NTP.""" 334 self._InstallAndVerifySamplePackagedApp() 335 app_info = self.GetNTPApps() 336 expected_app_info = [ 337 { 338 u'title': u'Countdown' 339 } 340 ] 341 expected_app_info.extend(self._EXPECTED_DEFAULT_APPS) 342 self._VerifyAppInfo(app_info, expected_app_info) 343 344 def testGetAppsWhenInstallNonApps(self): 345 """Ensures installed non-apps are not reflected in the NTP app info.""" 346 # Install a regular extension and a theme. 347 ext_crx_file = os.path.abspath(os.path.join(self.DataDir(), 'extensions', 348 'page_action.crx')) 349 self.InstallExtension(ext_crx_file) 350 theme_crx_file = os.path.abspath(os.path.join(self.DataDir(), 'extensions', 351 'theme.crx')) 352 self.SetTheme(theme_crx_file) 353 # Verify that no apps are listed on the NTP except for the Web Store. 354 app_info = self.GetNTPApps() 355 self._VerifyAppInfo(app_info, self._EXPECTED_DEFAULT_APPS) 356 357 def testUninstallApp(self): 358 """Ensures that an uninstalled app is reflected in the NTP app info.""" 359 # First, install an app and verify that it exists in the NTP app info. 360 installed_app_id = self._InstallAndVerifySamplePackagedApp() 361 app_info = self.GetNTPApps() 362 expected_app_info = [ 363 { 364 u'title': u'Countdown' 365 } 366 ] 367 expected_app_info.extend(self._EXPECTED_DEFAULT_APPS) 368 self._VerifyAppInfo(app_info, expected_app_info) 369 370 # Next, uninstall the app and verify that it is removed from the NTP. 371 self.assertTrue(self.UninstallExtensionById(installed_app_id), 372 msg='Call to UninstallExtensionById() returned False.') 373 app_info = self.GetNTPApps() 374 self._VerifyAppInfo(app_info, self._EXPECTED_DEFAULT_APPS) 375 376 def testCannotUninstallWebStore(self): 377 """Ensures that the WebStore app cannot be uninstalled.""" 378 # Verify that the WebStore app is already installed in a fresh profile. 379 app_info = self.GetNTPApps() 380 self._VerifyAppInfo(app_info, self._EXPECTED_DEFAULT_APPS) 381 self.assertTrue(app_info and 'id' in app_info[0], 382 msg='Cannot identify ID of WebStore app.') 383 webstore_id = app_info[0]['id'] 384 385 # Attempt to uninstall the WebStore app and verify that it still exists 386 # in the App info of the NTP even after we try to uninstall it. 387 self.assertFalse(self.UninstallExtensionById(webstore_id), 388 msg='Call to UninstallExtensionById() returned True.') 389 self._VerifyAppInfo(self.GetNTPApps(), self._EXPECTED_DEFAULT_APPS) 390 391 def testLaunchAppWithDefaultSettings(self): 392 """Verifies that an app can be launched with the default settings.""" 393 # Install an app. 394 installed_app_id = self._InstallAndVerifySamplePackagedApp() 395 396 # Launch the app from the NTP. 397 self.LaunchApp(installed_app_id) 398 399 # Verify that the second tab in the first window is the app launch URL. 400 # It should be the second tab, not the first, since the call to LaunchApp 401 # should have first opened the NTP in a new tab, and then launched the app 402 # from there. 403 info = self.GetBrowserInfo() 404 actual_tab_url = info['windows'][0]['tabs'][1]['url'] 405 expected_app_url_start = 'chrome-extension://' + installed_app_id 406 self.assertTrue(actual_tab_url.startswith(expected_app_url_start), 407 msg='The app was not launched.') 408 409 def testLaunchAppRegularTab(self): 410 """Verifies that an app can be launched in a regular tab.""" 411 installed_app_id = self._InstallAndVerifySamplePackagedApp() 412 413 self.SetAppLaunchType(installed_app_id, 'regular', windex=0) 414 self.LaunchApp(installed_app_id) 415 416 # Verify that the second tab in the first window is the app launch URL. 417 info = self.GetBrowserInfo() 418 actual_tab_url = info['windows'][0]['tabs'][1]['url'] 419 expected_app_url_start = 'chrome-extension://' + installed_app_id 420 self.assertTrue(actual_tab_url.startswith(expected_app_url_start), 421 msg='The app was not launched in a regular tab.') 422 423 def testLaunchAppPinnedTab(self): 424 """Verifies that an app can be launched in a pinned tab.""" 425 installed_app_id = self._InstallAndVerifySamplePackagedApp() 426 427 self.SetAppLaunchType(installed_app_id, 'pinned', windex=0) 428 self.LaunchApp(installed_app_id) 429 430 # Verify that the first tab in the first window is the app launch URL, and 431 # that it is a pinned tab. 432 info = self.GetBrowserInfo() 433 actual_tab_url = info['windows'][0]['tabs'][0]['url'] 434 expected_app_url_start = 'chrome-extension://' + installed_app_id 435 self.assertTrue(actual_tab_url.startswith(expected_app_url_start) and 436 info['windows'][0]['tabs'][0]['pinned'], 437 msg='The app was not launched in a pinned tab.') 438 439 def testLaunchAppFullScreen(self): 440 """Verifies that an app can be launched in fullscreen mode.""" 441 installed_app_id = self._InstallAndVerifySamplePackagedApp() 442 443 self.SetAppLaunchType(installed_app_id, 'fullscreen', windex=0) 444 self.LaunchApp(installed_app_id) 445 446 # Verify that the second tab in the first window is the app launch URL, and 447 # that the window is fullscreen. 448 info = self.GetBrowserInfo() 449 actual_tab_url = info['windows'][0]['tabs'][1]['url'] 450 expected_app_url_start = 'chrome-extension://' + installed_app_id 451 self.assertTrue(actual_tab_url.startswith(expected_app_url_start) and 452 info['windows'][0]['fullscreen'], 453 msg='The app was not launched in fullscreen mode.') 454 455 def testLaunchAppNewWindow(self): 456 """Verifies that an app can be launched in a new window.""" 457 installed_app_id = self._InstallAndVerifySamplePackagedApp() 458 459 self.SetAppLaunchType(installed_app_id, 'window', windex=0) 460 self.LaunchApp(installed_app_id) 461 462 # Verify that a second window exists (at index 1), and that its first tab 463 # is the app launch URL. 464 info = self.GetBrowserInfo() 465 self.assertTrue(len(info['windows']) == 2, 466 msg='A second window does not exist.') 467 actual_tab_url = info['windows'][1]['tabs'][0]['url'] 468 expected_app_url_start = 'chrome-extension://' + installed_app_id 469 self.assertTrue(actual_tab_url.startswith(expected_app_url_start), 470 msg='The app was not launched in the new window.') 471 472 if __name__ == '__main__': 473 pyauto_functional.Main() 474