# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. import time from unittest.util import safe_repr from marionette_driver.by import By from marionette_driver.keys import Keys from marionette_harness import MarionetteTestCase # This list shouldn't exist! # DO NOT ADD NEW EXCEPTIONS HERE! (unless they are specifically caused by # being run under marionette rather than in a "real" profile, or only occur # for browser developers) # The only reason this exists is that when this test was written we already # created a bunch of errors on startup, and it wasn't feasible to fix all # of them before landing the test. known_errors = [ { # Disabling Shield because app.normandy.api_url is not set. # (Marionette-only error, bug 1826314) "message": "app.normandy.api_url is not set", }, { # From Remote settings, because it's intercepted by our test # infrastructure which serves text/plain rather than JSON. # Even if we fixed that we'd probably see a different error, # unless we mock a full-blown remote settings server in the # test infra, which doesn't seem worth it. # Either way this wouldn't happen on "real" profiles. "message": 'Error: Unexpected content-type "text/plain', "filename": "RemoteSettingsClient", }, { # Triggered as soon as anything tries to use shortcut keys. # The browser toolbox shortcut is not portable. "message": "key_browserToolbox", }, { # Triggered as soon as anything tries to use shortcut keys. # The developer-only restart shortcut is not portable. "message": "key_quickRestart", }, { # Triggered as soon as anything tries to use shortcut keys. # The reader mode shortcut is not portable on Linux. # Bug 1825431 to fix this. "message": "key_toggleReaderMode", }, { # Triggered on Linux because it doesn't implement the # secondsSinceLastOSRestart property at all. "message": "(NS_ERROR_NOT_IMPLEMENTED) [nsIAppStartup.secondsSinceLastOSRestart]", "filename": "BrowserGlue", }, ] # Same rules apply here - please don't add anything! - but headless runs # produce more errors that aren't normal in regular runs, so we've separated # them out. headless_errors = [{"message": "TelemetryEnvironment::_isDefaultBrowser"}] class TestNoErrorsNewProfile(MarionetteTestCase): def setUp(self): super(MarionetteTestCase, self).setUp() self.maxDiff = None self.marionette.set_context("chrome") # Create a fresh profile. self.marionette.restart(in_app=False, clean=True) def ensure_proper_startup(self): # First wait for the browser to settle: self.marionette.execute_async_script( """ let resolve = arguments[0]; let { BrowserInitState } = ChromeUtils.importESModule("resource:///modules/BrowserGlue.sys.mjs"); let promises = [ BrowserInitState.startupIdleTaskPromise, gBrowserInit.idleTasksFinishedPromise, ]; Promise.all(promises).then(resolve); """ ) if self.marionette.session_capabilities["platformName"] == "mac": self.mod_key = Keys.META else: self.mod_key = Keys.CONTROL # Focus the URL bar by keyboard url_bar = self.marionette.find_element(By.ID, "urlbar-input") url_bar.send_keys(self.mod_key, "l") # and open a tab by mouse: new_tab_button = self.marionette.find_element(By.ID, "new-tab-button") new_tab_button.click() # Wait a bit more for async tasks to complete... time.sleep(5) def get_all_errors(self): return self.marionette.execute_async_script( """ let resolve = arguments[0]; // Get all the messages from the console service, // and then get all of the ones from the console API storage. let msgs = Services.console.getMessageArray(); const ConsoleAPIStorage = Cc[ "@mozilla.org/consoleAPI-storage;1" ].getService(Ci.nsIConsoleAPIStorage); const getCircularReplacer = () => { const seen = new WeakSet(); return (key, value) => { if (typeof value === "object" && value !== null) { if (seen.has(value)) { return ""; } seen.add(value); } return value; }; }; // Take cyclical values out, add a simplified 'message' prop // that matches how things work for the console service objects. const consoleApiMessages = ConsoleAPIStorage.getEvents().map(ev => { let rv; try { rv = structuredClone(ev); } catch (ex) { rv = JSON.parse(JSON.stringify(ev, getCircularReplacer())); } delete rv.wrappedJSObject; rv.message = ev.arguments.join(", "); return rv; }); resolve(msgs.concat(consoleApiMessages)); """ ) def should_ignore_error(self, error): if not "message" in error: print("Unparsable error:") print(safe_repr(error)) return False error_filename = error.get("filename", "") error_msg = error["message"] headless = self.marionette.session_capabilities["moz:headless"] all_known_errors = known_errors + (headless_errors if headless else []) for known_error in all_known_errors: known_filename = known_error.get("filename", "") known_msg = known_error["message"] if known_msg in error_msg and known_filename in error_filename: print( "Known error seen: %s (%s)" % (error["message"], error.get("filename", "no filename")) ) return True return False def short_error_display(self, errors): rv = [] for error in errors: rv += [ { "message": error.get("message", "No message!?"), "filename": error.get("filename", "No filename!?"), } ] return rv def test_no_errors(self): self.ensure_proper_startup() errors = self.get_all_errors() errors[:] = [error for error in errors if not self.should_ignore_error(error)] if len(errors) > 0: print("Unexpected errors encountered:") # Hack to get nice printing: for error in errors: print(safe_repr(error)) self.assertEqual(self.short_error_display(errors), [])