def marionette(request, timeout): """Return a marionette instance""" m = Marionette(bin=request.config.option.bin) m.start_session() m.set_prefs({'signon.rememberSignons': False}) request.addfinalizer(m.delete_session) m.set_search_timeout(timeout) return m
def setup(self): I18nSetup.locale_path = os.path.join(site.getsitepackages()[0], 'OWDTestToolkit/locale') if not I18nSetup.configured: marionette = Marionette() marionette.start_session() # Configure the translation to be used based on the previously retrieved language. Should # the translation is not found, the fallback will prevent a failure lang = marionette.execute_script("""return window.navigator.language;""") translation = gettext.translation('default', I18nSetup.locale_path, languages=[lang], fallback=True) I18nSetup._ = translation.ugettext I18nSetup.configured = True return I18nSetup._
def get_new_emulator(self): self.extra_emulator_index += 1 if len(self.marionette.extra_emulators) == self.extra_emulator_index: qemu = Marionette(emulator=self.marionette.emulator.arch, emulatorBinary=self.marionette.emulator.binary, homedir=self.marionette.homedir, baseurl=self.marionette.baseurl, noWindow=self.marionette.noWindow, gecko_path=self.marionette.gecko_path) qemu.start_session() self.marionette.extra_emulators.append(qemu) else: qemu = self.marionette.extra_emulators[self.extra_emulator_index] return qemu
def _wait_browser_ready(browser): '''waits for browser to become ready''' for line in iter(browser.stdout.readline, ''): if 'Bootstrapped 100%: Done' in line: break client = Marionette('localhost', port=2828) connected = False while not connected: try: client.start_session() connected = True except socket.error: time.sleep(.1) client.close()
def get_new_emulator(self): self.extra_emulator_index += 1 if len(self.marionette.extra_emulators) == self.extra_emulator_index: qemu = Marionette(emulator=self.marionette.emulator.arch, emulatorBinary=self.marionette.emulator.binary, homedir=self.marionette.homedir, baseurl=self.marionette.baseurl, noWindow=self.marionette.noWindow, gecko_path=self.marionette.gecko_path) qemu.start_session() self.marionette.extra_emulators.append(qemu) else: qemu = self.marionette.extra_emulators[self.extra_emulator_index] return qemu
def workerProc(arg): client = Marionette('localhost', port=2828) client.start_session() client.navigate('https://www.mercari.com/sell/') time.sleep(5) #first open the excel file to grab information #photos will be stored in photos file num_rows = arg items = load_excel("Mercari.xlsx", num_rows) for item in items: title = client.find_element(By.XPATH, "/html/body/div[1]/main/div[3]/div/div[2]/div[3]/div[1]/div[2]/input") title.send_keys(item[0]) textarea = client.find_element(By.TAG_NAME, 'textarea') textarea.send_keys(item[1]) zipcode = client.find_element(By.XPATH, "/html/body/div[1]/main/div[3]/div/div[2]/div[6]/div[1]/div[2]/input") zipcode.send_keys(item[8]) price = client.find_element(By.XPATH, "/html/body/div[1]/main/div[3]/div/div[2]/div[7]/div/div[2]/input") price.send_keys(item[10]) brand = client.find_element(By.XPATH, "/html/body/div[1]/main/div[3]/div/div[2]/div[7]/div/div[2]/input") brand.send_keys(item[6]) category(client, item[2], item[3], item[4]) if item[6] >= 0: submit_size(client, item[6]) time.sleep(1) #determineSize(client, 1) selectCondition(client, item[7]) ship_selfpaid(client) for i in range(item[10]): path = os.path.dirname(os.path.realpath("photos\img" + str(i) + ".jpg")) fileUpload(client, getPID(), path + "\img" + str(i) + ".jpg", i) submit_button(client) time.sleep(2) client.navigate('https://www.mercari.com/sell/')
def freeze(self, request, queryset): with open(os.path.join(settings.BASE_DIR, 'build', 'freeze.bundle.js')) as f: freeze_script = f.read() client = None try: client = Marionette(bin=settings.FIREFOX_BIN, headless=True) client.start_session() for webpage in queryset: print('Freezing {}...'.format(webpage.url)) client.navigate(webpage.url) results = client.execute_async_script( freeze_script, script_timeout=1000 * 60 * 5, ) webpage.frozen_html.save(webpage.url, ContentFile(results['html'])) webpage.save() finally: if client: client.cleanup()
def _open_with_timeout(browser, page, timeout=config.DURATION_LIMIT, burst_wait=3, bridge=None): '''navigates browser to url while capturing the packet dump, aborts after timeout. If bridge, that is the IP address of the connected bridge, just capture traffic to there (need to set this by hand) ''' client = Marionette('localhost', port=2828, socket_timeout=(timeout)) try: client.start_session() client.set_page_load_timeout((timeout) * 1000) except socket.timeout: _kill(browser) raise (url, domain) = _normalize_url(page) (tshark_process, file_name) = _open_packet_dump(domain, bridge) # thread = threading.Thread(target=client.navigate, args=(url,)) thread = threading.Thread(target=_navigate_or_fail, args=(client, url, file_name)) thread.daemon = True thread.start() # todo: this is code duplication for both _open functions start = time.time() while thread.is_alive(): time.sleep(.1) # will this still happen after the timeout is set like above? if time.time() - start > timeout + 30: _handle_exception("aborted after timeout2", file_name, client) _kill(browser, tshark_process) raise SystemExit("download aborted after timeout") time.sleep(burst_wait) _kill(browser, tshark_process)
class CommonTestCase(unittest.TestCase): __metaclass__ = MetaParameterized match_re = None failureException = AssertionError pydebugger = None def __init__(self, methodName, **kwargs): unittest.TestCase.__init__(self, methodName) self.loglines = [] self.duration = 0 self.expected = kwargs.pop("expected", "pass") self.logger = get_default_logger() self.profile = FirefoxProfile() self.binary = kwargs.pop("binary", None) def _enter_pm(self): if self.pydebugger: self.pydebugger.post_mortem(sys.exc_info()[2]) def _addSkip(self, result, reason): addSkip = getattr(result, "addSkip", None) if addSkip is not None: addSkip(self, reason) else: warnings.warn("TestResult has no addSkip method, skips not reported", RuntimeWarning, 2) result.addSuccess(self) def run(self, result=None): # Bug 967566 suggests refactoring run, which would hopefully # mean getting rid of this inner function, which only sits # here to reduce code duplication: def expected_failure(result, exc_info): addExpectedFailure = getattr(result, "addExpectedFailure", None) if addExpectedFailure is not None: addExpectedFailure(self, exc_info) else: warnings.warn("TestResult has no addExpectedFailure method, " "reporting as passes", RuntimeWarning) result.addSuccess(self) self.start_time = time.time() orig_result = result if result is None: result = self.defaultTestResult() startTestRun = getattr(result, "startTestRun", None) if startTestRun is not None: startTestRun() result.startTest(self) testMethod = getattr(self, self._testMethodName) if getattr(self.__class__, "__unittest_skip__", False) or getattr(testMethod, "__unittest_skip__", False): # If the class or method was skipped. try: skip_why = getattr(self.__class__, "__unittest_skip_why__", "") or getattr( testMethod, "__unittest_skip_why__", "" ) self._addSkip(result, skip_why) finally: result.stopTest(self) self.stop_time = time.time() return try: success = False try: if self.expected == "fail": try: self.setUp() except Exception: raise _ExpectedFailure(sys.exc_info()) else: self.setUp() except SkipTest as e: self._addSkip(result, str(e)) except KeyboardInterrupt: raise except _ExpectedFailure as e: expected_failure(result, e.exc_info) except: self._enter_pm() result.addError(self, sys.exc_info()) else: try: if self.expected == "fail": try: testMethod() except: raise _ExpectedFailure(sys.exc_info()) raise _UnexpectedSuccess else: testMethod() except self.failureException: self._enter_pm() result.addFailure(self, sys.exc_info()) except KeyboardInterrupt: raise except _ExpectedFailure as e: expected_failure(result, e.exc_info) except _UnexpectedSuccess: addUnexpectedSuccess = getattr(result, "addUnexpectedSuccess", None) if addUnexpectedSuccess is not None: addUnexpectedSuccess(self) else: warnings.warn( "TestResult has no addUnexpectedSuccess method, reporting as failures", RuntimeWarning ) result.addFailure(self, sys.exc_info()) except SkipTest as e: self._addSkip(result, str(e)) except: self._enter_pm() result.addError(self, sys.exc_info()) else: success = True try: if self.expected == "fail": try: self.tearDown() except: raise _ExpectedFailure(sys.exc_info()) else: self.tearDown() except KeyboardInterrupt: raise except _ExpectedFailure as e: expected_failure(result, e.exc_info) except: self._enter_pm() result.addError(self, sys.exc_info()) success = False # Here we could handle doCleanups() instead of calling cleanTest directly self.cleanTest() if success: result.addSuccess(self) finally: result.stopTest(self) if orig_result is None: stopTestRun = getattr(result, "stopTestRun", None) if stopTestRun is not None: stopTestRun() @classmethod def match(cls, filename): """ Determines if the specified filename should be handled by this test class; this is done by looking for a match for the filename using cls.match_re. """ if not cls.match_re: return False m = cls.match_re.match(filename) return m is not None @classmethod def add_tests_to_suite(cls, mod_name, filepath, suite, testloader, testvars): """ Adds all the tests in the specified file to the specified suite. """ raise NotImplementedError @property def test_name(self): if hasattr(self, "jsFile"): return os.path.basename(self.jsFile) else: return "%s.py %s.%s" % (self.__class__.__module__, self.__class__.__name__, self._testMethodName) def id(self): # TBPL starring requires that the "test name" field of a failure message # not differ over time. The test name to be used is passed to # mozlog via the test id, so this is overriden to maintain # consistency. return self.test_name def setUp(self): # Convert the marionette weakref to an object, just for the # duration of the test; this is deleted in tearDown() to prevent # a persistent circular reference which in turn would prevent # proper garbage collection. self.start_time = time.time() self.pingServer = PingServer() self.pingServer.start() self.marionette = Marionette(bin=self.binary, profile=self.profile) if self.marionette.session is None: self.marionette.start_session() if self.marionette.timeout is not None: self.marionette.timeouts(self.marionette.TIMEOUT_SEARCH, self.marionette.timeout) self.marionette.timeouts(self.marionette.TIMEOUT_SCRIPT, self.marionette.timeout) self.marionette.timeouts(self.marionette.TIMEOUT_PAGE, self.marionette.timeout) else: self.marionette.timeouts(self.marionette.TIMEOUT_PAGE, 30000) def tearDown(self): self.marionette.cleanup() self.pingServer.stop() def cleanTest(self): self._deleteSession() def _deleteSession(self): if hasattr(self, "start_time"): self.duration = time.time() - self.start_time if hasattr(self.marionette, "session"): if self.marionette.session is not None: try: self.loglines.extend(self.marionette.get_logs()) except Exception, inst: self.loglines = [["Error getting log: %s" % inst]] try: self.marionette.delete_session() except (socket.error, MarionetteException, IOError): # Gecko has crashed? self.marionette.session = None try: self.marionette.client.close() except socket.error: pass self.marionette = None
def run_tests(firefox_path=None): basedir = os.path.dirname(__file__) if sys.platform == 'darwin' and os.path.isdir(firefox_path): firefox_path = os.path.join(firefox_path, 'Contents', 'MacOS', 'firefox') driver = Marionette(app='fxdesktop', bin=firefox_path, gecko_log='-', prefs={'xpinstall.signatures.required': False}) driver.start_session() try: build1 = tempfile.NamedTemporaryFile(mode='wb', suffix='.xpi', delete=False) build2 = tempfile.NamedTemporaryFile(mode='wb', suffix='.xpi', delete=False) try: jpm_build(basedir, build1.name) jpm_build(os.path.join(basedir, 'testhelper'), build2.name) addons = Addons(driver) addons.install(build1.name, temp=True) addons.install(build2.name, temp=True) finally: os.unlink(build1.name) os.unlink(build2.name) driver.expected = expected driver.keys = Keys class restore_url: def __enter__(self): self.url = driver.get_url() def __exit__(self, type, value, traceback): driver.navigate('about:blank') driver.navigate(self.url) driver.restore_url = restore_url def wait_until(method): Wait(driver, default_timeout).until(lambda d: method()) driver.wait_until = wait_until def accept_alert(): driver.switch_to_alert().accept() driver.accept_alert = accept_alert max_timestamp = {'value': 0} def get_urls(): result = [] prefix = '[testhelper] Loading: ' new_timestamp = max_timestamp['value'] with driver.using_context(driver.CONTEXT_CHROME): messages = driver.execute_script( 'return ' + 'Cc["@mozilla.org/consoleservice;1"]' + '.getService(Ci.nsIConsoleService).getMessageArray()' + '.map(m => m instanceof Ci.nsIScriptError ? ' + '[m.timeStamp, m.errorMessage] : [null, null])' ) for timestamp, message in messages: if timestamp <= max_timestamp['value']: continue if not message.startswith(prefix): continue if timestamp > new_timestamp: new_timestamp = timestamp result.append(message[len(prefix):]) max_timestamp['value'] = new_timestamp return result driver.get_urls = get_urls def close_windows(keep): for h in [h for h in driver.chrome_window_handles if h != keep]: driver.switch_to_window(h) driver.close_chrome_window() driver.switch_to_window(keep) driver.close_windows = close_windows def close_background_tabs(): current_tab = driver.current_window_handle for h in [h for h in driver.window_handles if h != current_tab]: driver.switch_to_window(h) driver.close() driver.switch_to_window(current_tab) driver.close_background_tabs = close_background_tabs def wait_for_load(): code = 'return document.readyState == "complete";' driver.wait_until(lambda: driver.execute_script(code)) driver.wait_for_load = wait_for_load def click(self): action = Actions(driver) action.click(self) action.perform() HTMLElement.click = click def middle_click(self): action = Actions(driver) action.middle_click(self) action.perform() HTMLElement.middle_click = middle_click def context_click(self): action = Actions(driver) action.context_click(self) action.perform() HTMLElement.context_click = context_click testdir = os.path.join(basedir, 'tests') for filename in os.listdir(testdir): if filename.startswith('.') or not filename.endswith('.py'): continue filepath = os.path.join(testdir, filename) globals = {} execfile(filepath, globals) globals['run'](driver) finally: driver.cleanup()
" /tmp/foo" + str(timestamp) + "\"", shell=True) shutil.copy("firefox_prefs.js", "/tmp/foo" + str(timestamp) + "/prefs.js") time.sleep(1) # Launch Firefox with the new profile p = subprocess.Popen([ FIREFOX_PATH + " -profile /tmp/foo" + str(timestamp) + " -marionette -devtools" ], shell=True, preexec_fn=os.setsid) client = Marionette('localhost', port=2828) client.start_session() addons = Addons(client) addons.install(os.getcwd() + "/har-export-trigger-0.6.1.xpi", temp=True) for run in range(1, TIMES + 1): timestamp = datetime.datetime.now().strftime("%Y-%m-%d+%H-%M-%S.%f") unixtimestamp = int(time.time()) try: hostname = URL_TO_FETCH.split('/')[2] except IndexError: hostname = URL_TO_FETCH URL_TO_FETCH = "http://" + hostname print("Run " + str(run) + "/" + str(TIMES) + " - Fetching " + URL_TO_FETCH + " at " + timestamp)
def run_tests(self): """ Generate the PGO profile data """ from mozhttpd import MozHttpd from mozprofile import Preferences from mozdevice import ADBDevice, ADBTimeoutError from six import string_types from marionette_driver.marionette import Marionette app = self.query_package_name() IP = '10.0.2.2' PORT = 8888 PATH_MAPPINGS = { '/js-input/webkit/PerformanceTests': 'third_party/webkit/PerformanceTests', } dirs = self.query_abs_dirs() topsrcdir = os.path.join(dirs['abs_work_dir'], 'src') adb = self.query_exe('adb') path_mappings = { k: os.path.join(topsrcdir, v) for k, v in PATH_MAPPINGS.items() } httpd = MozHttpd(port=PORT, docroot=os.path.join(topsrcdir, "build", "pgo"), path_mappings=path_mappings) httpd.start(block=False) profile_data_dir = os.path.join(topsrcdir, 'testing', 'profiles') with open(os.path.join(profile_data_dir, 'profiles.json'), 'r') as fh: base_profiles = json.load(fh)['profileserver'] prefpaths = [ os.path.join(profile_data_dir, profile, 'user.js') for profile in base_profiles ] prefs = {} for path in prefpaths: prefs.update(Preferences.read_prefs(path)) interpolation = { "server": "%s:%d" % httpd.httpd.server_address, "OOP": "false" } for k, v in prefs.items(): if isinstance(v, string_types): v = v.format(**interpolation) prefs[k] = Preferences.cast(v) # Enforce 1proc. This isn't in one of the user.js files because those # are shared with desktop, which wants e10s. We can't interpolate # because the formatting code only works for strings, and this is a # bool pref. prefs["browser.tabs.remote.autostart"] = False outputdir = self.config.get('output_directory', '/sdcard/pgo_profile') jarlog = posixpath.join(outputdir, 'en-US.log') profdata = posixpath.join(outputdir, 'default_%p_random_%m.profraw') env = {} env["XPCOM_DEBUG_BREAK"] = "warn" env["MOZ_IN_AUTOMATION"] = "1" env["MOZ_JAR_LOG_FILE"] = jarlog env["LLVM_PROFILE_FILE"] = profdata adbdevice = ADBDevice(adb=adb, device='emulator-5554') adbdevice.mkdir(outputdir) try: # Run Fennec a first time to initialize its profile driver = Marionette( app='fennec', package_name=app, adb_path=adb, bin="target.apk", prefs=prefs, connect_to_running_emulator=True, startup_timeout=1000, env=env, ) driver.start_session() # Now generate the profile and wait for it to complete for page in PAGES: driver.navigate("http://%s:%d/%s" % (IP, PORT, page)) timeout = 2 if 'Speedometer/index.html' in page: # The Speedometer test actually runs many tests internally in # javascript, so it needs extra time to run through them. The # emulator doesn't get very far through the whole suite, but # this extra time at least lets some of them process. timeout = 360 time.sleep(timeout) driver.set_context("chrome") driver.execute_script(""" Components.utils.import("resource://gre/modules/Services.jsm"); let cancelQuit = Components.classes["@mozilla.org/supports-PRBool;1"] .createInstance(Components.interfaces.nsISupportsPRBool); Services.obs.notifyObservers(cancelQuit, "quit-application-requested", null); return cancelQuit.data; """) driver.execute_script(""" Components.utils.import("resource://gre/modules/Services.jsm"); Services.startup.quit(Ci.nsIAppStartup.eAttemptQuit) """) # There is a delay between execute_script() returning and the profile data # actually getting written out, so poll the device until we get a profile. for i in range(50): if not adbdevice.process_exist(app): break time.sleep(2) else: raise Exception("Android App (%s) never quit" % app) # Pull all the profraw files and en-US.log adbdevice.pull(outputdir, '/builds/worker/workspace/') except ADBTimeoutError: self.fatal('INFRA-ERROR: Failed with an ADBTimeoutError', EXIT_STATUS_DICT[TBPL_RETRY]) profraw_files = glob.glob('/builds/worker/workspace/*.profraw') if not profraw_files: self.fatal( 'Could not find any profraw files in /builds/worker/workspace') merge_cmd = [ os.path.join(os.environ['MOZ_FETCHES_DIR'], 'clang/bin/llvm-profdata'), 'merge', '-o', '/builds/worker/workspace/merged.profdata', ] + profraw_files rc = subprocess.call(merge_cmd) if rc != 0: self.fatal( 'INFRA-ERROR: Failed to merge profile data. Corrupt profile?', EXIT_STATUS_DICT[TBPL_RETRY]) # tarfile doesn't support xz in this version of Python tar_cmd = [ 'tar', '-acvf', '/builds/worker/artifacts/profdata.tar.xz', '-C', '/builds/worker/workspace', 'merged.profdata', 'en-US.log', ] subprocess.check_call(tar_cmd) httpd.stop()
class current_frame(): filename_screenshot = "" filename_htmldump = "" def main(self, LOGDIR): # # The first variable is the log directory. # ucount = 0 self.marionette = Marionette(host='localhost', port=2828) self.marionette.start_session() self.marionette.set_search_timeout(1000) # # Now loop through all the iframes, gathering details about each one. # print "" print "Iframe for 'top level' () ..." self.filename_screenshot = LOGDIR + "top_level" + ".png" self.filename_htmldump = LOGDIR + "top_level" + ".html" self.marionette.switch_to_frame() self.record_frame() frames = self.marionette.find_elements("tag name", "iframe") for fnum in range(len(frames)): # # App name is usually in the "src" attribute, so it's worth a shot.. # frame_src = frames[fnum].get_attribute("src") if frame_src != "": startpos = frame_src.index('/') + 2 stoppos = frame_src.index('.') appname = frame_src[startpos:stoppos] filename = appname else: ucount = ucount + 1 appname = "(unknown)" filename = "unknown_" + str(ucount) # # Because we call this script sometimes when we hit a Marionette issue, # these filenames may already exist (and we'd overwrite them!), so # add 'DEBUG_' to the start of the filename. # filename = "DEBUG_" + filename filename_details = LOGDIR + filename + "_iframe_details.txt" self.filename_screenshot = LOGDIR + filename + ".png" self.filename_htmldump = LOGDIR + filename + ".html" # # This iframe gives me problems sometimes, so I'm ignoring it for now. # if appname == "costcontrol": continue print "" print "Iframe for app \"" + appname + "\" ..." # # Record the iframe details (pretty verbose, but 'execute_script' # wasn't letting me use 'for' loops in js for some reason). # print " |_ iframe details saved to : " + filename_details f = open(filename_details, 'w') f.write("Attributes for this iframe ...\n") num_attribs = self.marionette.execute_script("return document.getElementsByTagName('iframe')[" + str(fnum) + "].attributes.length;") for i in range(num_attribs): attrib_name = self.marionette.execute_script("return document.getElementsByTagName('iframe')[" + str(fnum) + "].attributes[" + str(i) + "].nodeName;") attrib_value = self.marionette.execute_script("return document.getElementsByTagName('iframe')[" + str(fnum) + "].attributes[" + str(i) + "].nodeValue;") f.write(" |_ " + attrib_name.ljust(20) + ": \"" + attrib_value + "\"\n") f.close() # # Switch to this frame. # self.marionette.switch_to_frame(fnum) if appname == "keyboard": # # Take the html dump and save it to the file. # print " |_ html dump saved to : " + self.filename_htmldump f = open(self.filename_htmldump, 'w') f.write(self.marionette.page_source.encode('utf8', 'ignore')) f.close() else: self.record_frame() self.marionette.switch_to_frame() def record_frame(self): # # Take the screenshot and save it to the file. # print " |_ screenshot saved to : " + self.filename_screenshot screenshot = self.marionette.screenshot() with open(self.filename_screenshot, 'w') as f: f.write(base64.decodestring(screenshot)) f.close() # # Take the html dump and save it to the file. # print " |_ html dump saved to : " + self.filename_htmldump f = open(self.filename_htmldump, 'w') f.write(self.marionette.page_source.encode('utf8', 'ignore')) f.close()
class MuletReftest(RefTest): build_type = "mulet" marionette = None def __init__(self, marionette_args): RefTest.__init__(self) self.last_test = os.path.basename(__file__) self.marionette_args = marionette_args self.profile = None self.runner = None self.test_script = os.path.join(here, 'b2g_start_script.js') self.timeout = None def run_marionette_script(self): self.marionette = Marionette(**self.marionette_args) assert(self.marionette.wait_for_port()) self.marionette.start_session() if self.build_type == "mulet": self._wait_for_homescreen(timeout=300) self._unlockScreen() self.marionette.set_context(self.marionette.CONTEXT_CHROME) if os.path.isfile(self.test_script): f = open(self.test_script, 'r') self.test_script = f.read() f.close() self.marionette.execute_script(self.test_script) def run_tests(self, tests, options): manifests = self.resolver.resolveManifests(options, tests) self.profile = self.create_profile(options, manifests, profile_to_clone=options.profile) env = self.buildBrowserEnv(options, self.profile.profile) self._populate_logger(options) outputHandler = OutputHandler(self.log, options.utilityPath, symbolsPath=options.symbolsPath) kp_kwargs = { 'processOutputLine': [outputHandler], 'onTimeout': [self._on_timeout], 'kill_on_timeout': False } if not options.debugger: if not options.timeout: if mozinfo.info['debug']: options.timeout = 420 else: options.timeout = 300 self.timeout = options.timeout + 30.0 self.log.info("%s | Running tests: start." % os.path.basename(__file__)) cmd, args = self.build_command_line(options.app, ignore_window_size=options.ignoreWindowSize, browser_arg=options.browser_arg) self.runner = FirefoxRunner(profile=self.profile, binary=cmd, cmdargs=args, env=env, process_class=ProcessHandler, process_args=kp_kwargs, symbols_path=options.symbolsPath) status = 0 try: self.runner.start(outputTimeout=self.timeout) self.log.info("%s | Application pid: %d" % ( os.path.basename(__file__), self.runner.process_handler.pid)) # kick starts the reftest harness self.run_marionette_script() status = self.runner.wait() finally: self.runner.check_for_crashes(test_name=self.last_test) self.runner.cleanup() if status > 0: self.log.testFail("%s | application terminated with exit code %s" % ( self.last_test, status)) elif status < 0: self.log.info("%s | application killed with signal %s" % ( self.last_test, -status)) self.log.info("%s | Running tests: end." % os.path.basename(__file__)) return status def create_profile(self, options, manifests, profile_to_clone=None): profile = RefTest.createReftestProfile(self, options, manifests, profile_to_clone=profile_to_clone) prefs = {} # Turn off the locale picker screen prefs["browser.firstrun.show.localepicker"] = False if not self.build_type == "mulet": # FIXME: With Mulet we can't set this values since Gaia won't launch prefs["b2g.system_startup_url"] = \ "app://test-container.gaiamobile.org/index.html" prefs["b2g.system_manifest_url"] = \ "app://test-container.gaiamobile.org/manifest.webapp" # Make sure we disable system updates prefs["app.update.enabled"] = False prefs["app.update.url"] = "" # Disable webapp updates prefs["webapps.update.enabled"] = False # Disable tiles also prefs["browser.newtabpage.directory.source"] = "" prefs["browser.newtabpage.directory.ping"] = "" prefs["dom.ipc.tabs.disabled"] = False prefs["dom.mozBrowserFramesEnabled"] = True prefs["font.size.inflation.emPerLine"] = 0 prefs["font.size.inflation.minTwips"] = 0 prefs["network.dns.localDomains"] = "app://test-container.gaiamobile.org" prefs["reftest.browser.iframe.enabled"] = False prefs["reftest.remote"] = False # Set the extra prefs. profile.set_preferences(prefs) return profile def build_command_line(self, app, ignore_window_size=False, browser_arg=None): cmd = os.path.abspath(app) args = ['-marionette'] if browser_arg: args += [browser_arg] if not ignore_window_size: args.extend(['--screen', '800x1000']) if self.build_type == "mulet": args += ['-chrome', 'chrome://b2g/content/shell.html'] return cmd, args def _on_timeout(self): msg = "%s | application timed out after %s seconds with no output" self.log.testFail(msg % (self.last_test, self.timeout)) self.log.error("Force-terminating active process(es)."); # kill process to get a stack self.runner.stop(sig=signal.SIGABRT) def _unlockScreen(self): self.marionette.set_context(self.marionette.CONTEXT_CONTENT) self.marionette.import_script(os.path.abspath( os.path.join(__file__, os.path.pardir, "gaia_lock_screen.js"))) self.marionette.switch_to_frame() self.marionette.execute_async_script('GaiaLockScreen.unlock()') def _wait_for_homescreen(self, timeout): self.log.info("Waiting for home screen to load") Wait(self.marionette, timeout).until(expected.element_present( By.CSS_SELECTOR, '#homescreen[loading-state=false]'))
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.device = gaiatest.GaiaDevice(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.device.touch_home_button() def hold_home(self, args): self.device.hold_home_button() 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.device.lock() def screenshot(self, args): self.marionette.execute_script( "window.wrappedJSObject.dispatchEvent(new Event('volumedown+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.device.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 marionette(request): marionette = Marionette(bin=request.config.getoption('firefox_path')) marionette.start_session() request.node._marionette = marionette yield marionette marionette.cleanup()
class Puppet: def __init__(self, binary: str, profile: str): self.__has_marionette = False self.__auto_download = False self.__download_dir = "" if not Path(binary).is_file(): return if not Path(profile).is_dir(): return # geckodriver の log ファイル出力を抑止する NO_LOG = "-" self.marionette = Marionette(bin=binary, gecko_log=NO_LOG, profile=profile) # start_session しないと quit もできない self.marionette.start_session() self.__has_marionette = True @property def has_marionette(self): return self.__has_marionette @property def auto_download(self): return self.__auto_download def __activate_auto_download(self): # 一度有効にすると同セッション内では無効にできない # firefox52 では MIME_TYPES.rdf, firefox60 では handlers.json に # ファイルダウンロード時の動作設定が記述されている(text/plain はプログラムで開く、など) # 自動ダウンロードするため既存の設定は削除する MIME_TYPES_HANDLERS = ["MIME_TYPES.rdf", "handlers.json"] for name in MIME_TYPES_HANDLERS: p = Path(self.marionette.profile_path).joinpath(name) if p.is_file(): p.unlink() self.marionette.set_pref("browser.download.useDownloadDir", True) self.marionette.set_pref("browser.helperApps.neverAsk.saveToDisk", ",".join(MIME_TYPES)) USER_DEFINED = 2 self.marionette.set_pref("browser.download.folderList", USER_DEFINED) @property def download_dir(self): if self.__auto_download == False: raise Exception("auto download has not been activated") return self.__download_dir @download_dir.setter def download_dir(self, dir: str): p = Path(dir) if not p.is_dir(): return full_path = str(p.resolve()) if self.__auto_download == False: self.__activate_auto_download() self.__auto_download = True self.marionette.set_pref("browser.download.dir", full_path) self.__download_dir = full_path def set_download(self, dir: str): self.download_dir = dir def query_selector(self, selectors: str) -> HTMLElement: METHOD_CSS_SELECTOR = "css selector" return self.marionette.find_element(METHOD_CSS_SELECTOR, selectors) def query_selectors(self, selectors: str) -> List[HTMLElement]: METHOD_CSS_SELECTOR = "css selector" return self.marionette.find_elements(METHOD_CSS_SELECTOR, selectors) def wait(self, seconds: int): actions = Actions(self.marionette) actions.wait(seconds).perform() def quit(self): self.marionette.quit() def exec(self, script: str): # script 内での記述簡略化のため mrnt = self.marionette set_download = self.set_download wait = self.wait quit = self.quit query_selector = self.query_selector query_selectors = self.query_selectors exec(script)
class Firefox(DesktopBrowser): """Firefox""" def __init__(self, path, options, job): DesktopBrowser.__init__(self, path, options, job) self.job = job self.task = None self.options = options self.path = path self.event_name = None self.moz_log = None self.marionette = None def prepare(self, job, task): """Prepare the profile/OS for the browser""" self.moz_log = os.path.join(task['dir'], task['prefix']) + '_moz.log' os.environ["MOZ_LOG_FILE"] = self.moz_log os.environ["MOZ_LOG"] = 'timestamp,sync,nsHttp:5,nsSocketTransport:5,nsStreamPump:5,'\ 'nsHostResolver:5,pipnss:5' DesktopBrowser.prepare(self, job, task) profile_template = os.path.join( os.path.abspath(os.path.dirname(__file__)), 'profiles', 'Firefox') if not task['cached'] and os.path.isdir(profile_template): try: if os.path.isdir(task['profile']): shutil.rmtree(task['profile']) shutil.copytree(profile_template, task['profile']) except Exception: pass def launch(self, _job, task): """Launch the browser""" from marionette_driver.marionette import Marionette args = [ '-profile', '"{0}"'.format(task['profile']), '-no-remote', '-width', str(task['width']), '-height', str(task['height']), '-marionette', 'about:blank' ] if self.path.find(' ') > -1: command_line = '"{0}"'.format(self.path) else: command_line = self.path command_line += ' ' + ' '.join(args) DesktopBrowser.launch_browser(self, command_line) self.marionette = Marionette('localhost', port=2828) self.marionette.start_session(timeout=60) self.marionette.navigate(START_PAGE) def stop(self, job, task): if self.marionette is not None: self.marionette.close() DesktopBrowser.stop(self, job, task) os.environ["MOZ_LOG_FILE"] = '' os.environ["MOZ_LOG"] = '' def run_task(self, task): """Run an individual test""" if self.marionette is not None: self.task = task logging.debug("Running test") end_time = monotonic.monotonic() + task['time_limit'] task['current_step'] = 1 recording = False while len(task['script']) and task['error'] is None and \ monotonic.monotonic() < end_time: self.prepare_task(task) command = task['script'].pop(0) if not recording and command['record']: recording = True self.on_start_recording(task) self.process_command(command) if command['record']: self.wait_for_page_load() if not task['combine_steps'] or not len(task['script']): self.on_stop_recording(task) recording = False self.on_start_processing(task) self.wait_for_processing(task) if task['log_data']: # Move on to the next step task['current_step'] += 1 self.event_name = None # Always navigate to about:blank after finishing in case the tab is # remembered across sessions self.marionette.navigate('about:blank') self.task = None def wait_for_page_load(self): """Wait for the onload event from the extension""" pass def prepare_task(self, task): """Format the file prefixes for multi-step testing""" task['page_data'] = {} if task['current_step'] == 1: task['prefix'] = task['task_prefix'] task['video_subdirectory'] = task['task_video_prefix'] else: task['prefix'] = '{0}_{1:d}'.format(task['task_prefix'], task['current_step']) task['video_subdirectory'] = '{0}_{1:d}'.format( task['task_video_prefix'], task['current_step']) if task['video_subdirectory'] not in task['video_directories']: task['video_directories'].append(task['video_subdirectory']) if self.event_name is not None: task['step_name'] = self.event_name else: task['step_name'] = 'Step_{0:d}'.format(task['current_step']) def on_start_recording(self, task): """Notification that we are about to start an operation that needs to be recorded""" DesktopBrowser.on_start_recording(self, task) def on_stop_recording(self, task): """Notification that we are done with recording""" DesktopBrowser.on_stop_recording(self, task) def on_start_processing(self, task): """Start any processing of the captured data""" DesktopBrowser.on_start_processing(self, task) def wait_for_processing(self, task): """Wait for any background processing threads to finish""" DesktopBrowser.wait_for_processing(self, task) def process_command(self, command): """Process an individual script command""" logging.debug("Processing script command:") logging.debug(command) if command['command'] == 'navigate': self.marionette.navigate(command['target']) elif command['command'] == 'logdata': self.task['combine_steps'] = False if int(re.search(r'\d+', str(command['target'])).group()): logging.debug("Data logging enabled") self.task['log_data'] = True else: logging.debug("Data logging disabled") self.task['log_data'] = False elif command['command'] == 'combinesteps': self.task['log_data'] = True self.task['combine_steps'] = True elif command['command'] == 'seteventname': self.event_name = command['target'] elif command['command'] == 'exec': self.marionette.execute_js_script(command['target']) elif command['command'] == 'sleep': delay = min( 60, max(0, int(re.search(r'\d+', str(command['target'])).group()))) if delay > 0: time.sleep(delay) elif command['command'] == 'setabm': self.task['stop_at_onload'] = \ bool('target' in command and int(re.search(r'\d+', str(command['target'])).group()) == 0) elif command['command'] == 'setactivitytimeout': if 'target' in command: self.task['activity_time'] = \ max(0, min(30, int(re.search(r'\d+', str(command['target'])).group()))) elif command['command'] == 'setuseragent': self.task['user_agent_string'] = command['target'] elif command['command'] == 'setcookie': if 'target' in command and 'value' in command: url = command['target'].strip() cookie = command['value'] pos = cookie.find(';') if pos > 0: cookie = cookie[:pos] pos = cookie.find('=') if pos > 0: name = cookie[:pos].strip() value = cookie[pos + 1:].strip() if len(name) and len(value) and len(url): self.marionette.add_cookie({ 'url': url, 'name': name, 'value': value }) def navigate(self, url): """Navigate to the given URL""" if self.marionette is not None: self.marionette.navigate(url)
#!/usr/bin/python2.7 # # Script to work around Marionette bug 879816 (cannot click the modal 'ok' button # following clicking something else). # from marionette_driver.marionette import Marionette marionette = Marionette(host='localhost', port=2828) marionette.start_session() marionette.switch_to_frame() marionette.execute_script( "document.getElementById('modal-dialog-prompt-ok').click();")
def run_tests(self): """ Generate the PGO profile data """ from mozhttpd import MozHttpd from mozprofile import Preferences from mozdevice import ADBDevice, ADBProcessError, ADBTimeoutError from six import string_types from marionette_driver.marionette import Marionette app = self.query_package_name() IP = '10.0.2.2' PORT = 8888 PATH_MAPPINGS = { '/js-input/webkit/PerformanceTests': 'third_party/webkit/PerformanceTests', } dirs = self.query_abs_dirs() topsrcdir = os.path.join(dirs['abs_work_dir'], 'src') adb = self.query_exe('adb') path_mappings = { k: os.path.join(topsrcdir, v) for k, v in PATH_MAPPINGS.items() } httpd = MozHttpd(port=PORT, docroot=os.path.join(topsrcdir, "build", "pgo"), path_mappings=path_mappings) httpd.start(block=False) profile_data_dir = os.path.join(topsrcdir, 'testing', 'profiles') with open(os.path.join(profile_data_dir, 'profiles.json'), 'r') as fh: base_profiles = json.load(fh)['profileserver'] prefpaths = [ os.path.join(profile_data_dir, profile, 'user.js') for profile in base_profiles ] prefs = {} for path in prefpaths: prefs.update(Preferences.read_prefs(path)) interpolation = { "server": "%s:%d" % httpd.httpd.server_address, "OOP": "false" } for k, v in prefs.items(): if isinstance(v, string_types): v = v.format(**interpolation) prefs[k] = Preferences.cast(v) outputdir = self.config.get('output_directory', '/sdcard') jarlog = posixpath.join(outputdir, 'en-US.log') profdata = posixpath.join(outputdir, 'default.profraw') env = {} env["XPCOM_DEBUG_BREAK"] = "warn" env["MOZ_IN_AUTOMATION"] = "1" env["MOZ_JAR_LOG_FILE"] = jarlog env["LLVM_PROFILE_FILE"] = profdata adbdevice = ADBDevice(adb=adb, device='emulator-5554') try: # Run Fennec a first time to initialize its profile driver = Marionette( app='fennec', package_name=app, adb_path=adb, bin="target.apk", prefs=prefs, connect_to_running_emulator=True, startup_timeout=1000, env=env, ) driver.start_session() # Now generate the profile and wait for it to complete for page in PAGES: driver.navigate("http://%s:%d/%s" % (IP, PORT, page)) timeout = 2 if 'Speedometer/index.html' in page: # The Speedometer test actually runs many tests internally in # javascript, so it needs extra time to run through them. The # emulator doesn't get very far through the whole suite, but # this extra time at least lets some of them process. timeout = 360 time.sleep(timeout) driver.set_context("chrome") driver.execute_script(""" Components.utils.import("resource://gre/modules/Services.jsm"); let cancelQuit = Components.classes["@mozilla.org/supports-PRBool;1"] .createInstance(Components.interfaces.nsISupportsPRBool); Services.obs.notifyObservers(cancelQuit, "quit-application-requested", null); return cancelQuit.data; """) driver.execute_script(""" Components.utils.import("resource://gre/modules/Services.jsm"); Services.startup.quit(Ci.nsIAppStartup.eAttemptQuit) """) # There is a delay between execute_script() returning and the profile data # actually getting written out, so poll the device until we get a profile. for i in range(50): try: localprof = '/builds/worker/workspace/default.profraw' adbdevice.pull(profdata, localprof) stats = os.stat(localprof) if stats.st_size == 0: # The file may not have been fully written yet, so retry until we # get actual data. time.sleep(2) else: break except ADBProcessError: # The file may not exist at all yet, which would raise an # ADBProcessError, so retry. time.sleep(2) else: raise Exception("Unable to pull default.profraw") adbdevice.pull(jarlog, '/builds/worker/workspace/en-US.log') except ADBTimeoutError: self.fatal('INFRA-ERROR: Failed with an ADBTimeoutError', EXIT_STATUS_DICT[TBPL_RETRY]) # We normally merge as part of a GENERATED_FILES step in the profile-use # build, but Android runs sometimes result in a truncated profile. We do # a merge here to make sure the data isn't corrupt so we can retry the # 'run' task if necessary. merge_cmd = [ '/builds/worker/workspace/build/clang/bin/llvm-profdata', 'merge', '/builds/worker/workspace/default.profraw', '-o', '/builds/worker/workspace/merged.profraw', ] rc = subprocess.call(merge_cmd) if rc != 0: self.fatal( 'INFRA-ERROR: Failed to merge profile data. Corrupt profile?', EXIT_STATUS_DICT[TBPL_RETRY]) # tarfile doesn't support xz in this version of Python tar_cmd = [ 'tar', '-acvf', '/builds/worker/artifacts/profdata.tar.xz', '-C', '/builds/worker/workspace', 'merged.profraw', 'en-US.log', ] subprocess.check_call(tar_cmd) httpd.stop()
class FirefoxMarionetteBase(object): """ Wrap Marionette/Firefox into convenient interface. - https://marionette-client.readthedocs.io/ - https://marionette-client.readthedocs.io/en/master/reference.html - https://marionette-client.readthedocs.io/en/master/interactive.html """ def __init__(self): logger.info('Starting Marionette Gecko wrapper') # Configuration self.firefox_bin = self.find_firefox() self.firefox_host = 'localhost' self.firefox_port = 2828 # TODO: Make configurable self.firefox_verbosity = 1 #self.firefox_verbosity = 2 # Timeout configuration self.startup_timeout = 20.0 self.socket_timeout = 32.0 self.page_timeout = 30.0 self.script_timeout = 20.0 self.shutdown_timeout = 10.0 # Instance state defaults self.marionette = None self.firefox_run_headless = True self.firefox_do_shutdown = False self.firefox_already_started = False def enable_headless(self, run_headless=True): self.firefox_run_headless = run_headless def enable_shutdown(self, do_shutdown=True): self.firefox_do_shutdown = do_shutdown def boot_firefox(self, headless=True): # Indicate whether to run in headless mode self.enable_headless(headless) # Optionally shut down Marionette/Firefox after performing work # This will just be called if Python exits normally atexit.register(self.shutdown) # Check whether Firefox is already running logger.info( 'Check for running instance of Marionette/Firefox at {}:{}'.format( self.firefox_host, self.firefox_port)) if check_socket(self.firefox_host, self.firefox_port): logger.info('Will reuse running Marionette/Firefox') self.firefox_bin = None self.firefox_already_started = True else: logger.info('Will launch new Marionette/Firefox instance') # Connect to / start Marionette Gecko engine self.marionette = Marionette(host=self.firefox_host, port=self.firefox_port, bin=self.firefox_bin, socket_timeout=self.socket_timeout, startup_timeout=self.startup_timeout, headless=self.firefox_run_headless, verbose=self.firefox_verbosity) self.marionette.DEFAULT_SHUTDOWN_TIMEOUT = self.shutdown_timeout # Start a session with Marionette Gecko engine self.marionette.start_session() # Configure Marionette self.configure_marionette() def configure_marionette(self): # This specifies the time to wait for the page loading to complete. self.marionette.timeout.page_load = self.page_timeout # This specifies the time to wait for injected scripts to finish # before interrupting them. self.marionette.timeout.script = self.script_timeout # Configure a HTTP proxy server self.marionette.set_pref('network.proxy.type', 0, default_branch=True) @classmethod def find_firefox(cls): candidates = where.where('firefox') candidates += [ '/Applications/Firefox.app/Contents/MacOS/firefox-bin', ] firefox = find_program_candidate(candidates) logger.info('Found "firefox" program at {}'.format(firefox)) return firefox def get_status(self): attributes = ['session', 'session_id'] data = OrderedDict() for attribute in attributes: data[attribute] = getattr(self.marionette, attribute) return data def log_status(self): logger.info('Marionette report: {}'.format( json.dumps(self.get_status(), indent=4))) def has_active_session(self): is_initialized = self.marionette is not None and self.marionette.session_id is not None return is_initialized def ensure_session(self): #self.log_status() if not self.has_active_session(): self.boot_firefox() logger.info( 'No session with Marionette, started new session {}'.format( self.marionette.session_id)) def shutdown(self): if self.firefox_do_shutdown: logger.info('Aiming at shutdown') if self.firefox_already_started: logger.warning( 'Can not shutdown Firefox as it was already running before starting this program' ) return False logger.info('Shutting down Marionette/Firefox') if self.marionette is not None: self.marionette.quit() return True def find_tag(self, tagname): try: element = self.marionette.find_element("tag name", tagname) return element except NoSuchElementException: pass def wait_for_element_tag(self, tagname): """ Wait for element to appear. """ waiter = Wait(self.marionette, timeout=20.0, interval=0.1) element = waiter.until(lambda m: self.find_tag(tagname)) return element def render_image(self, element=None): """ Return screenshot from element. """ image = self.marionette.screenshot(element=element, format='binary') return image def set_window_size(self, width, height): self.marionette.set_window_rect(width=width, height=height) def get_window_rect(self): return self.marionette.window_rect
def start_client(): client = Marionette('localhost', port=2828) client.start_session() return client
class MarionetteHelper: """ Helper for starting firefox and for remote browsing """ def __init__(self, firefox_path, logger=None): self.logger = logger # type: logging.Logger self.client = None # type: Marionette self.ffpopen = None # type: subprocess.Popen self.ffpath = firefox_path # type: str if logger is None: self.logger = logging.getLogger("MarionetteHelper") self.logger.debug("Marionette helper init done") def _open_session(self, host='localhost', port=2828): """ Opens the session for marionette""" caps = {u'acceptInsecureCerts': True, } self.client = Marionette(host, port=port) self.client.start_session(capabilities=caps) def run_firefox(self): """ Start the firefox process""" self.logger.debug("Starting firefox process") self.ffpopen = subprocess.Popen([self.ffpath, "-marionette"]) self.logger.debug("Opening marionette session") self._open_session() def quit_firefox(self): """ Close the firefox process and close marionette session""" #self.logger.debug("Closing firefox") #self.client._send_message("Marionette:Quit") self.client._request_in_app_shutdown("eForceQuit") self.client.delete_session(False) self.client.cleanup() self.client = None # reset client state #try: # self.client.close() # try to close the window anyway #except InvalidSessionIdException: # pass #except socket.error: # pass #finally: # try: # self.logger.debug("Closing marionette session") # self.client.delete_session(False) # close client session # except InvalidSessionIdException: # pass # except socket.error: # pass # self.client = None # reset client state #self.logger.debug("Waiting for firefox to close") #for _ in range(3): # give the process 3 seconds to terminate # time.sleep(1) # self.ffpopen.poll() # if self.ffpopen.returncode is not None: # break #self.ffpopen.poll() #if self.ffpopen.returncode is None: # self.logger.warning("Firefox not closed in time, killing it!") # self.ffpopen.kill() # process was not quit in time, kill it #self.ffpopen = None # reset popen state #self.logger.debug("Firefox is closed") def ___get_client(self): """ Returns the internal marionette client object""" return self.client def navigate_to_url(self, url): """ Open an url in format of http[s]://example.com""" try: if "http" not in url: url = "http://" + url self.client.navigate(url) except TimeoutException: self.logger.warning("Error: Timeout") return False except InsecureCertificateException: self.logger.warning("Warning: Insecure Certificate") return True except UnknownException as e: if "Reached error page" in str(e): self.logger.warning("Reached error page, ignoring...") else: # reraise, something very unexpected happened here t, v, tb = sys.exc_info() raise t, v, tb return False return True def back(self): """ Go a page backward""" try: self.client.go_back() except TimeoutException: self.logger.warning("Error: Timeout") return False except InsecureCertificateException: self.logger.warning("Warning: Insecure Certificate") return True except UnknownException as e: if "Reached error page" in str(e): self.logger.warning("Reached error page, ignoring...") else: # reraise, something very unexpected happened here t, v, tb = sys.exc_info() raise t, v, tb return False return True def forward(self): """ Go a page forward""" try: self.client.go_forward() except TimeoutException: self.logger.warning("Error: Timeout") return False except InsecureCertificateException: self.logger.warning("Warning: Insecure Certificate") return True except UnknownException as e: if "Reached error page" in str(e): self.logger.warning("Reached error page, ignoring...") else: # reraise, something very unexpected happened here t, v, tb = sys.exc_info() raise t, v, tb return False return True def follow_link(self, index=0): """ Click on a link""" try: links = self.client.find_elements(By.TAG_NAME, "a") links[index].click() except ElementNotVisibleException: self.logger.warning("Error: Element not visible") return False except ElementNotSelectableException: self.logger.warning("Error: Element not selectable") return False except ElementNotAccessibleException: self.logger.warning("Error: Element not accessible") return False except ElementNotInteractableException: self.logger.warning("Error: Element not interactable") return False except NoSuchElementException: self.logger.warning("Error: Element does not exist") return False except TimeoutException: self.logger.warning("Error: Timeout") return False except IndexError: self.logger.warning("Error: Out of bound") return False except InsecureCertificateException: self.logger.warning("Warning: Insecure Certificate") return True except UnknownException as e: if "Reached error page" in str(e): self.logger.warning("Reached error page, ignoring...") else: # reraise, something very unexpected happened here t, v, tb = sys.exc_info() raise t, v, tb return False return True def click_element_by_class_name(self, html_class_name): """ Click on first element via class name""" try: e = self.client.find_element(By.CLASS_NAME, html_class_name) e.click() except ElementNotVisibleException: self.logger.warning("Error: Element not visible") return False except ElementNotSelectableException: self.logger.warning("Error: Element not selectable") return False except ElementNotAccessibleException: self.logger.warning("Error: Element not accessible") return False except ElementNotInteractableException: self.logger.warning("Error: Element not interactable") return False except NoSuchElementException: self.logger.warning("Error: Element does not exist") return False except TimeoutException: self.logger.warning("Error: Timeout") return False except InsecureCertificateException: self.logger.warning("Warning: Insecure Certificate") return True except UnknownException as e: if "Reached error page" in str(e): self.logger.warning("Reached error page, ignoring...") else: # reraise, something very unexpected happened here t, v, tb = sys.exc_info() raise t, v, tb return False return True def click_element_by_css_selector(self, css_selector): """ Click on first element via css selector""" try: e = self.client.find_element(By.CSS_SELECTOR, css_selector) e.click() except ElementNotVisibleException: self.logger.warning("Error: Element not visible") return False except ElementNotSelectableException: self.logger.warning("Error: Element not selectable") return False except ElementNotAccessibleException: self.logger.warning("Error: Element not accessible") return False except ElementNotInteractableException: self.logger.warning("Error: Element not interactable") return False except NoSuchElementException: self.logger.warning("Error: Element does not exist") return False except TimeoutException: self.logger.warning("Error: Timeout") return False except InsecureCertificateException: self.logger.warning("Warning: Insecure Certificate") return True except UnknownException as e: if "Reached error page" in str(e): self.logger.warning("Reached error page, ignoring...") else: # reraise, something very unexpected happened here t, v, tb = sys.exc_info() raise t, v, tb return False return True def click_element_by_id(self, html_id): """ Click on first element via element id""" try: e = self.client.find_element(By.ID, html_id) e.click() except ElementNotVisibleException: self.logger.warning("Error: Element not visible") return False except ElementNotSelectableException: self.logger.warning("Error: Element not selectable") return False except ElementNotAccessibleException: self.logger.warning("Error: Element not accessible") return False except ElementNotInteractableException: self.logger.warning("Error: Element not interactable") return False except NoSuchElementException: self.logger.warning("Error: Element does not exist") return False except TimeoutException: self.logger.warning("Error: Timeout") return False except InsecureCertificateException: self.logger.warning("Warning: Insecure Certificate") return True except UnknownException as e: if "Reached error page" in str(e): self.logger.warning("Reached error page, ignoring...") else: # reraise, something very unexpected happened here t, v, tb = sys.exc_info() raise t, v, tb return False return True def click_element_by_name(self, html_name): """ Click on first element via element name""" try: e = self.client.find_element(By.NAME, html_name) e.click() except ElementNotVisibleException: self.logger.warning("Error: Element not visible") return False except ElementNotSelectableException: self.logger.warning("Error: Element not selectable") return False except ElementNotAccessibleException: self.logger.warning("Error: Element not accessible") return False except ElementNotInteractableException: self.logger.warning("Error: Element not interactable") return False except NoSuchElementException: self.logger.warning("Error: Element does not exist") return False except TimeoutException: self.logger.warning("Error: Timeout") return False except InsecureCertificateException: self.logger.warning("Warning: Insecure Certificate") return True except UnknownException as e: if "Reached error page" in str(e): self.logger.warning("Reached error page, ignoring...") else: # reraise, something very unexpected happened here t, v, tb = sys.exc_info() raise t, v, tb return False return True def click_element_by_tag_name(self, html_tag_name): """ Click on first element via tag name""" try: e = self.client.find_element(By.TAG_NAME, html_tag_name) e.click() except ElementNotVisibleException: self.logger.warning("Error: Element not visible") return False except ElementNotSelectableException: self.logger.warning("Error: Element not selectable") return False except ElementNotAccessibleException: self.logger.warning("Error: Element not accessible") return False except ElementNotInteractableException: self.logger.warning("Error: Element not interactable") return False except NoSuchElementException: self.logger.warning("Error: Element does not exist") return False except TimeoutException: self.logger.warning("Error: Timeout") return False except InsecureCertificateException: self.logger.warning("Warning: Insecure Certificate") return True except UnknownException as e: if "Reached error page" in str(e): self.logger.warning("Reached error page, ignoring...") else: # reraise, something very unexpected happened here t, v, tb = sys.exc_info() raise t, v, tb return False return True def click_element_by_xpath(self, xpath): """ Click on first element via xpath""" try: e = self.client.find_element(By.XPATH, xpath) e.click() except ElementNotVisibleException: self.logger.warning("Error: Element not visible") return False except ElementNotSelectableException: self.logger.warning("Error: Element not selectable") return False except ElementNotAccessibleException: self.logger.warning("Error: Element not accessible") return False except ElementNotInteractableException: self.logger.warning("Error: Element not interactable") return False except NoSuchElementException: self.logger.warning("Error: Element does not exist") return False except TimeoutException: self.logger.warning("Error: Timeout") return False except InsecureCertificateException: self.logger.warning("Warning: Insecure Certificate") return True except UnknownException as e: if "Reached error page" in str(e): self.logger.warning("Reached error page, ignoring...") else: # reraise, something very unexpected happened here t, v, tb = sys.exc_info() raise t, v, tb return False return True def click_element_by_link_text(self, html_link_text): """ Click on first element via link text""" try: e = self.client.find_element(By.LINK_TEXT, html_link_text) e.click() except ElementNotVisibleException: self.logger.warning("Error: Element not visible") return False except ElementNotSelectableException: self.logger.warning("Error: Element not selectable") return False except ElementNotAccessibleException: self.logger.warning("Error: Element not accessible") return False except ElementNotInteractableException: self.logger.warning("Error: Element not interactable") return False except NoSuchElementException: self.logger.warning("Error: Element does not exist") return False except TimeoutException: self.logger.warning("Error: Timeout") return False except InsecureCertificateException: self.logger.warning("Warning: Insecure Certificate") return True except UnknownException as e: if "Reached error page" in str(e): self.logger.warning("Reached error page, ignoring...") else: # reraise, something very unexpected happened here t, v, tb = sys.exc_info() raise t, v, tb return False return True def send_keys_to_element_by_name(self, name, text): """ Sends text to an element via name""" try: e = self.client.find_element(By.NAME, name) e.send_keys(text) except ElementNotVisibleException: self.logger.warning("Error: Element not visible") return False except ElementNotSelectableException: self.logger.warning("Error: Element not selectable") return False except ElementNotAccessibleException: self.logger.warning("Error: Element not accessible") return False except ElementNotInteractableException: self.logger.warning("Error: Element not interactable") return False except NoSuchElementException: self.logger.warning("Error: Element does not exist") return False except TimeoutException: self.logger.warning("Error: Timeout") return False return True def select_window(self, index): """ Switch window via index""" try: self.client.switch_to_window(self.client.window_handles[index]) except NoSuchWindowException: self.logger.warning("Error: Window does not exist") return False return True def close_window(self): """ Close the current window""" self.client.close() # this won't close the last window, call quit_firefox instead def get_current_window_index(self): """ Get current windows index""" return self.client.window_handles.index(self.client.current_window_handle) def new_tab(self): """ Open a new empty tab""" with self.client.using_context("chrome"): self.client.find_element(By.ID, "menu_newNavigatorTab").click() def new_window(self): """ Open a new empty window""" with self.client.using_context("chrome"): self.client.execute_script("window.open();") def close_tab(self): """ Close the current tab""" self.close_window() # basically the same as close windows
#!/usr/bin/env python # -*- coding: utf-8 -*- from marionette_driver.marionette import Marionette import pdb client = Marionette("localhost", port=2828) client.start_session() client.navigate("http://www.tianyancha.com/company/75507246") element = client.find_element("class name", 'company_info') print element.text
class Puppet: MIME_TYPES = [ "application/epub+zip", "application/gzip", "application/java-archive", "application/json", "application/ld+json", "application/msword", "application/octet-stream", "application/ogg", "application/pdf", "application/rtf", "application/vnd.amazon.ebook", "application/vnd.apple.installer+xml", "application/vnd.mozilla.xul+xml", "application/vnd.ms-excel", "application/vnd.ms-fontobject", "application/vnd.ms-powerpoint", "application/vnd.oasis.opendocument.presentation", "application/vnd.oasis.opendocument.spreadsheet", "application/vnd.oasis.opendocument.text", "application/vnd.openxmlformats-officedocument.presentationml.presentation", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "application/vnd.visio", "application/x-7z-compressed", "application/x-abiword", "application/x-bzip", "application/x-bzip2", "application/x-csh", "application/x-freearc", "application/xhtml+xml", "application/xml", "application/x-rar-compressed", "application/x-sh", "application/x-shockwave-flash", "application/x-tar", "application/zip", "appliction/php", "audio/aac", "audio/midi audio/x-midi", "audio/mpeg", "audio/ogg", "audio/wav", "audio/webm", "font/otf", "font/ttf", "font/woff", "font/woff2", "image/bmp", "image/gif", "image/jpeg", "image/png", "image/svg+xml", "image/tiff", "image/vnd.microsoft.icon", "image/webp", "text/calendar", "text/css", "text/csv", "text/html", "text/javascript", "text/javascript", "text/plain", "text/xml", "video/3gpp", "video/3gpp2", "video/mp2t", "video/mpeg", "video/ogg", "video/webm", "video/x-msvideo" ] METHOD_CSS_SELECTOR = "css selector" NO_LOG = "-" DELETE_TARGET_FILES = ["mimeTypes.rdf", "handlers.json"] USER_DEFINED = 2 GECKO_LOG = Path(__file__).parent.resolve() def __init__(self, binary: str, profile: str): self.__has_session = False self.__auto_download = False self.__download_dir = "" if not Path(binary).is_file(): print(f"Binary {binary} Not Found") return if not Path(profile).is_dir(): print(f"Profile {profile} Not Found") return # geckodriver の log ファイル出力を抑止する self.marionette = Marionette( bin=binary, gecko_log=self.NO_LOG, profile=profile) # start_session 前にファイルを消しておかないと # 後で自動ダウンロードできない self.__delete_download_profile() # start_session しないと quit もできない self.marionette.start_session() self.__has_session = True def __enter__(self): return self def __exit__(self, ex_type, ex_value, trace): if self.has_session: self.quit() @property def has_session(self): return self.__has_session @property def auto_download(self): return self.__auto_download def __delete_download_profile(self): # mimeTypes.rdf と handlers.json に # ファイル読み込み時の動作設定が保存されている(text/plain はファイルを保存、など) # 自動ダウンロードするため既存の設定は削除する for name in self.DELETE_TARGET_FILES: p = Path(self.marionette.profile_path).joinpath(name) if p.is_file(): p.unlink() def __activate_auto_download(self): # 一度有効にすると同セッション内では無効にできない self.marionette.set_pref("browser.download.useDownloadDir", True) self.marionette.set_pref("browser.helperApps.neverAsk.saveToDisk", ",".join(self.MIME_TYPES)) self.marionette.set_pref( "browser.download.folderList", self.USER_DEFINED) self.marionette.set_pref("browser.download.lastDir", None) self.__auto_download = True @property def download_dir(self): if self.__auto_download == False: raise Exception("auto download not activated") return self.__download_dir @download_dir.setter def download_dir(self, dir: str): p = Path(dir) if not p.is_dir(): print(f"Download Dir {dir} Not Found") return full_path = str(p.resolve()) if self.__auto_download == False: self.__activate_auto_download() # self.__auto_download = True self.marionette.set_pref("browser.download.dir", full_path) self.marionette.set_pref("browser.download.downloadDir", full_path) self.__download_dir = full_path def set_download(self, dir: str): self.download_dir = dir def query_selector(self, selectors: str) -> HTMLElement: return self.marionette.find_element(self.METHOD_CSS_SELECTOR, selectors) def query_selectors(self, selectors: str) -> List[HTMLElement]: return self.marionette.find_elements(self.METHOD_CSS_SELECTOR, selectors) def wait(self, seconds: int): actions = Actions(self.marionette) actions.wait(seconds).perform() def quit(self): profile = Path(self.marionette.profile_path) self.marionette.quit(clean=True) # self.__forced_rmdir(profile) # Path(self.GECKO_LOG).unlink() self.__has_session = False def exec(self, script: str) -> Optional[str]: # script 内での記述簡略化のため mrnt = self.marionette set_download = self.set_download wait = self.wait quit = self.quit query_selector = self.query_selector query_selectors = self.query_selectors try: exec(script) return None except Exception as err: return str(err.args[0]) @classmethod def __forced_rmdir(self, p: Path): if p.is_dir(): for f in p.iterdir(): if f.is_file(): f.unlink() elif f.is_dir(): self.__forced_rmdir(f) p.rmdir()
def marionette(request): m = Marionette(bin=request.config.option.bin) m.start_session() m.set_prefs({'signon.rememberSignons': False}) request.addfinalizer(m.delete_session) return m
class CommonTestCase(unittest.TestCase): __metaclass__ = MetaParameterized match_re = None failureException = AssertionError pydebugger = None def __init__(self, methodName, **kwargs): unittest.TestCase.__init__(self, methodName) self.loglines = [] self.duration = 0 self.expected = kwargs.pop('expected', 'pass') self.logger = get_default_logger() self.profile = FirefoxProfile() self.binary = kwargs.pop('binary', None) def _enter_pm(self): if self.pydebugger: self.pydebugger.post_mortem(sys.exc_info()[2]) def _addSkip(self, result, reason): addSkip = getattr(result, 'addSkip', None) if addSkip is not None: addSkip(self, reason) else: warnings.warn( "TestResult has no addSkip method, skips not reported", RuntimeWarning, 2) result.addSuccess(self) def run(self, result=None): # Bug 967566 suggests refactoring run, which would hopefully # mean getting rid of this inner function, which only sits # here to reduce code duplication: def expected_failure(result, exc_info): addExpectedFailure = getattr(result, "addExpectedFailure", None) if addExpectedFailure is not None: addExpectedFailure(self, exc_info) else: warnings.warn( "TestResult has no addExpectedFailure method, " "reporting as passes", RuntimeWarning) result.addSuccess(self) self.start_time = time.time() orig_result = result if result is None: result = self.defaultTestResult() startTestRun = getattr(result, 'startTestRun', None) if startTestRun is not None: startTestRun() result.startTest(self) testMethod = getattr(self, self._testMethodName) if (getattr(self.__class__, "__unittest_skip__", False) or getattr(testMethod, "__unittest_skip__", False)): # If the class or method was skipped. try: skip_why = ( getattr(self.__class__, '__unittest_skip_why__', '') or getattr(testMethod, '__unittest_skip_why__', '')) self._addSkip(result, skip_why) finally: result.stopTest(self) self.stop_time = time.time() return try: success = False try: if self.expected == "fail": try: self.setUp() except Exception: raise _ExpectedFailure(sys.exc_info()) else: self.setUp() except SkipTest as e: self._addSkip(result, str(e)) except KeyboardInterrupt: raise except _ExpectedFailure as e: expected_failure(result, e.exc_info) except: self._enter_pm() result.addError(self, sys.exc_info()) else: try: if self.expected == 'fail': try: testMethod() except: raise _ExpectedFailure(sys.exc_info()) raise _UnexpectedSuccess else: testMethod() except self.failureException: self._enter_pm() result.addFailure(self, sys.exc_info()) except KeyboardInterrupt: raise except _ExpectedFailure as e: expected_failure(result, e.exc_info) except _UnexpectedSuccess: addUnexpectedSuccess = getattr(result, 'addUnexpectedSuccess', None) if addUnexpectedSuccess is not None: addUnexpectedSuccess(self) else: warnings.warn( "TestResult has no addUnexpectedSuccess method, reporting as failures", RuntimeWarning) result.addFailure(self, sys.exc_info()) except SkipTest as e: self._addSkip(result, str(e)) except: self._enter_pm() result.addError(self, sys.exc_info()) else: success = True try: if self.expected == "fail": try: self.tearDown() except: raise _ExpectedFailure(sys.exc_info()) else: self.tearDown() except KeyboardInterrupt: raise except _ExpectedFailure as e: expected_failure(result, e.exc_info) except: self._enter_pm() result.addError(self, sys.exc_info()) success = False # Here we could handle doCleanups() instead of calling cleanTest directly self.cleanTest() if success: result.addSuccess(self) finally: result.stopTest(self) if orig_result is None: stopTestRun = getattr(result, 'stopTestRun', None) if stopTestRun is not None: stopTestRun() @classmethod def match(cls, filename): """ Determines if the specified filename should be handled by this test class; this is done by looking for a match for the filename using cls.match_re. """ if not cls.match_re: return False m = cls.match_re.match(filename) return m is not None @classmethod def add_tests_to_suite(cls, mod_name, filepath, suite, testloader, testvars): """ Adds all the tests in the specified file to the specified suite. """ raise NotImplementedError @property def test_name(self): if hasattr(self, 'jsFile'): return os.path.basename(self.jsFile) else: return '{0}.py {1}.{2}'.format(self.__class__.__module__, self.__class__.__name__, self._testMethodName) def id(self): # TBPL starring requires that the "test name" field of a failure message # not differ over time. The test name to be used is passed to # mozlog via the test id, so this is overriden to maintain # consistency. return self.test_name def setUp(self): # Convert the marionette weakref to an object, just for the # duration of the test; this is deleted in tearDown() to prevent # a persistent circular reference which in turn would prevent # proper garbage collection. self.start_time = time.time() self.pingServer = PingServer() self.pingServer.start() self.marionette = Marionette(bin=self.binary, profile=self.profile) if self.marionette.session is None: self.marionette.start_session() self.marionette.reset_timeouts() def tearDown(self): self.marionette.cleanup() self.pingServer.stop() def cleanTest(self): self._deleteSession() def _deleteSession(self): if hasattr(self, 'start_time'): self.duration = time.time() - self.start_time if hasattr(self.marionette, 'session'): if self.marionette.session is not None: try: self.loglines.extend(self.marionette.get_logs()) except Exception, inst: self.loglines = [['Error getting log: {}'.format(inst)]] try: self.marionette.delete_session() except (socket.error, MarionetteException, IOError): # Gecko has crashed? self.marionette.session = None try: self.marionette.client.close() except socket.error: pass self.marionette = None
class MarionetteHelper: def __init__(self, logger, success_symbol, failure_symbol): """ Initialise the helper class. """ self.client = None self.logger = logger self.success_symbol = success_symbol self.failure_symbol = failure_symbol def init_ff(self): """ Initialises the connection to Firefox and starts a session. @return: - """ if not check_socket(MARIONETTE_HOST, MARIONETTE_PORT): self.logger.error( u" > [ERROR] Please check if you started Firefox with the '-marionette' " "option or set 'marionette.enabled' to 'true' in 'about:config'. {}" .format(self.failure_symbol)) sys.exit(1) self.client = Marionette(host=MARIONETTE_HOST, port=MARIONETTE_PORT) self.client.start_session() def get_existing_bookmarks(self): """ Get the existing bookmarks from the Google Bookmarks API. We need to do this in Firefox to have the cookie set which authorities us with the API. @return: - """ self.client.navigate( "https://www.google.com/bookmarks/?output=xml&num=10000") # Initialise XML object root = XML(self.client.page_source.encode("utf-8")) # Return set of bookmarks return set([bookmark[1].text for bookmark in root[0]]) def save_button_contains_correct_text_save(self, *args): """ Helper method for Marionette, here: check if fav button contains text "SAVE" @return: Whether or not the fav button contains the text "SAVE" """ save_button = self.client.find_element( By.CLASS_NAME, "section-entity-action-save-button") return save_button.text == "SAVE" def save_button_contains_correct_text_saved(self, *args): """ Helper method for Marionette, here: check if fav button contains text "SAVED" @return: Whether or not the fav button contains the text "SAVED" """ save_button = self.client.find_element( By.CLASS_NAME, "section-entity-action-save-button") return save_button.text == "SAVED" def interactive_add_feature(self, coordinates): """ Navigates to the Google Maps URL for the provided coordinates and waits for input. @return: - """ url = "https://www.google.com/maps/search/?api=1&query={},{}" # This navigates Firefox to the passed URL self.client.navigate(url.format(coordinates[0], coordinates[1])) # Wait for input if sys.version_info[0] < 3: raw_input("Press Enter to continue...") else: input("Press Enter to continue...") def add_feature_2(self, url, list_add): """ Tries to add a feature (bookmark / place) to your Google Maps fav list. @return: - """ self.client.navigate(url) try: saved_button = Wait(self.client, timeout=1).until( expected.element_present(By.CSS_SELECTOR, "[data-value='Saved']")) self.logger.info(" > Feature was already saved") return utils.constants.ADD_FEATURE_ALREADY_ADDED except TimeoutException: pass try: save_button = Wait(self.client, timeout=5).until( expected.element_present(By.CSS_SELECTOR, "[data-value='Save']")) Wait(self.client, timeout=5).until(expected.element_displayed(save_button)) except TimeoutException: self.logger.error(" > Unable to find save button") return utils.constants.ADD_FEATURE_UNKNOWN_ERROR save_button.click() if list_add == utils.constants.LIST_STARRED_PLACES: data_index = 2 elif list_add == utils.constants.LIST_WANT_TO_GO: data_index = 1 else: data_index = -1 css_selector = "#action-menu [data-index='{}']".format(data_index) sub_save_item = Wait(self.client, timeout=5).until( expected.element_present(By.CSS_SELECTOR, css_selector)) Wait(self.client, timeout=5).until(expected.element_displayed(sub_save_item)) sub_save_item.click() def add_feature(self, url): """ Tries to add a feature (bookmark / place) to your Google Maps fav list. @return: - ADD_FEATURE_FAILURE if adding resulted in a known failure - ADD_FEATURE_SUCCESS if everything went fine - ADD_FEATURE_UNKNOWN_ERROR if we don't know what happened """ # This navigates Firefox to the passed URL self.client.navigate(url) # We wait for the fav button to be present... save_button = Wait(self.client, timeout=10).until( expected.element_present(By.CLASS_NAME, "section-entity-action-save-button")) # ... and to be displayed displayed = Wait(self.client, timeout=10).until( expected.element_displayed(save_button)) try: # Now we look for the correct text, it should say "SAVE" Wait(self.client, timeout=6).until(self.save_button_contains_correct_text_save) try: # Click it to add the feature (bookmark / place) to the Google Maps fav list save_button.click() except NoSuchElementException: pass try: # Now the text should be "SAVED" and this indicates it was saved Wait(self.client, timeout=6).until( self.save_button_contains_correct_text_saved) except TimeoutException: # We clicked but the fav button text didn't change, i.e. the click went wrong or timed out self.logger.error(" > [ERROR] Feature: '{}'".format(url)) save_button = self.client.find_element( By.CLASS_NAME, "section-entity-action-save-button") self.logger.error( " > [ERROR] Save button didn't switch to 'SAVED', it contains '{}'" .format(save_button.text)) return ADD_FEATURE_FAILURE return ADD_FEATURE_SUCCESS except TimeoutException: # This is the case if the fave button didn't contain the text "SAVE". # This can happen if it contains "SAVED", but this shouldn't happen in the # first place because we don't try to add features if we know that they're # already added. # So most likely something truly went wrong here. self.logger.error(" > [ERROR] Feature: '{}'".format(url)) save_button = self.client.find_element( By.CLASS_NAME, "section-entity-action-save-button") self.logger.error( " > [ERROR] Save button contained unknown text '{}'".format( save_button.text)) return ADD_FEATURE_UNKNOWN_ERROR
def run_tests(self): """ Generate the PGO profile data """ from mozhttpd import MozHttpd from mozprofile import Preferences from mozdevice import ADBDeviceFactory, ADBTimeoutError from six import string_types from marionette_driver.marionette import Marionette app = self.query_package_name() IP = "10.0.2.2" PORT = 8888 PATH_MAPPINGS = { "/js-input/webkit/PerformanceTests": "third_party/webkit/PerformanceTests", } dirs = self.query_abs_dirs() topsrcdir = dirs["abs_src_dir"] adb = self.query_exe("adb") path_mappings = { k: os.path.join(topsrcdir, v) for k, v in PATH_MAPPINGS.items() } httpd = MozHttpd( port=PORT, docroot=os.path.join(topsrcdir, "build", "pgo"), path_mappings=path_mappings, ) httpd.start(block=False) profile_data_dir = os.path.join(topsrcdir, "testing", "profiles") with open(os.path.join(profile_data_dir, "profiles.json"), "r") as fh: base_profiles = json.load(fh)["profileserver"] prefpaths = [ os.path.join(profile_data_dir, profile, "user.js") for profile in base_profiles ] prefs = {} for path in prefpaths: prefs.update(Preferences.read_prefs(path)) interpolation = { "server": "%s:%d" % httpd.httpd.server_address, "OOP": "false" } for k, v in prefs.items(): if isinstance(v, string_types): v = v.format(**interpolation) prefs[k] = Preferences.cast(v) outputdir = self.config.get("output_directory", "/sdcard/pgo_profile") jarlog = posixpath.join(outputdir, "en-US.log") profdata = posixpath.join(outputdir, "default_%p_random_%m.profraw") env = {} env["XPCOM_DEBUG_BREAK"] = "warn" env["MOZ_IN_AUTOMATION"] = "1" env["MOZ_JAR_LOG_FILE"] = jarlog env["LLVM_PROFILE_FILE"] = profdata if self.query_minidump_stackwalk(): os.environ["MINIDUMP_STACKWALK"] = self.minidump_stackwalk_path os.environ["MINIDUMP_SAVE_PATH"] = self.query_abs_dirs( )["abs_blob_upload_dir"] if not self.symbols_path: self.symbols_path = os.environ.get("MOZ_FETCHES_DIR") # Force test_root to be on the sdcard for android pgo # builds which fail for Android 4.3 when profiles are located # in /data/local/tmp/test_root with # E AndroidRuntime: FATAL EXCEPTION: Gecko # E AndroidRuntime: java.lang.IllegalArgumentException: \ # Profile directory must be writable if specified: /data/local/tmp/test_root/profile # This occurs when .can-write-sentinel is written to # the profile in # mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoProfile.java. # This is not a problem on later versions of Android. This # over-ride of test_root should be removed when Android 4.3 is no # longer supported. sdcard_test_root = "/sdcard/test_root" adbdevice = ADBDeviceFactory(adb=adb, device="emulator-5554", test_root=sdcard_test_root) if adbdevice.test_root != sdcard_test_root: # If the test_root was previously set and shared # the initializer will not have updated the shared # value. Force it to match the sdcard_test_root. adbdevice.test_root = sdcard_test_root adbdevice.mkdir(outputdir, parents=True) try: # Run Fennec a first time to initialize its profile driver = Marionette( app="fennec", package_name=app, adb_path=adb, bin="geckoview-androidTest.apk", prefs=prefs, connect_to_running_emulator=True, startup_timeout=1000, env=env, symbols_path=self.symbols_path, ) driver.start_session() # Now generate the profile and wait for it to complete for page in PAGES: driver.navigate("http://%s:%d/%s" % (IP, PORT, page)) timeout = 2 if "Speedometer/index.html" in page: # The Speedometer test actually runs many tests internally in # javascript, so it needs extra time to run through them. The # emulator doesn't get very far through the whole suite, but # this extra time at least lets some of them process. timeout = 360 time.sleep(timeout) driver.set_context("chrome") driver.execute_script(""" Components.utils.import("resource://gre/modules/Services.jsm"); let cancelQuit = Components.classes["@mozilla.org/supports-PRBool;1"] .createInstance(Components.interfaces.nsISupportsPRBool); Services.obs.notifyObservers(cancelQuit, "quit-application-requested", null); return cancelQuit.data; """) driver.execute_script(""" Components.utils.import("resource://gre/modules/Services.jsm"); Services.startup.quit(Ci.nsIAppStartup.eAttemptQuit) """) # There is a delay between execute_script() returning and the profile data # actually getting written out, so poll the device until we get a profile. for i in range(50): if not adbdevice.process_exist(app): break time.sleep(2) else: raise Exception("Android App (%s) never quit" % app) # Pull all the profraw files and en-US.log adbdevice.pull(outputdir, "/builds/worker/workspace/") except ADBTimeoutError: self.fatal( "INFRA-ERROR: Failed with an ADBTimeoutError", EXIT_STATUS_DICT[TBPL_RETRY], ) profraw_files = glob.glob("/builds/worker/workspace/*.profraw") if not profraw_files: self.fatal( "Could not find any profraw files in /builds/worker/workspace") merge_cmd = [ os.path.join(os.environ["MOZ_FETCHES_DIR"], "clang/bin/llvm-profdata"), "merge", "-o", "/builds/worker/workspace/merged.profdata", ] + profraw_files rc = subprocess.call(merge_cmd) if rc != 0: self.fatal( "INFRA-ERROR: Failed to merge profile data. Corrupt profile?", EXIT_STATUS_DICT[TBPL_RETRY], ) # tarfile doesn't support xz in this version of Python tar_cmd = [ "tar", "-acvf", "/builds/worker/artifacts/profdata.tar.xz", "-C", "/builds/worker/workspace", "merged.profdata", "en-US.log", ] subprocess.check_call(tar_cmd) httpd.stop()
def run_tests(firefox_path=None): basedir = os.path.dirname(__file__) if sys.platform == 'darwin' and os.path.isdir(firefox_path): firefox_path = os.path.join(firefox_path, 'Contents', 'MacOS', 'firefox') driver = Marionette(app='fxdesktop', bin=firefox_path, gecko_log='-', prefs={'xpinstall.signatures.required': False}) driver.start_session() try: build1 = tempfile.NamedTemporaryFile(mode='wb', suffix='.xpi', delete=False) build2 = tempfile.NamedTemporaryFile(mode='wb', suffix='.xpi', delete=False) try: gulp_build(basedir, build1.name) jpm_build(basedir, os.path.join(basedir, 'testhelper'), build2.name) addons = Addons(driver) addons.install(build1.name, temp=True) addons.install(build2.name, temp=True) finally: os.unlink(build1.name) os.unlink(build2.name) driver.expected = expected driver.keys = Keys class restore_url: def __enter__(self): self.url = driver.get_url() def __exit__(self, type, value, traceback): driver.navigate('about:blank') driver.navigate(self.url) driver.restore_url = restore_url def wait_until(method): Wait(driver, default_timeout).until(lambda d: method()) driver.wait_until = wait_until def accept_alert(): driver.switch_to_alert().accept() driver.accept_alert = accept_alert max_timestamp = {'value': 0} def get_urls(): result = [] prefix = '[testhelper] Loading: ' new_timestamp = max_timestamp['value'] with driver.using_context(driver.CONTEXT_CHROME): messages = driver.execute_script( 'return ' + 'Cc["@mozilla.org/consoleservice;1"]' + '.getService(Ci.nsIConsoleService).getMessageArray()' + '.map(m => m instanceof Ci.nsIScriptError ? ' + '[m.timeStamp, m.errorMessage] : [null, null])') for timestamp, message in messages: if timestamp <= max_timestamp['value']: continue if not message.startswith(prefix): continue if timestamp > new_timestamp: new_timestamp = timestamp result.append(message[len(prefix):]) max_timestamp['value'] = new_timestamp return result driver.get_urls = get_urls def close_windows(keep): for h in [h for h in driver.chrome_window_handles if h != keep]: driver.switch_to_window(h) driver.close_chrome_window() driver.switch_to_window(keep) driver.close_windows = close_windows def close_background_tabs(): current_tab = driver.current_window_handle for h in [h for h in driver.window_handles if h != current_tab]: driver.switch_to_window(h) driver.close() driver.switch_to_window(current_tab) driver.close_background_tabs = close_background_tabs def wait_for_load(): code = 'return document.readyState == "complete";' driver.wait_until(lambda: driver.execute_script(code)) driver.wait_for_load = wait_for_load def click(self): action = Actions(driver) action.click(self) action.perform() HTMLElement.click = click def middle_click(self): action = Actions(driver) action.middle_click(self) action.perform() HTMLElement.middle_click = middle_click def context_click(self): action = Actions(driver) action.context_click(self) action.perform() HTMLElement.context_click = context_click testdir = os.path.join(basedir, 'tests') for filename in os.listdir(testdir): if filename.startswith('.') or not filename.endswith('.py'): continue filepath = os.path.join(testdir, filename) globals = {} execfile(filepath, globals) globals['run'](driver) finally: driver.cleanup()
class B2GDesktopReftest(RefTest): build_type = "desktop" marionette = None def __init__(self, marionette_args): RefTest.__init__(self) self.last_test = os.path.basename(__file__) self.marionette_args = marionette_args self.profile = None self.runner = None self.test_script = os.path.join(here, 'b2g_start_script.js') self.timeout = None def run_marionette_script(self): self.marionette = Marionette(**self.marionette_args) assert(self.marionette.wait_for_port()) self.marionette.start_session() if self.build_type == "mulet": self._wait_for_homescreen(timeout=300) self._unlockScreen() self.marionette.set_context(self.marionette.CONTEXT_CHROME) if os.path.isfile(self.test_script): f = open(self.test_script, 'r') self.test_script = f.read() f.close() self.marionette.execute_script(self.test_script) def run_tests(self, tests, options): manifests = self.resolver.resolveManifests(options, tests) self.profile = self.create_profile(options, manifests, profile_to_clone=options.profile) env = self.buildBrowserEnv(options, self.profile.profile) kp_kwargs = { 'processOutputLine': [self._on_output], 'onTimeout': [self._on_timeout], 'kill_on_timeout': False } if not options.debugger: if not options.timeout: if mozinfo.info['debug']: options.timeout = 420 else: options.timeout = 300 self.timeout = options.timeout + 30.0 log.info("%s | Running tests: start.", os.path.basename(__file__)) cmd, args = self.build_command_line(options.app, ignore_window_size=options.ignoreWindowSize, browser_arg=options.browser_arg) self.runner = FirefoxRunner(profile=self.profile, binary=cmd, cmdargs=args, env=env, process_class=ProcessHandler, process_args=kp_kwargs, symbols_path=options.symbolsPath) status = 0 try: self.runner.start(outputTimeout=self.timeout) log.info("%s | Application pid: %d", os.path.basename(__file__), self.runner.process_handler.pid) # kick starts the reftest harness self.run_marionette_script() status = self.runner.wait() finally: self.runner.check_for_crashes(test_name=self.last_test) self.runner.cleanup() if status > 0: log.testFail("%s | application terminated with exit code %s", self.last_test, status) elif status < 0: log.info("%s | application killed with signal %s", self.last_test, -status) log.info("%s | Running tests: end.", os.path.basename(__file__)) return status def create_profile(self, options, manifests, profile_to_clone=None): profile = RefTest.createReftestProfile(self, options, manifests, profile_to_clone=profile_to_clone) prefs = {} # Turn off the locale picker screen prefs["browser.firstrun.show.localepicker"] = False if not self.build_type == "mulet": # FIXME: With Mulet we can't set this values since Gaia won't launch prefs["b2g.system_startup_url"] = \ "app://test-container.gaiamobile.org/index.html" prefs["b2g.system_manifest_url"] = \ "app://test-container.gaiamobile.org/manifest.webapp" # Make sure we disable system updates prefs["app.update.enabled"] = False prefs["app.update.url"] = "" prefs["app.update.url.override"] = "" # Disable webapp updates prefs["webapps.update.enabled"] = False # Disable tiles also prefs["browser.newtabpage.directory.source"] = "" prefs["browser.newtabpage.directory.ping"] = "" prefs["dom.ipc.tabs.disabled"] = False prefs["dom.mozBrowserFramesEnabled"] = True prefs["font.size.inflation.emPerLine"] = 0 prefs["font.size.inflation.minTwips"] = 0 prefs["network.dns.localDomains"] = "app://test-container.gaiamobile.org" prefs["reftest.browser.iframe.enabled"] = False prefs["reftest.remote"] = False # Set a future policy version to avoid the telemetry prompt. prefs["toolkit.telemetry.prompted"] = 999 prefs["toolkit.telemetry.notifiedOptOut"] = 999 # Disable periodic updates of service workers prefs["dom.serviceWorkers.periodic-updates.enabled"] = False # Set the extra prefs. profile.set_preferences(prefs) return profile def build_command_line(self, app, ignore_window_size=False, browser_arg=None): cmd = os.path.abspath(app) args = ['-marionette'] if browser_arg: args += [browser_arg] if not ignore_window_size: args.extend(['--screen', '800x1000']) if self.build_type == "mulet": args += ['-chrome', 'chrome://b2g/content/shell.html'] return cmd, args def _on_output(self, line): sys.stdout.write("%s\n" % line) sys.stdout.flush() # TODO use structured logging if "TEST-START" in line and "|" in line: self.last_test = line.split("|")[1].strip() def _on_timeout(self): msg = "%s | application timed out after %s seconds with no output" log.testFail(msg % (self.last_test, self.timeout)) # kill process to get a stack self.runner.stop(sig=signal.SIGABRT)
def client(): # On MacOS: /Applications/Firefox.app/Contents/MacOS/firefox -marionette client = Marionette(host='localhost', port=2828) client.start_session() return client
def run_firefox(args): logger = logging.getLogger('sisyphus') logger.setLevel(logging.DEBUG) streamhandler = logging.StreamHandler(stream=sys.stdout) streamformatter = logging.Formatter( '%(asctime)s %(name)15s %(levelname)-8s %(message)s') streamhandler.setFormatter(streamformatter) logger.addHandler(streamhandler) # On Windows, make sure we point to the same temp directory as cygwin. if platform.system() == 'Windows': tempfile.tempdir = 'C:\\cygwin\\tmp' # Clean up tmpaddons tmpaddons = glob.glob(os.path.join(tempfile.gettempdir(), 'tmpaddon*')) for tmpaddon in tmpaddons: os.unlink(tmpaddon) # Work around Windows issues with shell metacharacters in url. if not args.url: if "URL" in os.environ: args.url = os.environ["URL"] else: logger.error("run_firefox: url is required") return # Load preferences of the form name=value from the # command line arguments. def eval_value(value): """Convert preference string value""" if value == 'true': value = True elif value == 'false': value = False else: try: value = eval(value) except NameError: # Leave string value alone. pass return value mozprofile_preferences = mozprofile.prefs.Preferences() preferences = {} set_preference_args = [] for set_preference_arg in args.set_preferences: (name, value) = set_preference_arg.split('=', 1) set_preference_args.append((name, eval_value(value))) preferences.update(dict(set_preference_args)) # Load preferences of from json files. for preference_json_arg in args.preference_jsons: preferences.update( dict(mozprofile_preferences.read_json(preference_json_arg))) # Load preferences from Firefox prefs.js/user.js files. for preference_file_arg in args.preference_files: preferences.update( dict(mozprofile_preferences.read_prefs(preference_file_arg))) logger.info("preferences: %s", json.dumps(preferences, indent=2, sort_keys=True)) profile = mozprofile.profile.FirefoxProfile(profile=args.profile, preferences=preferences) client = Marionette(host='localhost', port=2828, bin=args.binary, profile=profile, gecko_log=args.gecko_log, symbols_path=args.symbols_path) client.start_session() if args.restart: client.restart(clean=False, in_app=True) client.maximize_window() references = {'time_out_alarm_fired': False} if hasattr(signal, 'SIGALRM'): # Windows doesn't support SIGALRM. marionette # doesn't support cygwin paths... def timeout_handler(signum, frame): logger.warning("navigate: %s timed out" % args.url) references['time_out_alarm_fired'] = True client.quit() default_alarm_handler = signal.getsignal(signal.SIGALRM) signal.signal(signal.SIGALRM, timeout_handler) signal.alarm(args.page_load_timeout + 2 * args.script_timeout) try: client.timeout.page_load = args.page_load_timeout client.timeout.script = args.script_timeout # Register the dialog closer for the browser. If the download # dialog appears, it will be closed and the browser window # will be closed. This forces marionette to return from # navigate and works around Bug 1366035. This version does # not dismiss normal Alerts which can be handled by Marionette's Alert. dialog_closer_script = """ var gDialogCloser; var gDialogCloserObserver; var gDialogCloserSubjects = []; registerDialogCloser = function () { gDialogCloser = Components.classes['@mozilla.org/embedcomp/window-watcher;1'].getService(Components.interfaces.nsIWindowWatcher); gDialogCloserObserver = {observe: dialogCloser_observe}; gDialogCloser.registerNotification(gDialogCloserObserver); } unregisterDialogCloser = function () { if (!gDialogCloserObserver || !gDialogCloser) { return; } gDialogCloser.unregisterNotification(gDialogCloserObserver); gDialogCloserObserver = null; gDialogCloser = null; } dialogCloser_observe = function (subject, topic, data) { if (subject instanceof ChromeWindow && topic == 'domwindowopened' ) { gDialogCloserSubjects.push(subject); subject.setTimeout(closeDialog, 5000) } } closeDialog = function () { var subject; while ( (subject = gDialogCloserSubjects.pop()) != null) { if (subject.document instanceof XULDocument) { var uri = subject.document.documentURI; //if (uri.startsWith('chrome://') && uri.endsWith('ialog.xul')) { // subject.close(); //} else if (uri == 'chrome://mozapps/content/downloads/unknownContentType.xul') { dump('Sisyphus Runner: Closing Window due to download dialog\\n'); subject.close(); window.close(); } } } } registerDialogCloser(); """ client.set_context(client.CONTEXT_CHROME) client.execute_script(dialog_closer_script, new_sandbox=False, script_timeout=client.timeout.script) client.set_context(client.CONTEXT_CONTENT) try: logger.info('New Page: %s' % args.url) client.navigate(args.url) client.maximize_window() except Exception, e: logger.warning('navigate: %s', e) # Do not call client.check_for_crash() as that will invoke # mozcrash which will delete the dump files. Handle the dumps # in the caller. client.set_context(client.CONTEXT_CONTENT) for content_script_url in args.content_scripts: content_script = get_remote_text(content_script_url) if content_script: try: logger.info('<contentscript>\n%s\n</contentscript>', content_script) result = client.execute_script( content_script, script_args=[], script_timeout=client.timeout.script) logger.info('content script result: %s', result) except errors.ScriptTimeoutException, e: logger.warning('content script: %s', e) except Exception, e: logger.error('content script: %s', e)
def runApp(self, options, cmdargs=None, timeout=None, debuggerInfo=None, symbolsPath=None, valgrindPath=None, valgrindArgs=None, valgrindSuppFiles=None, **profileArgs): if cmdargs is None: cmdargs = [] cmdargs = cmdargs[:] if self.use_marionette: cmdargs.append("-marionette") binary = options.app profile = self.createReftestProfile(options, **profileArgs) # browser environment env = self.buildBrowserEnv(options, profile.profile) self.log.info("Running with e10s: {}".format(options.e10s)) self.log.info("Running with fission: {}".format(options.fission)) 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.""" def testid(test): if " " in test: return test.split(" ")[0] return test if message["action"] == "test_start": self.lastTestSeen = testid(message["test"]) elif message["action"] == "test_end": if self.lastTest and message["test"] == self.lastTest: self.lastTestSeen = "Last test finished" else: self.lastTestSeen = "{} (finished)".format( testid(message["test"])) self.log.add_handler(record_last_test) kp_kwargs = { "kill_on_timeout": False, "cwd": SCRIPT_DIRECTORY, "onTimeout": [timeoutHandler], "processOutputLine": [self.outputHandler], } if mozinfo.isWin or mozinfo.isMac: # Prevents log interleaving on Windows at the expense of losing # true log order. See bug 798300 and bug 1324961 for more details. kp_kwargs["processStderrLine"] = [self.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) 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 self.outputHandler.proc_name = "GECKO({})".format(proc.pid) # Used to defer a possible IOError exception from Marionette marionette_exception = None if self.use_marionette: marionette_args = { "socket_timeout": options.marionette_socket_timeout, "startup_timeout": options.marionette_startup_timeout, "symbols_path": options.symbolsPath, } if options.marionette: host, port = options.marionette.split(":") marionette_args["host"] = host marionette_args["port"] = int(port) try: 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() except IOError: # Any IOError as thrown by Marionette means that something is # wrong with the process, like a crash or the socket is no # longer open. We defer raising this specific error so that # post-test checks for leaks and crashes are performed and # reported first. marionette_exception = sys.exc_info() status = runner.wait() runner.process_handler = None self.outputHandler.proc_name = 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) crashed = mozcrash.log_crashes( self.log, os.path.join(profile.profile, "minidumps"), options.symbolsPath, test=self.lastTestSeen, ) if not status and crashed: status = 1 runner.cleanup() self.cleanup(profile.profile) if marionette_exception is not None: exc, value, tb = marionette_exception raise reraise(exc, value, tb) self.log.info( "Process mode: {}".format("e10s" if options.e10s else "non-e10s")) return status
class MuletReftest(RefTest): build_type = "mulet" marionette = None def __init__(self, marionette_args): RefTest.__init__(self) self.last_test = os.path.basename(__file__) self.marionette_args = marionette_args self.profile = None self.runner = None self.test_script = os.path.join(here, 'b2g_start_script.js') self.timeout = None def run_marionette_script(self): self.marionette = Marionette(**self.marionette_args) assert (self.marionette.wait_for_port()) self.marionette.start_session() if self.build_type == "mulet": self._wait_for_homescreen(timeout=300) self._unlockScreen() self.marionette.set_context(self.marionette.CONTEXT_CHROME) if os.path.isfile(self.test_script): f = open(self.test_script, 'r') self.test_script = f.read() f.close() self.marionette.execute_script(self.test_script) def run_tests(self, tests, options): manifests = self.resolver.resolveManifests(options, tests) self.profile = self.create_profile(options, manifests, profile_to_clone=options.profile) env = self.buildBrowserEnv(options, self.profile.profile) kp_kwargs = { 'processOutputLine': [self._on_output], 'onTimeout': [self._on_timeout], 'kill_on_timeout': False } if not options.debugger: if not options.timeout: if mozinfo.info['debug']: options.timeout = 420 else: options.timeout = 300 self.timeout = options.timeout + 30.0 log.info("%s | Running tests: start.", os.path.basename(__file__)) cmd, args = self.build_command_line( options.app, ignore_window_size=options.ignoreWindowSize, browser_arg=options.browser_arg) self.runner = FirefoxRunner(profile=self.profile, binary=cmd, cmdargs=args, env=env, process_class=ProcessHandler, process_args=kp_kwargs, symbols_path=options.symbolsPath) status = 0 try: self.runner.start(outputTimeout=self.timeout) log.info("%s | Application pid: %d", os.path.basename(__file__), self.runner.process_handler.pid) # kick starts the reftest harness self.run_marionette_script() status = self.runner.wait() finally: self.runner.check_for_crashes(test_name=self.last_test) self.runner.cleanup() if status > 0: log.testFail("%s | application terminated with exit code %s", self.last_test, status) elif status < 0: log.info("%s | application killed with signal %s", self.last_test, -status) log.info("%s | Running tests: end.", os.path.basename(__file__)) return status def create_profile(self, options, manifests, profile_to_clone=None): profile = RefTest.createReftestProfile( self, options, manifests, profile_to_clone=profile_to_clone) prefs = {} # Turn off the locale picker screen prefs["browser.firstrun.show.localepicker"] = False if not self.build_type == "mulet": # FIXME: With Mulet we can't set this values since Gaia won't launch prefs["b2g.system_startup_url"] = \ "app://test-container.gaiamobile.org/index.html" prefs["b2g.system_manifest_url"] = \ "app://test-container.gaiamobile.org/manifest.webapp" # Make sure we disable system updates prefs["app.update.enabled"] = False prefs["app.update.url"] = "" prefs["app.update.url.override"] = "" # Disable webapp updates prefs["webapps.update.enabled"] = False # Disable tiles also prefs["browser.newtabpage.directory.source"] = "" prefs["browser.newtabpage.directory.ping"] = "" prefs["dom.ipc.tabs.disabled"] = False prefs["dom.mozBrowserFramesEnabled"] = True prefs["font.size.inflation.emPerLine"] = 0 prefs["font.size.inflation.minTwips"] = 0 prefs[ "network.dns.localDomains"] = "app://test-container.gaiamobile.org" prefs["reftest.browser.iframe.enabled"] = False prefs["reftest.remote"] = False # Set a future policy version to avoid the telemetry prompt. prefs["toolkit.telemetry.prompted"] = 999 prefs["toolkit.telemetry.notifiedOptOut"] = 999 # Set the extra prefs. profile.set_preferences(prefs) return profile def build_command_line(self, app, ignore_window_size=False, browser_arg=None): cmd = os.path.abspath(app) args = ['-marionette'] if browser_arg: args += [browser_arg] if not ignore_window_size: args.extend(['--screen', '800x1000']) if self.build_type == "mulet": args += ['-chrome', 'chrome://b2g/content/shell.html'] return cmd, args def _on_output(self, line): sys.stdout.write("%s\n" % line) sys.stdout.flush() # TODO use structured logging if "TEST-START" in line and "|" in line: self.last_test = line.split("|")[1].strip() def _on_timeout(self): msg = "%s | application timed out after %s seconds with no output" log.testFail(msg % (self.last_test, self.timeout)) # kill process to get a stack self.runner.stop(sig=signal.SIGABRT) def _unlockScreen(self): self.marionette.set_context(self.marionette.CONTEXT_CONTENT) self.marionette.import_script( os.path.abspath( os.path.join(__file__, os.path.pardir, "gaia_lock_screen.js"))) self.marionette.switch_to_frame() self.marionette.execute_async_script('GaiaLockScreen.unlock()') def _wait_for_homescreen(self, timeout): log.info("Waiting for home screen to load") Wait(self.marionette, timeout).until( expected.element_present(By.CSS_SELECTOR, '#homescreen[loading-state=false]'))
class Firefox(DesktopBrowser): """Firefox""" def __init__(self, path, options, job): DesktopBrowser.__init__(self, path, options, job) self.job = job self.task = None self.options = options self.path = path self.event_name = None self.moz_log = None self.marionette = None self.addons = None self.extension_id = None self.nav_error = None self.page_loaded = None self.recording = False self.connected = False self.start_offset = None self.browser_version = None self.log_pos = {} self.page = {} self.requests = {} self.last_activity = monotonic.monotonic() self.script_dir = os.path.join( os.path.abspath(os.path.dirname(__file__)), 'js') self.start_page = 'http://127.0.0.1:8888/orange.html' def prepare(self, job, task): """Prepare the profile/OS for the browser""" self.moz_log = os.path.join(task['dir'], 'moz.log') self.log_pos = {} self.page = {} self.requests = {} os.environ["MOZ_LOG_FILE"] = self.moz_log os.environ["MOZ_LOG"] = 'timestamp,sync,nsHttp:5,nsSocketTransport:5'\ 'nsHostResolver:5,pipnss:5' DesktopBrowser.prepare(self, job, task) profile_template = os.path.join( os.path.abspath(os.path.dirname(__file__)), 'support', 'Firefox', 'profile') if not task['cached'] and os.path.isdir(profile_template): try: if os.path.isdir(task['profile']): shutil.rmtree(task['profile']) shutil.copytree(profile_template, task['profile']) except Exception: pass # delete any unsent crash reports crash_dir = None if platform.system() == 'Windows': if 'APPDATA' in os.environ: crash_dir = os.path.join(os.environ['APPDATA'], 'Mozilla', 'Firefox', 'Crash Reports') else: crash_dir = os.path.join(os.path.expanduser('~'), '.mozilla', 'firefox', 'Crash Reports') if crash_dir and os.path.isdir(crash_dir): logging.debug("Clearing crash reports in %s", crash_dir) try: shutil.rmtree(crash_dir) except Exception: pass # Prepare the config for the extension to query if self.job['message_server'] is not None: config = None names = [ 'block', 'block_domains', 'block_domains_except', 'headers', 'cookies' ] for name in names: if name in task and task[name]: if config is None: config = {} config[name] = task[name] self.job['message_server'].config = config def launch(self, job, task): """Launch the browser""" if self.job['message_server'] is not None: self.job['message_server'].flush_messages() self.connected = False from marionette_driver.marionette import Marionette from marionette_driver.addons import Addons args = [ '-profile', '"{0}"'.format(task['profile']), '-no-remote', '-marionette', 'about:blank' ] if self.path.find(' ') > -1: command_line = '"{0}"'.format(self.path) else: command_line = self.path command_line += ' ' + ' '.join(args) DesktopBrowser.launch_browser(self, command_line) try: self.marionette = Marionette('localhost', port=2828) self.marionette.start_session(timeout=self.task['time_limit']) self.configure_prefs() logging.debug('Installing extension') self.addons = Addons(self.marionette) extension_path = os.path.join( os.path.abspath(os.path.dirname(__file__)), 'support', 'Firefox', 'extension') self.extension_id = self.addons.install(extension_path, temp=True) logging.debug('Resizing browser to %dx%d', task['width'], task['height']) self.marionette.set_window_position(x=0, y=0) self.marionette.set_window_size(height=task['height'], width=task['width']) if 'browserVersion' in self.marionette.session_capabilities: self.browser_version = self.marionette.session_capabilities[ 'browserVersion'] self.marionette.navigate(self.start_page) time.sleep(0.5) self.wait_for_extension() if self.connected: # Override the UA String if necessary ua_string = self.execute_js('navigator.userAgent;') modified = False if 'uastring' in self.job: ua_string = self.job['uastring'] modified = True if ua_string is not None and 'AppendUA' in task: ua_string += ' ' + task['AppendUA'] modified = True if modified: logging.debug(ua_string) self.marionette.set_pref('general.useragent.override', ua_string) # Figure out the native viewport size size = self.execute_js( "[window.innerWidth, window.innerHeight]") logging.debug(size) if size is not None and len(size) == 2: task['actual_viewport'] = { "width": size[0], "height": size[1] } if 'adjust_viewport' in job and job['adjust_viewport']: delta_x = max(task['width'] - size[0], 0) delta_y = max(task['height'] - size[1], 0) if delta_x or delta_y: width = task['width'] + delta_x height = task['height'] + delta_y logging.debug('Resizing browser to %dx%d', width, height) self.marionette.set_window_position(x=0, y=0) self.marionette.set_window_size(height=height, width=width) # Wait for the browser startup to finish DesktopBrowser.wait_for_idle(self) except Exception as err: task['error'] = 'Error starting Firefox: {0}'.format(err.__str__()) def get_pref_value(self, value): """Convert a json pref value to Python""" str_match = re.match(r'^"(.*)"$', value) if value == 'true': value = True elif value == 'false': value = False elif re.match(r'^[\d]+$', value): value = int(value) elif str_match: value = str_match.group(1) else: value = None return value def configure_prefs(self): """Load the prefs file and configure them through marionette""" prefs = {} prefs_file = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'support', 'Firefox', 'profile', 'prefs.js') with open(prefs_file) as f_in: for line in f_in: matches = re.search( r'user_pref\("([^"]+)",[\s]*([^\)]*)[\s]*\);', line) if matches: key = matches.group(1).strip() value = self.get_pref_value(matches.group(2).strip()) if value is not None: prefs[key] = value if prefs: try: self.marionette.set_prefs(prefs, True) except Exception: pass def stop(self, job, task): """Kill the browser""" if self.extension_id is not None and self.addons is not None: try: self.addons.uninstall(self.extension_id) except Exception: pass self.extension_id = None self.addons = None if self.marionette is not None: try: self.marionette.close() except Exception: pass self.marionette = None DesktopBrowser.stop(self, job, task) # Make SURE the firefox processes are gone if platform.system() == "Linux": subprocess.call(['killall', '-9', 'firefox']) subprocess.call(['killall', '-9', 'firefox-trunk']) os.environ["MOZ_LOG_FILE"] = '' os.environ["MOZ_LOG"] = '' # delete the raw log files if self.moz_log is not None: files = sorted(glob.glob(self.moz_log + '*')) for path in files: try: os.remove(path) except Exception: pass def run_lighthouse_test(self, task): """Stub for lighthouse test""" pass def run_task(self, task): """Run an individual test""" if self.marionette is not None and self.connected: self.task = task logging.debug("Running test") end_time = monotonic.monotonic() + task['test_time_limit'] task['current_step'] = 1 recording = False while len(task['script']) and task['error'] is None and \ monotonic.monotonic() < end_time: self.prepare_task(task) command = task['script'].pop(0) if not recording and command['record']: recording = True self.on_start_recording(task) try: self.process_command(command) except Exception: logging.exception("Exception running task") if command['record']: self.wait_for_page_load() if not task['combine_steps'] or not len(task['script']): self.on_stop_recording(task) recording = False self.on_start_processing(task) self.wait_for_processing(task) self.step_complete(task) if task['log_data']: # Move on to the next step task['current_step'] += 1 self.event_name = None task['navigated'] = True # Always navigate to about:blank after finishing in case the tab is # remembered across sessions try: self.marionette.navigate('about:blank') except Exception: logging.debug( 'Marionette exception navigating to about:blank after the test' ) self.task = None def wait_for_extension(self): """Wait for the extension to send the started message""" if self.job['message_server'] is not None: end_time = monotonic.monotonic() + 30 while monotonic.monotonic() < end_time: try: self.job['message_server'].get_message(1) logging.debug('Extension started') self.connected = True break except Exception: pass def wait_for_page_load(self): """Wait for the onload event from the extension""" if self.job['message_server'] is not None and self.connected: start_time = monotonic.monotonic() end_time = start_time + self.task['time_limit'] done = False while not done: try: self.process_message( self.job['message_server'].get_message(1)) except Exception: pass now = monotonic.monotonic() elapsed_test = now - start_time if self.nav_error is not None: done = True if self.page_loaded is None: self.task['error'] = self.nav_error elif now >= end_time: done = True # only consider it an error if we didn't get a page load event if self.page_loaded is None: self.task['error'] = "Page Load Timeout" elif 'time' not in self.job or elapsed_test > self.job['time']: elapsed_activity = now - self.last_activity elapsed_page_load = now - self.page_loaded if self.page_loaded else 0 if elapsed_page_load >= 1 and elapsed_activity >= self.task[ 'activity_time']: done = True elif self.task['error'] is not None: done = True def execute_js(self, script): """Run javascipt""" ret = None if self.marionette is not None: try: ret = self.marionette.execute_script('return ' + script, script_timeout=30) except Exception: pass return ret def run_js_file(self, file_name): """Execute one of our js scripts""" ret = None script = None script_file_path = os.path.join(self.script_dir, file_name) if os.path.isfile(script_file_path): with open(script_file_path, 'rb') as script_file: script = script_file.read() if script is not None: try: ret = self.marionette.execute_script('return ' + script, script_timeout=30) except Exception: pass if ret is not None: logging.debug(ret) return ret def collect_browser_metrics(self, task): """Collect all of the in-page browser metrics that we need""" logging.debug("Collecting user timing metrics") user_timing = self.run_js_file('user_timing.js') if user_timing is not None: path = os.path.join(task['dir'], task['prefix'] + '_timed_events.json.gz') with gzip.open(path, 'wb', 7) as outfile: outfile.write(json.dumps(user_timing)) logging.debug("Collecting page-level metrics") page_data = self.run_js_file('page_data.js') if page_data is not None: task['page_data'].update(page_data) if 'customMetrics' in self.job: custom_metrics = {} for name in self.job['customMetrics']: logging.debug("Collecting custom metric %s", name) script = 'var wptCustomMetric = function() {' +\ self.job['customMetrics'][name] +\ '};try{return wptCustomMetric();}catch(e){};' try: custom_metrics[name] = self.marionette.execute_script( script, script_timeout=30) if custom_metrics[name] is not None: logging.debug(custom_metrics[name]) except Exception: pass path = os.path.join(task['dir'], task['prefix'] + '_metrics.json.gz') with gzip.open(path, 'wb', 7) as outfile: outfile.write(json.dumps(custom_metrics)) def process_message(self, message): """Process a message from the extension""" logging.debug(message) if self.recording: self.last_activity = monotonic.monotonic() try: # Make all of the timestamps relative to the test start to match the log events if 'timeStamp' in message['body']: timestamp = message['body']['timeStamp'] seconds = int(timestamp / 1000) milliseconds = timestamp - (seconds * 1000) event_time = datetime.utcfromtimestamp(seconds) event_time += timedelta(milliseconds=milliseconds) elapsed = event_time - self.task['start_time'] message['body']['timeStamp'] = elapsed.total_seconds() cat, msg = message['path'].split('.', 1) if cat == 'webNavigation': self.process_web_navigation(msg, message['body']) elif cat == 'webRequest': self.process_web_request(msg, message['body']) except Exception: pass def process_web_navigation(self, message, evt): """Handle webNavigation.*""" if evt is not None: if message == 'onBeforeNavigate': if 'frameId' in evt and evt['frameId'] == 0: self.page_loaded = None logging.debug("Starting navigation") if 'timeStamp' in evt and 'start' not in self.page: self.page['start'] = evt['timeStamp'] elif message == 'onCommitted': if 'timeStamp' in evt and 'frameId' in evt and evt['frameId'] == 0 \ and 'committed' not in self.page: self.page['committed'] = evt['timeStamp'] if 'injectScript' in self.job and self.marionette is not None: logging.debug("Injecting script: \n%s", self.job['injectScript']) try: self.marionette.execute_script( self.job['injectScript'], script_timeout=30) except Exception: pass elif message == 'onDOMContentLoaded': if 'timeStamp' in evt and 'frameId' in evt and evt[ 'frameId'] == 0: self.page['DOMContentLoaded'] = evt['timeStamp'] elif message == 'onCompleted': if 'frameId' in evt and evt['frameId'] == 0: self.page_loaded = monotonic.monotonic() logging.debug("Page loaded") if 'timeStamp' in evt: self.page['loaded'] = evt['timeStamp'] elif message == 'onErrorOccurred': if 'frameId' in evt and evt['frameId'] == 0: self.page_loaded = monotonic.monotonic() logging.debug("Page load failed") if 'error' in evt: self.nav_error = evt['error'] else: self.nav_error = 'Navigation failed' def process_web_request(self, message, evt): """Handle webNavigation.*""" if evt is not None and 'requestId' in evt and 'timeStamp' in evt: if evt['requestId'] not in self.requests: self.requests[evt['requestId']] = { 'id': evt['requestId'], 'from_net': True } request = self.requests[evt['requestId']] if 'url' in evt and 'url' not in request: request['url'] = evt['url'] if 'method' in evt and 'method' not in request: request['method'] = evt['method'] if 'type' in evt and 'type' not in request: request['type'] = evt['type'] if 'ip' in evt and 'ip' not in request: request['ip'] = evt['ip'] if 'fromCache' in evt and evt['fromCache']: request['from_net'] = False if 'statusLine' in evt: request['status_line'] = evt['statusLine'] if 'statusCode' in evt: request['status'] = evt['statusCode'] if 'requestHeaders' in evt and 'request_headers' not in request: request['request_headers'] = list(evt['requestHeaders']) if 'responseHeaders' in evt and 'response_headers' not in request: request['response_headers'] = list(evt['responseHeaders']) if message == 'onBeforeRequest': request['created'] = evt['timeStamp'] elif message == 'onSendHeaders': request['start'] = evt['timeStamp'] elif message == 'onBeforeRedirect': if 'first_byte' not in request: request['first_byte'] = evt['timeStamp'] if 'end' not in request or evt['timeStamp'] > request['end']: request['end'] = evt['timeStamp'] elif message == 'onHeadersReceived': if 'first_byte' not in request: request['first_byte'] = evt['timeStamp'] if 'end' not in request or evt['timeStamp'] > request['end']: request['end'] = evt['timeStamp'] elif message == 'onResponseStarted': if 'first_byte' not in request: request['first_byte'] = evt['timeStamp'] if 'end' not in request or evt['timeStamp'] > request['end']: request['end'] = evt['timeStamp'] elif message == 'onCompleted': if 'first_byte' not in request: request['first_byte'] = evt['timeStamp'] if 'end' not in request or evt['timeStamp'] > request['end']: request['end'] = evt['timeStamp'] elif message == 'onErrorOccurred': if 'end' not in request or evt['timeStamp'] > request['end']: request['end'] = evt['timeStamp'] if 'error' in evt: request['error'] = evt['error'] if 'status' not in request: request['status'] = 12999 def prepare_task(self, task): """Format the file prefixes for multi-step testing""" if task['current_step'] == 1: task['prefix'] = task['task_prefix'] task['video_subdirectory'] = task['task_video_prefix'] else: task['prefix'] = '{0}_{1:d}'.format(task['task_prefix'], task['current_step']) task['video_subdirectory'] = '{0}_{1:d}'.format( task['task_video_prefix'], task['current_step']) if task['video_subdirectory'] not in task['video_directories']: task['video_directories'].append(task['video_subdirectory']) if self.event_name is not None: task['step_name'] = self.event_name else: task['step_name'] = 'Step_{0:d}'.format(task['current_step']) def on_start_recording(self, task): """Notification that we are about to start an operation that needs to be recorded""" # Clear the state self.page = {} self.requests = {} task['page_data'] = {'date': time.time()} task['page_result'] = None task['run_start_time'] = monotonic.monotonic() if self.browser_version is not None and 'browserVersion' not in task[ 'page_data']: task['page_data']['browserVersion'] = self.browser_version task['page_data']['browser_version'] = self.browser_version # Mark the start point in the various log files self.log_pos = {} if self.moz_log is not None: files = sorted(glob.glob(self.moz_log + '*')) for path in files: self.log_pos[path] = os.path.getsize(path) self.recording = True now = monotonic.monotonic() if not self.task['stop_at_onload']: self.last_activity = now if self.page_loaded is not None: self.page_loaded = now DesktopBrowser.on_start_recording(self, task) logging.debug('Starting measurement') task['start_time'] = datetime.utcnow() def on_stop_recording(self, task): """Notification that we are done with recording""" self.recording = False DesktopBrowser.on_stop_recording(self, task) if self.connected: if self.job['pngScreenShot']: screen_shot = os.path.join(task['dir'], task['prefix'] + '_screen.png') self.grab_screenshot(screen_shot, png=True) else: screen_shot = os.path.join(task['dir'], task['prefix'] + '_screen.jpg') self.grab_screenshot(screen_shot, png=False, resize=600) # Collect end of test data from the browser self.collect_browser_metrics(task) # Collect the interactive periods interactive = self.execute_js( 'window.wrappedJSObject.wptagentGetInteractivePeriods();') if interactive is not None and len(interactive): interactive_file = os.path.join( task['dir'], task['prefix'] + '_interactive.json.gz') with gzip.open(interactive_file, 'wb', 7) as f_out: f_out.write(interactive) # Copy the log files if self.moz_log is not None: task['moz_log'] = os.path.join(task['dir'], task['prefix'] + '_moz.log') files = sorted(glob.glob(self.moz_log + '*')) for path in files: try: base_name = os.path.basename(path) dest = os.path.join( task['dir'], task['prefix'] + '_' + base_name + '.gz') start_pos = self.log_pos[ path] if path in self.log_pos else 0 end_pos = os.path.getsize(path) if end_pos > start_pos: length = end_pos - start_pos logging.debug( 'Preparing moz log %s (%d bytes from %d)', base_name, length, start_pos) with open(path, 'rb') as f_in: f_in.seek(start_pos) with gzip.open(dest, 'wb', 7) as f_out: while length > 0: read_bytes = min(length, 1024 * 1024) buff = f_in.read(read_bytes) read_bytes = len(buff) f_out.write(buff) length -= read_bytes except Exception: pass def on_start_processing(self, task): """Start any processing of the captured data""" DesktopBrowser.on_start_processing(self, task) # Parse the moz log for the accurate request timings request_timings = [] if 'moz_log' in task: from internal.support.firefox_log_parser import FirefoxLogParser parser = FirefoxLogParser() start_time = task['start_time'].strftime('%Y-%m-%d %H:%M:%S.%f') logging.debug('Parsing moz logs relative to %s start time', start_time) request_timings = parser.process_logs(task['moz_log'], start_time) files = sorted(glob.glob(task['moz_log'] + '*')) for path in files: try: os.remove(path) except Exception: pass # Build the request and page data if len(request_timings) and task['current_step'] == 1: self.adjust_timings(request_timings) self.process_requests(request_timings, task) def adjust_timings(self, requests): """Adjust the request timings to start at zero for the earliest timestamp""" timestamps = [ 'dns_start', 'dns_end', 'connect_start', 'connect_end', 'ssl_start', 'ssl_end', 'start', 'first_byte', 'end' ] earliest = None for request in requests: for entry in timestamps: if entry in request and request[entry] >= 0: if earliest is None or request[entry] < earliest: earliest = request[entry] logging.debug("Adjusting request timings by %0.3f seconds", earliest) if earliest is not None and earliest > 0: self.start_offset = earliest for request in requests: for entry in timestamps: if entry in request and request[entry] >= 0: request[entry] -= earliest def wait_for_processing(self, task): """Wait for any background processing threads to finish""" DesktopBrowser.wait_for_processing(self, task) def process_command(self, command): """Process an individual script command""" logging.debug("Processing script command:") logging.debug(command) if command['command'] == 'navigate': self.task['page_data']['URL'] = command['target'] url = str(command['target']).replace('"', '\"') script = 'window.location="{0}";'.format(url) script = self.prepare_script_for_record(script) self.marionette.execute_script(script) elif command['command'] == 'logdata': self.task['combine_steps'] = False if int(re.search(r'\d+', str(command['target'])).group()): logging.debug("Data logging enabled") self.task['log_data'] = True else: logging.debug("Data logging disabled") self.task['log_data'] = False elif command['command'] == 'combinesteps': self.task['log_data'] = True self.task['combine_steps'] = True elif command['command'] == 'seteventname': self.event_name = command['target'] elif command['command'] == 'exec': script = command['target'] if command['record']: script = self.prepare_script_for_record(script) self.marionette.execute_script(script) elif command['command'] == 'sleep': delay = min( 60, max(0, int(re.search(r'\d+', str(command['target'])).group()))) if delay > 0: time.sleep(delay) elif command['command'] == 'setabm': self.task['stop_at_onload'] = \ bool('target' in command and int(re.search(r'\d+', str(command['target'])).group()) == 0) elif command['command'] == 'setactivitytimeout': if 'target' in command: milliseconds = int( re.search(r'\d+', str(command['target'])).group()) self.task['activity_time'] = max( 0, min(30, float(milliseconds) / 1000.0)) elif command['command'] == 'setuseragent': self.task['user_agent_string'] = command['target'] elif command['command'] == 'firefoxpref': if 'target' in command and 'value' in command: self.set_pref(command['target'], command['value']) def navigate(self, url): """Navigate to the given URL""" if self.marionette is not None: try: self.marionette.navigate(url) except Exception as err: logging.debug("Error navigating Firefox: %s", str(err)) def set_pref(self, key, value_str): """Set an individual pref value""" value = self.get_pref_value(value_str.strip()) if value is not None: try: logging.debug('Setting Pref "%s" to %s', key, value_str) self.marionette.set_pref(key, value) except Exception: pass def grab_screenshot(self, path, png=True, resize=0): """Save the screen shot (png or jpeg)""" if self.marionette is not None: try: data = self.marionette.screenshot(format='binary', full=False) if data is not None: resize_string = '' if not resize else '-resize {0:d}x{0:d} '.format( resize) if png: with open(path, 'wb') as image_file: image_file.write(data) if len(resize_string): cmd = '{0} -format png -define png:color-type=2 '\ '-depth 8 {1}"{2}"'.format(self.job['image_magick']['mogrify'], resize_string, path) logging.debug(cmd) subprocess.call(cmd, shell=True) else: tmp_file = path + '.png' with open(tmp_file, 'wb') as image_file: image_file.write(data) command = '{0} "{1}" {2}-quality {3:d} "{4}"'.format( self.job['image_magick']['convert'], tmp_file, resize_string, self.job['imageQuality'], path) logging.debug(command) subprocess.call(command, shell=True) if os.path.isfile(tmp_file): try: os.remove(tmp_file) except Exception: pass except Exception as err: logging.debug('Exception grabbing screen shot: %s', str(err)) def process_requests(self, request_timings, task): """Convert all of the request and page events into the format needed for WPT""" result = {} result['requests'] = self.merge_requests(request_timings) result['pageData'] = self.calculate_page_stats(result['requests']) devtools_file = os.path.join( task['dir'], task['prefix'] + '_devtools_requests.json.gz') with gzip.open(devtools_file, 'wb', 7) as f_out: json.dump(result, f_out) def get_empty_request(self, request_id, url): """Return and empty, initialized request""" parts = urlparse.urlsplit(url) request = { 'type': 3, 'id': request_id, 'request_id': request_id, 'ip_addr': '', 'full_url': url, 'is_secure': 1 if parts.scheme == 'https' else 0, 'method': '', 'host': parts.netloc, 'url': parts.path, 'responseCode': -1, 'load_start': -1, 'load_ms': -1, 'ttfb_ms': -1, 'dns_start': -1, 'dns_end': -1, 'dns_ms': -1, 'connect_start': -1, 'connect_end': -1, 'connect_ms': -1, 'ssl_start': -1, 'ssl_end': -1, 'ssl_ms': -1, 'bytesIn': 0, 'bytesOut': 0, 'objectSize': 0, 'initiator': '', 'initiator_line': '', 'initiator_column': '', 'server_rtt': None, 'headers': { 'request': [], 'response': [] }, 'score_cache': -1, 'score_cdn': -1, 'score_gzip': -1, 'score_cookies': -1, 'score_keep-alive': -1, 'score_minify': -1, 'score_combine': -1, 'score_compress': -1, 'score_etags': -1, 'gzip_total': None, 'gzip_save': None, 'minify_total': None, 'minify_save': None, 'image_total': None, 'image_save': None, 'cache_time': None, 'cdn_provider': None, 'server_count': None, 'socket': -1 } if len(parts.query): request['url'] += '?' + parts.query return request def get_header_value(self, headers, name): """Return the value for the given header""" value = '' name = name.lower() for header in headers: pos = header.find(':') if pos > 0: key = header[0:pos].lower() if key.startswith(name): val = header[pos + 1:].strip() if len(value): value += '; ' value += val return value def merge_requests(self, request_timings): """Merge the requests from the extension and log files""" requests = [] # Start with the requests reported from the extension for req_id in self.requests: try: req = self.requests[req_id] if req['from_net'] and 'start' in req and 'url' in req: request = self.get_empty_request(req['id'], req['url']) if 'ip' in req: request['ip_addr'] = req['ip'] if 'method' in req: request['method'] = req['method'] if 'status' in req: request['responseCode'] = req['status'] if 'type' in req: request['requestType'] = req['type'] if 'request_headers' in req: for header in req['request_headers']: if 'name' in header and 'value' in header: header_text = '{0}: {1}'.format( header['name'], header['value']) request['bytesOut'] += len(header_text) + 2 request['headers']['request'].append( header_text) if 'status_line' in req: request['bytesIn'] += len(req['status_line']) + 2 request['headers']['response'].append( req['status_line']) if 'response_headers' in req: for header in req['response_headers']: if 'name' in header and 'value' in header: try: header_text = '{0}: {1}'.format( header['name'], header['value']) request['bytesIn'] += len(header_text) + 2 request['headers']['response'].append( header_text) except Exception: pass if 'created' in req: request['created'] = req['created'] request['load_start'] = int(round(req['start'] * 1000.0)) if 'first_byte' in req: ttfb = int( round((req['first_byte'] - req['start']) * 1000.0)) request['ttfb_ms'] = max(0, ttfb) if 'end' in req: load_time = int( round((req['end'] - req['start']) * 1000.0)) request['load_ms'] = max(0, load_time) size = self.get_header_value( request['headers']['response'], 'Content-Length') if len(size): request['bytesIn'] += int( re.search(r'\d+', str(size)).group()) requests.append(request) except Exception: pass # Overwrite them with the same requests from the logs for request in requests: for req in request_timings: try: if 'claimed' not in req and 'url' in req and 'full_url' in request \ and 'start' in req and request['full_url'] == req['url']: req['claimed'] = True self.populate_request(request, req) except Exception: pass # Add any events from the logs that weren't reported by the extension for req in request_timings: try: if 'claimed' not in req and 'url' in req and 'start' in req: request = self.get_empty_request(req['id'], req['url']) self.populate_request(request, req) requests.append(request) except Exception: pass # parse values out of the headers for request in requests: try: value = self.get_header_value(request['headers']['response'], 'Expires') if value: request['expires'] = value value = self.get_header_value(request['headers']['response'], 'Cache-Control') if value: request['cacheControl'] = value value = self.get_header_value(request['headers']['response'], 'Content-Type') if value: request['contentType'] = value value = self.get_header_value(request['headers']['response'], 'Content-Encoding') if value: request['contentEncoding'] = value value = self.get_header_value(request['headers']['response'], 'Content-Length') if value: request['objectSize'] = value except Exception: pass requests.sort(key=lambda x: x['load_start']) return requests def populate_request(self, request, log_request): """Populate a request object from the log request values""" request['load_start'] = int(log_request['start'] * 1000) if 'status' in log_request: request['responseCode'] = log_request['status'] if 'dns_start' in log_request and log_request['dns_start'] >= 0: request['dns_start'] = int(log_request['dns_start'] * 1000) if 'dns_end' in log_request and log_request['dns_end'] >= 0: request['dns_end'] = int(round(log_request['dns_end'] * 1000.0)) if 'connect_start' in log_request and log_request['connect_start'] >= 0: request['connect_start'] = int(log_request['connect_start'] * 1000) if 'connect_end' in log_request and log_request['connect_end'] >= 0: request['connect_end'] = int( round(log_request['connect_end'] * 1000.0)) if 'ssl_start' in log_request and log_request['ssl_start'] >= 0: request['ssl_start'] = int(log_request['ssl_start'] * 1000) if 'ssl_end' in log_request and log_request['ssl_end'] >= 0: request['ssl_end'] = int(round(log_request['ssl_end'] * 1000.0)) if 'connection' in log_request: request['socket'] = log_request['connection'] request['load_start'] = int(round(log_request['start'] * 1000.0)) if 'first_byte' in log_request: request['ttfb_ms'] = int(round((log_request['first_byte'] - \ log_request['start']) * 1000.0)) if 'end' in log_request: request['load_ms'] = int(round((log_request['end'] - \ log_request['start']) * 1000.0)) if 'bytes_in' in log_request: request['bytesIn'] = log_request['bytes_in'] if 'request_headers' in log_request: request['headers']['request'] = list( log_request['request_headers']) if 'response_headers' in log_request: request['headers']['response'] = list( log_request['response_headers']) def calculate_page_stats(self, requests): """Calculate the page-level stats""" page = {'loadTime': 0, 'docTime': 0, 'fullyLoaded': 0, 'bytesOut': 0, 'bytesOutDoc': 0, 'bytesIn': 0, 'bytesInDoc': 0, 'requests': len(requests), 'requestsDoc': 0, 'responses_200': 0, 'responses_404': 0, 'responses_other': 0, 'result': 0, 'testStartOffset': 0, 'cached': 1 if self.task['cached'] else 0, 'optimization_checked': 0, 'start_epoch': int((self.task['start_time'] - \ datetime.utcfromtimestamp(0)).total_seconds()) } if 'loaded' in self.page: page['loadTime'] = int(round(self.page['loaded'] * 1000.0)) page['docTime'] = page['loadTime'] page['loadEventStart'] = page['loadTime'] page['loadEventEnd'] = page['loadTime'] if 'DOMContentLoaded' in self.page: page['domContentLoadedEventStart'] = int( round(self.page['DOMContentLoaded'] * 1000.0)) page['domContentLoadedEventEnd'] = page[ 'domContentLoadedEventStart'] main_request = None index = 0 for request in requests: if request['load_ms'] >= 0: end_time = request['load_start'] + request['load_ms'] if end_time > page['fullyLoaded']: page['fullyLoaded'] = end_time if end_time <= page['loadTime']: page['requestsDoc'] += 1 page['bytesInDoc'] += request['bytesIn'] page['bytesOutDoc'] += request['bytesOut'] page['bytesIn'] += request['bytesIn'] page['bytesOut'] += request['bytesOut'] if request['responseCode'] == 200: page['responses_200'] += 1 elif request['responseCode'] == 404: page['responses_404'] += 1 page['result'] = 99999 elif request['responseCode'] > -1: page['responses_other'] += 1 if main_request is None and \ (request['responseCode'] == 200 or request['responseCode'] == 304): main_request = request['id'] request['is_base_page'] = True page['final_base_page_request'] = index page['final_base_page_request_id'] = main_request page['final_url'] = request['full_url'] if 'URL' not in self.task['page_data']: self.task['page_data']['URL'] = page['final_url'] if request['ttfb_ms'] >= 0: page['TTFB'] = request['load_start'] + request['ttfb_ms'] if request['ssl_end'] >= request['ssl_start'] and \ request['ssl_start'] >= 0: page['basePageSSLTime'] = int(round(request['ssl_end'] - \ request['ssl_start'])) if page['responses_200'] == 0 and len(requests): if 'responseCode' in requests[0]: page['result'] = requests[0]['responseCode'] else: page['result'] = 12999 self.task['page_result'] = page['result'] return page
#!/usr/bin/python2.7 # # Script to work around Marionette bug 879816 (cannot click the modal 'ok' button # following clicking something else). # from marionette_driver.marionette import Marionette marionette = Marionette(host='localhost', port=2828) marionette.start_session() marionette.switch_to_frame() marionette.execute_script("document.getElementById('modal-dialog-prompt-ok').click();")
from pprint import pprint from marionette_driver import By from marionette_driver.marionette import Marionette from www.fileserver import FileServer # Start services FILE_SERVER = FileServer() SERVICE_PORT = str(5000) FILE_PORT = str(FILE_SERVER.port) HOST = "http://localhost:" ENDPOINT_PREFIX = HOST + SERVICE_PORT TEST_HTML = HOST + FILE_PORT + "/"+ 'test_IAccessible.html' CLIENT = Marionette(host='localhost', port=2828) CLIENT.start_session() CLIENT.navigate(TEST_HTML) EVENT_PARAMS = urllib.urlencode({'interface': 'IAccessible', 'name': 'MSAA Checkbox', 'type' : 'EVENT_OBJECT_STATECHANGE'}) EVENT_ENDPOINT = ENDPOINT_PREFIX + "/event?%s" CMD_PARAMS = urllib.urlencode({'interface': 'IAccessible', 'name': 'MSAA Checkbox', 'function': 'State'}) CMD_ENPOINT = ENDPOINT_PREFIX + "/cmd?%s" ACCESSSIBLE_PARAMS = urllib.urlencode({'interface': 'IAccessible', 'name': 'MSAA Checkbox', 'depth': -1}) ACCESSIBLE_ENDPOINT = ENDPOINT_PREFIX + "/accessible?%s" print "-----------------ACCESSIBLE------------------" RESPONSE = json.load(urllib.urlopen(ACCESSIBLE_ENDPOINT % ACCESSSIBLE_PARAMS)) pprint(RESPONSE) CHECKBOX = 0x2C assert RESPONSE['IAccessible']['Role'] == CHECKBOX