Example #1
0
    def setUp(self):
        # start server
        self.loop_test_servers = LoopTestServers()

        MarionetteTestCase.setUp(self)
        LoopTestDriver.setUp(self, self.marionette)

        # Although some of these preferences might require restart, we don't
        # use enforce_gecko_prefs (which would restart), as we need to restart
        # for the add-on installation anyway.
        self.marionette.set_prefs(FIREFOX_PREFERENCES)

        xpi_file = os.environ.get("LOOP_XPI_FILE")

        if xpi_file:
            addons = Addons(self.marionette)
            # XXX We should really use temp=True here, but due to the later
            # restart to ensure the add-on is installed correctly, this won't work
            # at the moment. What we need is a fully restartless add-on - bug 1229352
            # at which point we should be able to make this install temporarily
            # and after the restart.
            addons.install(os.path.abspath(xpi_file))

        self.e10s_enabled = os.environ.get("TEST_E10S") == "1"

        # Restart the browser nicely, so the preferences and add-on installation
        # take full effect.
        self.marionette.restart(in_app=True)

        # this is browser chrome, kids, not the content window just yet
        self.marionette.set_context("chrome")
Example #2
0
    def setUp(self):
        # start server
        self.loop_test_servers = LoopTestServers()

        MarionetteTestCase.setUp(self)
        LoopTestDriver.setUp(self, self.marionette)

        # Although some of these preferences might require restart, we don't
        # use enforce_gecko_prefs (which would restart), as we need to restart
        # for the add-on installation anyway.
        self.marionette.set_prefs(FIREFOX_PREFERENCES)

        xpi_file = os.environ.get("LOOP_XPI_FILE")

        if xpi_file:
            addons = Addons(self.marionette)
            # XXX We should really use temp=True here, but due to the later
            # restart to ensure the add-on is installed correctly, this won't work
            # at the moment. What we need is a fully restartless add-on - bug 1229352
            # at which point we should be able to make this install temporarily
            # and after the restart.
            addons.install(os.path.abspath(xpi_file))

        self.e10s_enabled = os.environ.get("TEST_E10S") == "1"

        # Restart the browser nicely, so the preferences and add-on installation
        # take full effect.
        self.marionette.restart(in_app=True)

        # this is browser chrome, kids, not the content window just yet
        self.marionette.set_context("chrome")
Example #3
0
    def setup(self, runner):
        super(MarionetteTestharnessExecutor, self).setup(runner)
        for extension_path in self.install_extensions:
            self.logger.info("Installing extension from %s" % extension_path)
            addons = Addons(self.protocol.marionette)
            addons.install(extension_path)

        self.protocol.testharness.load_runner(self.last_environment["protocol"])
 def install_extension_with_service_worker(self):
     addons = Addons(self.marionette)
     test_extension_path = os.path.join(os.path.dirname(self.filepath),
                                        "data", EXT_DIR_PATH)
     addons.install(test_extension_path, temp=True)
     self.test_extension_base_url = self.get_extension_url()
     Wait(self.marionette).until(
         lambda _: self.is_extension_service_worker_registered,
         message="Wait the extension service worker to be registered")
Example #5
0
    def install_addon(self):
        """Install a minimal addon."""

        resources_dir = os.path.join(os.path.dirname(__file__), "resources")

        addon_path = os.path.abspath(os.path.join(resources_dir, "helloworld"))

        try:
            addons = Addons(self.marionette)
            addons.install(addon_path, temp=True)
        except MarionetteException as e:
            self.fail("{} - Error installing addon: {} - ".format(
                e.cause, e.message))
Example #6
0
 def _install_addon(self):
     # The addon that gets installed here is the easyscreenshot addon taken from AMO.
     # It has high compatibility with firefox and doesn't cause any adverse side affects that
     # could affect our tests like tabs opening, etc.
     # Developed by: MozillaOnline
     # Addon URL: https://addons.mozilla.org/en-US/firefox/addon/easyscreenshot/
     try:
         addon_path = os.path.join(resources_dir, 'easyscreenshot.xpi')
         addons = Addons(self.marionette)
         addons.install(addon_path)
     except MarionetteException as e:
         self.fail('{} - Error installing addon: {} - '.format(
             e.cause, e.message))
Example #7
0
    def install_addon(self):
        """Install a minimal addon and add its ID to self.addon_ids."""

        resources_dir = os.path.join(os.path.dirname(__file__), "resources")
        addon_path = os.path.abspath(os.path.join(resources_dir, "helloworld"))

        try:
            # Ensure the Environment has init'd so the installed addon
            # triggers an "environment-change" ping.
            script = """\
            let [resolve] = arguments;
            Cu.import("resource://gre/modules/TelemetryEnvironment.jsm");
            TelemetryEnvironment.onInitialized().then(resolve);
            """

            with self.marionette.using_context(self.marionette.CONTEXT_CHROME):
                self.marionette.execute_async_script(textwrap.dedent(script))

            addons = Addons(self.marionette)
            addon_id = addons.install(addon_path, temp=True)
        except MarionetteException as e:
            self.fail("{} - Error installing addon: {} - ".format(
                e.cause, e.message))
        else:
            self.addon_ids.append(addon_id)
Example #8
0
class TestAddons(MarionetteTestCase):

    def setUp(self):
        MarionetteTestCase.setUp(self)
        self.addons = Addons(self.marionette)

    @property
    def all_addon_ids(self):
        with self.marionette.using_context('chrome'):
            addons = self.marionette.execute_async_script("""
              Components.utils.import("resource://gre/modules/AddonManager.jsm");
              AddonManager.getAllAddons(function(addons){
                let ids = addons.map(function(x) {
                  return x.id;
                });
                marionetteScriptFinished(ids);
              });
            """)

        return addons

    def test_install_and_remove_temporary_unsigned_addon(self):
        addon_path = os.path.join(here, 'mn-restartless-unsigned.xpi')

        addon_id = self.addons.install(addon_path, temp=True)
        self.assertIn(addon_id, self.all_addon_ids)

        self.addons.uninstall(addon_id)
        self.assertNotIn(addon_id, self.all_addon_ids)

    def test_install_unsigned_addon(self):
        addon_path = os.path.join(here, 'mn-restartless-unsigned.xpi')

        with self.assertRaises(AddonInstallException):
            self.addons.install(addon_path)

    @skip("Need to get the test extension signed")
    def test_install_and_remove_signed_addon(self):
        addon_path = os.path.join(here, 'mn-restartless-signed.xpi')

        addon_id = self.addons.install(addon_path)
        self.assertIn(addon_id, self.all_addon_ids)

        self.addons.uninstall(addon_id)
        self.assertNotIn(addon_id, self.all_addon_ids)
Example #9
0
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()
Example #10
0
    def runApp(self,
               profile,
               binary,
               cmdargs,
               env,
               timeout=None,
               debuggerInfo=None,
               symbolsPath=None,
               options=None,
               valgrindPath=None,
               valgrindArgs=None,
               valgrindSuppFiles=None):
        def timeoutHandler():
            self.handleTimeout(timeout, proc, options.utilityPath,
                               debuggerInfo)

        interactive = False
        debug_args = None
        if debuggerInfo:
            interactive = debuggerInfo.interactive
            debug_args = [debuggerInfo.path] + debuggerInfo.args

        def record_last_test(message):
            """Records the last test seen by this harness for the benefit of crash logging."""
            if message['action'] == 'test_start':
                if " " in message['test']:
                    self.lastTestSeen = message['test'].split(" ")[0]
                else:
                    self.lastTestSeen = message['test']

        self.log.add_handler(record_last_test)

        outputHandler = OutputHandler(self.log,
                                      options.utilityPath,
                                      symbolsPath=symbolsPath)

        kp_kwargs = {
            'kill_on_timeout': False,
            'cwd': SCRIPT_DIRECTORY,
            'onTimeout': [timeoutHandler],
            'processOutputLine': [outputHandler],
        }

        if interactive:
            # If an interactive debugger is attached,
            # don't use timeouts, and don't capture ctrl-c.
            timeout = None
            signal.signal(signal.SIGINT, lambda sigid, frame: None)

        runner_cls = mozrunner.runners.get(
            mozinfo.info.get('appname', 'firefox'), mozrunner.Runner)
        runner = runner_cls(profile=profile,
                            binary=binary,
                            process_class=mozprocess.ProcessHandlerMixin,
                            cmdargs=cmdargs,
                            env=env,
                            process_args=kp_kwargs)
        runner.start(debug_args=debug_args,
                     interactive=interactive,
                     outputTimeout=timeout)
        proc = runner.process_handler

        if self.use_marionette:
            marionette_args = {
                'socket_timeout': options.marionette_socket_timeout,
                '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)

            marionette = Marionette(**marionette_args)
            marionette.start_session(timeout=options.marionette_port_timeout)

            addons = Addons(marionette)
            if options.specialPowersExtensionPath:
                addons.install(options.specialPowersExtensionPath, temp=True)

            addons.install(options.reftestExtensionPath, temp=True)

            marionette.delete_session()

        status = runner.wait()
        runner.process_handler = None

        if status:
            msg = "TEST-UNEXPECTED-FAIL | %s | application terminated with exit code %s" % \
                (self.lastTestSeen, status)
            # use process_output so message is logged verbatim
            self.log.process_output(None, msg)
        else:
            self.lastTestSeen = self.TEST_SEEN_FINAL

        crashed = mozcrash.log_crashes(self.log,
                                       os.path.join(profile.profile,
                                                    'minidumps'),
                                       symbolsPath,
                                       test=self.lastTestSeen)

        runner.cleanup()
        if not status and crashed:
            status = 1
        return status, self.lastTestSeen
Example #11
0
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()
Example #12
0
    def runApp(self, profile, binary, cmdargs, env,
               timeout=None, debuggerInfo=None,
               symbolsPath=None, options=None,
               valgrindPath=None, valgrindArgs=None, valgrindSuppFiles=None):

        def timeoutHandler():
            self.handleTimeout(
                timeout, proc, options.utilityPath, debuggerInfo)

        interactive = False
        debug_args = None
        if debuggerInfo:
            interactive = debuggerInfo.interactive
            debug_args = [debuggerInfo.path] + debuggerInfo.args

        def record_last_test(message):
            """Records the last test seen by this harness for the benefit of crash logging."""
            if message['action'] == 'test_start':
                if isinstance(message['test'], tuple):
                    self.lastTestSeen = message['test'][0]
                else:
                    self.lastTestSeen = message['test']

        self.log.add_handler(record_last_test)

        outputHandler = OutputHandler(self.log, options.utilityPath, symbolsPath=symbolsPath)

        kp_kwargs = {
            'kill_on_timeout': False,
            'cwd': SCRIPT_DIRECTORY,
            'onTimeout': [timeoutHandler],
            'processOutputLine': [outputHandler],
        }

        if interactive:
            # If an interactive debugger is attached,
            # don't use timeouts, and don't capture ctrl-c.
            timeout = None
            signal.signal(signal.SIGINT, lambda sigid, frame: None)

        if mozinfo.info.get('appname') == 'b2g' and mozinfo.info.get('toolkit') != 'gonk':
            runner_cls = mozrunner.Runner
        else:
            runner_cls = mozrunner.runners.get(mozinfo.info.get('appname', 'firefox'),
                                               mozrunner.Runner)
        runner = runner_cls(profile=profile,
                            binary=binary,
                            process_class=mozprocess.ProcessHandlerMixin,
                            cmdargs=cmdargs,
                            env=env,
                            process_args=kp_kwargs)
        runner.start(debug_args=debug_args,
                     interactive=interactive,
                     outputTimeout=timeout)
        proc = runner.process_handler

        if self.use_marionette:
            marionette_args = {
                'socket_timeout': options.marionette_socket_timeout,
                'symbols_path': options.symbolsPath,
            }
            if options.marionette:
                host, port = options.marionette.split(':')
                marionette_args['host'] = host
                marionette_args['port'] = int(port)

            marionette = Marionette(**marionette_args)
            marionette.start_session(timeout=options.marionette_port_timeout)

            addons = Addons(marionette)
            if options.specialPowersExtensionPath:
                addons.install(options.specialPowersExtensionPath, temp=True)

            addons.install(options.reftestExtensionPath, temp=True)

            marionette.delete_session()

        status = runner.wait()
        runner.process_handler = None

        if status:
            msg = "TEST-UNEXPECTED-FAIL | %s | application terminated with exit code %s" % \
                (self.lastTestSeen, status)
            # use process_output so message is logged verbatim
            self.log.process_output(None, msg)
        else:
            self.lastTestSeen = 'Main app process exited normally'

        crashed = mozcrash.log_crashes(self.log, os.path.join(profile.profile, 'minidumps'),
                                       symbolsPath, test=self.lastTestSeen)

        runner.cleanup()
        if not status and crashed:
            status = 1
        return status
Example #13
0
    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))

        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:
            # 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 exc, value, tb

        self.log.info(
            "Process mode: {}".format('e10s' if options.e10s else 'non-e10s'))
        return status
    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)
        time.sleep(1)
        event = None
        messages = None
Example #15
0
class TestAddons(MarionetteTestCase):

    def setUp(self):
        super(TestAddons, self).setUp()

        self.addons = Addons(self.marionette)
        self.preinstalled_addons = self.all_addon_ids

    def tearDown(self):
        self.reset_addons()

        super(TestAddons, self).tearDown()

    @property
    def all_addon_ids(self):
        with self.marionette.using_context("chrome"):
            addons = self.marionette.execute_async_script("""
              let [resolve] = arguments;
              Components.utils.import("resource://gre/modules/AddonManager.jsm");

              AddonManager.getAllAddons().then(function(addons) {
                let ids = addons.map(x => x.id);
                resolve(ids);
              });
            """)

        return set(addons)

    def reset_addons(self):
        with self.marionette.using_context("chrome"):
            for addon in (self.all_addon_ids - self.preinstalled_addons):
                addon_id = self.marionette.execute_async_script("""
                  let [resolve] = arguments;
                  Components.utils.import("resource://gre/modules/AddonManager.jsm");

                  return new Promise(await resolve => {
                    let addon = await AddonManager.getAddonByID(arguments[0]);
                    addon.uninstall();
                    resolve(addon.id);
                  });
                """, script_args=(addon,))
                self.assertEqual(addon_id, addon,
                                 msg="Failed to uninstall {}".format(addon))

    def test_temporary_install_and_remove_unsigned_addon(self):
        addon_path = os.path.join(here, "webextension-unsigned.xpi")

        addon_id = self.addons.install(addon_path, temp=True)
        self.assertIn(addon_id, self.all_addon_ids)
        self.assertEqual(addon_id, "{d3e7c1f1-2e35-4a49-89fe-9f46eb8abf0a}")

        self.addons.uninstall(addon_id)
        self.assertNotIn(addon_id, self.all_addon_ids)

    def test_temporary_install_invalid_addon(self):
        addon_path = os.path.join(here, "webextension-invalid.xpi")

        with self.assertRaises(AddonInstallException):
            self.addons.install(addon_path, temp=True)
        self.assertNotIn("{d3e7c1f1-2e35-4a49-89fe-9f46eb8abf0a}", self.all_addon_ids)

    def test_install_and_remove_signed_addon(self):
        addon_path = os.path.join(here, "webextension-signed.xpi")

        addon_id = self.addons.install(addon_path)
        self.assertIn(addon_id, self.all_addon_ids)
        self.assertEqual(addon_id, "{d3e7c1f1-2e35-4a49-89fe-9f46eb8abf0a}")

        self.addons.uninstall(addon_id)
        self.assertNotIn(addon_id, self.all_addon_ids)

    def test_install_invalid_addon(self):
        addon_path = os.path.join(here, "webextension-invalid.xpi")

        with self.assertRaises(AddonInstallException):
            self.addons.install(addon_path)
        self.assertNotIn("{d3e7c1f1-2e35-4a49-89fe-9f46eb8abf0a}", self.all_addon_ids)

    def test_install_unsigned_addon_fails(self):
        addon_path = os.path.join(here, "webextension-unsigned.xpi")

        with self.assertRaises(AddonInstallException):
            self.addons.install(addon_path)

    def test_install_nonexistent_addon(self):
        addon_path = os.path.join(here, "does-not-exist.xpi")

        with self.assertRaises(AddonInstallException):
            self.addons.install(addon_path)

    def test_install_with_relative_path(self):
        with self.assertRaises(AddonInstallException):
            self.addons.install('webextension.xpi')
Example #16
0
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
Example #17
0
    def runApp(self, profile, binary, cmdargs, env,
               timeout=None, debuggerInfo=None,
               symbolsPath=None, options=None,
               valgrindPath=None, valgrindArgs=None, valgrindSuppFiles=None):

        def timeoutHandler():
            self.handleTimeout(
                timeout, proc, options.utilityPath, debuggerInfo)

        interactive = False
        debug_args = None
        if debuggerInfo:
            interactive = debuggerInfo.interactive
            debug_args = [debuggerInfo.path] + debuggerInfo.args

        def record_last_test(message):
            """Records the last test seen by this harness for the benefit of crash logging."""
            if message['action'] == 'test_start':
                if " " in message['test']:
                    self.lastTestSeen = message['test'].split(" ")[0]
                else:
                    self.lastTestSeen = message['test']

        self.log.add_handler(record_last_test)

        outputHandler = OutputHandler(self.log, options.utilityPath, symbolsPath=symbolsPath)

        kp_kwargs = {
            'kill_on_timeout': False,
            'cwd': SCRIPT_DIRECTORY,
            'onTimeout': [timeoutHandler],
            'processOutputLine': [outputHandler],
        }

        if mozinfo.isWin:
            # 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'] = [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
        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
        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)
        else:
            self.lastTestSeen = self.TEST_SEEN_FINAL

        crashed = mozcrash.log_crashes(self.log, os.path.join(profile.profile, 'minidumps'),
                                       symbolsPath, test=self.lastTestSeen)
        if not status and crashed:
            status = 1

        runner.cleanup()

        if marionette_exception is not None:
            exc, value, tb = marionette_exception
            raise exc, value, tb

        return status, self.lastTestSeen
Example #18
0
class TestAddons(MarionetteTestCase):

    def setUp(self):
        super(TestAddons, self).setUp()

        self.addons = Addons(self.marionette)
        self.preinstalled_addons = self.all_addon_ids

    def tearDown(self):
        self.reset_addons()

        super(TestAddons, self).tearDown()

    @property
    def all_addon_ids(self):
        with self.marionette.using_context("chrome"):
            addons = self.marionette.execute_async_script("""
              Components.utils.import("resource://gre/modules/AddonManager.jsm");
              AddonManager.getAllAddons(function(addons) {
                let ids = addons.map(x => x.id);
                marionetteScriptFinished(ids);
              });
            """)

        return set(addons)

    def reset_addons(self):
        with self.marionette.using_context("chrome"):
            for addon in (self.all_addon_ids - self.preinstalled_addons):
                addon_id = self.marionette.execute_async_script("""
                  Components.utils.import("resource://gre/modules/AddonManager.jsm");
                  return new Promise(resolve => {
                    AddonManager.getAddonByID(arguments[0], function(addon) {
                      addon.uninstall();
                      marionetteScriptFinished(addon.id);
                    });
                  });
                """, script_args=(addon,))
                self.assertEqual(addon_id, addon,
                                 msg="Failed to uninstall {}".format(addon))

    def test_temporary_install_and_remove_unsigned_addon(self):
        addon_path = os.path.join(here, "webextension-unsigned.xpi")

        addon_id = self.addons.install(addon_path, temp=True)
        self.assertIn(addon_id, self.all_addon_ids)
        self.assertEqual(addon_id, "{d3e7c1f1-2e35-4a49-89fe-9f46eb8abf0a}")

        self.addons.uninstall(addon_id)
        self.assertNotIn(addon_id, self.all_addon_ids)

    def test_temporary_install_invalid_addon(self):
        addon_path = os.path.join(here, "webextension-invalid.xpi")

        with self.assertRaises(AddonInstallException):
            self.addons.install(addon_path, temp=True)
        self.assertNotIn("{d3e7c1f1-2e35-4a49-89fe-9f46eb8abf0a}", self.all_addon_ids)

    def test_install_and_remove_signed_addon(self):
        addon_path = os.path.join(here, "webextension-signed.xpi")

        addon_id = self.addons.install(addon_path)
        self.assertIn(addon_id, self.all_addon_ids)
        self.assertEqual(addon_id, "{d3e7c1f1-2e35-4a49-89fe-9f46eb8abf0a}")

        self.addons.uninstall(addon_id)
        self.assertNotIn(addon_id, self.all_addon_ids)

    def test_install_invalid_addon(self):
        addon_path = os.path.join(here, "webextension-invalid.xpi")

        with self.assertRaises(AddonInstallException):
            self.addons.install(addon_path)
        self.assertNotIn("{d3e7c1f1-2e35-4a49-89fe-9f46eb8abf0a}", self.all_addon_ids)

    def test_install_unsigned_addon_fails(self):
        addon_path = os.path.join(here, "webextension-unsigned.xpi")

        with self.assertRaises(AddonInstallException):
            self.addons.install(addon_path)

    def test_install_nonexistent_addon(self):
        addon_path = os.path.join(here, "does-not-exist.xpi")

        with self.assertRaisesRegexp(AddonInstallException, "Could not find add-on at"):
            self.addons.install(addon_path)

    def test_install_with_relative_path(self):
        with self.assertRaisesRegexp(AddonInstallException, "is not an absolute path."):
            self.addons.install('webextension.xpi')