Reimplement support for wdspec tests running in Firefox for android
diff --git a/tools/wptrunner/requirements_firefox.txt b/tools/wptrunner/requirements_firefox.txt
index 74d16ad..c8ac33a 100644
--- a/tools/wptrunner/requirements_firefox.txt
+++ b/tools/wptrunner/requirements_firefox.txt
@@ -1,5 +1,6 @@
 marionette_driver==3.1.0
 mozcrash==2.1.0
+mozdevice==4.0.3
 mozinstall==2.0.1
 mozleak==0.2
 moznetwork==1.1.0
diff --git a/tools/wptrunner/wptrunner/browsers/firefox.py b/tools/wptrunner/wptrunner/browsers/firefox.py
index d95ee4a..e533bf0 100644
--- a/tools/wptrunner/wptrunner/browsers/firefox.py
+++ b/tools/wptrunner/wptrunner/browsers/firefox.py
@@ -815,8 +815,8 @@
     def __init__(self, logger, binary, prefs_root, webdriver_binary, webdriver_args,
                  extra_prefs=None, debug_info=None, symbols_path=None, stackwalk_binary=None,
                  certutil_binary=None, ca_certificate_path=None, e10s=False,
-                 enable_webrender=False, enable_fission=False, stackfix_dir=None, leak_check=False,
-                 asan=False, stylo_threads=1,chaos_mode_flags=None, config=None,
+                 enable_fission=False, stackfix_dir=None, leak_check=False,
+                 asan=False, stylo_threads=1, chaos_mode_flags=None, config=None,
                  browser_channel="nightly", headless=None, **kwargs):
 
         super().__init__(logger, binary, webdriver_binary, webdriver_args)
@@ -831,19 +831,7 @@
         self.leak_check = leak_check
         self.leak_report_file = None
 
-        self.env = get_environ(self.logger,
-                               binary,
-                               debug_info,
-                               stylo_threads,
-                               headless,
-                               enable_webrender,
-                               chaos_mode_flags)
-        self.env["RUST_BACKTRACE"] = "1"
-        # This doesn't work with wdspec tests
-        # In particular tests can create a session without passing in the capabilites
-        # and in those cases we get the default geckodriver profile which doesn't
-        # guarantee zero network access
-        del self.env["MOZ_DISABLE_NONLOCAL_CONNECTIONS"]
+        self.env = self.get_env(binary, debug_info, stylo_threads, headless, chaos_mode_flags)
 
         profile_creator = ProfileCreator(logger,
                                          prefs_root,
@@ -860,6 +848,21 @@
         self.profile = profile_creator.create()
         self.marionette_port = None
 
+    def get_env(self, binary, debug_info, stylo_threads, headless, chaos_mode_flags):
+        env = get_environ(self.logger,
+                          binary,
+                          debug_info,
+                          stylo_threads,
+                          headless,
+                          chaos_mode_flags)
+        env["RUST_BACKTRACE"] = "1"
+        # This doesn't work with wdspec tests
+        # In particular tests can create a session without passing in the capabilites
+        # and in those cases we get the default geckodriver profile which doesn't
+        # guarantee zero network access
+        del env["MOZ_DISABLE_NONLOCAL_CONNECTIONS"]
+        return env
+
     def create_output_handler(self, cmd):
         return FirefoxOutputHandler(self.logger,
                                     cmd,
@@ -883,9 +886,11 @@
             while time.time() < end_time:
                 self.logger.debug("Waiting for WebDriver session to end")
                 try:
+                    self.logger.debug(f"Connecting to http://{self.host}:{self.port}/status")
                     conn = HTTPConnection(self.host, self.port)
                     conn.request("GET", "/status")
                     res = conn.getresponse()
+                    self.logger.debug(f"Got response from http://{self.host}:{self.port}/status")
                 except Exception:
                     self.logger.debug(
                         f"Connecting to http://{self.host}:{self.port}/status failed")
@@ -903,6 +908,7 @@
                 if msg.get("value", {}).get("ready") is True:
                     self.logger.debug("Got ready status")
                     break
+                self.logger.debug(f"Got status response {data}")
                 time.sleep(1)
             else:
                 self.logger.debug("WebDriver session didn't end")
diff --git a/tools/wptrunner/wptrunner/browsers/firefox_android.py b/tools/wptrunner/wptrunner/browsers/firefox_android.py
index 9b57db0..66821b8 100644
--- a/tools/wptrunner/wptrunner/browsers/firefox_android.py
+++ b/tools/wptrunner/wptrunner/browsers/firefox_android.py
@@ -1,7 +1,7 @@
 import os
 
 import moznetwork
-from mozrunner import FennecEmulatorRunner
+from mozrunner import FennecEmulatorRunner, get_app_context
 
 from .base import (get_free_port,
                    cmd_arg,
@@ -16,12 +16,14 @@
                       run_info_extras as fx_run_info_extras,
                       update_properties,  # noqa: F401
                       executor_kwargs as fx_executor_kwargs,  # noqa: F401
+                      FirefoxWdSpecBrowser,
                       ProfileCreator as FirefoxProfileCreator)
 
 
 __wptrunner__ = {"product": "firefox_android",
                  "check_args": "check_args",
-                 "browser": "FirefoxAndroidBrowser",
+                 "browser": {None: "FirefoxAndroidBrowser",
+                             "wdspec": "FirefoxAndroidWdSpecBrowser"},
                  "executor": {"testharness": "MarionetteTestharnessExecutor",
                               "reftest": "MarionetteRefTestExecutor",
                               "crashtest": "MarionetteCrashtestExecutor",
@@ -41,6 +43,8 @@
 
 def browser_kwargs(logger, test_type, run_info_data, config, **kwargs):
     return {"adb_binary": kwargs["adb_binary"],
+            "webdriver_binary": kwargs["webdriver_binary"],
+            "webdriver_args": kwargs["webdriver_args"],
             "package_name": kwargs["package_name"],
             "device_serial": kwargs["device_serial"],
             "prefs_root": kwargs["prefs_root"],
@@ -97,6 +101,17 @@
             "supports_debugger": True}
 
 
+def get_environ(stylo_threads, chaos_mode_flags):
+    env = {}
+    env["MOZ_CRASHREPORTER"] = "1"
+    env["MOZ_CRASHREPORTER_SHUTDOWN"] = "1"
+    env["MOZ_DISABLE_NONLOCAL_CONNECTIONS"] = "1"
+    env["STYLO_THREADS"] = str(stylo_threads)
+    if chaos_mode_flags is not None:
+        env["MOZ_CHAOSMODE"] = str(chaos_mode_flags)
+    return env
+
+
 class ProfileCreator(FirefoxProfileCreator):
     def __init__(self, logger, prefs_root, config, test_type, extra_prefs,
                  enable_fission, browser_channel, certutil_binary, ca_certificate_path):
@@ -216,17 +231,7 @@
                                           [cmd_arg("marionette"), "about:blank"],
                                           self.debug_info)
 
-        env = {}
-        env["MOZ_CRASHREPORTER"] = "1"
-        env["MOZ_CRASHREPORTER_SHUTDOWN"] = "1"
-        env["MOZ_DISABLE_NONLOCAL_CONNECTIONS"] = "1"
-        env["STYLO_THREADS"] = str(self.stylo_threads)
-        if self.chaos_mode_flags is not None:
-            env["MOZ_CHAOSMODE"] = str(self.chaos_mode_flags)
-        if self.enable_webrender:
-            env["MOZ_WEBRENDER"] = "1"
-        else:
-            env["MOZ_WEBRENDER"] = "0"
+        env = get_environ(self.stylo_threads, self.chaos_mode_flags)
 
         self.runner = FennecEmulatorRunner(app=self.package_name,
                                            profile=self.profile,
@@ -299,3 +304,58 @@
         if not os.environ.get("MINIDUMP_STACKWALK", "") and self.stackwalk_binary:
             os.environ["MINIDUMP_STACKWALK"] = self.stackwalk_binary
         return bool(self.runner.check_for_crashes(test_name=test))
+
+
+class FirefoxAndroidWdSpecBrowser(FirefoxWdSpecBrowser):
+    def __init__(self, logger, prefs_root, webdriver_binary, webdriver_args,
+                 extra_prefs=None, debug_info=None, symbols_path=None, stackwalk_binary=None,
+                 certutil_binary=None, ca_certificate_path=None, e10s=False,
+                 enable_fission=False, stackfix_dir=None, leak_check=False,
+                 asan=False, stylo_threads=1, chaos_mode_flags=None, config=None,
+                 browser_channel="nightly", headless=None,
+                 package_name="org.mozilla.geckoview.test_runner", device_serial=None,
+                 adb_binary=None, **kwargs):
+
+        super().__init__(logger, None, prefs_root, webdriver_binary, webdriver_args,
+                         extra_prefs=extra_prefs, debug_info=debug_info, symbols_path=symbols_path,
+                         stackwalk_binary=stackwalk_binary, certutil_binary=certutil_binary,
+                         ca_certificate_path=ca_certificate_path, e10s=e10s,
+                         enable_fission=enable_fission, stackfix_dir=stackfix_dir,
+                         leak_check=leak_check, asan=asan, stylo_threads=stylo_threads,
+                         chaos_mode_flags=chaos_mode_flags, config=config,
+                         browser_channel=browser_channel, headless=headless, **kwargs)
+
+        self.config = config
+        self.package_name = package_name
+        self.device_serial = device_serial
+        # This is just to support the same adb lookup as for other test types
+        context = get_app_context("fennec")(adb_path=adb_binary, device_serial=device_serial)
+        self.device = context.get_device(context.adb, self.device_serial)
+
+    def start(self, group_metadata, **kwargs):
+        for ports in self.config.ports.values():
+            for port in ports:
+                self.device.reverse(
+                    local="tcp:{}".format(port),
+                    remote="tcp:{}".format(port))
+        super().start(group_metadata, **kwargs)
+
+    def stop(self, force=False):
+        try:
+            self.device.remove_reverses()
+        except Exception as e:
+            self.logger.warning("Failed to remove forwarded or reversed ports: %s" % e)
+        super().stop(force=force)
+
+    def get_env(self, binary, debug_info, stylo_threads, headless, chaos_mode_flags):
+        env = get_environ(stylo_threads, chaos_mode_flags)
+        env["RUST_BACKTRACE"] = "1"
+        del env["MOZ_DISABLE_NONLOCAL_CONNECTIONS"]
+        return env
+
+    def executor_browser(self):
+        cls, args = super().executor_browser()
+        args["androidPackage"] = self.package_name
+        args["androidDeviceSerial"] = self.device_serial
+        args["env"] = self.env
+        return cls, args
diff --git a/tools/wptrunner/wptrunner/executors/executormarionette.py b/tools/wptrunner/wptrunner/executors/executormarionette.py
index 447474b..b1ee026 100644
--- a/tools/wptrunner/wptrunner/executors/executormarionette.py
+++ b/tools/wptrunner/wptrunner/executors/executormarionette.py
@@ -1273,6 +1273,6 @@
         args = self.capabilities["moz:firefoxOptions"].setdefault("args", [])
         args.extend(["--profile", self.browser.profile])
 
-        for option in ["env"]:
+        for option in ["androidPackage", "androidDeviceSerial", "env"]:
             if hasattr(browser, option):
                 self.capabilities["moz:firefoxOptions"][option] = getattr(browser, option)