async def run_all(args): if args.scenario != "all": selected_scenario = [args.scenario] else: selected_scenario = scenarii.keys() # this is the loop that generates all combinations of profile # for the current platform when "all" is selected res = [] failures = 0 for scenario in selected_scenario: if args.customization != "all": try: res.append(await one_run(scenario, args.customization)) except Exception: failures += 1 ERROR("Something went wrong on this one.") if args.strict: raise else: for customization in get_customizations(): LOG("Customization %s" % customization) try: res.append(await one_run(scenario, customization)) except Exception: failures += 1 ERROR("Something went wrong on this one.") if args.strict: raise return failures, [one_res for one_res in res if one_res]
async def switch(self): if self._mobile: return try: if self.handles is None: self.handles = await self.session.get_window_handles() self.current = 0 except Exception: ERROR("Could not get window handles") return if self.current not in self.handles: ERROR("Handle %s not in current set of windows" % str(self.current)) return handle = self.handles[self.current] if self.current == len(self.handles) - 1: self.current = 0 else: self.current += 1 try: await self.session.switch_to_window(handle) except Exception: ERROR("Could not switch to handle %s" % str(handle))
async def build_profile(self, device, headless): scenario = self.scenario profile = self.env.profile customization_data = self.customization_data scenario_func = scenarii[scenario] if scenario in customization_data.get("scenario", {}): options = customization_data["scenario"][scenario] LOG("Loaded options for that scenario %s" % str(options)) else: options = {} # Adding general options options["platform"] = self.env.target_platform if not self.force_new: try: custom_name = customization_data["name"] get_profile(profile, self.env.target_platform, scenario, custom_name) except ProfileNotFoundError: # XXX we'll use a fresh profile for now fresh_profile(profile, customization_data) else: fresh_profile(profile, customization_data) LOG("Updating profile located at %r" % profile) metadata = Metadata(profile) LOG("Starting the Gecko app...") self.env.prepare(logfile=self._log_filename("adb")) geckodriver_logs = self._log_filename("geckodriver") LOG("Writing geckodriver logs in %s" % geckodriver_logs) try: firefox_instance = Firefox(**self.env.get_browser_args(headless)) with open(geckodriver_logs, "w") as glog: async with get_session( self.env.get_geckodriver(log_file=glog), firefox_instance ) as session: self.env.check_session(session) LOG("Running the %s scenario" % scenario) metadata.update(await scenario_func(session, options)) LOG("%s scenario done." % scenario) except Exception: ERROR("%s scenario broke!" % scenario) self.env.stop_browser() self.env.collect_profile() # writing metadata metadata.write( name=self.scenario, customization=self.customization_data["name"], version=self.env.get_browser_version(), platform=self.env.target_platform, ) LOG("Profile at %s" % profile) LOG("Done.") return metadata
async def run_all(args): if args.scenario != "all": return await one_run(args.scenario, args.customization) # this is the loop that generates all combinations of profile # for the current platform when "all" is selected res = [] for scenario in scenarii.keys(): if args.customization != "all": try: res.append(await one_run(scenario, args.customization)) except Exception: ERROR("Something went wrong on this one.") if args.strict: raise else: for customization in get_customizations(): try: res.append(await one_run(scenario, customization)) except Exception: ERROR("Something went wrong on this one.") if args.strict: raise return res
def dump_logs(self): LOG("Dumping Android logs") try: logcat = self.device.get_logcat() if logcat: # local path, not using posixpath logfile = os.path.join(self.archive, "logcat.log") LOG("Writing logcat at %s" % logfile) with open(logfile, "wb") as f: for line in logcat: f.write(line.encode("utf8", errors="replace") + b"\n") else: LOG("logcat came back empty") except Exception: ERROR("Could not extract the logcat")
def dump_logs(self): try: logcat = self.device.get_logcat() except ADBError: ERROR("logcat call failure") return if logcat: # local path, not using posixpath logfile = os.path.join(self.archive, "logcat.log") LOG("Writing logcat at %s" % logfile) with open(logfile, "wb") as f: for line in logcat: f.write(line.encode("utf8") + b"\n") else: LOG("logcat came back empty")
def get_browser_version(self): try: return get_version(self.firefox) except Exception as e: ERROR("Could not get Firefox version %s" % str(e)) return "unknown"
def prepare(self, profile, logfile): self._set_adb_logger(logfile) try: self.device = ADBDevice(verbose=self.verbose, logger_name="adb") except Exception: ERROR("Cannot initialize device") raise device = self.device self.profile = profile # checking that the app is installed if not device.is_app_installed(self.app_name): raise Exception("%s is not installed" % self.app_name) # debug flag LOG("Setting %s as the debug app on the phone" % self.app_name) device.shell("am set-debug-app --persistent %s" % self.app_name, stdout_callback=LOG) # creating the profile on the device LOG("Creating the profile on the device") remote_test_root = posixpath.join(device.test_root, "condprof") remote_profile = posixpath.join(remote_test_root, "profile") LOG("The profile on the phone will be at %s" % remote_profile) device.rm(remote_test_root, force=True, recursive=True) device.mkdir(remote_test_root) device.chmod(remote_test_root, recursive=True, root=True) device.rm(remote_profile, force=True, recursive=True) LOG("Pushing %s on the phone" % self.profile) device.push(profile, remote_profile) device.chmod(remote_profile, recursive=True, root=True) self.profile = profile self.remote_profile = remote_profile # creating the yml file yml_data = { "args": ["-marionette", "-profile", self.remote_profile], "prefs": DEFAULT_PREFS, "env": { "LOG_VERBOSE": 1, "R_LOG_LEVEL": 6, "MOZ_LOG": "" }, } yml_name = "%s-geckoview-config.yaml" % self.app_name yml_on_host = posixpath.join(tempfile.mkdtemp(), yml_name) write_yml_file(yml_on_host, yml_data) tmp_on_device = posixpath.join("/data", "local", "tmp") if not device.exists(tmp_on_device): raise IOError("%s does not exists on the device" % tmp_on_device) yml_on_device = posixpath.join(tmp_on_device, yml_name) try: device.rm(yml_on_device, force=True, recursive=True) device.push(yml_on_host, yml_on_device) device.chmod(yml_on_device, recursive=True, root=True) except Exception: LOG("could not create the yaml file on device. Permission issue?") raise # command line 'extra' args not used with geckoview apps; instead we use # an on-device config.yml file intent = "android.intent.action.VIEW" device.stop_application(self.app_name) device.launch_application(self.app_name, self.activity, intent, extras=None, url="about:blank") if not device.process_exist(self.app_name): raise Exception("Could not start %s" % self.app_name) LOG("Creating socket forwarding on port %d" % self.marionette_port) device.forward( local="tcp:%d" % self.marionette_port, remote="tcp:%d" % self.marionette_port, ) # we don't have a clean way for now to check that GV or Fenix # is ready to handle our tests. So here we just wait 30s LOG("Sleeping for 30s") time.sleep(30)
def get_profile( target_dir, platform, scenario, customization="default", task_id=None, download_cache=True, repo="mozilla-central", ): """Extract a conditioned profile in the target directory. If task_id is provided, will grab the profile from that task. when not provided (default) will grab the latest profile. """ # XXX assert values params = { "platform": platform, "scenario": scenario, "customization": customization, "task_id": task_id, "repo": repo, } filename = ARTIFACT_NAME % params if task_id is None: url = TC_LINK % params + filename else: url = DIRECT_LINK % params + filename if not download_cache: download_dir = tempfile.mkdtemp() else: # using a cache dir in the user home dir download_dir = os.path.expanduser(CONDPROF_CACHE) if not os.path.exists(download_dir): os.makedirs(download_dir) downloaded_archive = os.path.join(download_dir, filename) retries = 0 while retries < RETRIES: try: LOG("Getting %s" % url) try: archive = download_file(url, target=downloaded_archive) except ArchiveNotFound: raise ProfileNotFoundError(url) try: with tarfile.open(archive, "r:gz") as tar: LOG("Extracting the tarball content in %s" % target_dir) size = len(list(tar)) with progress.Bar(expected_size=size) as bar: def _extract(self, *args, **kw): if not TASK_CLUSTER: bar.show(bar.last_progress + 1) return self.old(*args, **kw) tar.old = tar.extract tar.extract = functools.partial(_extract, tar) tar.extractall(target_dir) except (OSError, tarfile.ReadError) as e: LOG("Failed to extract the tarball") if download_cache and os.path.exists(archive): LOG("Removing cached file to attempt a new download") os.remove(archive) raise ProfileNotFoundError(str(e)) finally: if not download_cache: shutil.rmtree(download_dir) LOG("Success, we have a profile to work with") return target_dir except Exception: LOG("Failed to get the profile.") retries += 1 if os.path.exists(downloaded_archive): try: os.remove(downloaded_archive) except Exception: ERROR("Could not remove the file") time.sleep(RETRY_PAUSE) # If we reach that point, it means all attempts failed ERROR("All attempt failed") raise ProfileNotFoundError(url)