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()
def run_marionette(self, dir): self.logger.info("Starting test run") # Start up marionette m = Marionette(emulator=True, homedir=dir) assert m.start_session() for test in self.testlist: run_test(test, m) m.delete_session()
def launch_app(app_name, adb_path="adb", script_timeout=5000, marionette=None, device_serial=None): """ Launches the given app NOTE: if a marionette session is passed, this function switches to the top-most frame. """ 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") if not marionette: m = Marionette() m.start_session() else: m = marionette m.switch_to_frame() launch_app = """ var launchWithName = 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()) { app.launch(); return true; } } } else { let appName = app.manifest.name; if (name == appName.toLowerCase()) { app.launch(); return true; } } } return false; }; return launchWithName("%s"); """ m.set_script_timeout(script_timeout) m.execute_script(launch_app % app_name.lower()) if not marionette: m.delete_session()
def uninstall_app(app_name, adb_path="adb", script_timeout=5000, marionette=None): """ Uninstalls the given app. NOTE: if a marionette session is passed, this function switches to the top-most frame. """ dm = mozdevice.DeviceManagerADB(adbPath=adb_path) installed_app_name = app_name.lower() installed_app_name = installed_app_name.replace(" ", "-") if dm.forward("tcp:2828", "tcp:2828") != 0: raise Exception("Can't use localhost:2828 for port forwarding." \ "Is something else using port 2828?") if not marionette: m = Marionette() m.start_session() else: m = marionette m.switch_to_frame() 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) m.execute_script(uninstall_app % app_name.lower()) if not marionette: m.delete_session()
def dual_driving(): mm = Marionette(host='localhost', port=2829) mm.start_session() md = Marionette(host='localhost', port=2828) md.start_session() md.set_search_timeout(1000) # especially required for webcompat.com JS-driven loading ignored_bugs = [] buglist = [] for line in open(ignore_file, 'r'): if line[0] == '#': continue ignored_bugs.append(line.strip()) if start_url: print 'Starting from bug search %s' % start_url md.navigate(start_url) buglist = extract_buglist(md) else: buglist = extract_buglist_from_file(filename) i = 1 for item in buglist: if len(item) <= 1: print 'Warning: we expect data format ID Summary URL, something is missing' continue if '://' not in item[0]: # assuming this is Bugzilla data from a tab-separated file - in other words a plain bug number md.navigate('https://bugzilla.mozilla.org/show_bug.cgi?id=%s' % item[0]) else: # we've got a bug tracker URL (right?) md.navigate(item[0]) if len(item) == 2: # URL is not in the data - let's load the bug first and try to get it from there url = get_url_from_bugpage(md) else: url = item[2] if not url: i+=1 continue if i<start_at or url.strip() == '': i+=1 continue if '://' not in url: url = 'http://%s' % url url = url.strip().rstrip('\r\n') location = urlparse.urlparse(url) hostname = location.hostname.rstrip('\r\n') print str(i) + ' : ' + url reset_spoof(mm) mm.navigate(url) print 'READY to analyze %s, \n%s' % (item[0], item[1]) options_menu(mm, url, md) mm.delete_session() md.delete_session()
def launch_app(app_name, adb_path="adb", script_timeout=5000, marionette=None, device_serial=None): """ Launches the given app NOTE: if a marionette session is passed, this function switches to the top-most frame. """ 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") if not marionette: m = Marionette() m.start_session() else: m = marionette m.switch_to_frame() launch_app = """ var launchWithName = 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()) { app.launch(); return true; } } } else { let appName = app.manifest.name; if (name == appName.toLowerCase()) { app.launch(); return true; } } } return false; }; return launchWithName("%s"); """ m.set_script_timeout(script_timeout) m.execute_script(launch_app % app_name.lower()) if not marionette: m.delete_session()
def runemulator(homedir=None, url=None, pidfile=None, arch='x86'): qemu = Emulator(homedir=homedir, arch=arch) qemu.start() port = qemu.setup_port_forwarding(2828) assert(qemu.wait_for_port()) if pidfile: f = open(pidfile, 'w') f.write("%d" % qemu.proc.pid) f.close() print 'emulator launched, pid:', qemu.proc.pid if url: marionette = Marionette(port=port) marionette.start_session() marionette.navigate(url) marionette.delete_session()
def runemulator(homedir=None, url=None, pidfile=None, arch='x86'): qemu = Emulator(homedir=homedir, arch=arch) qemu.start() port = qemu.setup_port_forwarding(2828) assert (qemu.wait_for_port()) if pidfile: f = open(pidfile, 'w') f.write("%d" % qemu.proc.pid) f.close() print 'emulator launched, pid:', qemu.proc.pid if url: marionette = Marionette(port=port) marionette.start_session() marionette.navigate(url) marionette.delete_session()
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 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 TestRun(object): def __init__(self, adb="adb", serial=None): self.test_results = {} self.test_results_file = None self.m = None self.gaia_apps = None self.screenshot_path = None self.logcat_path = None self.app_name = None self.attempt = None self.num_apps = None self.device = None self.serial = serial self.port = None self.run_log = logging.getLogger('marketplace-test') if self.serial: self.dm = DeviceManagerADB(adbPath=adb, deviceSerial=serial) else: self.dm = DeviceManagerADB(adbPath=adb) def reset_marionette(self): try: self.m.delete_session() except Exception: pass self.m = None self.get_marionette() def get_marionette(self): if not self.m: self.m = Marionette(port=self.port) self.m.start_session() self.device = GaiaDevice(self.m) self.device.add_device_manager(self.dm) self.gaia_apps = GaiaApps(self.m) else: tries = 5 while tries > 0: try: self.m.get_url() break except MarionetteException as e: if "Please start a session" in str(e): time.sleep(5) self.m = Marionette(port=self.port) self.m.start_session() self.device = GaiaDevice(self.m) self.device.add_device_manager(self.dm) self.gaia_apps = GaiaApps(self.m) tries -= 1 else: raise e else: self.run_log.error("Can't connect to marionette, rebooting") self.restart_device() return self.m def write_to_file(self, data): with open("%s.tmp" % self.test_results_file, "w") as f: f.write(data) shutil.copyfile("%s.tmp" % self.test_results_file, self.test_results_file) def add_values(self, key, value): if self.serial: self.test_results["%s_%s" % (key, self.serial)] = value else: self.test_results["%s" % key] = value def add_result(self, passed=False, status=None, uninstalled_failure=False): values = {} if status: if not passed: values["status"] = "FAILED: %s" % status else: values["status"] = "PASS" if self.screenshot_path: values["screenshot"] = self.screenshot_path if self.logcat_path: values["logcat"] = self.logcat_path if uninstalled_failure: values["uninstalled_failure"] = uninstalled_failure entry = "%s_%s" % (self.app_name, self.attempt) self.test_results[entry] = values def launch_with_manifest(self, manifest): self.m.switch_to_frame() result = self.m.execute_async_script( "GaiaApps.launchWithManifestURL('%s')" % manifest, script_timeout=30000) if result == False: raise Exception("launch timed out") app = GaiaApp(frame=result.get('frame'), src=result.get('src'), name=result.get('name'), origin=result.get('origin')) if app.frame_id is None: raise Exception("failed to launch; there is no app frame") self.m.switch_to_frame(app.frame_id) return app def uninstall_with_manifest(self, manifest): self.m.switch_to_frame() script = """ GaiaApps.locateWithManifestURL('%s', null, function uninstall(app) { navigator.mozApps.mgmt.uninstall(app); marionetteScriptFinished(true); }); """ result = self.m.execute_async_script(script % manifest, script_timeout=60000) if result != True: self.add_result(status="Failed to uninstall app with url '%s'" % manifest) return app def forward_port(self): # get unused port s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind(('localhost', 0)) addr, port = s.getsockname() s.close() dm_tries = 0 self.run_log.info("using port %s" % port) while dm_tries < 20: if self.dm.forward("tcp:%d" % port, "tcp:2828") == 0: break dm_tries += 1 time.sleep(3) else: return False self.port = port return True def restart_device(self, restart_tries=0): self.run_log.info("rebooting") # TODO restarting b2g doesn't seem to work... reboot then while restart_tries < 3: restart_tries += 1 self.dm.reboot(wait=True) self.run_log.info("forwarding") if not self.forward_port(): self.run_log.error("couldn't forward port in time, rebooting") continue self.m = Marionette(port=self.port) if not self.m.wait_for_port(180): self.run_log.error( "couldn't contact marionette in time, rebooting") continue time.sleep(1) self.m.start_session() try: Wait(self.m, timeout=240).until(lambda m: m.find_element( "id", "lockscreen-container").is_displayed()) # It retuns a little early time.sleep(2) self.device = GaiaDevice(self.m) self.device.add_device_manager(self.dm) self.device.unlock() self.gaia_apps = GaiaApps(self.m) except (MarionetteException, IOError, socket.error) as e: self.run_log.error("got exception: %s, going to retry" % e) try: self.m.delete_session() except: # at least attempt to clear the session if possible pass continue break else: raise Exception( "Couldn't restart the device in time, even after 3 tries") def readystate_wait(self, app): try: Wait(self.get_marionette(), timeout=30).until(lambda m: m.execute_script( "return window.document.readyState;") == "complete") except ScriptTimeoutException as e: return False return True def record_icons(self): self.device.touch_home_button() icons = self.m.find_elements("class name", "icon") self.num_apps = len(icons) def check_if_app_installed(self, timeout=180): # TODO: Find a better way to do this than checking homescreen # I hope there is one... self.device.touch_home_button() icons = self.m.find_elements("class name", "icon") start = time.time() end = start + 180 found_icon = None claims_its_loaded = 0 # this is used in case 'loading' isn't applied immediately to the icon while time.time() < end: if not found_icon: icons = self.m.find_elements("class name", "icon") # We can't do set comparison b/c references change if len(icons) > self.num_apps: for icon in icons: if "loading" in icon.get_attribute("innerHTML"): found_icon = icon break else: claims_its_loaded += 1 if claims_its_loaded == 3: return True else: if "loading" not in found_icon.get_attribute("innerHTML"): return True time.sleep(2) return False
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()
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 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 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 GCli(object): def __init__(self): self.commands = { 'connectwifi': { 'function': self.connect_to_wifi, 'args': [ {'name': 'ssid', 'help': 'SSID of the network to connect to'}, {'name': '--security', 'choices': ['WPA-PSK', 'WEP'], 'help': 'Security model of the network'}, {'name': '--password', 'help': 'Password to access the network'}], 'help': 'Connect to a WiFi network'}, 'disablewifi': { 'function': self.disable_wifi, 'help': 'Disable WiFi'}, 'enablewifi': { 'function': self.enable_wifi, 'help': 'Enable WiFi'}, 'forgetallwifi': { 'function': self.forget_all_wifi_networks, 'help': 'Forget all WiFi networks'}, 'getknownwifi': { 'function': self.known_wifi_networks, 'help': 'Show known WiFi networks'}, 'getsetting': { 'function': self.get_setting, 'args': [{ 'name': 'name', 'help': 'Name of the setting to retrieve'}], 'help': 'Show the current value of a setting'}, 'killapps': { 'function': self.kill_all_apps, 'help': 'Kill all running apps'}, 'launchapp': { 'function': self.launch_app, 'args': [ {'name': 'name', 'nargs': argparse.REMAINDER, 'help': 'Name of app to launch'}], 'help': 'Launch an application'}, 'lock': { 'function': self.lock, 'help': 'Lock screen'}, 'setsetting': { 'function': self.set_setting, 'args': [ {'name': 'name', 'help': 'Name of setting to change'}, {'name': 'value', 'help': 'New value for setting'}], 'help': 'Change the value of a setting'}, 'unlock': { 'function': self.unlock, 'help': 'Unlock screen'}} self.parser = argparse.ArgumentParser() self.add_options(self.parser) self.add_commands(self.parser) def run(self, args=sys.argv[1:]): args = self.parser.parse_args() host, port = args.address.split(':') self.marionette = Marionette(host=host, port=int(port)) self.marionette.start_session() self.apps = gaiatest.GaiaApps(self.marionette) self.data_layer = gaiatest.GaiaData(self.marionette) self.lock_screen = gaiatest.LockScreen(self.marionette) ret = args.func(args) if ret is None: ret = 0 self.marionette.delete_session() sys.exit(ret) def add_options(self, parser): parser.add_argument( '--address', default='localhost:2828', help='Address (host:port) of running Gecko instance to connect to ' '(default: %(default)s)') def add_commands(self, parser): subparsers = parser.add_subparsers(title='Commands', metavar='<command>') for (name, props) in sorted(self.commands.iteritems()): subparser = subparsers.add_parser(name, help=props['help']) if props.get('args'): for arg in props['args']: kwargs = {k: v for k, v in arg.items() if k is not 'name'} subparser.add_argument(arg['name'], **kwargs) subparser.set_defaults(func=props['function']) def connect_to_wifi(self, args): network = { 'ssid': args.ssid, 'keyManagement': args.security or 'NONE'} if args.security == 'WEP': network['wep'] = args.password elif args.security == 'WPA-PSK': network['psk'] = args.password self.data_layer.connect_to_wifi(network) def disable_wifi(self, args): self.data_layer.disable_wifi() def enable_wifi(self, args): self.data_layer.enable_wifi() def forget_all_wifi_networks(self, args): self.data_layer.forget_all_networks() def get_setting(self, args): print '%s: %s' % ( args.name, self.data_layer.get_setting(args.name)) def kill_all_apps(self, args): self.apps.kill_all() def known_wifi_networks(self, args): networks = [n for n in self.data_layer.known_networks if 'ssid' in n] if len(networks) > 0: for i, network in enumerate(networks): print '%s: %s' % (i + 1, network['ssid']) else: print 'No known networks.' def launch_app(self, args): for name in args.name: self.apps.launch(name) def lock(self, args): self.lock_screen.lock() def set_setting(self, args): self.data_layer.set_setting(args.name, args.value) def unlock(self, args): self.lock_screen.unlock()
def dual_driving(): try: print('Will connect to mobile..') mm = Marionette(host='localhost', port=2829) mm.start_session() if disable_ua_overrides_by_default: set_mozilla_pref(mm, 'general.useragent.site_specific_overrides', False) set_mozilla_pref(mm, 'general.useragent.updates.enabled', False) print('Will connect to desktop...') md = Marionette(host='localhost', port=2828) md.start_session() md.set_search_timeout(1000) # especially required for webcompat.com JS-driven loading ignored_bugs = [] buglist = [] device_map = get_device_map() for line in open(ignore_file, 'r'): if line[0] == '#': continue ignored_bugs.append(line.strip()) if start_url: print 'Starting from bug search %s' % start_url md.navigate(start_url) buglist = extract_buglist(md) else: buglist = extract_buglist_from_file(filename) i = 1 #print(len(buglist)) for item in buglist: if len(item) <= 1: print 'Warning: we expect data format ID Summary URL, something is missing' continue if i<start_at: i+=1 continue buglink = '' if '://' not in item[0]: # assuming this is Bugzilla data from a tab-separated file - in other words a plain bug number # TODO: will we ever process lists of webcompat.com "plain numbers"?? buglink = 'https://bugzilla.mozilla.org/show_bug.cgi?id=%s' % item[0] else: # we've got a bug tracker URL (right?) buglink = item[0] # users who have bugzilla's "load next bug in search" don't need an extra navigate() call if buglink not in md.get_url(): print('Item %s, Loading bug %s'%(i,item[0])) md.navigate(item[0]) if len(item) == 2: # URL is not in the data - let's load the bug first and try to get it from there url = get_url_from_bugpage(md) else: url = item[2] if not url: i+=1 continue if url.strip() == '': i+=1 continue if '://' not in url: url = 'http://%s' % url url = url.strip().rstrip('\r\n') location = urlparse.urlparse(url) hostname = location.hostname.rstrip('\r\n') print str(i) + ' : ' + url reset_spoof(mm) try: mm.navigate(url) try_to_launch_url_in_android(device_map, url) print 'READY to analyze %s, \n%s' % (item[0], item[1]) except: print('could not load %s, try again by pressing u\n\n' % url) options_menu(mm, url, md, device_map) mm.delete_session() md.delete_session() except Exception as err: print err try: mm.delete_session() except: pass try: md.delete_session() except: pass
class GCli(object): def __init__(self): self.commands = { 'connectwifi': { 'function': self.connect_to_wifi, 'args': [ {'name': 'ssid', 'help': 'SSID of the network to connect to'}, {'name': '--security', 'choices': ['WPA-PSK', 'WEP'], 'help': 'Security model of the network'}, {'name': '--password', 'help': 'Password to access the network'}], 'help': 'Connect to a WiFi network'}, 'disablewifi': { 'function': self.disable_wifi, 'help': 'Disable WiFi'}, 'enablewifi': { 'function': self.enable_wifi, 'help': 'Enable WiFi'}, 'forgetallwifi': { 'function': self.forget_all_wifi_networks, 'help': 'Forget all WiFi networks'}, 'getknownwifi': { 'function': self.known_wifi_networks, 'help': 'Show known WiFi networks'}, 'getsetting': { 'function': self.get_setting, 'args': [{ 'name': 'name', 'help': 'Name of the setting to retrieve'}], 'help': 'Show the current value of a setting'}, 'holdhome': { 'function': self.hold_home, 'help': 'Simulate holding the home button'}, 'holdsleep': { 'function': self.hold_sleep, 'help': 'Simulate holding the sleep button'}, 'home': { 'function': self.home, 'help': 'Simulate pressing the home button'}, 'killapps': { 'function': self.kill_all_apps, 'help': 'Kill all running apps'}, 'launchapp': { 'function': self.launch_app, 'args': [ {'name': 'name', 'nargs': argparse.REMAINDER, 'help': 'Name of app to launch'}], 'help': 'Launch an application'}, 'lock': { 'function': self.lock, 'help': 'Lock screen'}, 'screenshot': { 'function': self.screenshot, 'help': 'Take a screenshot'}, 'sendsms': { 'function': self.send_sms, 'args': [ {'name': 'number', 'help': 'Phone number of recipient'}, {'name': 'message', 'help': 'Message content'}], 'help': 'Send an SMS'}, 'setsetting': { 'function': self.set_setting, 'args': [ {'name': 'name', 'help': 'Name of setting to change'}, {'name': 'value', 'help': 'New value for setting'}], 'help': 'Change the value of a setting'}, 'sleep': { 'function': self.sleep, 'help': 'Enter sleep mode'}, 'unlock': { 'function': self.unlock, 'help': 'Unlock screen'}, 'volume': { 'function': self.volume, 'args': [ {'name': 'direction', 'choices': ['down', 'up'], 'help': 'Direction to change the volume'}], 'help': 'Change the volume'}, 'wake': { 'function': self.wake, 'help': 'Wake from sleep mode'}} self.parser = argparse.ArgumentParser() self.add_options(self.parser) self.add_commands(self.parser) def run(self, args=sys.argv[1:]): args = self.parser.parse_args() host, port = args.address.split(':') self.marionette = Marionette(host=host, port=int(port)) self.marionette.start_session() self.apps = gaiatest.GaiaApps(self.marionette) self.data_layer = gaiatest.GaiaData(self.marionette) self.lock_screen = gaiatest.LockScreen(self.marionette) ret = args.func(args) if ret is None: ret = 0 self.marionette.delete_session() sys.exit(ret) def add_options(self, parser): parser.add_argument( '--address', default='localhost:2828', help='Address (host:port) of running Gecko instance to connect to ' '(default: %(default)s)') def add_commands(self, parser): subparsers = parser.add_subparsers( title='Commands', metavar='<command>') for (name, props) in sorted(self.commands.iteritems()): subparser = subparsers.add_parser(name, help=props['help']) if props.get('args'): for arg in props['args']: kwargs = {k: v for k, v in arg.items() if k is not 'name'} subparser.add_argument(arg['name'], **kwargs) subparser.set_defaults(func=props['function']) def connect_to_wifi(self, args): network = { 'ssid': args.ssid, 'keyManagement': args.security or 'NONE'} if args.security == 'WEP': network['wep'] = args.password elif args.security == 'WPA-PSK': network['psk'] = args.password self.data_layer.connect_to_wifi(network) def disable_wifi(self, args): self.data_layer.disable_wifi() def enable_wifi(self, args): self.data_layer.enable_wifi() def forget_all_wifi_networks(self, args): self.data_layer.forget_all_networks() def get_setting(self, args): print '%s: %s' % ( args.name, self.data_layer.get_setting(args.name)) def home(self, args): self.marionette.execute_script( "window.wrappedJSObject.dispatchEvent(new Event('home'));") def hold_home(self, args): self.marionette.execute_script( "window.wrappedJSObject.dispatchEvent(new Event('holdhome'));") def hold_sleep(self, args): self.marionette.execute_script( "window.wrappedJSObject.dispatchEvent(new Event('holdsleep'));") def kill_all_apps(self, args): self.apps.kill_all() def known_wifi_networks(self, args): networks = [n for n in self.data_layer.known_networks if 'ssid' in n] if len(networks) > 0: for i, network in enumerate(networks): print '%s: %s' % (i + 1, network['ssid']) else: print 'No known networks.' def launch_app(self, args): for name in args.name: self.apps.launch(name) def lock(self, args): self.lock_screen.lock() def screenshot(self, args): self.marionette.execute_script( "window.wrappedJSObject.dispatchEvent(new Event('home+sleep'));") def send_sms(self, args): self.data_layer.send_sms(args.number, args.message) def set_setting(self, args): self.data_layer.set_setting(args.name, args.value) def sleep(self, args): self.marionette.execute_script( "window.wrappedJSObject.dispatchEvent(new Event('sleep'));") def unlock(self, args): self.lock_screen.unlock() def volume(self, args): self.marionette.execute_script( "window.wrappedJSObject.dispatchEvent(new Event('volume%s'));" % args.direction) def wake(self, args): self.marionette.execute_script( "window.wrappedJSObject.dispatchEvent(new Event('wake'));")
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
class B2GRunner(object): remote_profile_dir = None def __init__(self, dm, url, tmpdir, marionette_host=None, marionette_port=None): self.dm = dm self.url = url self.tmpdir = tmpdir self.userJS = "/data/local/user.js" self.marionette_host = marionette_host or 'localhost' self.marionette_port = marionette_port or 2828 self.marionette = None def wait_for_port(self, timeout): starttime = datetime.datetime.now() while datetime.datetime.now() - starttime < datetime.timedelta(seconds=timeout): try: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect(('localhost', self.marionette_port)) data = sock.recv(16) sock.close() if '"from"' in data: return True except: import traceback print traceback.format_exc() time.sleep(1) return False 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 if not self.wait_for_port(30000): raise Exception("Could not communicate with Marionette port after restarting B2G") self.marionette = Marionette(self.marionette_host, self.marionette_port) def setup_profile(self): #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]) user_js = open(our_user_js, 'a') user_js.write(""" user_pref("power.screen.timeout", 999999); """) user_js.close() self.dm.checkCmd(['push', our_user_js, self.userJS]) self.restart_b2g() def start(self): #forward the marionette port self.dm.checkCmd(['forward', 'tcp:%s' % self.marionette_port, 'tcp:%s' % self.marionette_port]) print "Setting up profile" self.setup_profile() #enable ethernet connection print "Running netcfg, it may take some time." self.dm.checkCmd(['shell', 'netcfg', 'eth0', 'dhcp']) #launch app session = self.marionette.start_session() if 'b2g' not in session: raise Exception("bad session value %s returned by start_session" % session) # start the tests by navigating to the mochitest url self.marionette.execute_script("window.location.href='%s';" % self.url) def stop(self): self.marionette.delete_session()
class TestRun(object): def __init__(self, adb="adb", serial=None): self.test_results = {} self.test_results_file = None self.m = None self.gaia_apps = None self.screenshot_path = None self.logcat_path = None self.app_name = None self.attempt = None self.num_apps = None self.device = None self.serial = serial self.port = None self.run_log = logging.getLogger('marketplace-test') if self.serial: self.dm = DeviceManagerADB(adbPath=adb, deviceSerial=serial) else: self.dm = DeviceManagerADB(adbPath=adb) def reset_marionette(self): try: self.m.delete_session() except Exception: pass self.m = None self.get_marionette() def get_marionette(self): if not self.m: self.m = Marionette(port=self.port) self.m.start_session() self.device = GaiaDevice(self.m) self.device.add_device_manager(self.dm) self.gaia_apps = GaiaApps(self.m) else: tries = 5 while tries > 0: try: self.m.get_url() break except MarionetteException as e: if "Please start a session" in str(e): time.sleep(5) self.m = Marionette(port=self.port) self.m.start_session() self.device = GaiaDevice(self.m) self.device.add_device_manager(self.dm) self.gaia_apps = GaiaApps(self.m) tries -= 1 else: raise e else: self.run_log.error("Can't connect to marionette, rebooting") self.restart_device() return self.m def write_to_file(self, data): with open("%s.tmp" % self.test_results_file, "w") as f: f.write(data) shutil.copyfile("%s.tmp" % self.test_results_file, self.test_results_file) def add_values(self, key, value): if self.serial: self.test_results["%s_%s" % (key, self.serial)] = value else: self.test_results["%s" % key] = value def add_result(self, passed=False, status=None, uninstalled_failure=False): values = {} if status: if not passed: values["status"] = "FAILED: %s" % status else: values["status"] = "PASS" if self.screenshot_path: values["screenshot"] = self.screenshot_path if self.logcat_path: values["logcat"] = self.logcat_path if uninstalled_failure: values["uninstalled_failure"] = uninstalled_failure entry = "%s_%s" % (self.app_name, self.attempt) self.test_results[entry] = values def launch_with_manifest(self, manifest): self.m.switch_to_frame() result = self.m.execute_async_script("GaiaApps.launchWithManifestURL('%s')" % manifest, script_timeout=30000) if result == False: raise Exception("launch timed out") app = GaiaApp(frame=result.get('frame'), src=result.get('src'), name=result.get('name'), origin=result.get('origin')) if app.frame_id is None: raise Exception("failed to launch; there is no app frame") self.m.switch_to_frame(app.frame_id) return app def uninstall_with_manifest(self, manifest): self.m.switch_to_frame() script = """ GaiaApps.locateWithManifestURL('%s', null, function uninstall(app) { navigator.mozApps.mgmt.uninstall(app); marionetteScriptFinished(true); }); """ result = self.m.execute_async_script(script % manifest, script_timeout=60000) if result != True: self.add_result(status="Failed to uninstall app with url '%s'" % manifest) return app def forward_port(self): # get unused port s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind(('localhost', 0)) addr, port = s.getsockname() s.close() dm_tries = 0 self.run_log.info("using port %s" % port) while dm_tries < 20: if self.dm.forward("tcp:%d" % port, "tcp:2828") == 0: break dm_tries += 1 time.sleep(3) else: return False self.port = port return True def restart_device(self, restart_tries=0): self.run_log.info("rebooting") # TODO restarting b2g doesn't seem to work... reboot then while restart_tries < 3: restart_tries += 1 self.dm.reboot(wait=True) self.run_log.info("forwarding") if not self.forward_port(): self.run_log.error("couldn't forward port in time, rebooting") continue self.m = Marionette(port=self.port) if not self.m.wait_for_port(180): self.run_log.error("couldn't contact marionette in time, rebooting") continue time.sleep(1) self.m.start_session() try: Wait(self.m, timeout=240).until(lambda m: m.find_element("id", "lockscreen-container").is_displayed()) # It retuns a little early time.sleep(2) self.device = GaiaDevice(self.m) self.device.add_device_manager(self.dm) self.device.unlock() self.gaia_apps = GaiaApps(self.m) except (MarionetteException, IOError, socket.error) as e: self.run_log.error("got exception: %s, going to retry" % e) try: self.m.delete_session() except: # at least attempt to clear the session if possible pass continue break else: raise Exception("Couldn't restart the device in time, even after 3 tries") def readystate_wait(self, app): try: Wait(self.get_marionette(), timeout=30).until(lambda m: m.execute_script("return window.document.readyState;") == "complete") except ScriptTimeoutException as e: return False return True def record_icons(self): self.device.touch_home_button() icons = self.m.find_elements("class name", "icon") self.num_apps = len(icons) def check_if_app_installed(self, timeout=180): # TODO: Find a better way to do this than checking homescreen # I hope there is one... self.device.touch_home_button() icons = self.m.find_elements("class name", "icon") start = time.time() end = start + 180 found_icon = None claims_its_loaded = 0 # this is used in case 'loading' isn't applied immediately to the icon while time.time() < end: if not found_icon: icons = self.m.find_elements("class name", "icon") # We can't do set comparison b/c references change if len(icons) > self.num_apps: for icon in icons: if "loading" in icon.get_attribute("innerHTML"): found_icon = icon break else: claims_its_loaded += 1 if claims_its_loaded == 3: return True else: if "loading" not in found_icon.get_attribute("innerHTML"): return True time.sleep(2) return False
def runApp(self, profile, binary, cmdargs, env, timeout=None, debuggerInfo=None, symbolsPath=None, options=None, valgrindPath=None, valgrindArgs=None, valgrindSuppFiles=None): def timeoutHandler(): self.handleTimeout(timeout, proc, options.utilityPath, debuggerInfo) interactive = False debug_args = None if debuggerInfo: interactive = debuggerInfo.interactive debug_args = [debuggerInfo.path] + debuggerInfo.args def record_last_test(message): """Records the last test seen by this harness for the benefit of crash logging.""" if message['action'] == 'test_start': if isinstance(message['test'], tuple): self.lastTestSeen = message['test'][0] else: self.lastTestSeen = message['test'] self.log.add_handler(record_last_test) outputHandler = OutputHandler(self.log, options.utilityPath, symbolsPath=symbolsPath) kp_kwargs = { 'kill_on_timeout': False, 'cwd': SCRIPT_DIRECTORY, 'onTimeout': [timeoutHandler], 'processOutputLine': [outputHandler], } if interactive: # If an interactive debugger is attached, # don't use timeouts, and don't capture ctrl-c. timeout = None signal.signal(signal.SIGINT, lambda sigid, frame: None) if mozinfo.info.get( 'appname') == 'b2g' and mozinfo.info.get('toolkit') != 'gonk': runner_cls = mozrunner.Runner else: runner_cls = mozrunner.runners.get( mozinfo.info.get('appname', 'firefox'), mozrunner.Runner) runner = runner_cls(profile=profile, binary=binary, process_class=mozprocess.ProcessHandlerMixin, cmdargs=cmdargs, env=env, process_args=kp_kwargs) runner.start(debug_args=debug_args, interactive=interactive, outputTimeout=timeout) proc = runner.process_handler if self.use_marionette: marionette_args = {'symbols_path': options.symbolsPath} if options.marionette: host, port = options.marionette.split(':') marionette_args['host'] = host marionette_args['port'] = int(port) marionette = Marionette(**marionette_args) marionette.start_session() addons = Addons(marionette) if options.specialPowersExtensionPath: addons.install(options.specialPowersExtensionPath, temp=True) addons.install(options.reftestExtensionPath, temp=True) marionette.delete_session() status = runner.wait() runner.process_handler = None if status: msg = "TEST-UNEXPECTED-FAIL | %s | application terminated with exit code %s" % \ (self.lastTestSeen, status) # use process_output so message is logged verbatim self.log.process_output(None, msg) else: self.lastTestSeen = 'Main app process exited normally' crashed = mozcrash.log_crashes(self.log, os.path.join(profile.profile, 'minidumps'), symbolsPath, test=self.lastTestSeen) runner.cleanup() if not status and crashed: status = 1 return status
class GCli(object): def __init__(self): self.commands = { 'connectwifi': { 'function': self.connect_to_wifi, 'args': [ {'name': 'ssid', 'help': 'SSID of the network to connect to'}, {'name': '--security', 'choices': ['WPA-PSK', 'WEP'], 'help': 'Security model of the network'}, {'name': '--password', 'help': 'Password to access the network'}], 'help': 'Connect to a WiFi network'}, 'disablewifi': { 'function': self.disable_wifi, 'help': 'Disable WiFi'}, 'enablewifi': { 'function': self.enable_wifi, 'help': 'Enable WiFi'}, 'forgetallwifi': { 'function': self.forget_all_wifi_networks, 'help': 'Forget all WiFi networks'}, 'getknownwifi': { 'function': self.known_wifi_networks, 'help': 'Show known WiFi networks'}, 'getsetting': { 'function': self.get_setting, 'args': [{ 'name': 'name', 'help': 'Name of the setting to retrieve'}], 'help': 'Show the current value of a setting'}, 'holdhome': { 'function': self.hold_home, 'help': 'Simulate holding the home button'}, 'holdsleep': { 'function': self.hold_sleep, 'help': 'Simulate holding the sleep button'}, 'home': { 'function': self.home, 'help': 'Simulate pressing the home button'}, 'killapps': { 'function': self.kill_all_apps, 'help': 'Kill all running apps'}, 'launchapp': { 'function': self.launch_app, 'args': [ {'name': 'name', 'nargs': argparse.REMAINDER, 'help': 'Name of app to launch'}], 'help': 'Launch an application'}, 'listallapps': { 'function': self.list_all_apps, 'help': 'List all apps'}, 'listrunningapps': { 'function': self.list_running_apps, 'help': 'List the running apps'}, 'lock': { 'function': self.lock, 'help': 'Lock screen'}, 'screenshot': { 'function': self.screenshot, 'help': 'Take a screenshot'}, 'sendsms': { 'function': self.send_sms, 'args': [ {'name': 'number', 'help': 'Phone number of recipient'}, {'name': 'message', 'help': 'Message content'}], 'help': 'Send an SMS'}, 'setsetting': { 'function': self.set_setting, 'args': [ {'name': 'name', 'help': 'Name of setting to change'}, {'name': 'value', 'help': 'New value for setting'}], 'help': 'Change the value of a setting'}, 'sleep': { 'function': self.sleep, 'help': 'Enter sleep mode'}, 'unlock': { 'function': self.unlock, 'help': 'Unlock screen'}, 'volume': { 'function': self.volume, 'args': [ {'name': 'direction', 'choices': ['down', 'up'], 'help': 'Direction to change the volume'}], 'help': 'Change the volume'}, 'wake': { 'function': self.wake, 'help': 'Wake from sleep mode'}} self.parser = argparse.ArgumentParser() self.add_options(self.parser) self.add_commands(self.parser) def run(self, args=sys.argv[1:]): args = self.parser.parse_args() host, port = args.address.split(':') self.marionette = Marionette(host=host, port=int(port)) self.marionette.start_session() self.apps = gaiatest.GaiaApps(self.marionette) self.data_layer = gaiatest.GaiaData(self.marionette) self.lock_screen = gaiatest.LockScreen(self.marionette) ret = args.func(args) if ret is None: ret = 0 self.marionette.delete_session() sys.exit(ret) def add_options(self, parser): parser.add_argument( '--address', default='localhost:2828', help='Address (host:port) of running Gecko instance to connect to ' '(default: %(default)s)') def add_commands(self, parser): subparsers = parser.add_subparsers( title='Commands', metavar='<command>') for (name, props) in sorted(self.commands.iteritems()): subparser = subparsers.add_parser(name, help=props['help']) if props.get('args'): for arg in props['args']: kwargs = {k: v for k, v in arg.items() if k is not 'name'} subparser.add_argument(arg['name'], **kwargs) subparser.set_defaults(func=props['function']) def connect_to_wifi(self, args): network = { 'ssid': args.ssid, 'keyManagement': args.security or 'NONE'} if args.security == 'WEP': network['wep'] = args.password elif args.security == 'WPA-PSK': network['psk'] = args.password self.data_layer.connect_to_wifi(network) def disable_wifi(self, args): self.data_layer.disable_wifi() def enable_wifi(self, args): self.data_layer.enable_wifi() def forget_all_wifi_networks(self, args): self.data_layer.forget_all_networks() def get_setting(self, args): print '%s: %s' % ( args.name, self.data_layer.get_setting(args.name)) def home(self, args): self.marionette.execute_script( "window.wrappedJSObject.dispatchEvent(new Event('home'));") def hold_home(self, args): self.marionette.execute_script( "window.wrappedJSObject.dispatchEvent(new Event('holdhome'));") def hold_sleep(self, args): self.marionette.execute_script( "window.wrappedJSObject.dispatchEvent(new Event('holdsleep'));") def kill_all_apps(self, args): self.apps.kill_all() def known_wifi_networks(self, args): networks = [n for n in self.data_layer.known_networks if 'ssid' in n] if len(networks) > 0: for i, network in enumerate(networks): print '%s: %s' % (i + 1, network['ssid']) else: print 'No known networks.' def launch_app(self, args): for name in args.name: self.apps.launch(name) def list_all_apps(self, args): for i, app in enumerate(sorted(self.apps.installed_apps, key=lambda a: a.name.lower())): print '%d: %s' % (i + 1, app.name) def list_running_apps(self, args): for i, app in enumerate(sorted(self.apps.running_apps, key=lambda a: a.name.lower())): print '%d: %s' % (i + 1, app.name) def lock(self, args): self.lock_screen.lock() def screenshot(self, args): self.marionette.execute_script( "window.wrappedJSObject.dispatchEvent(new Event('home+sleep'));") def send_sms(self, args): self.data_layer.send_sms(args.number, args.message) def set_setting(self, args): self.data_layer.set_setting(args.name, args.value) def sleep(self, args): self.marionette.execute_script( "window.wrappedJSObject.dispatchEvent(new Event('sleep'));") def unlock(self, args): self.lock_screen.unlock() def volume(self, args): self.marionette.execute_script( "window.wrappedJSObject.dispatchEvent(new Event('volume%s'));" % args.direction) def wake(self, args): self.marionette.execute_script( "window.wrappedJSObject.dispatchEvent(new Event('wake'));")
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()
class GCli(object): def __init__(self): self.commands = { "connectwifi": { "function": self.connect_to_wifi, "args": [ {"name": "ssid", "help": "SSID of the network to connect to"}, {"name": "--security", "choices": ["WPA-PSK", "WEP"], "help": "Security model of the network"}, {"name": "--password", "help": "Password to access the network"}, ], "help": "Connect to a WiFi network", }, "disablewifi": {"function": self.disable_wifi, "help": "Disable WiFi"}, "enablewifi": {"function": self.enable_wifi, "help": "Enable WiFi"}, "forgetallwifi": {"function": self.forget_all_wifi_networks, "help": "Forget all WiFi networks"}, "getknownwifi": {"function": self.known_wifi_networks, "help": "Show known WiFi networks"}, "getsetting": { "function": self.get_setting, "args": [{"name": "name", "help": "Name of the setting to retrieve"}], "help": "Show the current value of a setting", }, "holdhome": {"function": self.hold_home, "help": "Simulate holding the home button"}, "holdsleep": {"function": self.hold_sleep, "help": "Simulate holding the sleep button"}, "home": {"function": self.home, "help": "Simulate pressing the home button"}, "killapps": {"function": self.kill_all_apps, "help": "Kill all running apps"}, "launchapp": { "function": self.launch_app, "args": [{"name": "name", "nargs": argparse.REMAINDER, "help": "Name of app to launch"}], "help": "Launch an application", }, "lock": {"function": self.lock, "help": "Lock screen"}, "screenshot": {"function": self.screenshot, "help": "Take a screenshot"}, "sendsms": { "function": self.send_sms, "args": [ {"name": "number", "help": "Phone number of recipient"}, {"name": "message", "help": "Message content"}, ], "help": "Send an SMS", }, "setsetting": { "function": self.set_setting, "args": [ {"name": "name", "help": "Name of setting to change"}, {"name": "value", "help": "New value for setting"}, ], "help": "Change the value of a setting", }, "sleep": {"function": self.sleep, "help": "Enter sleep mode"}, "unlock": {"function": self.unlock, "help": "Unlock screen"}, "volume": { "function": self.volume, "args": [{"name": "direction", "choices": ["down", "up"], "help": "Direction to change the volume"}], "help": "Change the volume", }, "wake": {"function": self.wake, "help": "Wake from sleep mode"}, } self.parser = argparse.ArgumentParser() self.add_options(self.parser) self.add_commands(self.parser) def run(self, args=sys.argv[1:]): args = self.parser.parse_args() host, port = args.address.split(":") self.marionette = Marionette(host=host, port=int(port)) self.marionette.start_session() self.apps = gaiatest.GaiaApps(self.marionette) self.data_layer = gaiatest.GaiaData(self.marionette) self.lock_screen = gaiatest.LockScreen(self.marionette) ret = args.func(args) if ret is None: ret = 0 self.marionette.delete_session() sys.exit(ret) def add_options(self, parser): parser.add_argument( "--address", default="localhost:2828", help="Address (host:port) of running Gecko instance to connect to " "(default: %(default)s)", ) def add_commands(self, parser): subparsers = parser.add_subparsers(title="Commands", metavar="<command>") for (name, props) in sorted(self.commands.iteritems()): subparser = subparsers.add_parser(name, help=props["help"]) if props.get("args"): for arg in props["args"]: kwargs = {k: v for k, v in arg.items() if k is not "name"} subparser.add_argument(arg["name"], **kwargs) subparser.set_defaults(func=props["function"]) def connect_to_wifi(self, args): network = {"ssid": args.ssid, "keyManagement": args.security or "NONE"} if args.security == "WEP": network["wep"] = args.password elif args.security == "WPA-PSK": network["psk"] = args.password self.data_layer.connect_to_wifi(network) def disable_wifi(self, args): self.data_layer.disable_wifi() def enable_wifi(self, args): self.data_layer.enable_wifi() def forget_all_wifi_networks(self, args): self.data_layer.forget_all_networks() def get_setting(self, args): print "%s: %s" % (args.name, self.data_layer.get_setting(args.name)) def home(self, args): self.marionette.execute_script("window.wrappedJSObject.dispatchEvent(new Event('home'));") def hold_home(self, args): self.marionette.execute_script("window.wrappedJSObject.dispatchEvent(new Event('holdhome'));") def hold_sleep(self, args): self.marionette.execute_script("window.wrappedJSObject.dispatchEvent(new Event('holdsleep'));") def kill_all_apps(self, args): self.apps.kill_all() def known_wifi_networks(self, args): networks = [n for n in self.data_layer.known_networks if "ssid" in n] if len(networks) > 0: for i, network in enumerate(networks): print "%s: %s" % (i + 1, network["ssid"]) else: print "No known networks." def launch_app(self, args): for name in args.name: self.apps.launch(name) def lock(self, args): self.lock_screen.lock() def screenshot(self, args): self.marionette.execute_script("window.wrappedJSObject.dispatchEvent(new Event('home+sleep'));") def send_sms(self, args): self.data_layer.send_sms(args.number, args.message) def set_setting(self, args): self.data_layer.set_setting(args.name, args.value) def sleep(self, args): self.marionette.execute_script("window.wrappedJSObject.dispatchEvent(new Event('sleep'));") def unlock(self, args): self.lock_screen.unlock() def volume(self, args): self.marionette.execute_script("window.wrappedJSObject.dispatchEvent(new Event('volume%s'));" % args.direction) def wake(self, args): self.marionette.execute_script("window.wrappedJSObject.dispatchEvent(new Event('wake'));")
help='host:port of running Gecko instance to connect to') parser.add_option('--homedir', dest='homedir', action='store', help='home directory of emulator files') options, tests = parser.parse_args() if not tests: parser.print_usage() parser.exit() if options.address: host, port = options.address.split(':') if options.emulator: m = Marionette(host=host, port=int(port), connectToRunningEmulator=True, homedir=options.homedir) else: m = Marionette(host=host, port=int(port)) elif options.emulator: m = Marionette(emulator=True, homedir=options.homedir) else: raise Exception("must specify --address or --emulator") assert(m.start_session()) for test in tests: run_test(test, m) m.delete_session()
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
def runApp(self, profile, binary, cmdargs, env, timeout=None, debuggerInfo=None, symbolsPath=None, options=None, valgrindPath=None, valgrindArgs=None, valgrindSuppFiles=None): def timeoutHandler(): self.handleTimeout( timeout, proc, options.utilityPath, debuggerInfo) interactive = False debug_args = None if debuggerInfo: interactive = debuggerInfo.interactive debug_args = [debuggerInfo.path] + debuggerInfo.args def record_last_test(message): """Records the last test seen by this harness for the benefit of crash logging.""" if message['action'] == 'test_start': if isinstance(message['test'], tuple): self.lastTestSeen = message['test'][0] else: self.lastTestSeen = message['test'] self.log.add_handler(record_last_test) outputHandler = OutputHandler(self.log, options.utilityPath, symbolsPath=symbolsPath) kp_kwargs = { 'kill_on_timeout': False, 'cwd': SCRIPT_DIRECTORY, 'onTimeout': [timeoutHandler], 'processOutputLine': [outputHandler], } if interactive: # If an interactive debugger is attached, # don't use timeouts, and don't capture ctrl-c. timeout = None signal.signal(signal.SIGINT, lambda sigid, frame: None) if mozinfo.info.get('appname') == 'b2g' and mozinfo.info.get('toolkit') != 'gonk': runner_cls = mozrunner.Runner else: runner_cls = mozrunner.runners.get(mozinfo.info.get('appname', 'firefox'), mozrunner.Runner) runner = runner_cls(profile=profile, binary=binary, process_class=mozprocess.ProcessHandlerMixin, cmdargs=cmdargs, env=env, process_args=kp_kwargs) runner.start(debug_args=debug_args, interactive=interactive, outputTimeout=timeout) proc = runner.process_handler if self.use_marionette: marionette_args = { 'socket_timeout': options.marionette_socket_timeout, 'symbols_path': options.symbolsPath, } if options.marionette: host, port = options.marionette.split(':') marionette_args['host'] = host marionette_args['port'] = int(port) marionette = Marionette(**marionette_args) marionette.start_session(timeout=options.marionette_port_timeout) addons = Addons(marionette) if options.specialPowersExtensionPath: addons.install(options.specialPowersExtensionPath, temp=True) addons.install(options.reftestExtensionPath, temp=True) marionette.delete_session() status = runner.wait() runner.process_handler = None if status: msg = "TEST-UNEXPECTED-FAIL | %s | application terminated with exit code %s" % \ (self.lastTestSeen, status) # use process_output so message is logged verbatim self.log.process_output(None, msg) else: self.lastTestSeen = 'Main app process exited normally' crashed = mozcrash.log_crashes(self.log, os.path.join(profile.profile, 'minidumps'), symbolsPath, test=self.lastTestSeen) runner.cleanup() if not status and crashed: status = 1 return status
class B2GRunner(object): remote_profile_dir = None def __init__(self, dm, url, tmpdir, marionette_host=None, marionette_port=None): self.dm = dm self.url = url self.tmpdir = tmpdir self.userJS = "/data/local/user.js" self.marionette_host = marionette_host or 'localhost' self.marionette_port = marionette_port or 2828 self.marionette = None def wait_for_port(self, timeout): starttime = datetime.datetime.now() while datetime.datetime.now() - starttime < datetime.timedelta( seconds=timeout): try: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect(('localhost', self.marionette_port)) data = sock.recv(16) sock.close() if '"from"' in data: return True except: import traceback print traceback.format_exc() time.sleep(1) return False 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 if not self.wait_for_port(30000): raise Exception( "Could not communicate with Marionette port after restarting B2G" ) self.marionette = Marionette(self.marionette_host, self.marionette_port) def setup_profile(self): #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 ]) user_js = open(our_user_js, 'a') user_js.write(""" user_pref("power.screen.timeout", 999999); """) user_js.close() self.dm.checkCmd(['push', our_user_js, self.userJS]) self.restart_b2g() def start(self): #forward the marionette port self.dm.checkCmd([ 'forward', 'tcp:%s' % self.marionette_port, 'tcp:%s' % self.marionette_port ]) print "Setting up profile" self.setup_profile() #enable ethernet connection print "Running netcfg, it may take some time." self.dm.checkCmd(['shell', 'netcfg', 'eth0', 'dhcp']) #launch app session = self.marionette.start_session() if 'b2g' not in session: raise Exception("bad session value %s returned by start_session" % session) # start the tests by navigating to the mochitest url self.marionette.execute_script("window.location.href='%s';" % self.url) def stop(self): self.marionette.delete_session()