def main(options): print "Setting up CertTest app to device" dm = None if options.adb_path: dm = mozdevice.DeviceManagerADB(adbPath=options.adb_path) else: dm = mozdevice.DeviceManagerADB() if dm.dirExists("/data/local/webapps/certtest-app"): print "CertTest app is already installed" return dm.pushFile("certtest_app.zip", "/data/local/certtest_app.zip") # forward the marionette port print "Forwarding marionette port" ret = dm.forward("tcp:2828", "tcp:2828") if ret != 0: #TODO: right thing here is to keep trying local ports and pass that value in our config raise Exception("Can't use localhost:2828 for port forwarding. Is something else using port 2828?") # install the app print "installing the app" f = open("app_install.js", "r") script = f.read() f.close() m = Marionette() m.start_session() m.set_context("chrome") m.set_script_timeout(5000) m.execute_async_script(script) m.delete_session()
class TestConsoleLogCapture(): def setup(self): try: self.client = Marionette(host='localhost', port=2828) self.client.start_session() self.client.set_pref('general.warnOnAboutConfig', False) except: sys.exit("Could not find Firefox browser running") def test_push_notification_received(self): self.client.navigate( "https://people.mozilla.org/~ewong2/push-notification-test/") unregister_button = self.client.find_element(By.ID, "unreg_btn") if unregister_button.is_displayed() == True: unregister_button.click() Wait(self.client, timeout=5, interval=1).until( expected.element_not_displayed(By.ID, "unreg_btn")) Wait(self.client).until(expected.element_displayed(By.ID, "reg_btn")) self.client.find_element(By.ID, "reg_btn").click() Wait(self.client).until( expected.element_displayed(By.ID, "subscribe_btn")) self.client.find_element(By.ID, "subscribe_btn").click() Wait(self.client).until(expected.element_displayed(By.ID, "doXhr_btn")) self.client.find_element(By.ID, "doXhr_btn").click() result = self.web_console_filter_for_string("Received a push message") assert result == 1 def web_console_filter_for_string(self, console_string=None): self.client.set_context(self.client.CONTEXT_CHROME) handles = self.client.window_handles chrome_handles = self.client.chrome_window_handles browser_handle = self.client.current_chrome_window_handle notifications = 0 for handle in chrome_handles: if handle != browser_handle: console_handle = handle self.client.switch_to_window(console_handle) time.sleep(1) results = self.client.find_elements(By.CLASS_NAME, "console-string") for result in results: if console_string in result.text: notifications = notifications + 1 self.client.find_element(By.CLASS_NAME, "webconsole-clear-console-button").click() return notifications def tear_down(self): self.client.close()
class TestConsoleLogCapture(): def setup(self): try: self.client = Marionette(host='localhost', port=2828) self.client.start_session() self.client.set_pref('general.warnOnAboutConfig', False) except: sys.exit("Could not find Firefox browser running") def test_push_notification_received(self): self.client.navigate("https://people.mozilla.org/~ewong2/push-notification-test/") unregister_button = self.client.find_element(By.ID, "unreg_btn") if unregister_button.is_displayed() == True: unregister_button.click() Wait(self.client, timeout=5, interval=1).until(expected.element_not_displayed(By.ID, "unreg_btn")) Wait(self.client).until(expected.element_displayed(By.ID, "reg_btn")) self.client.find_element(By.ID, "reg_btn").click() Wait(self.client).until(expected.element_displayed(By.ID, "subscribe_btn")) self.client.find_element(By.ID, "subscribe_btn").click() Wait(self.client).until(expected.element_displayed(By.ID, "doXhr_btn")) self.client.find_element(By.ID, "doXhr_btn").click() result = self.web_console_filter_for_string("Received a push message") assert result == 1 def web_console_filter_for_string(self, console_string=None): self.client.set_context(self.client.CONTEXT_CHROME) handles = self.client.window_handles chrome_handles = self.client.chrome_window_handles browser_handle = self.client.current_chrome_window_handle notifications = 0 for handle in chrome_handles: if handle != browser_handle: console_handle = handle self.client.switch_to_window(console_handle) time.sleep(1) results = self.client.find_elements(By.CLASS_NAME, "console-string") for result in results: if console_string in result.text: notifications = notifications + 1 self.client.find_element(By.CLASS_NAME, "webconsole-clear-console-button").click() return notifications def tear_down(self): self.client.close()
def install_app(app_name, app_path, adb_path="adb", script_timeout=5000, marionette=None, device_serial=None): """ This installs the given application. NOTE: if a marionette session is passed, this function switches to 'content' context and will be at the top-most frame. """ if is_installed(app_name, adb_path=adb_path, device_serial=device_serial): raise Exception("%s is already installed" % app_name) sys.exit(1) app_zip = os.path.basename(app_path) dm = mozdevice.DeviceManagerADB(adbPath=adb_path, deviceSerial=device_serial) dm.pushFile("%s" % app_path, "/data/local/%s" % app_zip) # forward the marionette port dm.forward("tcp:2828", "tcp:2828") # install the app install_js = pkg_resources.resource_filename( __name__, os.path.sep.join(['app_install.js'])) with open(install_js, "r") as f: script = f.read() installed_app_name = app_name.lower().replace(" ", "-") script = script.replace("YOURAPPID", installed_app_name) script = script.replace("YOURAPPZIP", app_zip) if not marionette: m = Marionette() m.start_session() else: m = marionette m.switch_to_frame() m.set_context("chrome") m.set_script_timeout(script_timeout) m.execute_async_script(script) if not marionette: m.delete_session() else: m.set_context("content")
def install_app(app_name, app_path, adb_path="adb", script_timeout=5000, marionette=None): """ This installs the given application. NOTE: if a marionette session is passed, this function switches to 'content' context and will be at the top-most frame. """ if is_installed(app_name, adb_path=adb_path): raise Exception("%s is already installed" % app_name) sys.exit(1) app_zip = os.path.basename(app_path) dm = mozdevice.DeviceManagerADB(adbPath=adb_path) dm.pushFile("%s" % app_path, "/data/local/%s" % app_zip) # forward the marionette port if dm.forward("tcp:2828", "tcp:2828") != 0: raise Exception("Can't use localhost:2828 for port forwarding." \ "Is something else using port 2828?") # install the app install_js = pkg_resources.resource_filename(__name__, os.path.sep.join([ 'app_install.js'])) with open(install_js, "r") as f: script = f.read() installed_app_name = app_name.lower().replace(" ", "-") script = script.replace("YOURAPPID", installed_app_name) script = script.replace("YOURAPPZIP", app_zip) if not marionette: m = Marionette() m.start_session() else: m = marionette m.switch_to_frame() m.set_context("chrome") m.set_script_timeout(script_timeout) m.execute_async_script(script) if not marionette: m.delete_session() else: m.set_context("content")
def listapps(): marionette = Marionette(host='localhost', port=2828) marionette.start_session() marionette.set_context(marionette.CONTEXT_CONTENT) marionette.set_script_timeout(1000) apps = marionette.execute_async_script(""" let req = navigator.mozApps.mgmt.getAll(); req.onsuccess = function() { let apps = req.result; let l = [] for (let a of apps) { let data = {origin: a.origin, name: a.manifest.name}; if (a.manifest.entry_points) data.entry_points = a.manifest.entry_points; l.push(data); } marionetteScriptFinished(l); }; """) for a in apps: print a["name"]
def install_app(app_name, app_path, adb_path=None): dm = None if adb_path: dm = mozdevice.DeviceManagerADB(adbPath=adb_path) else: dm = mozdevice.DeviceManagerADB() #TODO: replace with app name installed_app_name = app_name.lower() installed_app_name = installed_app_name.replace(" ", "-") if dm.dirExists("/data/local/webapps/%s" % installed_app_name): raise Exception("%s is already installed" % app_name) sys.exit(1) app_zip = os.path.basename(app_path) dm.pushFile("%s" % app_path, "/data/local/%s" % app_zip) # forward the marionette port ret = dm.forward("tcp:2828", "tcp:2828") if ret != 0: raise Exception("Can't use localhost:2828 for port forwarding." \ "Is something else using port 2828?") # install the app install_js = pkg_resources.resource_filename(__name__, os.path.sep.join([ 'app_install.js'])) f = open(install_js, "r") script = f.read() f.close() script = script.replace("YOURAPPID", installed_app_name) script = script.replace("YOURAPPZIP", app_zip) m = Marionette() m.start_session() m.set_context("chrome") m.set_script_timeout(5000) m.execute_async_script(script) m.delete_session()
class MatsRunner(object): def __init__(self, config_file = 'config.ini', url = 'about:blank'): self.config_file = config_file self.url = url def start(self): ''' This method starts MATS. ''' print 'Using ' + MatsController.__name__ + ' as controller.' print 'Loading config from "' + self.config_file + '"...', self.config = get_config(self.config_file) #get_config makes sure that the config makes sense. More details in get_config.py self.marionette_port = self.config['Marionette']['port'] print 'OK' print 'Starting Firefox/Nightly from "' + self.config['Firefox']['binary'] + '" with Marionette on port ' + str(self.marionette_port) + '.' self.FirefoxThread = FirefoxThread(self.config['Firefox']['binary'], self.marionette_port) self.FirefoxThread.start() print 'Creating controller' pid = self.FirefoxThread.getPID() # this function blocks until PID is available from FirefoxThread self.controller = MatsController(pid) print 'Starting controller' self.controller.start() self.controller.wait_for_ready() print 'Waiting for Marionette port to open (' + str(self.marionette_port) + ')' portReady = self.FirefoxThread.waitForMarionettePortOpenReady(self.config['Marionette']['port_timeout']) if portReady: print 'Marionette port open' else: print 'Error: timeout, shutting down MATS' self.controller.stop() self.FirefoxThread.stop() return #TODO: remove line below once https://bugzilla.mozilla.org/show_bug.cgi?id=753273 is fixed #sleep(10) try: print 'Starting Marionette' self.marionette = Marionette('localhost', self.marionette_port) #TODO: move starting session and navigation to separate methods print 'Starting session' sleep(5) #TODO temporary workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=757078 self.marionette_session = self.marionette.start_session() print 'Navigating to ' + self.url print self.marionette.navigate(self.url) except Exception as e: print 'Error starting Marionette' fall(e) self.controller.stop() self.FirefoxThread.stop() print 'MATS up and running. Waiting until Firefox/Nightly stops.' def wait_for_stop(self): self.FirefoxThread.join() print 'Stopping controller' self.controller.stop() self.controller.join() print 'MATS runner finishes.' def stop(self): self.FirefoxThread.stop() self.FirefoxThread.join() print 'Stopping controller' self.controller.stop() self.controller.join() print 'MATS runner finishes.' def instantiate_a11y(self): ''' runs via marionette script taken from http://dxr.lanedo.com/search.cgi?tree=mozilla-central&string=nsIAccessibleApplication to force A11y engine instantiation. sets context to content after doing it's stuff ''' script = \ ''' const nsIAccessibleRetrieval = Components.interfaces.nsIAccessibleRetrieval; const nsIAccessibleApplication = Components.interfaces.nsIAccessibleApplication; var gAccRetrieval = Components.classes["@mozilla.org/accessibleRetrieval;1"]. getService(nsIAccessibleRetrieval); app = gAccRetrieval.getApplicationAccessible(). QueryInterface(nsIAccessibleApplication); return (app) ? true : false; ''' self.marionette.set_context("chrome") notNull = self.marionette.execute_script(script) self.marionette.set_context("content") return notNull def is_a11y_instantiated(self): ''' runs via marionette script that checks if A11y engine is instantiated. and returns true if so. sets context to content after doing it's stuff ''' script = \ ''' var enabled; enabled = Components.manager.QueryInterface(Ci.nsIServiceManager) .isServiceInstantiatedByContractID( "@mozilla.org/accessibilityService;1", Ci.nsISupports); return enabled; ''' self.marionette.set_context('chrome') result = self.marionette.execute_script(script) self.marionette.set_context('content') return result def wait_for_event(self, event_string, callable, timeout = 60): ''' this method is the easiest interface to wait for an event. First, it registers listener. Second, it calls callable (TODO: add arguments) Third, it waits for the event or timeouts it returns True if the event was captured, and False if timeout occured TODO: abstract it to cross-platform ''' arrived = threading.Event() def callback(event): print 'got event! ' + str(event) arrived.set() self.controller.register_event_listener(event_string, callback) self.controller.unpause_event_loop() callable() result = arrived.wait(timeout) self.controller.pause_event_loop() self.controller.deregister_event_listener(event_string, callback) return result
class B2GMochitest(MochitestUtilsMixin): marionette = None def __init__(self, marionette_args, out_of_process=True, profile_data_dir=None, locations=os.path.join(here, 'server-locations.txt')): super(B2GMochitest, self).__init__() self.marionette_args = marionette_args self.out_of_process = out_of_process self.locations_file = locations self.preferences = [] self.webapps = None self.test_script = os.path.join(here, 'b2g_start_script.js') self.test_script_args = [self.out_of_process] self.product = 'b2g' # structured logging self.message_logger = MessageLogger(logger=log) if profile_data_dir: self.preferences = [os.path.join(profile_data_dir, f) for f in os.listdir(profile_data_dir) if f.startswith('pref')] self.webapps = [os.path.join(profile_data_dir, f) for f in os.listdir(profile_data_dir) if f.startswith('webapp')] # mozinfo is populated by the parent class if mozinfo.info['debug']: self.SERVER_STARTUP_TIMEOUT = 180 else: self.SERVER_STARTUP_TIMEOUT = 90 def setup_common_options(self, options): test_url = self.buildTestPath(options) # For B2G emulators buildURLOptions has been called # without calling buildTestPath first and that # causes manifestFile not to be set if not "manifestFile=tests.json" in self.urlOpts: self.urlOpts.append("manifestFile=%s" % options.manifestFile) if len(self.urlOpts) > 0: test_url += "?" + "&".join(self.urlOpts) self.test_script_args.append(test_url) def buildTestPath(self, options, testsToFilter=None): if options.manifestFile != 'tests.json': super(B2GMochitest, self).buildTestPath(options, testsToFilter, disabled=False) return self.buildTestURL(options) def build_profile(self, options): # preferences prefs = {} for path in self.preferences: prefs.update(Preferences.read_prefs(path)) for v in options.extraPrefs: thispref = v.split("=", 1) if len(thispref) < 2: print "Error: syntax error in --setpref=" + v sys.exit(1) prefs[thispref[0]] = thispref[1] # interpolate the preferences interpolation = { "server": "%s:%s" % (options.webServer, options.httpPort), "OOP": "true" if self.out_of_process else "false" } prefs = json.loads(json.dumps(prefs) % interpolation) for pref in prefs: prefs[pref] = Preferences.cast(prefs[pref]) kwargs = { 'addons': self.getExtensionsToInstall(options), 'apps': self.webapps, 'locations': self.locations_file, 'preferences': prefs, 'proxy': {"remote": options.webServer} } if options.profile: self.profile = Profile.clone(options.profile, **kwargs) else: self.profile = Profile(**kwargs) options.profilePath = self.profile.profile # TODO bug 839108 - mozprofile should probably handle this manifest = self.addChromeToProfile(options) self.copyExtraFilesToProfile(options) return manifest def run_tests(self, options): """ Prepare, configure, run tests and cleanup """ manifest = self.build_profile(options) self.leak_report_file = os.path.join(options.profilePath, "runtests_leaks.log") # configuring the message logger's buffering self.message_logger.buffering = options.quiet if options.debugger or not options.autorun: timeout = None else: if not options.timeout: if mozinfo.info['debug']: options.timeout = 420 else: options.timeout = 300 timeout = options.timeout + 30.0 log.info("runtestsb2g.py | Running tests: start.") status = 0 try: def on_output(line): messages = self.message_logger.write(line) for message in messages: if message['action'] == 'test_start': self.runner.last_test = message['test'] # The logging will be handled by on_output, so we set the stream to None process_args = {'processOutputLine': on_output, 'stream': None} self.marionette_args['process_args'] = process_args self.marionette_args['profile'] = self.profile self.marionette = Marionette(**self.marionette_args) self.runner = self.marionette.runner self.app_ctx = self.runner.app_ctx self.remote_log = posixpath.join(self.app_ctx.remote_test_root, 'log', 'mochitest.log') if not self.app_ctx.dm.dirExists(posixpath.dirname(self.remote_log)): self.app_ctx.dm.mkDirs(self.remote_log) self.startServers(options, None) self.buildURLOptions(options, {'MOZ_HIDE_RESULTS_TABLE': '1'}) self.test_script_args.append(not options.emulator) self.test_script_args.append(options.wifi) self.runner.start(outputTimeout=timeout) self.marionette.wait_for_port() self.marionette.start_session() self.marionette.set_context(self.marionette.CONTEXT_CHROME) # Disable offline status management (bug 777145), otherwise the network # will be 'offline' when the mochitests start. Presumably, the network # won't be offline on a real device, so we only do this for emulators. self.marionette.execute_script(""" Components.utils.import("resource://gre/modules/Services.jsm"); Services.io.manageOfflineStatus = false; Services.io.offline = false; """) if os.path.isfile(self.test_script): with open(self.test_script, 'r') as script: self.marionette.execute_script(script.read(), script_args=self.test_script_args) else: self.marionette.execute_script(self.test_script, script_args=self.test_script_args) status = self.runner.wait() if status is None: # the runner has timed out status = 124 except KeyboardInterrupt: log.info("runtests.py | Received keyboard interrupt.\n"); status = -1 except: traceback.print_exc() log.error("Automation Error: Received unexpected exception while running application\n") if hasattr(self, 'runner'): self.runner.check_for_crashes() status = 1 self.stopServers() log.info("runtestsb2g.py | Running tests: end.") if manifest is not None: self.cleanup(manifest, options) return status def getGMPPluginPath(self, options): # TODO: bug 1043403 return None
class B2GMixin(object): profileDir = None userJS = "/data/local/user.js" marionette = None def __init__(self, host=None, marionetteHost=None, marionettePort=2828, **kwargs): # (allowing marionneteHost to be specified seems a bit # counter-intuitive since we normally get it below from the ip # address, however we currently need it to be able to connect # via adb port forwarding and localhost) if marionetteHost: self.marionetteHost = marionetteHost elif host: self.marionetteHost = host self.marionettePort = marionettePort def cleanup(self): if self.profileDir: self.restoreProfile() def waitForPort(self, timeout): """ Wait for the marionette server to respond. Timeout parameter is in seconds """ print "waiting for port" starttime = datetime.datetime.now() while datetime.datetime.now() - starttime < datetime.timedelta(seconds=timeout): try: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) print "trying %s %s" % (self.marionettePort, self.marionetteHost) sock.connect((self.marionetteHost, self.marionettePort)) data = sock.recv(16) sock.close() if '"from"' in data: return True except socket.error: pass except Exception as e: raise DMError("Could not connect to marionette: %s" % e) time.sleep(1) raise DMError("Could not communicate with Marionette port") def setupMarionette(self): """ Start a marionette session. If no host is given, then this will get the ip of the device, and set up networking if needed. """ if not self.marionetteHost: self.setupDHCP() self.marionetteHost = self.getIP() if not self.marionette: self.marionette = Marionette(self.marionetteHost, self.marionettePort) if not self.marionette.session: self.waitForPort(30) self.marionette.start_session() def restartB2G(self): """ Restarts the b2g process on the device """ #restart b2g so we start with a clean slate if self.marionette and self.marionette.session: self.marionette.delete_session() self.shellCheckOutput(['stop', 'b2g']) # Wait for a bit to make sure B2G has completely shut down. tries = 10 while "b2g" in self.shellCheckOutput(['ps', 'b2g']) and tries > 0: tries -= 1 time.sleep(1) if tries == 0: raise DMError("Could not kill b2g process") self.shellCheckOutput(['start', 'b2g']) def setupProfile(self, prefs=None): """ Sets up the user profile on the device, The 'prefs' is a string of user_prefs to add to the profile. If it is not set, it will default to a standard b2g testing profile. """ if not prefs: prefs = """ user_pref("power.screen.timeout", 999999); user_pref("devtools.debugger.force-local", false); """ #remove previous user.js if there is one if not self.profileDir: self.profileDir = tempfile.mkdtemp() our_userJS = os.path.join(self.profileDir, "user.js") if os.path.exists(our_userJS): os.remove(our_userJS) #copy profile try: output = self.getFile(self.userJS, our_userJS) except subprocess.CalledProcessError: pass #if we successfully copied the profile, make a backup of the file if os.path.exists(our_userJS): self.shellCheckOutput(['dd', 'if=%s' % self.userJS, 'of=%s.orig' % self.userJS]) with open(our_userJS, 'a') as user_file: user_file.write("%s" % prefs) self.pushFile(our_userJS, self.userJS) self.restartB2G() self.setupMarionette() def setupDHCP(self, conn_type='eth0'): """ Sets up networking. If conn_type is not set, it will assume eth0. """ tries = 5 while tries > 0: print "attempts left: %d" % tries try: self.shellCheckOutput(['netcfg', conn_type, 'dhcp'], timeout=10) if self.getIP(): return except DMError: pass tries = tries - 1 raise DMError("Could not set up network connection") def restoreProfile(self): """ Restores the original profile """ if not self.profileDir: raise DMError("There is no profile to restore") #if we successfully copied the profile, make a backup of the file our_userJS = os.path.join(self.profileDir, "user.js") if os.path.exists(our_userJS): self.shellCheckOutput(['dd', 'if=%s.orig' % self.userJS, 'of=%s' % self.userJS]) shutil.rmtree(self.profileDir) self.profileDir = None def getAppInfo(self): """ Returns the appinfo, with an additional "date" key. """ if not self.marionette or not self.marionette.session: self.setupMarionette() self.marionette.set_context("chrome") appinfo = self.marionette.execute_script(""" var appInfo = Components.classes["@mozilla.org/xre/app-info;1"] .getService(Components.interfaces.nsIXULAppInfo); return appInfo; """) (year, month, day) = (appinfo["appBuildID"][0:4], appinfo["appBuildID"][4:6], appinfo["appBuildID"][6:8]) appinfo['date'] = "%s-%s-%s" % (year, month, day) return appinfo
class B2GManager(object): def __init__(self, dm, tmpdir=None, userJS=None, marionette_host=None, marionette_port=None): self.dm = dm self.tmpdir = tmpdir self.userJS = userJS or "/data/local/user.js" self.marionette_host = marionette_host or 'localhost' self.marionette_port = marionette_port or 2828 self.marionette = None #timeout in seconds def wait_for_port(self, timeout): print "waiting for port" starttime = datetime.datetime.now() while datetime.datetime.now() - starttime < datetime.timedelta(seconds=timeout): try: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) print "trying %s %s" % (self.marionette_port, self.marionette_host) sock.connect((self.marionette_host, self.marionette_port)) data = sock.recv(16) sock.close() if '"from"' in data: print "got it" return True except: import traceback print traceback.format_exc() time.sleep(1) return False def get_marionette(self): self.marionette = Marionette(self.marionette_host, self.marionette_port) def restart_b2g(self): #restart b2g so we start with a clean slate self.dm.checkCmd(['shell', 'stop', 'b2g']) # Wait for a bit to make sure B2G has completely shut down. time.sleep(10) self.dm.checkCmd(['shell', 'start', 'b2g']) #wait for marionette port to come up print "connect to marionette" if not self.wait_for_port(30): raise Exception("Could not communicate with Marionette port after restarting B2G") self.get_marionette() def set_tmpdir(self, tmpdir): self.tmpdir = tmpdir def setup_profile(self, prefs): if not self.tmpdir: raise Exception("You must set the tmpdir") #remove previous user.js if there is one our_user_js = os.path.join(self.tmpdir, "user.js") if os.path.exists(our_user_js): os.remove(our_user_js) #copy profile try: self.dm.checkCmd(["pull", self.userJS, our_user_js]) except subprocess.CalledProcessError: pass #if we successfully copied the profile, make a backup of the file if os.path.exists(our_user_js): self.dm.checkCmd(['shell', 'dd', 'if=%s' % self.userJS, 'of=%s.orig' % self.userJS]) print "opening userjs" user_js = open(our_user_js, 'a') print "Writing: %s" % prefs user_js.write("%s" % prefs) print "closing" user_js.close() self.dm.checkCmd(['push', our_user_js, self.userJS]) self.restart_b2g() def forward_port(self): self.dm.checkCmd(['forward', 'tcp:%s' % self.marionette_port, 'tcp:%s' % self.marionette_port]) def setup_ethernet(self): #TODO: need to add timeout tries = 3 while tries > 0: print "on try: %d" % tries output = StringIO.StringIO() self.dm.shell(['ifconfig', 'eth0'], output) print "we get back %s" % output.getvalue() if "ip" in output.getvalue(): return output.close() try: self.dm.checkCmd(['shell', 'netcfg', 'eth0', 'dhcp'], timeout=10) except DMError: pass tries = tries - 1 raise DMError("Could not set up ethernet connection") def restore_profile(self): if not self.tmpdir: raise Exception("You must set the tmpdir") #if we successfully copied the profile, make a backup of the file our_user_js = os.path.join(self.tmpdir, "user.js") if os.path.exists(our_user_js): self.dm.checkCmd(['shell', 'dd', 'if=%s.orig' % self.userJS, 'of=%s' % self.userJS]) def get_appinfo(self): if not self.marionette: self.forward_port() self.wait_for_port(30) self.get_marionette() self.marionette.start_session() self.marionette.set_context("chrome") appinfo = self.marionette.execute_script(""" var appInfo = Components.classes["@mozilla.org/xre/app-info;1"] .getService(Components.interfaces.nsIXULAppInfo); return appInfo; """) (year, month, day) = (appinfo["appBuildID"][0:4], appinfo["appBuildID"][4:6], appinfo["appBuildID"][6:8]) appinfo['date'] = "%s-%s-%s" % (year, month, day) return appinfo
class B2GMochitest(MochitestUtilsMixin): marionette = None def __init__(self, marionette_args, out_of_process=True, profile_data_dir=None, locations=os.path.join(here, 'server-locations.txt')): super(B2GMochitest, self).__init__() self.marionette_args = marionette_args self.out_of_process = out_of_process self.locations_file = locations self.preferences = [] self.webapps = None self.test_script = os.path.join(here, 'b2g_start_script.js') self.test_script_args = [self.out_of_process] self.product = 'b2g' if profile_data_dir: self.preferences = [ os.path.join(profile_data_dir, f) for f in os.listdir(profile_data_dir) if f.startswith('pref') ] self.webapps = [ os.path.join(profile_data_dir, f) for f in os.listdir(profile_data_dir) if f.startswith('webapp') ] # mozinfo is populated by the parent class if mozinfo.info['debug']: self.SERVER_STARTUP_TIMEOUT = 180 else: self.SERVER_STARTUP_TIMEOUT = 90 def setup_common_options(self, options): test_url = self.buildTestPath(options) # For B2G emulators buildURLOptions has been called # without calling buildTestPath first and that # causes manifestFile not to be set if not "manifestFile=tests.json" in self.urlOpts: self.urlOpts.append("manifestFile=%s" % options.manifestFile) if len(self.urlOpts) > 0: test_url += "?" + "&".join(self.urlOpts) self.test_script_args.append(test_url) def buildTestPath(self, options): if options.manifestFile != 'tests.json': super(B2GMochitest, self).buildTestPath(options, disabled=False) return self.buildTestURL(options) def build_profile(self, options): # preferences prefs = {} for path in self.preferences: prefs.update(Preferences.read_prefs(path)) for v in options.extraPrefs: thispref = v.split("=", 1) if len(thispref) < 2: print "Error: syntax error in --setpref=" + v sys.exit(1) prefs[thispref[0]] = thispref[1] # interpolate the preferences interpolation = { "server": "%s:%s" % (options.webServer, options.httpPort), "OOP": "true" if self.out_of_process else "false" } prefs = json.loads(json.dumps(prefs) % interpolation) for pref in prefs: prefs[pref] = Preferences.cast(prefs[pref]) kwargs = { 'addons': self.getExtensionsToInstall(options), 'apps': self.webapps, 'locations': self.locations_file, 'preferences': prefs, 'proxy': { "remote": options.webServer } } if options.profile: self.profile = Profile.clone(options.profile, **kwargs) else: self.profile = Profile(**kwargs) options.profilePath = self.profile.profile # TODO bug 839108 - mozprofile should probably handle this manifest = self.addChromeToProfile(options) self.copyExtraFilesToProfile(options) return manifest def run_tests(self, options): """ Prepare, configure, run tests and cleanup """ manifest = self.build_profile(options) self.leak_report_file = os.path.join(options.profilePath, "runtests_leaks.log") if options.debugger or not options.autorun: timeout = None else: if not options.timeout: if mozinfo.info['debug']: options.timeout = 420 else: options.timeout = 300 timeout = options.timeout + 30.0 log.info("runtestsb2g.py | Running tests: start.") status = 0 try: self.marionette_args['profile'] = self.profile self.marionette = Marionette(**self.marionette_args) self.runner = self.marionette.runner self.app_ctx = self.runner.app_ctx self.remote_log = posixpath.join(self.app_ctx.remote_test_root, 'log', 'mochitest.log') if not self.app_ctx.dm.dirExists(posixpath.dirname( self.remote_log)): self.app_ctx.dm.mkDirs(self.remote_log) self.startServers(options, None) self.buildURLOptions(options, {'MOZ_HIDE_RESULTS_TABLE': '1'}) self.test_script_args.append(not options.emulator) self.test_script_args.append(options.wifi) self.runner.start(outputTimeout=timeout) self.marionette.wait_for_port() self.marionette.start_session() self.marionette.set_context(self.marionette.CONTEXT_CHROME) # Disable offline status management (bug 777145), otherwise the network # will be 'offline' when the mochitests start. Presumably, the network # won't be offline on a real device, so we only do this for emulators. self.marionette.execute_script(""" Components.utils.import("resource://gre/modules/Services.jsm"); Services.io.manageOfflineStatus = false; Services.io.offline = false; """) if os.path.isfile(self.test_script): with open(self.test_script, 'r') as script: self.marionette.execute_script( script.read(), script_args=self.test_script_args) else: self.marionette.execute_script( self.test_script, script_args=self.test_script_args) status = self.runner.wait() if status is None: # the runner has timed out status = 124 except KeyboardInterrupt: log.info("runtests.py | Received keyboard interrupt.\n") status = -1 except: traceback.print_exc() log.error( "Automation Error: Received unexpected exception while running application\n" ) if hasattr(self, 'runner'): self.runner.check_for_crashes() status = 1 self.stopServers() log.info("runtestsb2g.py | Running tests: end.") if manifest is not None: self.cleanup(manifest, options) return status
class B2GMochitest(MochitestUtilsMixin): marionette = None def __init__(self, marionette_args, logger_options, out_of_process=True, profile_data_dir=None, locations=os.path.join(here, 'server-locations.txt')): super(B2GMochitest, self).__init__(logger_options) self.marionette_args = marionette_args self.out_of_process = out_of_process self.locations_file = locations self.preferences = [] self.webapps = None self.test_script = os.path.join(here, 'b2g_start_script.js') self.test_script_args = [self.out_of_process] self.product = 'b2g' self.remote_chrome_test_dir = None if profile_data_dir: self.preferences = [ os.path.join(profile_data_dir, f) for f in os.listdir(profile_data_dir) if f.startswith('pref') ] self.webapps = [ os.path.join(profile_data_dir, f) for f in os.listdir(profile_data_dir) if f.startswith('webapp') ] # mozinfo is populated by the parent class if mozinfo.info['debug']: self.SERVER_STARTUP_TIMEOUT = 180 else: self.SERVER_STARTUP_TIMEOUT = 90 def setup_common_options(self, options): test_url = self.buildTestPath(options) # For B2G emulators buildURLOptions has been called # without calling buildTestPath first and that # causes manifestFile not to be set if not "manifestFile=tests.json" in self.urlOpts: self.urlOpts.append("manifestFile=%s" % options.manifestFile) if len(self.urlOpts) > 0: test_url += "?" + "&".join(self.urlOpts) self.test_script_args.append(test_url) def buildTestPath(self, options, testsToFilter=None): if options.manifestFile != 'tests.json': super(B2GMochitest, self).buildTestPath(options, testsToFilter, disabled=False) return self.buildTestURL(options) def build_profile(self, options): # preferences prefs = {} for path in self.preferences: prefs.update(Preferences.read_prefs(path)) for v in options.extraPrefs: thispref = v.split("=", 1) if len(thispref) < 2: print "Error: syntax error in --setpref=" + v sys.exit(1) prefs[thispref[0]] = thispref[1] # interpolate the preferences interpolation = { "server": "%s:%s" % (options.webServer, options.httpPort), "OOP": "true" if self.out_of_process else "false" } prefs = json.loads(json.dumps(prefs) % interpolation) for pref in prefs: prefs[pref] = Preferences.cast(prefs[pref]) kwargs = { 'addons': self.getExtensionsToInstall(options), 'apps': self.webapps, 'locations': self.locations_file, 'preferences': prefs, 'proxy': { "remote": options.webServer } } if options.profile: self.profile = Profile.clone(options.profile, **kwargs) else: self.profile = Profile(**kwargs) options.profilePath = self.profile.profile # TODO bug 839108 - mozprofile should probably handle this manifest = self.addChromeToProfile(options) self.copyExtraFilesToProfile(options) return manifest def run_tests(self, options): """ Prepare, configure, run tests and cleanup """ manifest = self.build_profile(options) self.logPreamble(self.getActiveTests(options)) # configuring the message logger's buffering self.message_logger.buffering = options.quiet if options.debugger or not options.autorun: timeout = None else: if not options.timeout: if mozinfo.info['debug']: options.timeout = 420 else: options.timeout = 300 timeout = options.timeout + 30.0 self.log.info("runtestsb2g.py | Running tests: start.") status = 0 try: def on_output(line): messages = self.message_logger.write(line) for message in messages: if message['action'] == 'test_start': self.runner.last_test = message['test'] # The logging will be handled by on_output, so we set the stream to None process_args = {'processOutputLine': on_output, 'stream': None} self.marionette_args['process_args'] = process_args self.marionette_args['profile'] = self.profile self.marionette = Marionette(**self.marionette_args) self.runner = self.marionette.runner self.app_ctx = self.runner.app_ctx self.remote_log = posixpath.join(self.app_ctx.remote_test_root, 'log', 'mochitest.log') if not self.app_ctx.dm.dirExists(posixpath.dirname( self.remote_log)): self.app_ctx.dm.mkDirs(self.remote_log) if options.chrome: # Update chrome manifest file in profile with correct path. self.writeChromeManifest(options) self.leak_report_file = posixpath.join( self.app_ctx.remote_test_root, 'log', 'runtests_leaks.log') # We don't want to copy the host env onto the device, so pass in an # empty env. self.browserEnv = self.buildBrowserEnv(options, env={}) # B2G emulator debug tests still make external connections, so don't # pass MOZ_DISABLE_NONLOCAL_CONNECTIONS to them for now (bug 1039019). if mozinfo.info[ 'debug'] and 'MOZ_DISABLE_NONLOCAL_CONNECTIONS' in self.browserEnv: del self.browserEnv['MOZ_DISABLE_NONLOCAL_CONNECTIONS'] self.runner.env.update(self.browserEnv) self.startServers(options, None) self.buildURLOptions(options, {'MOZ_HIDE_RESULTS_TABLE': '1'}) self.test_script_args.append(not options.emulator) self.test_script_args.append(options.wifi) self.test_script_args.append(options.chrome) self.runner.start(outputTimeout=timeout) self.marionette.wait_for_port() self.marionette.start_session() self.marionette.set_context(self.marionette.CONTEXT_CHROME) # Disable offline status management (bug 777145), otherwise the network # will be 'offline' when the mochitests start. Presumably, the network # won't be offline on a real device, so we only do this for emulators. self.marionette.execute_script(""" Components.utils.import("resource://gre/modules/Services.jsm"); Services.io.manageOfflineStatus = false; Services.io.offline = false; """) if options.chrome: self.app_ctx.dm.removeDir(self.remote_chrome_test_dir) self.app_ctx.dm.mkDir(self.remote_chrome_test_dir) local = super(B2GMochitest, self).getChromeTestDir(options) local = os.path.join(local, "chrome") remote = self.remote_chrome_test_dir self.log.info("pushing %s to %s on device..." % (local, remote)) self.app_ctx.dm.pushDir(local, remote) if os.path.isfile(self.test_script): with open(self.test_script, 'r') as script: self.marionette.execute_script( script.read(), script_args=self.test_script_args) else: self.marionette.execute_script( self.test_script, script_args=self.test_script_args) status = self.runner.wait() if status is None: # the runner has timed out status = 124 local_leak_file = tempfile.NamedTemporaryFile() self.app_ctx.dm.getFile(self.leak_report_file, local_leak_file.name) self.app_ctx.dm.removeFile(self.leak_report_file) processLeakLog(local_leak_file.name, options) except KeyboardInterrupt: self.log.info("runtests.py | Received keyboard interrupt.\n") status = -1 except: traceback.print_exc() self.log.error( "Automation Error: Received unexpected exception while running application\n" ) if hasattr(self, 'runner'): self.runner.check_for_crashes() status = 1 self.stopServers() self.log.info("runtestsb2g.py | Running tests: end.") if manifest is not None: self.cleanup(manifest, options) return status def getGMPPluginPath(self, options): # TODO: bug 1043403 return None def getChromeTestDir(self, options): # The chrome test directory returned here is the remote location # of chrome test files. A reference to this directory is requested # when building the profile locally, before self.app_ctx is defined. # To get around this, return a dummy directory until self.app_ctx # is defined; the correct directory will be returned later, over- # writing the dummy. if hasattr(self, 'app_ctx'): self.remote_chrome_test_dir = posixpath.join( self.app_ctx.remote_test_root, 'chrome') return self.remote_chrome_test_dir return 'dummy-chrome-test-dir'
class B2GMochitest(MochitestUtilsMixin): marionette = None def __init__(self, marionette_args, logger_options, out_of_process=True, profile_data_dir=None, locations=os.path.join(here, 'server-locations.txt')): super(B2GMochitest, self).__init__(logger_options) self.marionette_args = marionette_args self.out_of_process = out_of_process self.locations_file = locations self.preferences = [] self.webapps = None self.test_script = os.path.join(here, 'b2g_start_script.js') self.test_script_args = [self.out_of_process] self.product = 'b2g' self.remote_chrome_test_dir = None if profile_data_dir: self.preferences = [ os.path.join( profile_data_dir, f) for f in os.listdir(profile_data_dir) if f.startswith('pref')] self.webapps = [ os.path.join( profile_data_dir, f) for f in os.listdir(profile_data_dir) if f.startswith('webapp')] # mozinfo is populated by the parent class if mozinfo.info['debug']: self.SERVER_STARTUP_TIMEOUT = 180 else: self.SERVER_STARTUP_TIMEOUT = 90 def setup_common_options(self, options): test_url = self.buildTestPath(options) # For B2G emulators buildURLOptions has been called # without calling buildTestPath first and that # causes manifestFile not to be set if not "manifestFile=tests.json" in self.urlOpts: self.urlOpts.append("manifestFile=%s" % options.manifestFile) if len(self.urlOpts) > 0: test_url += "?" + "&".join(self.urlOpts) self.test_script_args.append(test_url) def buildTestPath(self, options, testsToFilter=None): if options.manifestFile != 'tests.json': super(B2GMochitest, self).buildTestPath(options, testsToFilter, disabled=False) return self.buildTestURL(options) def build_profile(self, options): # preferences prefs = {} for path in self.preferences: prefs.update(Preferences.read_prefs(path)) for v in options.extraPrefs: thispref = v.split("=", 1) if len(thispref) < 2: print "Error: syntax error in --setpref=" + v sys.exit(1) prefs[thispref[0]] = thispref[1] # interpolate the preferences interpolation = { "server": "%s:%s" % (options.webServer, options.httpPort), "OOP": "true" if self.out_of_process else "false"} prefs = json.loads(json.dumps(prefs) % interpolation) for pref in prefs: prefs[pref] = Preferences.cast(prefs[pref]) kwargs = { 'addons': self.getExtensionsToInstall(options), 'apps': self.webapps, 'locations': self.locations_file, 'preferences': prefs, 'proxy': {"remote": options.webServer} } if options.profile: self.profile = Profile.clone(options.profile, **kwargs) else: self.profile = Profile(**kwargs) options.profilePath = self.profile.profile # TODO bug 839108 - mozprofile should probably handle this manifest = self.addChromeToProfile(options) self.copyExtraFilesToProfile(options) return manifest def run_tests(self, options): """ Prepare, configure, run tests and cleanup """ self.setTestRoot(options) manifest = self.build_profile(options) self.logPreamble(self.getActiveTests(options)) # configuring the message logger's buffering self.message_logger.buffering = options.quiet if options.debugger or not options.autorun: timeout = None else: if not options.timeout: if mozinfo.info['debug']: options.timeout = 420 else: options.timeout = 300 timeout = options.timeout + 30.0 self.log.info("runtestsb2g.py | Running tests: start.") status = 0 try: def on_output(line): messages = self.message_logger.write(line) for message in messages: if message['action'] == 'test_start': self.runner.last_test = message['test'] # The logging will be handled by on_output, so we set the stream to # None process_args = {'processOutputLine': on_output, 'stream': None} self.marionette_args['process_args'] = process_args self.marionette_args['profile'] = self.profile self.marionette = Marionette(**self.marionette_args) self.runner = self.marionette.runner self.app_ctx = self.runner.app_ctx self.remote_log = posixpath.join(self.app_ctx.remote_test_root, 'log', 'mochitest.log') if not self.app_ctx.dm.dirExists( posixpath.dirname( self.remote_log)): self.app_ctx.dm.mkDirs(self.remote_log) if options.chrome: # Update chrome manifest file in profile with correct path. self.writeChromeManifest(options) self.leak_report_file = posixpath.join( self.app_ctx.remote_test_root, 'log', 'runtests_leaks.log') # We don't want to copy the host env onto the device, so pass in an # empty env. self.browserEnv = self.buildBrowserEnv(options, env={}) # B2G emulator debug tests still make external connections, so don't # pass MOZ_DISABLE_NONLOCAL_CONNECTIONS to them for now (bug # 1039019). if mozinfo.info[ 'debug'] and 'MOZ_DISABLE_NONLOCAL_CONNECTIONS' in self.browserEnv: del self.browserEnv['MOZ_DISABLE_NONLOCAL_CONNECTIONS'] self.runner.env.update(self.browserEnv) # Despite our efforts to clean up servers started by this script, in practice # we still see infrequent cases where a process is orphaned and interferes # with future tests, typically because the old server is keeping the port in use. # Try to avoid those failures by checking for and killing orphan servers before # trying to start new ones. self.killNamedOrphans('ssltunnel') self.killNamedOrphans('xpcshell') self.startServers(options, None) # In desktop mochitests buildTestPath is called before buildURLOptions. This # means options.manifestFile has already been converted to the proper json # style manifest. Not so with B2G, that conversion along with updating the URL # option will happen later. So backup and restore options.manifestFile to # prevent us from trying to pass in an instance of TestManifest via url param. manifestFile = options.manifestFile options.manifestFile = None self.buildURLOptions(options, {'MOZ_HIDE_RESULTS_TABLE': '1'}) options.manifestFile = manifestFile self.test_script_args.append(not options.emulator) self.test_script_args.append(options.wifi) self.test_script_args.append(options.chrome) self.runner.start(outputTimeout=timeout) self.marionette.wait_for_port() self.marionette.start_session() self.marionette.set_context(self.marionette.CONTEXT_CHROME) # Disable offline status management (bug 777145), otherwise the network # will be 'offline' when the mochitests start. Presumably, the network # won't be offline on a real device, so we only do this for # emulators. self.marionette.execute_script(""" Components.utils.import("resource://gre/modules/Services.jsm"); Services.io.manageOfflineStatus = false; Services.io.offline = false; """) self.marionette.execute_script(""" let SECURITY_PREF = "security.turn_off_all_security_so_that_viruses_can_take_over_this_computer"; Services.prefs.setBoolPref(SECURITY_PREF, true); if (!testUtils.hasOwnProperty("specialPowersObserver")) { let loader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"] .getService(Components.interfaces.mozIJSSubScriptLoader); loader.loadSubScript("chrome://specialpowers/content/SpecialPowersObserver.js", testUtils); testUtils.specialPowersObserver = new testUtils.SpecialPowersObserver(); testUtils.specialPowersObserver.init(); testUtils.specialPowersObserver._loadFrameScript(); } """) if options.chrome: self.app_ctx.dm.removeDir(self.remote_chrome_test_dir) self.app_ctx.dm.mkDir(self.remote_chrome_test_dir) local = super(B2GMochitest, self).getChromeTestDir(options) local = os.path.join(local, "chrome") remote = self.remote_chrome_test_dir self.log.info( "pushing %s to %s on device..." % (local, remote)) self.app_ctx.dm.pushDir(local, remote) if os.path.isfile(self.test_script): with open(self.test_script, 'r') as script: self.marionette.execute_script( script.read(), script_args=self.test_script_args) else: self.marionette.execute_script( self.test_script, script_args=self.test_script_args) status = self.runner.wait() if status is None: # the runner has timed out status = 124 local_leak_file = tempfile.NamedTemporaryFile() self.app_ctx.dm.getFile( self.leak_report_file, local_leak_file.name) self.app_ctx.dm.removeFile(self.leak_report_file) mozleak.process_leak_log( local_leak_file.name, leak_thresholds=options.leakThresholds, ignore_missing_leaks=options.ignoreMissingLeaks, log=self.log, stack_fixer=get_stack_fixer_function(options.utilityPath, options.symbolsPath), ) except KeyboardInterrupt: self.log.info("runtests.py | Received keyboard interrupt.\n") status = -1 except: traceback.print_exc() self.log.error( "Automation Error: Received unexpected exception while running application\n") if hasattr(self, 'runner'): self.runner.check_for_crashes() status = 1 self.stopServers() self.log.info("runtestsb2g.py | Running tests: end.") if manifest is not None: self.cleanup(manifest, options) return status def getGMPPluginPath(self, options): if options.gmp_path: return options.gmp_path return '/system/b2g/gmp-clearkey/0.1' def getChromeTestDir(self, options): # The chrome test directory returned here is the remote location # of chrome test files. A reference to this directory is requested # when building the profile locally, before self.app_ctx is defined. # To get around this, return a dummy directory until self.app_ctx # is defined; the correct directory will be returned later, over- # writing the dummy. if hasattr(self, 'app_ctx'): self.remote_chrome_test_dir = posixpath.join( self.app_ctx.remote_test_root, 'chrome') return self.remote_chrome_test_dir return 'dummy-chrome-test-dir'
def kill(origin): marionette = Marionette(host='localhost', port=2828) marionette.start_session() marionette.set_context(marionette.CONTEXT_CONTENT) marionette.execute_script( "window.wrappedJSObject.WindowManager.kill('%s')" % origin)
class B2GMixin(object): profileDir = None userJS = "/data/local/user.js" marionette = None def __init__(self, host=None, marionetteHost=None, marionettePort=2828, **kwargs): # (allowing marionneteHost to be specified seems a bit # counter-intuitive since we normally get it below from the ip # address, however we currently need it to be able to connect # via adb port forwarding and localhost) if marionetteHost: self.marionetteHost = marionetteHost elif host: self.marionetteHost = host self.marionettePort = marionettePort def cleanup(self): """ If a user profile was setup on the device, restore it to the original. """ if self.profileDir: self.restoreProfile() def waitForPort(self, timeout): """Waits for the marionette server to respond, until the timeout specified. :param timeout: Timeout parameter in seconds. """ print "waiting for port" starttime = datetime.datetime.now() while datetime.datetime.now() - starttime < datetime.timedelta( seconds=timeout): try: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) print "trying %s %s" % (self.marionettePort, self.marionetteHost) sock.connect((self.marionetteHost, self.marionettePort)) data = sock.recv(16) sock.close() if '"from"' in data: return True except socket.error: pass except Exception as e: raise DMError("Could not connect to marionette: %s" % e) time.sleep(1) raise DMError("Could not communicate with Marionette port") def setupMarionette(self, scriptTimeout=60000): """ Starts a marionette session. If no host was given at init, the ip of the device will be retrieved and networking will be established. """ if not self.marionetteHost: self.setupDHCP() self.marionetteHost = self.getIP() if not self.marionette: self.marionette = Marionette(self.marionetteHost, self.marionettePort) if not self.marionette.session: self.waitForPort(30) self.marionette.start_session() self.marionette.set_script_timeout(scriptTimeout) def restartB2G(self): """ Restarts the b2g process on the device. """ #restart b2g so we start with a clean slate if self.marionette and self.marionette.session: self.marionette.delete_session() self.shellCheckOutput(['stop', 'b2g']) # Wait for a bit to make sure B2G has completely shut down. tries = 10 while "b2g" in self.shellCheckOutput(['ps', 'b2g']) and tries > 0: tries -= 1 time.sleep(1) if tries == 0: raise DMError("Could not kill b2g process") self.shellCheckOutput(['start', 'b2g']) def setupProfile(self, prefs=None): """Sets up the user profile on the device. :param prefs: String of user_prefs to add to the profile. Defaults to a standard b2g testing profile. """ # currently we have no custom prefs to set (when bug 800138 is fixed, # we will probably want to enable marionette on an external ip by # default) if not prefs: prefs = "" #remove previous user.js if there is one if not self.profileDir: self.profileDir = tempfile.mkdtemp() our_userJS = os.path.join(self.profileDir, "user.js") if os.path.exists(our_userJS): os.remove(our_userJS) #copy profile try: self.getFile(self.userJS, our_userJS) except subprocess.CalledProcessError: pass #if we successfully copied the profile, make a backup of the file if os.path.exists(our_userJS): self.shellCheckOutput( ['dd', 'if=%s' % self.userJS, 'of=%s.orig' % self.userJS]) with open(our_userJS, 'a') as user_file: user_file.write("%s" % prefs) self.pushFile(our_userJS, self.userJS) self.restartB2G() self.setupMarionette() def setupDHCP(self, interfaces=['eth0', 'wlan0']): """Sets up networking. :param interfaces: Network connection types to try. Defaults to eth0 and wlan0. """ all_interfaces = [line.split()[0] for line in \ self.shellCheckOutput(['netcfg']).splitlines()[1:]] interfaces_to_try = filter(lambda i: i in interfaces, all_interfaces) tries = 5 print "Setting up DHCP..." while tries > 0: print "attempts left: %d" % tries try: for interface in interfaces_to_try: self.shellCheckOutput(['netcfg', interface, 'dhcp'], timeout=10) if self.getIP(interfaces=[interface]): return except DMError: pass time.sleep(1) tries -= 1 raise DMError("Could not set up network connection") def restoreProfile(self): """ Restores the original user profile on the device. """ if not self.profileDir: raise DMError("There is no profile to restore") #if we successfully copied the profile, make a backup of the file our_userJS = os.path.join(self.profileDir, "user.js") if os.path.exists(our_userJS): self.shellCheckOutput( ['dd', 'if=%s.orig' % self.userJS, 'of=%s' % self.userJS]) shutil.rmtree(self.profileDir) self.profileDir = None def getAppInfo(self): """ Returns the appinfo, with an additional "date" key. :rtype: dictionary """ if not self.marionette or not self.marionette.session: self.setupMarionette() self.marionette.set_context("chrome") appinfo = self.marionette.execute_script(""" var appInfo = Components.classes["@mozilla.org/xre/app-info;1"] .getService(Components.interfaces.nsIXULAppInfo); return appInfo; """) (year, month, day) = (appinfo["appBuildID"][0:4], appinfo["appBuildID"][4:6], appinfo["appBuildID"][6:8]) appinfo['date'] = "%s-%s-%s" % (year, month, day) return appinfo
class B2gExecutor(Executor): def __init__(self, app_name, app_id, device=False): self.device = device if self.device: call(['adb', 'forward', 'tcp:2828', 'tcp:2828']) self._app_name = app_name self._app_id = app_id self._marionette = Marionette() self._marionette.start_session() self._gaia_apps = GaiaApps(self._marionette) self._gaia_data = GaiaData(self._marionette) self._gaia_device = GaiaDevice(self._marionette) ''' Deprecated # https://github.com/mozilla-b2g/gaia/blob/b568b7ae8adb6ee3651bd75acbaaedff86a08912/tests/python/gaia-ui-tests/gaiatest/gaia_test.py js = os.path.abspath(os.path.join(__file__, os.path.pardir, 'atoms', "gaia_apps.js")) self._marionette.import_script(js) js = os.path.abspath(os.path.join(__file__, os.path.pardir, 'atoms', "gaia_data_layer.js")) self._marionette.set_context(self._marionette.CONTEXT_CHROME) self._marionette.import_script(js) self._marionette.set_context(self._marionette.CONTEXT_CONTENT) # C:\Users\Jun-Wei\Desktop\b2g\battery\manifest.webapp #app = GaiaApps(self._marionette).launch(self._app_name) #app = GaiaApps(self._marionette).launch('Battery', manifest_url='C:/Users/Jun-Wei/Desktop/b2g/battery/manifest.webapp', entry_point='/index.html') app = GaiaApps(self._marionette).launch('Battery') print app.frame print app.src print app.origin print app.name #print g_app.manifest_url #self._app_frame = g_app.frame self._app_frame_id = app.frame self._app_src = app.src self._app_origin = app.origin #self.app_manifest_url = g_app.manifest_url #self.gaia_apps = GaiaApps(self.__marionette) #print self.gaia_apps.displayed_app.name #print self.gaia_apps.installed_apps #print self.gaia_apps.running_apps() #js = os.path.abspath(os.path.join(__file__, os.path.pardir, 'atoms', "gaia_apps.js")) #self.__marionette.import_script(js) ''' def fire_event(self, clickable): logger.info('fire_event: id: %s (xpath: %s)', clickable.get_id(), clickable.get_xpath()) try: # id staring with DomAnalyzer.serial_prefix is given by our monkey and should be ignored when locating if clickable.get_id() and not clickable.get_id().startswith( DomAnalyzer.serial_prefix): self._marionette.find_element('id', clickable.get_id()).tap() elif clickable.get_xpath(): self._marionette.find_element('xpath', clickable.get_xpath()).tap() else: logger.error( 'No id nor xpath for the clickable: id: %s (xpath: %s)', clickable.get_id(), clickable.get_xpath()) sys.exit() except (ElementNotVisibleException, InvalidElementStateException, NoSuchElementException): logger.info( 'Element is not interactable in fire_event(): id: %s (xpath: %s)', clickable.get_id(), clickable.get_xpath()) except Exception as e: logger.error( 'Unknown Exception: %s in fire_event(): id: %s (xpath: %s)', str(e), clickable.get_id(), clickable.get_xpath()) sys.exit() def fill_form(self, clickable): for f in clickable.get_forms(): for input_field in f.get_inputs(): try: if input_field.get_id() and not input_field.get_id( ).startswith(DomAnalyzer.serial_prefix): self._marionette.find_element( 'id', input_field.get_id()).send_keys( input_field.get_value()) elif input_field.get_xpath(): self._marionette.find_element( 'xpath', input_field.get_xpath()).send_keys( input_field.get_value()) else: logger.error( 'No id nor xpath for an input field in the form id: %s (xpath: %s)', f.get_id(), f.get_xpath()) sys.exit() except (ElementNotVisibleException, InvalidElementStateException, NoSuchElementException): logger.info( 'Element is not interactable in fill_form(): id: %s (xpath: %s)', f.get_id(), f.get_xpath()) except Exception as e: logger.error( 'Unknown Exception: %s in fill_form(): id: %s (xpath: %s)', str(e), f.get_id(), f.get_xpath()) sys.exit() def empty_form(self, clickable): for f in clickable.get_forms(): for input_field in f.get_inputs(): try: if input_field.get_id() and not input_field.get_id( ).startswith(DomAnalyzer.serial_prefix): self._marionette.find_element( 'id', input_field.get_id()).clear() elif input_field.get_xpath(): self._marionette.find_element( 'xpath', input_field.get_xpath()).clear() else: logger.error( 'No id nor xpath for an input field in the form %s (%s)', f.get_id(), f.get_xpath()) sys.exit() except (ElementNotVisibleException, InvalidElementStateException, NoSuchElementException): logger.info( 'Element is not interactable in empty_form(): id: %s (xpath: %s)', f.get_id(), f.get_xpath()) except Exception as e: logger.error( 'Unknown Exception: %s in empty_form(): id: %s (xpath: %s)', str(e), f.get_id(), f.get_xpath()) sys.exit() def get_source(self): return self._marionette.page_source.encode(sys.stdout.encoding, 'ignore') def get_screenshot(self, clickable=None): element = None if clickable: try: if clickable.get_id() and not clickable.get_id().startswith( DomAnalyzer.serial_prefix): element = self._marionette.find_element( 'id', clickable.get_id()) elif clickable.get_xpath(): element = self._marionette.find_element( 'xpath', clickable.get_xpath()) else: logger.error( 'No id nor xpath for the clickable: id: %s (xpath: %s)', clickable.get_id(), clickable.get_xpath()) sys.exit() except (ElementNotVisibleException, InvalidElementStateException, NoSuchElementException): logger.info( 'Element is not interactable in get_screenshot(): id: %s (xpath: %s)', clickable.get_id(), clickable.get_xpath()) except Exception as e: logger.error( 'Unknown Exception: %s in get_screenshot(): id: %s (xpath: %s)', str(e), clickable.get_id(), clickable.get_xpath()) sys.exit() if not element: # set context to CHROME to capture whole screen # system frame e.g. FileNotFound cannot be captured without CONTEXT_CHROME (Don't know why) self._marionette.set_context(self._marionette.CONTEXT_CHROME) screenshot = self._marionette.screenshot(element) self._marionette.set_context(self._marionette.CONTEXT_CONTENT) return screenshot def switch_to_frame(self, by, frame_str): """ :param by: options: "id", "xpath", "link text", "partial link text", "name", "tag name", "class name", "css selector", "anon attribute" """ # self.switch_to_top_frame() frame = self._marionette.find_element(by, frame_str) self._marionette.switch_to_frame(frame) def switch_to_top_frame(self): self._marionette.switch_to_frame() # switch to the top-level frame def restart_app(self): # remember to disable screen timeout and screen lock before testing # todo: open b2g simulator, install app, # unlock_screen # self._marionette.execute_script('window.wrappedJSObject.lockScreen.unlock();') self.kill_all_apps() # kill_all_apps() will also kill the 'homescreen app' on real device # so trigger a home event to restore homescreen if self.device: self._dispatch_home_button_event() self.clear_data() self.touch_home_button() # launch the app self._gaia_apps.launch(self._app_name) ''' Deprecated if self.device: icon = self._marionette.find_element('xpath', "//li[contains(@aria-label, '" + self._app_name + "')]") else: icon = self._marionette.find_element('xpath', "//div[contains(@class, 'icon')]//span[contains(text(),'" + self._app_name + "')]") icon.tap() ''' time.sleep(5) # wait for app screen self._marionette.switch_to_frame() # this wait seems not working, need to find another useful one Wait(self._marionette).until(lambda m: m.find_element( 'css selector', "iframe[data-url*='" + self._app_id + "']"). is_displayed()) app_frame = self._marionette.find_element( 'css selector', "iframe[data-url*='" + self._app_id + "']") self._marionette.switch_to_frame(app_frame) def touch_home_button(self): # ref: https://github.com/mozilla-b2g/gaia/blob/master/tests/python/gaia-ui-tests/gaiatest/gaia_test.py#L751 apps = self._gaia_apps if apps.displayed_app.name.lower() != 'homescreen': # touching home button will return to homescreen self._dispatch_home_button_event() Wait(self._marionette).until( lambda m: apps.displayed_app.name.lower() == 'homescreen') apps.switch_to_displayed_app() else: apps.switch_to_displayed_app() mode = self._marionette.find_element(By.TAG_NAME, 'body').get_attribute('class') self._dispatch_home_button_event() apps.switch_to_displayed_app() if 'edit-mode' in mode: # touching home button will exit edit mode Wait(self._marionette).until(lambda m: m.find_element( By.TAG_NAME, 'body').get_attribute('class') != mode) else: # touching home button inside homescreen will scroll it to the top Wait(self._marionette).until(lambda m: m.execute_script( "return window.wrappedJSObject.scrollY") == 0) def _dispatch_home_button_event(self): self._gaia_device._dispatch_home_button_event() ''' Deprecated self._marionette.switch_to_frame() self._marionette.execute_script("window.wrappedJSObject.dispatchEvent(new Event('home'));") ''' time.sleep(0.5) def clear_data(self): # for now, clear contact data # https://github.com/mozilla-b2g/gaia/blob/v2.2/tests/python/gaia-ui-tests/gaiatest/gaia_test.py#L208 self._marionette.set_context(self._marionette.CONTEXT_CHROME) result = self._marionette.execute_async_script( 'return GaiaDataLayer.removeAllContacts();') assert result, 'Unable to remove all contacts' self._marionette.set_context(self._marionette.CONTEXT_CONTENT) time.sleep(0.5) def kill_all_apps(self): self._marionette.switch_to_frame() self._marionette.execute_async_script(""" // Kills all running apps, except the homescreen. function killAll() { let manager = window.wrappedJSObject.appWindowManager; let apps = manager.getApps(); for (let id in apps) { let origin = apps[id].origin; if (origin.indexOf('verticalhome') == -1) { manager.kill(origin); } } }; killAll(); // return true so execute_async_script knows the script is complete marionetteScriptFinished(true); """) time.sleep(0.5)
def run(self, script, address='localhost:2828', symbols=None, treeherder='https://treeherder.mozilla.org/', reset=False, **kwargs): try: host, port = address.split(':') except ValueError: raise ValueError('--address must be in the format host:port') # Check that Orangutan is installed self.adb_device = ADBDevice(self.device_serial) orng_path = posixpath.join('data', 'local', 'orng') if not self.adb_device.exists(orng_path): raise Exception('Orangutan not found! Please install it according ' 'to the documentation.') self.runner = B2GDeviceRunner(serial=self.device_serial, process_args={'stream': None}, symbols_path=symbols, logdir=self.temp_dir) if reset: self.runner.start() else: self.runner.device.connect() port = self.runner.device.setup_port_forwarding(remote_port=port) assert self.runner.device.wait_for_port(port), \ 'Timed out waiting for port!' marionette = Marionette(host=host, port=port) marionette.start_session() try: marionette.set_context(marionette.CONTEXT_CHROME) self.is_debug = marionette.execute_script( 'return Components.classes["@mozilla.org/xpcom/debug;1"].' 'getService(Components.interfaces.nsIDebug2).isDebugBuild;') marionette.set_context(marionette.CONTEXT_CONTENT) if reset: gaia_device = GaiaDevice(marionette) gaia_device.wait_for_b2g_ready(timeout=120) gaia_device.unlock() gaia_apps = GaiaApps(marionette) gaia_apps.kill_all() # TODO: Disable bluetooth, emergency calls, carrier, etc # Run Orangutan script remote_script = posixpath.join(self.adb_device.test_root, 'orng.script') self.adb_device.push(script, remote_script) self.start_time = time.time() # TODO: Kill remote process on keyboard interrupt self.adb_device.shell( '%s %s %s' % (orng_path, self.device_properties['input'], remote_script)) self.end_time = time.time() self.adb_device.rm(remote_script) except (MarionetteException, IOError): if self.runner.crashed: # Crash has been detected pass else: raise self.runner.check_for_crashes(test_name='b2gmonkey') # Report results to Treeherder required_envs = ['TREEHERDER_KEY', 'TREEHERDER_SECRET'] if all([os.environ.get(v) for v in required_envs]): self.post_to_treeherder(script, treeherder) else: self._logger.info( 'Results will not be posted to Treeherder. Please set the ' 'following environment variables to enable Treeherder ' 'reports: %s' % ', '.join([v for v in required_envs if not os.environ.get(v)]))
def uninstall_app(app_name, adb_path="adb", script_timeout=5000, marionette=None, device_serial=None): """ Uninstalls the given app. NOTE: if a marionette session is passed, this function switches to the top-most frame. """ def check_uninstall(marionette): uninstall = marionette.execute_script("return window.wrappedJSObject.uninstall;") return uninstall != 'none' def check_click_uninstall(marionette): button = marionette.find_element('css selector', 'gaia-confirm .confirm') try: button.click() not_displayed = not button.is_displayed() except StaleElementException: not_displayed = True return not_displayed dm = mozdevice.DeviceManagerADB(adbPath=adb_path, deviceSerial=device_serial) installed_app_name = app_name.lower() installed_app_name = installed_app_name.replace(" ", "-") dm.forward("tcp:2828", "tcp:2828") print 'requesting uninstall of app', app_name if not marionette: m = Marionette() m.start_session() else: m = marionette m.switch_to_frame() m.set_context("chrome") m.execute_script(""" Components.utils.import("resource://gre/modules/Services.jsm"); window.wrappedJSObject.uninstall = 'none'; Services.obs.addObserver(function observer(subject, topic) { Services.obs.removeObserver(observer, topic); window.wrappedJSObject.uninstall = 'ask'; }, "webapps-ask-uninstall", false); Services.obs.addObserver(function observer(subject, topic) { Services.obs.removeObserver(observer, topic); window.wrappedJSObject.uninstall = 'uninstall'; }, "webapps-uninstall", false); """) m.set_context("content") uninstall_app = """ var uninstallWithName = function(name) { let apps = window.wrappedJSObject.applications || window.wrappedJSObject.Applications; let installedApps = apps.installedApps; for (let manifestURL in installedApps) { let app = installedApps[manifestURL]; let origin = null; let entryPoints = app.manifest.entry_points; if (entryPoints) { for (let ep in entryPoints) { let currentEntryPoint = entryPoints[ep]; let appName = currentEntryPoint.name; if (name == appName.toLowerCase()) { window.wrappedJSObject.navigator.mozApps.mgmt.uninstall(app); return true; } } } else { let appName = app.manifest.name; if (name == appName.toLowerCase()) { window.wrappedJSObject.navigator.mozApps.mgmt.uninstall(app); return true; } } } return false; }; return uninstallWithName("%s"); """ m.set_script_timeout(script_timeout) result = m.execute_script(uninstall_app % app_name.lower()) if result: m.set_context("chrome") Wait(m, 10).until(check_uninstall) uninstall = m.execute_script("return window.wrappedJSObject.uninstall;") m.set_context("content") if uninstall == 'ask': m.switch_to_frame() Wait(m, 20).until(element_displayed(m.find_element('css selector', 'gaia-confirm .confirm'))) Wait(m, 20).until(check_click_uninstall) if not marionette: m.delete_session()
def run(self, script, address='localhost:2828', symbols=None, treeherder='https://treeherder.mozilla.org/', reset=False, **kwargs): try: host, port = address.split(':') except ValueError: raise ValueError('--address must be in the format host:port') # Check that Orangutan is installed self.adb_device = ADBDevice(self.device_serial) orng_path = posixpath.join('data', 'local', 'orng') if not self.adb_device.exists(orng_path): raise Exception('Orangutan not found! Please install it according ' 'to the documentation.') self.runner = B2GDeviceRunner( serial=self.device_serial, process_args={'stream': None}, symbols_path=symbols, logdir=self.temp_dir) if reset: self.runner.start() else: self.runner.device.connect() port = self.runner.device.setup_port_forwarding(remote_port=port) assert self.runner.device.wait_for_port(port), \ 'Timed out waiting for port!' marionette = Marionette(host=host, port=port) marionette.start_session() try: marionette.set_context(marionette.CONTEXT_CHROME) self.is_debug = marionette.execute_script( 'return Components.classes["@mozilla.org/xpcom/debug;1"].' 'getService(Components.interfaces.nsIDebug2).isDebugBuild;') marionette.set_context(marionette.CONTEXT_CONTENT) if reset: gaia_device = GaiaDevice(marionette) gaia_device.wait_for_b2g_ready(timeout=120) gaia_device.unlock() gaia_apps = GaiaApps(marionette) gaia_apps.kill_all() # TODO: Disable bluetooth, emergency calls, carrier, etc # Run Orangutan script remote_script = posixpath.join(self.adb_device.test_root, 'orng.script') self.adb_device.push(script, remote_script) self.start_time = time.time() # TODO: Kill remote process on keyboard interrupt self.adb_device.shell('%s %s %s' % (orng_path, self.device_properties['input'], remote_script)) self.end_time = time.time() self.adb_device.rm(remote_script) except (MarionetteException, IOError): if self.runner.crashed: # Crash has been detected pass else: raise self.runner.check_for_crashes(test_name='b2gmonkey') # Report results to Treeherder required_envs = ['TREEHERDER_KEY', 'TREEHERDER_SECRET'] if all([os.environ.get(v) for v in required_envs]): self.post_to_treeherder(script, treeherder) else: self._logger.info( 'Results will not be posted to Treeherder. Please set the ' 'following environment variables to enable Treeherder ' 'reports: %s' % ', '.join([ v for v in required_envs if not os.environ.get(v)]))
class B2GDesktopReftest(RefTest): marionette = None def __init__(self, marionette_args): RefTest.__init__(self) self.last_test = os.path.basename(__file__) self.marionette_args = marionette_args self.profile = None self.runner = None self.test_script = os.path.join(here, 'b2g_start_script.js') self.timeout = None def run_marionette_script(self): self.marionette = Marionette(**self.marionette_args) assert (self.marionette.wait_for_port()) self.marionette.start_session() self.marionette.set_context(self.marionette.CONTEXT_CHROME) if os.path.isfile(self.test_script): f = open(self.test_script, 'r') self.test_script = f.read() f.close() self.marionette.execute_script(self.test_script) def run_tests(self, test_path, options): reftestlist = self.getManifestPath(test_path) if not reftestlist.startswith('file://'): reftestlist = 'file://%s' % reftestlist self.profile = self.create_profile(options, reftestlist, profile_to_clone=options.profile) env = self.buildBrowserEnv(options, self.profile.profile) kp_kwargs = { 'processOutputLine': [self._on_output], 'onTimeout': [self._on_timeout], 'kill_on_timeout': False } if not options.debugger: if not options.timeout: if mozinfo.info['debug']: options.timeout = 420 else: options.timeout = 300 self.timeout = options.timeout + 30.0 log.info("%s | Running tests: start.", os.path.basename(__file__)) cmd, args = self.build_command_line( options.app, ignore_window_size=options.ignoreWindowSize, browser_arg=options.browser_arg) self.runner = FirefoxRunner(profile=self.profile, binary=cmd, cmdargs=args, env=env, process_class=ProcessHandler, process_args=kp_kwargs, symbols_path=options.symbolsPath) status = 0 try: self.runner.start(outputTimeout=self.timeout) log.info("%s | Application pid: %d", os.path.basename(__file__), self.runner.process_handler.pid) # kick starts the reftest harness self.run_marionette_script() status = self.runner.wait() finally: self.runner.check_for_crashes(test_name=self.last_test) self.runner.cleanup() if status > 0: log.testFail("%s | application terminated with exit code %s", self.last_test, status) elif status < 0: log.info("%s | application killed with signal %s", self.last_test, -status) log.info("%s | Running tests: end.", os.path.basename(__file__)) return status def create_profile(self, options, reftestlist, profile_to_clone=None): profile = RefTest.createReftestProfile( self, options, reftestlist, profile_to_clone=profile_to_clone) prefs = {} # Turn off the locale picker screen prefs["browser.firstrun.show.localepicker"] = False prefs[ "b2g.system_startup_url"] = "app://test-container.gaiamobile.org/index.html" prefs[ "b2g.system_manifest_url"] = "app://test-container.gaiamobile.org/manifest.webapp" prefs["browser.tabs.remote"] = False prefs["dom.ipc.tabs.disabled"] = False prefs["dom.mozBrowserFramesEnabled"] = True prefs["font.size.inflation.emPerLine"] = 0 prefs["font.size.inflation.minTwips"] = 0 prefs[ "network.dns.localDomains"] = "app://test-container.gaiamobile.org" prefs["reftest.browser.iframe.enabled"] = False prefs["reftest.remote"] = False prefs["reftest.uri"] = "%s" % reftestlist # Set a future policy version to avoid the telemetry prompt. prefs["toolkit.telemetry.prompted"] = 999 prefs["toolkit.telemetry.notifiedOptOut"] = 999 # Set the extra prefs. profile.set_preferences(prefs) return profile def build_command_line(self, app, ignore_window_size=False, browser_arg=None): cmd = os.path.abspath(app) args = ['-marionette'] if browser_arg: args += [browser_arg] if not ignore_window_size: args.extend(['--screen', '800x1000']) return cmd, args def _on_output(self, line): print(line) # TODO use structured logging if "TEST-START" in line and "|" in line: self.last_test = line.split("|")[1].strip() def _on_timeout(self): msg = "%s | application timed out after %s seconds with no output" log.testFail(msg % (self.last_test, self.timeout)) # kill process to get a stack self.runner.stop(sig=signal.SIGABRT)
class B2GMixin(object): profileDir = None userJS = "/data/local/user.js" marionette = None def __init__(self, host=None, marionetteHost=None, marionettePort=2828, **kwargs): # (allowing marionneteHost to be specified seems a bit # counter-intuitive since we normally get it below from the ip # address, however we currently need it to be able to connect # via adb port forwarding and localhost) if marionetteHost: self.marionetteHost = marionetteHost elif host: self.marionetteHost = host self.marionettePort = marionettePort def cleanup(self): """ If a user profile was setup on the device, restore it to the original. """ if self.profileDir: self.restoreProfile() def waitForPort(self, timeout): """Waits for the marionette server to respond, until the timeout specified. :param timeout: Timeout parameter in seconds. """ print "waiting for port" starttime = datetime.datetime.now() while datetime.datetime.now() - starttime < datetime.timedelta(seconds=timeout): try: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) print "trying %s %s" % (self.marionettePort, self.marionetteHost) sock.connect((self.marionetteHost, self.marionettePort)) data = sock.recv(16) sock.close() if '"from"' in data: return True except socket.error: pass except Exception as e: raise DMError("Could not connect to marionette: %s" % e) time.sleep(1) raise DMError("Could not communicate with Marionette port") def setupMarionette(self, scriptTimeout=60000): """ Starts a marionette session. If no host was given at init, the ip of the device will be retrieved and networking will be established. """ if not self.marionetteHost: self.setupDHCP() self.marionetteHost = self.getIP() if not self.marionette: self.marionette = Marionette(self.marionetteHost, self.marionettePort) if not self.marionette.session: self.waitForPort(30) self.marionette.start_session() self.marionette.set_script_timeout(scriptTimeout) def restartB2G(self): """ Restarts the b2g process on the device. """ #restart b2g so we start with a clean slate if self.marionette and self.marionette.session: self.marionette.delete_session() self.shellCheckOutput(['stop', 'b2g']) # Wait for a bit to make sure B2G has completely shut down. tries = 10 while "b2g" in self.shellCheckOutput(['ps', 'b2g']) and tries > 0: tries -= 1 time.sleep(1) if tries == 0: raise DMError("Could not kill b2g process") self.shellCheckOutput(['start', 'b2g']) def setupProfile(self, prefs=None): """Sets up the user profile on the device. :param prefs: String of user_prefs to add to the profile. Defaults to a standard b2g testing profile. """ # currently we have no custom prefs to set (when bug 800138 is fixed, # we will probably want to enable marionette on an external ip by # default) if not prefs: prefs = "" #remove previous user.js if there is one if not self.profileDir: self.profileDir = tempfile.mkdtemp() our_userJS = os.path.join(self.profileDir, "user.js") mozfile.remove(our_userJS) #copy profile try: self.getFile(self.userJS, our_userJS) except subprocess.CalledProcessError: pass #if we successfully copied the profile, make a backup of the file if os.path.exists(our_userJS): self.shellCheckOutput(['dd', 'if=%s' % self.userJS, 'of=%s.orig' % self.userJS]) with open(our_userJS, 'a') as user_file: user_file.write("%s" % prefs) self.pushFile(our_userJS, self.userJS) self.restartB2G() self.setupMarionette() def setupDHCP(self, interfaces=['eth0', 'wlan0']): """Sets up networking. :param interfaces: Network connection types to try. Defaults to eth0 and wlan0. """ all_interfaces = [line.split()[0] for line in \ self.shellCheckOutput(['netcfg']).splitlines()[1:]] interfaces_to_try = filter(lambda i: i in interfaces, all_interfaces) tries = 5 print "Setting up DHCP..." while tries > 0: print "attempts left: %d" % tries try: for interface in interfaces_to_try: self.shellCheckOutput(['netcfg', interface, 'dhcp'], timeout=10) if self.getIP(interfaces=[interface]): return except DMError: pass time.sleep(1) tries -= 1 raise DMError("Could not set up network connection") def restoreProfile(self): """ Restores the original user profile on the device. """ if not self.profileDir: raise DMError("There is no profile to restore") #if we successfully copied the profile, make a backup of the file our_userJS = os.path.join(self.profileDir, "user.js") if os.path.exists(our_userJS): self.shellCheckOutput(['dd', 'if=%s.orig' % self.userJS, 'of=%s' % self.userJS]) mozfile.remove(self.profileDir) self.profileDir = None def getAppInfo(self): """ Returns the appinfo, with an additional "date" key. :rtype: dictionary """ if not self.marionette or not self.marionette.session: self.setupMarionette() self.marionette.set_context("chrome") appinfo = self.marionette.execute_script(""" var appInfo = Components.classes["@mozilla.org/xre/app-info;1"] .getService(Components.interfaces.nsIXULAppInfo); return appInfo; """) (year, month, day) = (appinfo["appBuildID"][0:4], appinfo["appBuildID"][4:6], appinfo["appBuildID"][6:8]) appinfo['date'] = "%s-%s-%s" % (year, month, day) return appinfo
class B2gExecutor(Executor): def __init__(self, app_name, app_id, device=False): self.device = device if self.device: call(['adb', 'forward', 'tcp:2828', 'tcp:2828']) self._app_name = app_name self._app_id = app_id self._marionette = Marionette() self._marionette.start_session() self._gaia_apps = GaiaApps(self._marionette) self._gaia_data = GaiaData(self._marionette) self._gaia_device = GaiaDevice(self._marionette) ''' Deprecated # https://github.com/mozilla-b2g/gaia/blob/b568b7ae8adb6ee3651bd75acbaaedff86a08912/tests/python/gaia-ui-tests/gaiatest/gaia_test.py js = os.path.abspath(os.path.join(__file__, os.path.pardir, 'atoms', "gaia_apps.js")) self._marionette.import_script(js) js = os.path.abspath(os.path.join(__file__, os.path.pardir, 'atoms', "gaia_data_layer.js")) self._marionette.set_context(self._marionette.CONTEXT_CHROME) self._marionette.import_script(js) self._marionette.set_context(self._marionette.CONTEXT_CONTENT) # C:\Users\Jun-Wei\Desktop\b2g\battery\manifest.webapp #app = GaiaApps(self._marionette).launch(self._app_name) #app = GaiaApps(self._marionette).launch('Battery', manifest_url='C:/Users/Jun-Wei/Desktop/b2g/battery/manifest.webapp', entry_point='/index.html') app = GaiaApps(self._marionette).launch('Battery') print app.frame print app.src print app.origin print app.name #print g_app.manifest_url #self._app_frame = g_app.frame self._app_frame_id = app.frame self._app_src = app.src self._app_origin = app.origin #self.app_manifest_url = g_app.manifest_url #self.gaia_apps = GaiaApps(self.__marionette) #print self.gaia_apps.displayed_app.name #print self.gaia_apps.installed_apps #print self.gaia_apps.running_apps() #js = os.path.abspath(os.path.join(__file__, os.path.pardir, 'atoms', "gaia_apps.js")) #self.__marionette.import_script(js) ''' def fire_event(self, clickable): logger.info('fire_event: id: %s (xpath: %s)', clickable.get_id(), clickable.get_xpath()) try: # id staring with DomAnalyzer.serial_prefix is given by our monkey and should be ignored when locating if clickable.get_id() and not clickable.get_id().startswith(DomAnalyzer.serial_prefix): self._marionette.find_element('id', clickable.get_id()).tap() elif clickable.get_xpath(): self._marionette.find_element('xpath', clickable.get_xpath()).tap() else: logger.error('No id nor xpath for the clickable: id: %s (xpath: %s)', clickable.get_id(), clickable.get_xpath()) sys.exit() except (ElementNotVisibleException, InvalidElementStateException, NoSuchElementException): logger.info('Element is not interactable in fire_event(): id: %s (xpath: %s)', clickable.get_id(), clickable.get_xpath()) except Exception as e: logger.error('Unknown Exception: %s in fire_event(): id: %s (xpath: %s)', str(e), clickable.get_id(), clickable.get_xpath()) sys.exit() def fill_form(self, clickable): for f in clickable.get_forms(): for input_field in f.get_inputs(): try: if input_field.get_id() and not input_field.get_id().startswith(DomAnalyzer.serial_prefix): self._marionette.find_element('id', input_field.get_id()).send_keys(input_field.get_value()) elif input_field.get_xpath(): self._marionette.find_element('xpath', input_field.get_xpath()).send_keys(input_field.get_value()) else: logger.error('No id nor xpath for an input field in the form id: %s (xpath: %s)', f.get_id(), f.get_xpath()) sys.exit() except (ElementNotVisibleException, InvalidElementStateException, NoSuchElementException): logger.info('Element is not interactable in fill_form(): id: %s (xpath: %s)', f.get_id(), f.get_xpath()) except Exception as e: logger.error('Unknown Exception: %s in fill_form(): id: %s (xpath: %s)', str(e), f.get_id(), f.get_xpath()) sys.exit() def empty_form(self, clickable): for f in clickable.get_forms(): for input_field in f.get_inputs(): try: if input_field.get_id() and not input_field.get_id().startswith(DomAnalyzer.serial_prefix): self._marionette.find_element('id', input_field.get_id()).clear() elif input_field.get_xpath(): self._marionette.find_element('xpath', input_field.get_xpath()).clear() else: logger.error('No id nor xpath for an input field in the form %s (%s)', f.get_id(), f.get_xpath()) sys.exit() except (ElementNotVisibleException, InvalidElementStateException, NoSuchElementException): logger.info('Element is not interactable in empty_form(): id: %s (xpath: %s)', f.get_id(), f.get_xpath()) except Exception as e: logger.error('Unknown Exception: %s in empty_form(): id: %s (xpath: %s)', str(e), f.get_id(), f.get_xpath()) sys.exit() def get_source(self): return self._marionette.page_source.encode(sys.stdout.encoding, 'ignore') def get_screenshot(self, clickable=None): element = None if clickable: try: if clickable.get_id() and not clickable.get_id().startswith(DomAnalyzer.serial_prefix): element = self._marionette.find_element('id', clickable.get_id()) elif clickable.get_xpath(): element = self._marionette.find_element('xpath', clickable.get_xpath()) else: logger.error('No id nor xpath for the clickable: id: %s (xpath: %s)', clickable.get_id(), clickable.get_xpath()) sys.exit() except (ElementNotVisibleException, InvalidElementStateException, NoSuchElementException): logger.info('Element is not interactable in get_screenshot(): id: %s (xpath: %s)', clickable.get_id(), clickable.get_xpath()) except Exception as e: logger.error('Unknown Exception: %s in get_screenshot(): id: %s (xpath: %s)', str(e), clickable.get_id(), clickable.get_xpath()) sys.exit() if not element: # set context to CHROME to capture whole screen # system frame e.g. FileNotFound cannot be captured without CONTEXT_CHROME (Don't know why) self._marionette.set_context(self._marionette.CONTEXT_CHROME) screenshot = self._marionette.screenshot(element) self._marionette.set_context(self._marionette.CONTEXT_CONTENT) return screenshot def switch_to_frame(self, by, frame_str): """ :param by: options: "id", "xpath", "link text", "partial link text", "name", "tag name", "class name", "css selector", "anon attribute" """ # self.switch_to_top_frame() frame = self._marionette.find_element(by, frame_str) self._marionette.switch_to_frame(frame) def switch_to_top_frame(self): self._marionette.switch_to_frame() # switch to the top-level frame def restart_app(self): # remember to disable screen timeout and screen lock before testing # todo: open b2g simulator, install app, # unlock_screen # self._marionette.execute_script('window.wrappedJSObject.lockScreen.unlock();') self.kill_all_apps() # kill_all_apps() will also kill the 'homescreen app' on real device # so trigger a home event to restore homescreen if self.device: self._dispatch_home_button_event() self.clear_data() self.touch_home_button() # launch the app self._gaia_apps.launch(self._app_name) ''' Deprecated if self.device: icon = self._marionette.find_element('xpath', "//li[contains(@aria-label, '" + self._app_name + "')]") else: icon = self._marionette.find_element('xpath', "//div[contains(@class, 'icon')]//span[contains(text(),'" + self._app_name + "')]") icon.tap() ''' time.sleep(5) # wait for app screen self._marionette.switch_to_frame() # this wait seems not working, need to find another useful one Wait(self._marionette).until(lambda m: m.find_element('css selector', "iframe[data-url*='" + self._app_id + "']").is_displayed()) app_frame = self._marionette.find_element('css selector', "iframe[data-url*='" + self._app_id + "']") self._marionette.switch_to_frame(app_frame) def touch_home_button(self): # ref: https://github.com/mozilla-b2g/gaia/blob/master/tests/python/gaia-ui-tests/gaiatest/gaia_test.py#L751 apps = self._gaia_apps if apps.displayed_app.name.lower() != 'homescreen': # touching home button will return to homescreen self._dispatch_home_button_event() Wait(self._marionette).until( lambda m: apps.displayed_app.name.lower() == 'homescreen') apps.switch_to_displayed_app() else: apps.switch_to_displayed_app() mode = self._marionette.find_element(By.TAG_NAME, 'body').get_attribute('class') self._dispatch_home_button_event() apps.switch_to_displayed_app() if 'edit-mode' in mode: # touching home button will exit edit mode Wait(self._marionette).until(lambda m: m.find_element( By.TAG_NAME, 'body').get_attribute('class') != mode) else: # touching home button inside homescreen will scroll it to the top Wait(self._marionette).until(lambda m: m.execute_script( "return window.wrappedJSObject.scrollY") == 0) def _dispatch_home_button_event(self): self._gaia_device._dispatch_home_button_event() ''' Deprecated self._marionette.switch_to_frame() self._marionette.execute_script("window.wrappedJSObject.dispatchEvent(new Event('home'));") ''' time.sleep(0.5) def clear_data(self): # for now, clear contact data # https://github.com/mozilla-b2g/gaia/blob/v2.2/tests/python/gaia-ui-tests/gaiatest/gaia_test.py#L208 self._marionette.set_context(self._marionette.CONTEXT_CHROME) result = self._marionette.execute_async_script('return GaiaDataLayer.removeAllContacts();') assert result, 'Unable to remove all contacts' self._marionette.set_context(self._marionette.CONTEXT_CONTENT) time.sleep(0.5) def kill_all_apps(self): self._marionette.switch_to_frame() self._marionette.execute_async_script(""" // Kills all running apps, except the homescreen. function killAll() { let manager = window.wrappedJSObject.appWindowManager; let apps = manager.getApps(); for (let id in apps) { let origin = apps[id].origin; if (origin.indexOf('verticalhome') == -1) { manager.kill(origin); } } }; killAll(); // return true so execute_async_script knows the script is complete marionetteScriptFinished(true); """) time.sleep(0.5)
class B2GDesktopReftest(RefTest): marionette = None def __init__(self, marionette_args): RefTest.__init__(self) self.last_test = os.path.basename(__file__) self.marionette_args = marionette_args self.profile = None self.runner = None self.test_script = os.path.join(here, 'b2g_start_script.js') self.timeout = None def run_marionette_script(self): self.marionette = Marionette(**self.marionette_args) assert(self.marionette.wait_for_port()) self.marionette.start_session() self.marionette.set_context(self.marionette.CONTEXT_CHROME) if os.path.isfile(self.test_script): f = open(self.test_script, 'r') self.test_script = f.read() f.close() self.marionette.execute_script(self.test_script) def run_tests(self, test_path, options): reftestlist = self.getManifestPath(test_path) if not reftestlist.startswith('file://'): reftestlist = 'file://%s' % reftestlist self.profile = self.create_profile(options, reftestlist, profile_to_clone=options.profile) env = self.buildBrowserEnv(options, self.profile.profile) kp_kwargs = { 'processOutputLine': [self._on_output], 'onTimeout': [self._on_timeout], 'kill_on_timeout': False } if not options.debugger: if not options.timeout: if mozinfo.info['debug']: options.timeout = 420 else: options.timeout = 300 self.timeout = options.timeout + 30.0 log.info("%s | Running tests: start.", os.path.basename(__file__)) cmd, args = self.build_command_line(options.app, ignore_window_size=options.ignoreWindowSize, browser_arg=options.browser_arg) self.runner = FirefoxRunner(profile=self.profile, binary=cmd, cmdargs=args, env=env, process_class=ProcessHandler, process_args=kp_kwargs, symbols_path=options.symbolsPath) status = 0 try: self.runner.start(outputTimeout=self.timeout) log.info("%s | Application pid: %d", os.path.basename(__file__), self.runner.process_handler.pid) # kick starts the reftest harness self.run_marionette_script() status = self.runner.wait() finally: self.runner.check_for_crashes(test_name=self.last_test) self.runner.cleanup() if status > 0: log.testFail("%s | application terminated with exit code %s", self.last_test, status) elif status < 0: log.info("%s | application killed with signal %s", self.last_test, -status) log.info("%s | Running tests: end.", os.path.basename(__file__)) return status def create_profile(self, options, reftestlist, profile_to_clone=None): profile = RefTest.createReftestProfile(self, options, reftestlist, profile_to_clone=profile_to_clone) prefs = {} # Turn off the locale picker screen prefs["browser.firstrun.show.localepicker"] = False prefs["b2g.system_startup_url"] = "app://test-container.gaiamobile.org/index.html" prefs["b2g.system_manifest_url"] = "app://test-container.gaiamobile.org/manifest.webapp" prefs["dom.ipc.tabs.disabled"] = False prefs["dom.mozBrowserFramesEnabled"] = True prefs["font.size.inflation.emPerLine"] = 0 prefs["font.size.inflation.minTwips"] = 0 prefs["network.dns.localDomains"] = "app://test-container.gaiamobile.org" prefs["reftest.browser.iframe.enabled"] = False prefs["reftest.remote"] = False prefs["reftest.uri"] = "%s" % reftestlist # Set a future policy version to avoid the telemetry prompt. prefs["toolkit.telemetry.prompted"] = 999 prefs["toolkit.telemetry.notifiedOptOut"] = 999 # Set the extra prefs. profile.set_preferences(prefs) return profile def build_command_line(self, app, ignore_window_size=False, browser_arg=None): cmd = os.path.abspath(app) args = ['-marionette'] if browser_arg: args += [browser_arg] if not ignore_window_size: args.extend(['--screen', '800x1000']) return cmd, args def _on_output(self, line): print(line) # TODO use structured logging if "TEST-START" in line and "|" in line: self.last_test = line.split("|")[1].strip() def _on_timeout(self): msg = "%s | application timed out after %s seconds with no output" log.testFail(msg % (self.last_test, self.timeout)) # kill process to get a stack self.runner.stop(sig=signal.SIGABRT)
def kill(origin): marionette = Marionette(host='localhost', port=2828) marionette.start_session() marionette.set_context(marionette.CONTEXT_CONTENT) marionette.execute_script("window.wrappedJSObject.WindowManager.kill('%s')" % origin)
assert(m.execute_js_script(server.TEST_EXECUTE_SCRIPT)) assert(m.execute_js_script(server.TEST_EXECUTE_SCRIPT, server.TEST_EXECUTE_SCRIPT_ARGS)) assert(m.execute_script(server.TEST_EXECUTE_SCRIPT, server.TEST_EXECUTE_SCRIPT_ARGS)) assert(m.execute_async_script(server.TEST_EXECUTE_SCRIPT)) assert(m.execute_async_script(server.TEST_EXECUTE_SCRIPT, server.TEST_EXECUTE_SCRIPT_ARGS)) assert(str(m.find_element(HTMLElement.CLASS, 'heading')) == server.TEST_FIND_ELEMENT) assert([str(x) for x in m.find_elements(HTMLElement.TAG, 'p')] == server.TEST_FIND_ELEMENTS) assert(str(m.find_element(HTMLElement.CLASS, 'heading').find_element(HTMLElement.TAG, 'h1')) == server.TEST_FIND_ELEMENT) assert([str(x) for x in m.find_element(HTMLElement.ID, 'div1').find_elements(HTMLElement.SELECTOR, '.main')] == \ server.TEST_FIND_ELEMENTS) assert(m.find_element(HTMLElement.ID, 'id1').click()) assert(m.find_element(HTMLElement.ID, 'id2').text() == server.TEST_GET_TEXT) assert(m.find_element(HTMLElement.ID, 'id3').send_keys('Mozilla Firefox')) assert(m.find_element(HTMLElement.ID, 'id3').value() == server.TEST_GET_VALUE) assert(m.find_element(HTMLElement.ID, 'id3').clear()) assert(m.find_element(HTMLElement.ID, 'id3').selected()) assert(m.find_element(HTMLElement.ID, 'id1').equals(m.find_element(HTMLElement.TAG, 'p'))) assert(m.find_element(HTMLElement.ID, 'id3').enabled()) assert(m.find_element(HTMLElement.ID, 'id3').displayed()) assert(m.find_element(HTMLElement.ID, 'id3').get_attribute('value') == server.TEST_GET_VALUE) assert(m.delete_session()) # verify a session is started automatically for us if needed assert(m.switch_to_frame('frame1')) assert(m.switch_to_frame(1)) assert(m.switch_to_frame(m.find_element(HTMLElement.ID, 'frameid'))) assert(m.switch_to_frame()) assert(m.get_window() == server.TEST_CURRENT_WINDOW) assert(m.set_context(m.CONTEXT_CHROME)) assert(m.delete_session())
class B2GMochitest(MochitestUtilsMixin): marionette = None def __init__(self, marionette_args, logger_options, out_of_process=True, profile_data_dir=None, locations=os.path.join(here, 'server-locations.txt')): super(B2GMochitest, self).__init__(logger_options) self.marionette_args = marionette_args self.out_of_process = out_of_process self.locations_file = locations self.preferences = [] self.webapps = None self.test_script = os.path.join(here, 'b2g_start_script.js') self.test_script_args = [self.out_of_process] self.product = 'b2g' self.remote_chrome_test_dir = None if profile_data_dir: self.preferences = [ os.path.join(profile_data_dir, f) for f in os.listdir(profile_data_dir) if f.startswith('pref') ] self.webapps = [ os.path.join(profile_data_dir, f) for f in os.listdir(profile_data_dir) if f.startswith('webapp') ] # mozinfo is populated by the parent class if mozinfo.info['debug']: self.SERVER_STARTUP_TIMEOUT = 180 else: self.SERVER_STARTUP_TIMEOUT = 90 def setup_common_options(self, options): test_url = self.buildTestPath(options) # For B2G emulators buildURLOptions has been called # without calling buildTestPath first and that # causes manifestFile not to be set if not "manifestFile=tests.json" in self.urlOpts: self.urlOpts.append("manifestFile=%s" % options.manifestFile) if len(self.urlOpts) > 0: test_url += "?" + "&".join(self.urlOpts) self.test_script_args.append(test_url) def buildTestPath(self, options, testsToFilter=None): if options.manifestFile != 'tests.json': super(B2GMochitest, self).buildTestPath(options, testsToFilter, disabled=False) return self.buildTestURL(options) def build_profile(self, options): # preferences prefs = {} for path in self.preferences: prefs.update(Preferences.read_prefs(path)) for v in options.extraPrefs: thispref = v.split("=", 1) if len(thispref) < 2: print "Error: syntax error in --setpref=" + v sys.exit(1) prefs[thispref[0]] = thispref[1] # interpolate the preferences interpolation = { "server": "%s:%s" % (options.webServer, options.httpPort), "OOP": "true" if self.out_of_process else "false" } prefs = json.loads(json.dumps(prefs) % interpolation) for pref in prefs: prefs[pref] = Preferences.cast(prefs[pref]) kwargs = { 'addons': self.getExtensionsToInstall(options), 'apps': self.webapps, 'locations': self.locations_file, 'preferences': prefs, 'proxy': { "remote": options.webServer } } if options.profile: self.profile = Profile.clone(options.profile, **kwargs) else: self.profile = Profile(**kwargs) options.profilePath = self.profile.profile # TODO bug 839108 - mozprofile should probably handle this manifest = self.addChromeToProfile(options) self.copyExtraFilesToProfile(options) return manifest def run_tests(self, options): """ Prepare, configure, run tests and cleanup """ self.setTestRoot(options) manifest = self.build_profile(options) self.logPreamble(self.getActiveTests(options)) # configuring the message logger's buffering self.message_logger.buffering = options.quiet if options.debugger or not options.autorun: timeout = None else: if not options.timeout: if mozinfo.info['debug']: options.timeout = 420 else: options.timeout = 300 timeout = options.timeout + 30.0 self.log.info("runtestsb2g.py | Running tests: start.") status = 0 try: def on_output(line): messages = self.message_logger.write(line) for message in messages: if message['action'] == 'test_start': self.runner.last_test = message['test'] # The logging will be handled by on_output, so we set the stream to # None process_args = {'processOutputLine': on_output, 'stream': None} self.marionette_args['process_args'] = process_args self.marionette_args['profile'] = self.profile self.marionette = Marionette(**self.marionette_args) self.runner = self.marionette.runner self.app_ctx = self.runner.app_ctx self.remote_log = posixpath.join(self.app_ctx.remote_test_root, 'log', 'mochitest.log') if not self.app_ctx.dm.dirExists(posixpath.dirname( self.remote_log)): self.app_ctx.dm.mkDirs(self.remote_log) if options.chrome: # Update chrome manifest file in profile with correct path. self.writeChromeManifest(options) self.leak_report_file = posixpath.join( self.app_ctx.remote_test_root, 'log', 'runtests_leaks.log') # We don't want to copy the host env onto the device, so pass in an # empty env. self.browserEnv = self.buildBrowserEnv(options, env={}) # B2G emulator debug tests still make external connections, so don't # pass MOZ_DISABLE_NONLOCAL_CONNECTIONS to them for now (bug # 1039019). if mozinfo.info[ 'debug'] and 'MOZ_DISABLE_NONLOCAL_CONNECTIONS' in self.browserEnv: del self.browserEnv['MOZ_DISABLE_NONLOCAL_CONNECTIONS'] self.runner.env.update(self.browserEnv) # Despite our efforts to clean up servers started by this script, in practice # we still see infrequent cases where a process is orphaned and interferes # with future tests, typically because the old server is keeping the port in use. # Try to avoid those failures by checking for and killing orphan servers before # trying to start new ones. self.killNamedOrphans('ssltunnel') self.killNamedOrphans('xpcshell') self.startServers(options, None) # In desktop mochitests buildTestPath is called before buildURLOptions. This # means options.manifestFile has already been converted to the proper json # style manifest. Not so with B2G, that conversion along with updating the URL # option will happen later. So backup and restore options.manifestFile to # prevent us from trying to pass in an instance of TestManifest via url param. manifestFile = options.manifestFile options.manifestFile = None self.buildURLOptions(options, {'MOZ_HIDE_RESULTS_TABLE': '1'}) options.manifestFile = manifestFile self.test_script_args.append(not options.emulator) self.test_script_args.append(options.wifi) self.test_script_args.append(options.chrome) self.runner.start(outputTimeout=timeout) self.marionette.wait_for_port() self.marionette.start_session() self.marionette.set_context(self.marionette.CONTEXT_CHROME) # Disable offline status management (bug 777145), otherwise the network # will be 'offline' when the mochitests start. Presumably, the network # won't be offline on a real device, so we only do this for # emulators. self.marionette.execute_script(""" Components.utils.import("resource://gre/modules/Services.jsm"); Services.io.manageOfflineStatus = false; Services.io.offline = false; """) self.marionette.execute_script(""" let SECURITY_PREF = "security.turn_off_all_security_so_that_viruses_can_take_over_this_computer"; Services.prefs.setBoolPref(SECURITY_PREF, true); if (!testUtils.hasOwnProperty("specialPowersObserver")) { let loader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"] .getService(Components.interfaces.mozIJSSubScriptLoader); loader.loadSubScript("chrome://specialpowers/content/SpecialPowersObserver.js", testUtils); testUtils.specialPowersObserver = new testUtils.SpecialPowersObserver(); testUtils.specialPowersObserver.init(); testUtils.specialPowersObserver._loadFrameScript(); } """) if options.chrome: self.app_ctx.dm.removeDir(self.remote_chrome_test_dir) self.app_ctx.dm.mkDir(self.remote_chrome_test_dir) local = super(B2GMochitest, self).getChromeTestDir(options) local = os.path.join(local, "chrome") remote = self.remote_chrome_test_dir self.log.info("pushing %s to %s on device..." % (local, remote)) self.app_ctx.dm.pushDir(local, remote) if os.path.isfile(self.test_script): with open(self.test_script, 'r') as script: self.marionette.execute_script( script.read(), script_args=self.test_script_args) else: self.marionette.execute_script( self.test_script, script_args=self.test_script_args) status = self.runner.wait() if status is None: # the runner has timed out status = 124 local_leak_file = tempfile.NamedTemporaryFile() self.app_ctx.dm.getFile(self.leak_report_file, local_leak_file.name) self.app_ctx.dm.removeFile(self.leak_report_file) mozleak.process_leak_log( local_leak_file.name, leak_thresholds=options.leakThresholds, ignore_missing_leaks=options.ignoreMissingLeaks, log=self.log, ) except KeyboardInterrupt: self.log.info("runtests.py | Received keyboard interrupt.\n") status = -1 except: traceback.print_exc() self.log.error( "Automation Error: Received unexpected exception while running application\n" ) if hasattr(self, 'runner'): self.runner.check_for_crashes() status = 1 self.stopServers() self.log.info("runtestsb2g.py | Running tests: end.") if manifest is not None: self.cleanup(manifest, options) return status def getGMPPluginPath(self, options): if options.gmp_path: return options.gmp_path return '/system/b2g/gmp-clearkey/0.1' def getChromeTestDir(self, options): # The chrome test directory returned here is the remote location # of chrome test files. A reference to this directory is requested # when building the profile locally, before self.app_ctx is defined. # To get around this, return a dummy directory until self.app_ctx # is defined; the correct directory will be returned later, over- # writing the dummy. if hasattr(self, 'app_ctx'): self.remote_chrome_test_dir = posixpath.join( self.app_ctx.remote_test_root, 'chrome') return self.remote_chrome_test_dir return 'dummy-chrome-test-dir'
class B2GMixin(object): profileDir = None userJS = "/data/local/user.js" marionette = None def __init__(self, host=None, marionette_port=2828, **kwargs): self.marionetteHost = host self.marionettePort = marionette_port def cleanup(self): if self.profileDir: self.restoreProfile() def waitForPort(self, timeout): """ Wait for the marionette server to respond. Timeout parameter is in seconds """ print "waiting for port" starttime = datetime.datetime.now() while datetime.datetime.now() - starttime < datetime.timedelta( seconds=timeout): try: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) print "trying %s %s" % (self.marionettePort, self.marionetteHost) sock.connect((self.marionetteHost, self.marionettePort)) data = sock.recv(16) sock.close() if '"from"' in data: return True except socket.error: pass except Exception as e: raise DMError("Could not connect to marionette: %s" % e) time.sleep(1) raise DMError("Could not communicate with Marionette port") def setupMarionette(self): """ Start a marionette session. If no host is given, then this will get the ip of the device, and set up networking if needed. """ if not self.marionetteHost: self.setupDHCP() self.marionetteHost = self.getIP() if not self.marionette: self.marionette = Marionette(self.marionetteHost, self.marionettePort) if not self.marionette.session: self.waitForPort(30) self.marionette.start_session() def restartB2G(self): """ Restarts the b2g process on the device """ #restart b2g so we start with a clean slate if self.marionette and self.marionette.session: self.marionette.delete_session() self.shellCheckOutput(['stop', 'b2g']) # Wait for a bit to make sure B2G has completely shut down. tries = 10 while "b2g" in self.shellCheckOutput(['ps', 'b2g']) and tries > 0: tries -= 1 time.sleep(1) if tries == 0: raise DMError("Could not kill b2g process") self.shellCheckOutput(['start', 'b2g']) def setupProfile(self, prefs=None): """ Sets up the user profile on the device, The 'prefs' is a string of user_prefs to add to the profile. If it is not set, it will default to a standard b2g testing profile. """ if not prefs: prefs = """ user_pref("power.screen.timeout", 999999); user_pref("devtools.debugger.force-local", false); """ #remove previous user.js if there is one if not self.profileDir: self.profileDir = tempfile.mkdtemp() our_userJS = os.path.join(self.profileDir, "user.js") if os.path.exists(our_userJS): os.remove(our_userJS) #copy profile try: output = self.getFile(self.userJS, our_userJS) except subprocess.CalledProcessError: pass #if we successfully copied the profile, make a backup of the file if os.path.exists(our_userJS): self.shellCheckOutput( ['dd', 'if=%s' % self.userJS, 'of=%s.orig' % self.userJS]) with open(our_userJS, 'a') as user_file: user_file.write("%s" % prefs) self.pushFile(our_userJS, self.userJS) self.restartB2G() self.setupMarionette() def setupDHCP(self, conn_type='eth0'): """ Sets up networking. If conn_type is not set, it will assume eth0. """ tries = 5 while tries > 0: print "attempts left: %d" % tries try: self.shellCheckOutput(['netcfg', conn_type, 'dhcp'], timeout=10) if self.getIP(): return except DMError: pass tries = tries - 1 raise DMError("Could not set up network connection") def restoreProfile(self): """ Restores the original profile """ if not self.profileDir: raise DMError("There is no profile to restore") #if we successfully copied the profile, make a backup of the file our_userJS = os.path.join(self.profileDir, "user.js") if os.path.exists(our_userJS): self.shellCheckOutput( ['dd', 'if=%s.orig' % self.userJS, 'of=%s' % self.userJS]) shutil.rmtree(self.profileDir) self.profileDir = None def getAppInfo(self): """ Returns the appinfo, with an additional "date" key. """ if not self.marionette or not self.marionette.session: self.setupMarionette() self.marionette.set_context("chrome") appinfo = self.marionette.execute_script(""" var appInfo = Components.classes["@mozilla.org/xre/app-info;1"] .getService(Components.interfaces.nsIXULAppInfo); return appInfo; """) (year, month, day) = (appinfo["appBuildID"][0:4], appinfo["appBuildID"][4:6], appinfo["appBuildID"][6:8]) appinfo['date'] = "%s-%s-%s" % (year, month, day) return appinfo