test_quit_restart.py (21391B)
1 # This Source Code Form is subject to the terms of the Mozilla Public 2 # License, v. 2.0. If a copy of the MPL was not distributed with this 3 # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 5 import sys 6 import unittest 7 from urllib.parse import quote 8 9 import mozinfo 10 11 from marionette_driver import errors 12 from marionette_driver.by import By 13 from marionette_harness import MarionetteTestCase 14 15 16 def inline(doc): 17 return "data:text/html;charset=utf-8,{}".format(quote(doc)) 18 19 20 class TestServerQuitApplication(MarionetteTestCase): 21 def tearDown(self): 22 if self.marionette.session is None: 23 self.marionette.start_session() 24 25 def quit(self, flags=None, safe_mode=False): 26 body = {} 27 if flags is not None: 28 body["flags"] = list( 29 flags, 30 ) 31 if safe_mode: 32 body["safeMode"] = safe_mode 33 34 resp = self.marionette._send_message("Marionette:Quit", body) 35 self.marionette.session_id = None 36 self.marionette.session = None 37 self.marionette.process_id = None 38 self.marionette.profile = None 39 self.marionette.window = None 40 41 self.assertIn("cause", resp) 42 43 self.marionette.client.close() 44 self.marionette.instance.runner.wait() 45 46 return resp["cause"] 47 48 def test_types(self): 49 for typ in [42, True, "foo", []]: 50 print("testing type {}".format(type(typ))) 51 with self.assertRaises(errors.InvalidArgumentException): 52 self.marionette._send_message("Marionette:Quit", typ) 53 54 with self.assertRaises(errors.InvalidArgumentException): 55 self.quit("foo") 56 57 def test_undefined_default(self): 58 cause = self.quit() 59 self.assertEqual("shutdown", cause) 60 61 def test_empty_default(self): 62 cause = self.quit(()) 63 self.assertEqual("shutdown", cause) 64 65 def test_incompatible_quit_flags(self): 66 with self.assertRaises(errors.InvalidArgumentException): 67 self.quit(("eAttemptQuit", "eForceQuit")) 68 69 def test_attempt_quit(self): 70 cause = self.quit(("eAttemptQuit",)) 71 self.assertEqual("shutdown", cause) 72 73 def test_force_quit(self): 74 cause = self.quit(("eForceQuit",)) 75 self.assertEqual("shutdown", cause) 76 77 def test_safe_mode_requires_restart(self): 78 with self.assertRaises(errors.InvalidArgumentException): 79 self.quit(("eAttemptQuit",), True) 80 81 @unittest.skipUnless(sys.platform.startswith("darwin"), "Only supported on MacOS") 82 def test_silent_quit_missing_windowless_capability(self): 83 with self.assertRaises(errors.UnsupportedOperationException): 84 self.quit(("eSilently",)) 85 86 87 class TestQuitRestart(MarionetteTestCase): 88 def setUp(self): 89 MarionetteTestCase.setUp(self) 90 91 self.pid = self.marionette.process_id 92 self.profile = self.marionette.profile 93 self.session_id = self.marionette.session_id 94 95 # Use a preference to check that the restart was successful. If its 96 # value has not been forced, a restart will cause a reset of it. 97 self.assertNotEqual( 98 self.marionette.get_pref("startup.homepage_welcome_url"), "about:about" 99 ) 100 self.marionette.set_pref("startup.homepage_welcome_url", "about:about") 101 102 def tearDown(self): 103 # Ensure to restart a session if none exist for clean-up 104 if self.marionette.session is None: 105 self.marionette.start_session() 106 107 self.marionette.clear_pref("startup.homepage_welcome_url") 108 109 MarionetteTestCase.tearDown(self) 110 111 @property 112 def is_safe_mode(self): 113 with self.marionette.using_context("chrome"): 114 return self.marionette.execute_script( 115 """ 116 return Services.appinfo.inSafeMode; 117 """ 118 ) 119 120 def shutdown(self, restart=False): 121 self.marionette.set_context("chrome") 122 self.marionette.execute_script( 123 """ 124 let flags = Ci.nsIAppStartup.eAttemptQuit; 125 if (arguments[0]) { 126 flags |= Ci.nsIAppStartup.eRestart; 127 } 128 Services.startup.quit(flags); 129 """, 130 script_args=(restart,), 131 ) 132 133 def test_force_restart(self): 134 self.marionette.restart(in_app=False) 135 self.assertEqual(self.marionette.profile, self.profile) 136 self.assertNotEqual(self.marionette.session_id, self.session_id) 137 138 # A forced restart will cause a new process id 139 self.assertNotEqual(self.marionette.process_id, self.pid) 140 self.assertNotEqual( 141 self.marionette.get_pref("startup.homepage_welcome_url"), "about:about" 142 ) 143 144 def test_force_clean_restart(self): 145 self.marionette.restart(in_app=False, clean=True) 146 self.assertNotEqual(self.marionette.profile, self.profile) 147 self.assertNotEqual(self.marionette.session_id, self.session_id) 148 # A forced restart will cause a new process id 149 self.assertNotEqual(self.marionette.process_id, self.pid) 150 self.assertNotEqual( 151 self.marionette.get_pref("startup.homepage_welcome_url"), "about:about" 152 ) 153 154 def test_force_quit(self): 155 self.marionette.quit(in_app=False) 156 157 self.assertEqual(self.marionette.session, None) 158 with self.assertRaisesRegex( 159 errors.InvalidSessionIdException, "Please start a session" 160 ): 161 self.marionette.get_url() 162 163 def test_force_clean_quit(self): 164 self.marionette.quit(in_app=False, clean=True) 165 166 self.assertEqual(self.marionette.session, None) 167 with self.assertRaisesRegex( 168 errors.InvalidSessionIdException, "Please start a session" 169 ): 170 self.marionette.get_url() 171 172 self.marionette.start_session() 173 self.assertNotEqual(self.marionette.profile, self.profile) 174 self.assertNotEqual(self.marionette.session_id, self.session_id) 175 self.assertNotEqual( 176 self.marionette.get_pref("startup.homepage_welcome_url"), "about:about" 177 ) 178 179 def test_quit_no_in_app_and_clean(self): 180 # Test that in_app and clean cannot be used in combination 181 with self.assertRaisesRegex( 182 ValueError, "cannot be triggered with the clean flag set" 183 ): 184 self.marionette.quit(in_app=True, clean=True) 185 186 def test_restart_no_in_app_and_clean(self): 187 # Test that in_app and clean cannot be used in combination 188 with self.assertRaisesRegex( 189 ValueError, "cannot be triggered with the clean flag set" 190 ): 191 self.marionette.restart(in_app=True, clean=True) 192 193 def test_restart_preserves_requested_capabilities(self): 194 self.marionette.delete_session() 195 self.marionette.start_session(capabilities={"test:fooBar": True}) 196 197 self.marionette.restart(in_app=False) 198 self.assertEqual(self.marionette.session.get("test:fooBar"), True) 199 200 def test_restart_safe_mode(self): 201 try: 202 self.assertFalse(self.is_safe_mode, "Safe Mode is unexpectedly enabled") 203 self.marionette.restart(safe_mode=True) 204 self.assertTrue(self.is_safe_mode, "Safe Mode is not enabled") 205 finally: 206 self.marionette.quit(in_app=False, clean=True) 207 208 def test_restart_safe_mode_requires_in_app(self): 209 self.assertFalse(self.is_safe_mode, "Safe Mode is unexpectedly enabled") 210 211 with self.assertRaisesRegex(ValueError, "in_app restart is required"): 212 self.marionette.restart(in_app=False, safe_mode=True) 213 214 self.assertFalse(self.is_safe_mode, "Safe Mode is unexpectedly enabled") 215 self.marionette.quit(in_app=False, clean=True) 216 217 def test_in_app_restart(self): 218 details = self.marionette.restart() 219 self.assertTrue(details["in_app"], "Expected in_app restart") 220 self.assertFalse(details["forced"], "Expected non-forced shutdown") 221 222 self.assertEqual(self.marionette.profile, self.profile) 223 self.assertNotEqual(self.marionette.session_id, self.session_id) 224 225 self.assertNotEqual(self.marionette.process_id, self.pid) 226 227 self.assertNotEqual( 228 self.marionette.get_pref("startup.homepage_welcome_url"), "about:about" 229 ) 230 231 def test_in_app_restart_component_prevents_shutdown(self): 232 with self.marionette.using_context("chrome"): 233 self.marionette.execute_script( 234 """ 235 Services.obs.addObserver(subject => { 236 let cancelQuit = subject.QueryInterface(Ci.nsISupportsPRBool); 237 cancelQuit.data = true; 238 }, "quit-application-requested"); 239 """ 240 ) 241 242 details = self.marionette.restart() 243 self.assertTrue(details["in_app"], "Expected in_app restart") 244 self.assertTrue(details["forced"], "Expected forced shutdown") 245 246 self.assertEqual(self.marionette.profile, self.profile) 247 self.assertNotEqual(self.marionette.session_id, self.session_id) 248 249 self.assertNotEqual(self.marionette.process_id, self.pid) 250 251 self.assertNotEqual( 252 self.marionette.get_pref("startup.homepage_welcome_url"), "about:about" 253 ) 254 255 def test_in_app_restart_with_callback(self): 256 details = self.marionette.restart(callback=lambda: self.shutdown(restart=True)) 257 self.assertTrue(details["in_app"], "Expected in_app restart") 258 259 self.assertEqual(self.marionette.profile, self.profile) 260 self.assertNotEqual(self.marionette.session_id, self.session_id) 261 262 self.assertNotEqual(self.marionette.process_id, self.pid) 263 264 self.assertNotEqual( 265 self.marionette.get_pref("startup.homepage_welcome_url"), "about:about" 266 ) 267 268 def test_in_app_restart_with_non_callable_callback(self): 269 with self.assertRaisesRegex(ValueError, "is not callable"): 270 self.marionette.restart(callback=4) 271 272 self.assertEqual(self.marionette.instance.runner.returncode, None) 273 self.assertEqual(self.marionette.is_shutting_down, False) 274 275 @unittest.skipIf(sys.platform.startswith("win"), "Bug 1493796") 276 def test_in_app_restart_with_callback_but_process_quits_instead(self): 277 try: 278 timeout_shutdown = self.marionette.shutdown_timeout 279 timeout_startup = self.marionette.startup_timeout 280 self.marionette.shutdown_timeout = 5 281 self.marionette.startup_timeout = 0 282 283 with self.assertRaisesRegex( 284 IOError, "Process unexpectedly quit without restarting" 285 ): 286 self.marionette.restart(callback=lambda: self.shutdown(restart=False)) 287 finally: 288 self.marionette.shutdown_timeout = timeout_shutdown 289 self.marionette.startup_timeout = timeout_startup 290 291 @unittest.skipIf(sys.platform.startswith("win"), "Bug 1493796") 292 def test_in_app_restart_with_callback_missing_shutdown(self): 293 try: 294 timeout_shutdown = self.marionette.shutdown_timeout 295 timeout_startup = self.marionette.startup_timeout 296 self.marionette.shutdown_timeout = 5 297 self.marionette.startup_timeout = 0 298 299 with self.assertRaisesRegex( 300 IOError, "the connection to Marionette server is lost" 301 ): 302 self.marionette.restart(in_app=True, callback=lambda: False) 303 finally: 304 self.marionette.shutdown_timeout = timeout_shutdown 305 self.marionette.startup_timeout = timeout_startup 306 307 def test_in_app_restart_preserves_requested_capabilities(self): 308 self.marionette.delete_session() 309 self.marionette.start_session(capabilities={"test:fooBar": True}) 310 311 details = self.marionette.restart() 312 self.assertTrue(details["in_app"], "Expected in_app restart") 313 self.assertEqual(self.marionette.session.get("test:fooBar"), True) 314 315 @unittest.skipUnless(sys.platform.startswith("darwin"), "Only supported on MacOS") 316 def test_in_app_silent_restart_fails_without_windowless_flag_on_mac_os(self): 317 self.marionette.delete_session() 318 self.marionette.start_session() 319 320 with self.assertRaises(errors.UnsupportedOperationException): 321 self.marionette.restart(silent=True) 322 323 @unittest.skipUnless(sys.platform.startswith("darwin"), "Only supported on MacOS") 324 def test_in_app_silent_restart_windowless_flag_on_mac_os(self): 325 self.marionette.delete_session() 326 self.marionette.start_session(capabilities={"moz:windowless": True}) 327 328 self.marionette.restart(silent=True) 329 self.assertTrue(self.marionette.session_capabilities["moz:windowless"]) 330 331 self.marionette.restart() 332 self.assertTrue(self.marionette.session_capabilities["moz:windowless"]) 333 334 self.marionette.delete_session() 335 336 @unittest.skipUnless(sys.platform.startswith("darwin"), "Only supported on MacOS") 337 def test_in_app_silent_restart_requires_in_app(self): 338 self.marionette.delete_session() 339 self.marionette.start_session(capabilities={"moz:windowless": True}) 340 341 with self.assertRaisesRegex(ValueError, "in_app restart is required"): 342 self.marionette.restart(in_app=False, silent=True) 343 344 self.marionette.delete_session() 345 346 @unittest.skipIf( 347 sys.platform.startswith("darwin"), "Not supported on other platforms than MacOS" 348 ) 349 def test_in_app_silent_restart_windowless_flag_unsupported_platforms(self): 350 self.marionette.delete_session() 351 352 with self.assertRaises(errors.SessionNotCreatedException): 353 self.marionette.start_session(capabilities={"moz:windowless": True}) 354 355 def test_in_app_quit(self): 356 details = self.marionette.quit() 357 self.assertTrue(details["in_app"], "Expected in_app shutdown") 358 self.assertFalse(details["forced"], "Expected non-forced shutdown") 359 self.assertEqual(self.marionette.instance.runner.returncode, 0) 360 361 self.assertEqual(self.marionette.session, None) 362 with self.assertRaisesRegex( 363 errors.InvalidSessionIdException, "Please start a session" 364 ): 365 self.marionette.get_url() 366 367 self.marionette.start_session() 368 self.assertEqual(self.marionette.profile, self.profile) 369 self.assertNotEqual(self.marionette.session_id, self.session_id) 370 self.assertNotEqual( 371 self.marionette.get_pref("startup.homepage_welcome_url"), "about:about" 372 ) 373 374 def test_in_app_quit_forced_because_component_prevents_shutdown(self): 375 with self.marionette.using_context("chrome"): 376 self.marionette.execute_script( 377 """ 378 Services.obs.addObserver(subject => { 379 let cancelQuit = subject.QueryInterface(Ci.nsISupportsPRBool); 380 cancelQuit.data = true; 381 }, "quit-application-requested"); 382 """ 383 ) 384 385 details = self.marionette.quit() 386 self.assertTrue(details["in_app"], "Expected in_app shutdown") 387 self.assertTrue(details["forced"], "Expected forced shutdown") 388 self.assertEqual(self.marionette.instance.runner.returncode, 0) 389 390 self.assertEqual(self.marionette.session, None) 391 with self.assertRaisesRegex( 392 errors.InvalidSessionIdException, "Please start a session" 393 ): 394 self.marionette.get_url() 395 396 self.marionette.start_session() 397 self.assertEqual(self.marionette.profile, self.profile) 398 self.assertNotEqual(self.marionette.session_id, self.session_id) 399 self.assertNotEqual( 400 self.marionette.get_pref("startup.homepage_welcome_url"), "about:about" 401 ) 402 403 def test_in_app_quit_with_callback(self): 404 details = self.marionette.quit(callback=self.shutdown) 405 self.assertTrue(details["in_app"], "Expected in_app shutdown") 406 self.assertFalse(details["forced"], "Expected non-forced shutdown") 407 408 self.assertEqual(self.marionette.instance.runner.returncode, 0) 409 self.assertEqual(self.marionette.is_shutting_down, False) 410 411 self.assertEqual(self.marionette.session, None) 412 with self.assertRaisesRegex( 413 errors.InvalidSessionIdException, "Please start a session" 414 ): 415 self.marionette.get_url() 416 417 self.marionette.start_session() 418 self.assertEqual(self.marionette.profile, self.profile) 419 self.assertNotEqual(self.marionette.session_id, self.session_id) 420 self.assertNotEqual( 421 self.marionette.get_pref("startup.homepage_welcome_url"), "about:about" 422 ) 423 424 def test_in_app_quit_with_non_callable_callback(self): 425 with self.assertRaisesRegex(ValueError, "is not callable"): 426 self.marionette.quit(callback=4) 427 self.assertEqual(self.marionette.instance.runner.returncode, None) 428 self.assertEqual(self.marionette.is_shutting_down, False) 429 430 def test_in_app_quit_forced_because_callback_does_not_shutdown(self): 431 try: 432 timeout = self.marionette.shutdown_timeout 433 self.marionette.shutdown_timeout = 5 434 435 with self.assertRaisesRegex(IOError, "Process still running"): 436 self.marionette.quit(in_app=True, callback=lambda: False) 437 438 self.assertNotEqual(self.marionette.instance.runner.returncode, None) 439 self.assertEqual(self.marionette.is_shutting_down, False) 440 finally: 441 self.marionette.shutdown_timeout = timeout 442 443 self.marionette.start_session() 444 445 def test_in_app_quit_with_callback_that_raises_an_exception(self): 446 def errorneous_callback(): 447 raise Exception("foo") 448 449 with self.assertRaisesRegex(Exception, "foo"): 450 self.marionette.quit(in_app=True, callback=errorneous_callback) 451 self.assertEqual(self.marionette.instance.runner.returncode, None) 452 self.assertEqual(self.marionette.is_shutting_down, False) 453 454 self.assertIsNotNone(self.marionette.session) 455 self.marionette.current_window_handle 456 457 def test_in_app_quit_with_dismissed_beforeunload_prompt(self): 458 self.marionette.navigate( 459 inline( 460 """ 461 <input type="text"> 462 <script> 463 window.addEventListener("beforeunload", function (event) { 464 event.preventDefault(); 465 }); 466 </script> 467 """ 468 ) 469 ) 470 471 self.marionette.find_element(By.TAG_NAME, "input").send_keys("foo") 472 self.marionette.quit() 473 self.assertNotEqual(self.marionette.instance.runner.returncode, None) 474 self.marionette.start_session() 475 476 def test_reset_context_after_quit_by_set_context(self): 477 # Check that we are in content context which is used by default in 478 # Marionette 479 self.assertNotIn( 480 "chrome://", 481 self.marionette.get_url(), 482 "Context does not default to content", 483 ) 484 485 self.marionette.set_context("chrome") 486 self.marionette.quit() 487 self.assertEqual(self.marionette.session, None) 488 self.marionette.start_session() 489 self.assertNotIn( 490 "chrome://", 491 self.marionette.get_url(), 492 "Not in content context after quit with using_context", 493 ) 494 495 def test_reset_context_after_quit_by_using_context(self): 496 # Check that we are in content context which is used by default in 497 # Marionette 498 self.assertNotIn( 499 "chrome://", 500 self.marionette.get_url(), 501 "Context does not default to content", 502 ) 503 504 with self.marionette.using_context("chrome"): 505 self.marionette.quit() 506 self.assertEqual(self.marionette.session, None) 507 self.marionette.start_session() 508 self.assertNotIn( 509 "chrome://", 510 self.marionette.get_url(), 511 "Not in content context after quit with using_context", 512 ) 513 514 def test_keep_context_after_restart_by_set_context(self): 515 # Check that we are in content context which is used by default in 516 # Marionette 517 self.assertNotIn( 518 "chrome://", self.marionette.get_url(), "Context doesn't default to content" 519 ) 520 521 # restart while we are in chrome context 522 self.marionette.set_context("chrome") 523 self.marionette.restart() 524 525 self.assertNotEqual(self.marionette.process_id, self.pid) 526 527 self.assertIn( 528 "chrome://", 529 self.marionette.get_url(), 530 "Not in chrome context after a restart with set_context", 531 ) 532 533 def test_keep_context_after_restart_by_using_context(self): 534 # Check that we are in content context which is used by default in 535 # Marionette 536 self.assertNotIn( 537 "chrome://", 538 self.marionette.get_url(), 539 "Context does not default to content", 540 ) 541 542 # restart while we are in chrome context 543 with self.marionette.using_context("chrome"): 544 self.marionette.restart() 545 546 self.assertNotEqual(self.marionette.process_id, self.pid) 547 548 self.assertIn( 549 "chrome://", 550 self.marionette.get_url(), 551 "Not in chrome context after a restart with using_context", 552 )