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
class B2GPopulate(object): PERSISTENT_STORAGE_PATH = '/data/local/storage/persistent' handler = mozlog.StreamHandler() handler.setFormatter(mozlog.MozFormatter(include_timestamp=True)) logger = mozlog.getLogger('B2GPopulate', handler) def __init__(self, marionette, log_level='INFO'): self.marionette = marionette self.data_layer = GaiaData(self.marionette) self.device = GaiaDevice(self.marionette) dm = mozdevice.DeviceManagerADB() self.device.add_device_manager(dm) self.logger.setLevel(getattr(mozlog, log_level.upper())) if self.device.is_android_build: self.idb_dir = 'idb' for candidate in self.device.manager.listFiles( '/'.join([self.PERSISTENT_STORAGE_PATH, 'chrome'])): if re.match('\d.*idb', candidate): self.idb_dir = candidate break def populate(self, call_count=None, contact_count=None, message_count=None, music_count=None, picture_count=None, video_count=None, event_count=None): restart = any([call_count, contact_count, message_count]) if restart: self.logger.debug('Stopping B2G') self.device.stop_b2g() if call_count is not None: self.populate_calls(call_count, restart=False) if contact_count is not None: self.populate_contacts(contact_count, restart=False) if event_count is not None: self.populate_events(event_count, restart=False) if message_count is not None: self.populate_messages(message_count, restart=False) if restart: self.start_b2g() if music_count > 0: self.populate_music(music_count) if picture_count > 0: self.populate_pictures(picture_count) if video_count > 0: self.populate_videos(video_count) def populate_calls(self, count, restart=True): # only allow preset db values for calls db_counts = DB_PRESET_COUNTS['call'] if not count in db_counts: raise InvalidCountError('call') self.logger.info('Populating %d calls' % count) db_counts.sort(reverse=True) for marker in db_counts: if count >= marker: key = 'communications.gaiamobile.org' local_id = json.loads(self.device.manager.pullFile( '/data/local/webapps/webapps.json'))[key]['localId'] db_zip_name = pkg_resources.resource_filename( __name__, os.path.sep.join(['resources', 'dialerDb.zip'])) db_name = 'dialerDb-%d.sqlite' % marker self.logger.debug('Extracting %s from %s' % ( db_name, db_zip_name)) db = ZipFile(db_zip_name).extract(db_name) if restart: self.device.stop_b2g() destination = '/'.join([self.PERSISTENT_STORAGE_PATH, '%s+f+app+++%s' % (local_id, key), self.idb_dir, '2584670174dsitanleecreR.sqlite']) self.logger.debug('Pushing %s to %s' % (db, destination)) self.device.push_file(db, destination=destination) self.logger.debug('Removing %s' % db) os.remove(db) if restart: self.start_b2g() break def populate_contacts(self, count, restart=True, include_pictures=True): # only allow preset db values for contacts db_counts = DB_PRESET_COUNTS['contact'] if not count in db_counts: raise InvalidCountError('contact') self.device.manager.removeDir('/'.join([ self.PERSISTENT_STORAGE_PATH, 'chrome', self.idb_dir, '*csotncta*'])) self.logger.info('Populating %d contacts' % count) db_counts.sort(reverse=True) for marker in db_counts: if count >= marker: db_zip_name = pkg_resources.resource_filename( __name__, os.path.sep.join(['resources', 'contactsDb.zip'])) db_name = 'contactsDb-%d.sqlite' % marker self.logger.debug('Extracting %s from %s' % ( db_name, db_zip_name)) db = ZipFile(db_zip_name).extract(db_name) if restart: self.device.stop_b2g() destination = '/'.join([ self.PERSISTENT_STORAGE_PATH, 'chrome', self.idb_dir, '3406066227csotncta.sqlite']) self.logger.debug('Pushing %s to %s' % (db, destination)) self.device.push_file(db, destination=destination) self.logger.debug('Removing %s' % db) os.remove(db) if marker > 0 and include_pictures: self.logger.debug('Adding contact pictures') pictures_zip_name = pkg_resources.resource_filename( __name__, os.path.sep.join( ['resources', 'contactsPictures.zip'])) temp = tempfile.mkdtemp() self.logger.debug('Extracting %s to %s' % ( pictures_zip_name, temp)) ZipFile(pictures_zip_name).extractall(temp) destination = '/'.join([ self.PERSISTENT_STORAGE_PATH, 'chrome', self.idb_dir, '3406066227csotncta']) self.logger.debug('Pushing %s to %s' % (temp, destination)) self.device.manager.pushDir(temp, destination) self.logger.debug('Removing %s' % temp) shutil.rmtree(temp) if restart: self.start_b2g() break def populate_events(self, count, restart=True): # only allow preset db values for events db_counts = DB_PRESET_COUNTS['event'] if not count in db_counts: raise InvalidCountError('event') self.logger.info('Populating %d events' % count) db_counts.sort(reverse=True) for marker in db_counts: if count >= marker: key = 'calendar.gaiamobile.org' local_id = json.loads(self.device.manager.pullFile( '/data/local/webapps/webapps.json'))[key]['localId'] db_zip_name = pkg_resources.resource_filename( __name__, os.path.sep.join(['resources', 'calendarDb.zip'])) db_name = 'calendarDb-%d.sqlite' % marker self.logger.debug('Extracting %s from %s' % ( db_name, db_zip_name)) db = ZipFile(db_zip_name).extract(db_name) if restart: self.device.stop_b2g() destination = '/'.join([self.PERSISTENT_STORAGE_PATH, '%s+f+app+++%s' % (local_id, key), self.idb_dir, '125582036br2agd-nceal.sqlite']) self.logger.debug('Pushing %s to %s' % (db, destination)) self.device.push_file(db, destination=destination) self.logger.debug('Removing %s' % db) os.remove(db) if restart: self.start_b2g() break def populate_messages(self, count, restart=True): # only allow preset db values for messages db_counts = DB_PRESET_COUNTS['message'] if not count in db_counts: raise InvalidCountError('message') self.logger.info('Populating %d messages' % count) db_counts.sort(reverse=True) for marker in db_counts: if count >= marker: db_zip_name = pkg_resources.resource_filename( __name__, os.path.sep.join(['resources', 'smsDb.zip'])) db_name = 'smsDb-%d.sqlite' % marker self.logger.debug('Extracting %s from %s' % ( db_name, db_zip_name)) db = ZipFile(db_zip_name).extract(db_name) if restart: self.device.stop_b2g() destination = '/'.join([ self.PERSISTENT_STORAGE_PATH, 'chrome', self.idb_dir, '226660312ssm.sqlite']) self.logger.debug('Pushing %s to %s' % (db, destination)) self.device.push_file(db, destination=destination) os.remove(db) if marker > 0: self.logger.debug('Adding message attachments') all_attachments_zip_name = pkg_resources.resource_filename( __name__, os.path.sep.join( ['resources', 'smsAttachments.zip'])) attachments_zip_name = 'smsAttachments-%d.zip' % marker self.logger.debug('Extracting %s from %s' % ( attachments_zip_name, all_attachments_zip_name)) attachments_zip = ZipFile( all_attachments_zip_name).extract(attachments_zip_name) temp = tempfile.mkdtemp() self.logger.debug('Extracting %s to %s' % ( attachments_zip, temp)) ZipFile(attachments_zip).extractall(temp) destination = '/'.join([ self.PERSISTENT_STORAGE_PATH, 'chrome', self.idb_dir, '226660312ssm']) self.logger.debug('Pushing %s to %s' % (temp, destination)) self.device.manager.pushDir(temp, destination) self.logger.debug('Removing %s' % temp) shutil.rmtree(temp) self.logger.debug('Removing %s' % attachments_zip) os.remove(attachments_zip) if restart: self.start_b2g() break def populate_music(self, count, source='MUS_0001.mp3', destination='sdcard', tracks_per_album=10): self.remove_media('music') import math from mutagen.easyid3 import EasyID3 music_file = pkg_resources.resource_filename( __name__, os.path.sep.join(['resources', source])) local_filename = music_file.rpartition(os.path.sep)[-1] # copy the mp3 file into a temp location with tempfile.NamedTemporaryFile() as local_copy: self.logger.debug('Creating copy of %s at %s' % ( music_file, local_copy.name)) local_copy.write(open(music_file).read()) music_file = local_copy.name mp3 = EasyID3(music_file) album_count = math.ceil(float(count) / tracks_per_album) self.logger.info('Populating %d music files (%d album%s)' % ( count, album_count, 's' if album_count > 1 else '')) for i in range(1, count + 1): album = math.ceil(float(i) / float(tracks_per_album)) track = i - ((album - 1) * tracks_per_album) mp3['title'] = 'Track %d' % track mp3['artist'] = 'Artist %d' % album mp3['album'] = 'Album %d' % album mp3['tracknumber'] = str(track) mp3.save() remote_filename = '_%s.'.join( iter(local_filename.split('.'))) % i remote_destination = os.path.join(destination, remote_filename) self.logger.debug('Pushing %s to %s' % ( music_file, remote_destination)) self.device.push_file(music_file, 1, remote_destination) def populate_pictures(self, count, source='IMG_0001.jpg', destination='sdcard/DCIM/100MZLLA'): self.populate_files('picture', source, count, destination) def populate_videos(self, count, source='VID_0001.3gp', destination='sdcard/DCIM/100MZLLA'): self.populate_files('video', source, count, destination) def populate_files(self, file_type, source, count, destination=''): self.remove_media(file_type) self.logger.info('Populating %d %s files' % (count, file_type)) source_file = pkg_resources.resource_filename( __name__, os.path.sep.join(['resources', source])) self.logger.debug('Pushing %d copies of %s to %s' % ( count, source_file, destination)) self.device.push_file(source_file, count, destination) def remove_media(self, file_type): if self.device.is_android_build: files = getattr(self.data_layer, '%s_files' % file_type) or [] if len(files) > 0: self.logger.info('Removing %d %s files' % ( len(files), file_type)) for filename in files: self.logger.debug('Removing %s' % filename) self.device.manager.removeFile(filename) # TODO Wait for files to be deleted time.sleep(5) files = getattr(self.data_layer, '%s_files' % file_type) or [] if not len(files) == 0: raise IncorrectCountError( '%s files' % file_type, 0, len(files)) def start_b2g(self): self.logger.debug('Starting B2G') self.device.start_b2g() self.data_layer = GaiaData(self.marionette)
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