How to run tests on mobile Firefox browser in java

We are trying to automate tests on mobile Firefox browser but facing couple of issues.

Environment> Mac OS , Android, Appium, Selenium WebDriver, mobile emulators

What I am expecting: I want correct capabilities or setup to run tests on mobile Firefox browser in java

:heavy_check_mark: Listing installed drivers

appium --version
2.4.1
npm -v
10.2.4
node --version
v21.5.0
Java client - 9.0.0

Main issue: I am not sure what capabilities requires for Firefox browsers, went through gecko and other information but not found any correct solution for this.

Code:

UiAutomator2Options androidOptions = new UiAutomator2Options()
                     .withBrowserName("firefox")
                    .setAutomationName(AutomationName.GECKO)
                    .setPlatformName("android");

                    Map<String, Object> geckoOptions = new HashMap<>();
                    geckoOptions.put("androidPackage", "org.mozilla.firefox");
                    geckoOptions.put("androidDeviceSerial", deviceName);
                    geckoOptions.put("moz:geckoDriverExecutable","/src/test/resources/drivers/geckodriver.exe");

                    androidOptions.setCapability("moz:firefoxOptions", geckoOptions);

                    FirefoxOptions firefoxOptions = new FirefoxOptions();
                    androidOptions.setCapability("moz:firefoxOptions", firefoxOptions);
                    
                    driver = new AndroidDriver(url, androidOptions);

Apologies for necro-ing this thread (if that’s frowned upon), but I work for a training company, was tasked with updating our Appium course and got a bit of a bee in my bonnet about getting this working. After way too much time spent I have figured this out. Although with Firefox on Android’s market share being what it is I’m not sure this will make it in to the final cut.

So here’s the solution:
Java Client: 9.4 + Selenium Client 4.28 (as per the table at the compatability matrix )
Appium server version: 2.17.1
Install Appium Gecko Driver:

appium driver install gecko

appium driver list
[email protected] [installed (npm)]

UIAutomator2 is not relevant. As far as I can tell it can only utilise chromedriver

Determine which version of Firefox you have installed on your device/emulator. Then consult the support table here to determine which version of geckodriver is appropriate.

Download gekodriver for your host system (e.g. I’m on WIndows so I grab the win64 build) from the Mozilla Geckodriver GitHub repo. Extract geckodriver.exe to some folder
OR
Use the newfangled selenium-manager binary that comes with the latest selenium releases to automaticlly download an appropriate geckodriver for your target Firefox Android version for you

PS C:\Users\<username>\.cache\selenium\manager\0.4.28>.\selenium-manager.exe --driver geckodriver --browser-version 137 --cache-path e:\DriverServers

Either way - add the folder that ultimately contains geckodriver to your path environment variable. Open a new terminal (so the path gets picked up and then Appium Gecko can find geckodriver.exe) and start appium server. Sadly there doesn’t seem to be a way to tell Appium Server at run time where geckodriver lives, so on the path it has to go.

Now for the Java code

        var options = new GeckoOptions();
        options.setPlatformName("windows"); //Set to host system
        options.setVerbosity(Verbosity.TRACE); //Useful for debugging connection issies

        FirefoxOptions options2 = new FirefoxOptions(); //Android options triggers android connection instead of host
        options2.setAndroidPackage("org.mozilla.firefox");
        options2.setAndroidDeviceSerialNumber("emulator-5554");
        //options2.setAndroidDeviceSerialNumber("<or use real device serial>");

        options.amend("moz:firefoxOptions", options2); //Add the Firefox Android options to the GeckoOptions

        driver = new AppiumDriver(new URL("http://127.0.0.1:4723/"), options);

The JSON sent over the wire that this produces should look something like this

{
    "capabilities": {
        "firstMatch": [
            {}
        ],
        "alwaysMatch": {
            "browserName": "firefox",
            "moz:debuggerAddress": true,
            "moz:firefoxOptions": {
                "androidPackage": "org.mozilla.firefox",
                "androidDeviceSerial": "emulator-5554"
            },            
            "prefs": {
                "remote.active-protocols": 3
            }
        },
        "platformName": "windows"
    }
}

I’m fairly certain prior to GeckoDriver 0.30 you also needed to also specify an “androidActivity” inside moz:firefoxOptions, but this is now worked out on the fly. Again just to reiterate this, the platformName is set to “windows” - the host system - while having android options inside “moz:firefoxOptions” tells geckodriver to run against the android device.

The JSON is at least partially visible in the appium servers console output, but gets a bit abbreviated:

[a312e5ee][GeckoDriver@a6ca] [geckodriver.exe] 1745418168611    webdriver::server       DEBUG   <- 200 OK {"value":{"message":"","ready":true}}
1745418168626   webdriver::server       DEBUG   -> POST /session {"capabilities":{"firstMatch":[{}],"alwaysMatch":{"browserName":"firefox","moz:debuggerAddress":true,"accept ... <DEVICESERIALNUMBERCENSORED>","androidPackage":"org.mozilla.firefox","prefs":{"remote.active-protocols":3}},"platformName":"windows"}}}

If you’re running against an emlator you might find this launches Firefox but the connection fails before your test code runs. This appears to be because the standard AVDs are often memory starved and Firefox Android hates that. For example I set up a Pixel 7a AVD as I have that physical device and I thought it would be a good idea to experiment with something familiar. After a lot of frustration I discovered that the real physical Pixel 7a has 8GB ram - the corresponding AVD is locked to 2GB. Using a custom AVD with the Google APIs and 4GB works for me.

And that’s how you get Appium to talk to Firefox mobile to automate web content.

ToDo: Get Firefox Desktop’s developer tools talking to Firefox on Android.


Now for a couple of extra notes.

To interact with Firefox’s browser chrome (the UI) from GeckoDriver 0.36 and potentially Firefox 138 onwards GeckoDriver will need to be started with the comandline argument --allow-system-access. I’ve not yet begun to look in to what this entails or looks like exactly, but it will also involve enabling the Marionette protcol.

Connecting to geckodriver directly from test code (bypassing Appium) is also possible with a slightly annoying caveat.

        FirefoxOptions launchOptions = new FirefoxOptions();
        launchOptions.setPlatformName("windows");
        //launchOptions.addArguments("--allow-system-access"); //GeckoDriver 0.36 fx138 needs this for browser chrome access
        launchOptions.setAndroidDeviceSerialNumber("emulator-5554");
        launchOptions.setAndroidPackage("org.mozilla.firefox");
        //launchOptions.addArguments("--android-storage=app"); //some samsung devices may need this?

        System.out.println(launchOptions.toJson().toString());
        driver = new RemoteWebDriver(new URL("http://127.0.0.1:4444/"), launchOptions); //Manually run geckodriver first

        //This doesnt work because for some reason it adds the windows binary path (I think) and that cant be used with androidPackage
        //driver = new FirefoxDriver(launchOptions);

Instantiating the driver using driver = new FirefoxDriver(launchOptions); normally uses the new selenium-maager to automatically discover and/or the Firefox binary and an appropriate gecko driver greatly simplifying setup. For desktop browsers at least there’s no longer a need to manually manage geckodriver versions and put them on your path.

Sadly for Firefox Android, passsing FirefoxOptions to FirefoxDriver is broken because it seems somewhere along the line the path to the WIndows Firefox binary gets added in to moz:firefoxOptions i.e. the JSON passed to GeckoDriver ends up looking something like this…

{
    "capabilities": {
        "firstMatch": [
            {}
        ],
        "alwaysMatch": {
            "browserName": "firefox",
            "moz:debuggerAddress": true,
            "moz:firefoxOptions": {
                "androidPackage": "org.mozilla.firefox",
                "androidDeviceSerial": "emulator-5554",
                "binary": "/path/to/firefox.exe"
            },            
            "prefs": {
                "remote.active-protocols": 3
            }
        },
        "platformName": "windows"
    }
}

..and having both “androidPackage”: and “binary” isn’t allowed so geckodriver dies. So far I haven’t figured out a way to stop that binary property being added.

I assume ‘binary’ is added by FirefoxOptions class (which was originally created to only support desktop browsers). Try to provide options as a simple map instead (check setMozFirefoxOptions setter)