test_no_errors_clean_profile.py (7594B)
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 time 6 from unittest.util import safe_repr 7 8 from marionette_driver.by import By 9 from marionette_driver.keys import Keys 10 from marionette_harness import MarionetteTestCase 11 12 # This list shouldn't exist! 13 # DO NOT ADD NEW EXCEPTIONS HERE! (unless they are specifically caused by 14 # being run under marionette rather than in a "real" profile, or only occur 15 # for browser developers) 16 # The only reason this exists is that when this test was written we already 17 # created a bunch of errors on startup, and it wasn't feasible to fix all 18 # of them before landing the test. 19 known_errors = [ 20 { 21 # Disabling Shield because app.normandy.api_url is not set. 22 # (Marionette-only error, bug 1826314) 23 "message": "app.normandy.api_url is not set", 24 }, 25 { 26 # From Remote settings, because it's intercepted by our test 27 # infrastructure which serves text/plain rather than JSON. 28 # Even if we fixed that we'd probably see a different error, 29 # unless we mock a full-blown remote settings server in the 30 # test infra, which doesn't seem worth it. 31 # Either way this wouldn't happen on "real" profiles. 32 "message": 'Error: Unexpected content-type "text/plain', 33 "filename": "RemoteSettingsClient", 34 }, 35 { 36 # Triggered as soon as anything tries to use shortcut keys. 37 # The browser toolbox shortcut is not portable. 38 "message": "key_browserToolbox", 39 }, 40 { 41 # Triggered as soon as anything tries to use shortcut keys. 42 # The developer-only restart shortcut is not portable. 43 "message": "key_quickRestart", 44 }, 45 { 46 # Triggered as soon as anything tries to use shortcut keys. 47 # The reader mode shortcut is not portable on Linux. 48 # Bug 1825431 to fix this. 49 "message": "key_toggleReaderMode", 50 }, 51 { 52 # Triggered as soon as anything tries to use shortcut keys. 53 # Bug 1936426 to reconsider warning as we want ctrl-alt-x for chatbot. 54 "message": "viewGenaiChatSidebarKb", 55 }, 56 { 57 # Triggered as soon as anything tries to use shortcut keys. 58 # Bug 1936426 to reconsider warning as we want ctrl-z / ctrl-alt-z 59 # for sidebar. 60 "message": "toggleSidebarKb", 61 }, 62 { 63 # Triggered as soon as anything tries to access window.fullScreen. 64 # Bug 1709294 to stop exposing window.fullScreen to the web content and 65 # the warning can be removed. 66 "message": 'JavaScript Warning: "Window.fullScreen attribute is deprecated and will be removed in the future."', 67 }, 68 ] 69 70 # Same rules apply here - please don't add anything! - but headless runs 71 # produce more errors that aren't normal in regular runs, so we've separated 72 # them out. 73 headless_errors = [{"message": "TelemetryEnvironment::_isDefaultBrowser"}] 74 75 76 class TestNoErrorsNewProfile(MarionetteTestCase): 77 def setUp(self): 78 super(MarionetteTestCase, self).setUp() 79 80 self.maxDiff = None 81 self.marionette.set_context("chrome") 82 83 # Create a fresh profile. 84 self.marionette.restart(in_app=False, clean=True) 85 86 def ensure_proper_startup(self): 87 # First wait for the browser to settle: 88 self.marionette.execute_async_script( 89 """ 90 let resolve = arguments[0]; 91 let { BrowserInitState } = ChromeUtils.importESModule("resource:///modules/BrowserGlue.sys.mjs"); 92 let promises = [ 93 BrowserInitState.startupIdleTaskPromise, 94 gBrowserInit.idleTasksFinished.promise, 95 ]; 96 Promise.all(promises).then(resolve); 97 """ 98 ) 99 100 if self.marionette.session_capabilities["platformName"] == "mac": 101 self.mod_key = Keys.META 102 else: 103 self.mod_key = Keys.CONTROL 104 # Focus the URL bar by keyboard 105 url_bar = self.marionette.execute_script("return gURLBar.inputField") 106 url_bar.send_keys(self.mod_key, "l") 107 # and open a tab by mouse: 108 new_tab_button = self.marionette.find_element(By.ID, "new-tab-button") 109 new_tab_button.click() 110 111 # Wait a bit more for async tasks to complete... 112 time.sleep(5) 113 114 def get_all_errors(self): 115 return self.marionette.execute_async_script( 116 """ 117 let resolve = arguments[0]; 118 // Get all the messages from the console service, 119 // and then get all of the ones from the console API storage. 120 let msgs = Services.console.getMessageArray(); 121 122 const ConsoleAPIStorage = Cc[ 123 "@mozilla.org/consoleAPI-storage;1" 124 ].getService(Ci.nsIConsoleAPIStorage); 125 const getCircularReplacer = () => { 126 const seen = new WeakSet(); 127 return (key, value) => { 128 if (typeof value === "object" && value !== null) { 129 if (seen.has(value)) { 130 return "<circular ref>"; 131 } 132 seen.add(value); 133 } 134 return value; 135 }; 136 }; 137 // Take cyclical values out, add a simplified 'message' prop 138 // that matches how things work for the console service objects. 139 const consoleApiMessages = ConsoleAPIStorage.getEvents().map(ev => { 140 let rv; 141 try { 142 rv = structuredClone(ev); 143 } catch (ex) { 144 rv = JSON.parse(JSON.stringify(ev, getCircularReplacer())); 145 } 146 delete rv.wrappedJSObject; 147 rv.message = ev.arguments.join(", "); 148 return rv; 149 }); 150 resolve(msgs.concat(consoleApiMessages)); 151 """ 152 ) 153 154 def should_ignore_error(self, error): 155 if not "message" in error: 156 print("Unparsable error:") 157 print(safe_repr(error)) 158 return False 159 160 error_filename = error.get("filename", "") 161 error_msg = error["message"] 162 headless = self.marionette.session_capabilities["moz:headless"] 163 all_known_errors = known_errors + (headless_errors if headless else []) 164 165 for known_error in all_known_errors: 166 known_filename = known_error.get("filename", "") 167 known_msg = known_error["message"] 168 if known_msg in error_msg and known_filename in error_filename: 169 print( 170 "Known error seen: %s (%s)" 171 % (error["message"], error.get("filename", "no filename")) 172 ) 173 return True 174 175 return False 176 177 def short_error_display(self, errors): 178 rv = [] 179 for error in errors: 180 rv += [ 181 { 182 "message": error.get("message", "No message!?"), 183 "filename": error.get("filename", "No filename!?"), 184 } 185 ] 186 return rv 187 188 def test_no_errors(self): 189 self.ensure_proper_startup() 190 errors = self.get_all_errors() 191 errors[:] = [error for error in errors if not self.should_ignore_error(error)] 192 if len(errors) > 0: 193 print("Unexpected errors encountered:") 194 # Hack to get nice printing: 195 for error in errors: 196 print(safe_repr(error)) 197 self.assertEqual(self.short_error_display(errors), [])