BrowserStack App Automate Integration from Java

7 min read

BrowserStack provides thousands of real Android and iOS devices in the cloud. Connecting your Appium Java suite requires changing the server URL and capabilities — no test logic changes.

Authentication

BrowserStack authenticates via username and access key in the server URL:

String userName = System.getenv("BROWSERSTACK_USERNAME");
String accessKey = System.getenv("BROWSERSTACK_ACCESS_KEY");
String serverUrl = String.format(
    "https://%s:%s@hub-cloud.browserstack.com/wd/hub",
    userName, accessKey
);
URL url = new URL(serverUrl);

Never hardcode credentials in test code. Use environment variables, which CI systems set as secrets.

BrowserStack capabilities

BrowserStack requires some capabilities that local runs don't:

UiAutomator2Options options = new UiAutomator2Options()
    .setDeviceName("Samsung Galaxy S23")
    .setPlatformVersion("13.0")
    // App uploaded to BrowserStack — use bs:// URL
    .setApp("bs://your-app-hash-here");
 
// BrowserStack-specific options
HashMap<String, Object> bsOptions = new HashMap<>();
bsOptions.put("projectName", "Mobile Regression");
bsOptions.put("buildName", "Build " + System.getenv("BUILD_NUMBER"));
bsOptions.put("sessionName", "LoginTest");
bsOptions.put("networkLogs", true);
bsOptions.put("deviceLogs", true);
bsOptions.put("video", true);
 
options.setCapability("bstack:options", bsOptions);
 
AndroidDriver driver = new AndroidDriver(url, options);

Uploading your app to BrowserStack

Before tests can run, upload the APK or IPA:

curl -u "username:accesskey" \
  -X POST "https://api-cloud.browserstack.com/app-automate/upload" \
  -F "file=@/path/to/app-debug.apk"

Response:

{ "app_url": "bs://abc123def456..." }

Use the app_url value as setApp("bs://abc123def456...") in your capabilities.

In CI, upload the app in a pre-test step and pass the URL as an environment variable:

APP_URL=$(curl -u "$BS_USER:$BS_KEY" \
  -X POST https://api-cloud.browserstack.com/app-automate/upload \
  -F "file=@target/app-debug.apk" | jq -r '.app_url')
export BROWSERSTACK_APP_URL="$APP_URL"

DriverManager adapted for BrowserStack

public static void initDriver(String platform) {
    boolean useBrowserStack = Boolean.parseBoolean(
        System.getProperty("browserstack", "false")
    );
 
    if (useBrowserStack) {
        initBrowserStackDriver(platform);
    } else {
        initLocalDriver(platform);
    }
}
 
private static void initBrowserStackDriver(String platform) {
    try {
        URL url = new URL(getBrowserStackUrl());
        AppiumDriver driver;
 
        if ("Android".equalsIgnoreCase(platform)) {
            UiAutomator2Options options = buildBrowserStackAndroidOptions();
            driver = new AndroidDriver(url, options);
        } else {
            XCUITestOptions options = buildBrowserStackIosOptions();
            driver = new IOSDriver(url, options);
        }
        driverThreadLocal.set(driver);
    } catch (MalformedURLException e) {
        throw new RuntimeException(e);
    }
}
 
private static UiAutomator2Options buildBrowserStackAndroidOptions() {
    UiAutomator2Options options = new UiAutomator2Options()
        .setDeviceName(System.getProperty("bs.device", "Samsung Galaxy S23"))
        .setPlatformVersion(System.getProperty("bs.version", "13.0"))
        .setApp(System.getenv("BROWSERSTACK_APP_URL"));
 
    HashMap<String, Object> bsOptions = new HashMap<>();
    bsOptions.put("projectName", "Mobile Suite");
    bsOptions.put("buildName", getBuildName());
    bsOptions.put("networkLogs", true);
    bsOptions.put("deviceLogs", true);
    bsOptions.put("video", true);
    options.setCapability("bstack:options", bsOptions);
 
    return options;
}

Parallel device matrix on BrowserStack

testng.xml for BrowserStack parallel runs:

<suite name="BrowserStack Parallel" parallel="tests" thread-count="4">
 
  <test name="Samsung Galaxy S23 - Android 13">
    <parameter name="platform" value="Android"/>
    <parameter name="bs.device" value="Samsung Galaxy S23"/>
    <parameter name="bs.version" value="13.0"/>
    <classes><class name="com.example.tests.LoginTest"/></classes>
  </test>
 
  <test name="Google Pixel 7 - Android 13">
    <parameter name="platform" value="Android"/>
    <parameter name="bs.device" value="Google Pixel 7"/>
    <parameter name="bs.version" value="13.0"/>
    <classes><class name="com.example.tests.LoginTest"/></classes>
  </test>
 
  <test name="iPhone 15 - iOS 17">
    <parameter name="platform" value="iOS"/>
    <parameter name="bs.device" value="iPhone 15"/>
    <parameter name="bs.version" value="17"/>
    <classes><class name="com.example.tests.LoginTest"/></classes>
  </test>
 
  <test name="iPad Pro 12.9 - iOS 16">
    <parameter name="platform" value="iOS"/>
    <parameter name="bs.device" value="iPad Pro 12.9 2022"/>
    <parameter name="bs.version" value="16"/>
    <classes><class name="com.example.tests.LoginTest"/></classes>
  </test>
 
</suite>

Run with:

mvn test -DsuiteFile=testng-browserstack.xml -Dbrowserstack=true

Marking test status on BrowserStack

BrowserStack shows sessions as "Failed" or "Passed" based on whether the driver received an exception. To explicitly mark a test passed or failed with a reason:

public class BrowserStackListener implements ITestListener {
 
    @Override
    public void onTestSuccess(ITestResult result) {
        markSession("passed", "Test passed");
    }
 
    @Override
    public void onTestFailure(ITestResult result) {
        String reason = result.getThrowable().getMessage();
        markSession("failed", reason);
    }
 
    private void markSession(String status, String reason) {
        AppiumDriver driver = DriverManager.getDriver();
        if (driver == null) return;
 
        JavascriptExecutor js = (JavascriptExecutor) driver;
        js.executeScript("browserstack_executor: {\"action\": \"setSessionStatus\", \"arguments\": {\"status\": \""
            + status + "\", \"reason\": \"" + reason.replace("\"", "'") + "\"}}");
    }
}

This updates the BrowserStack dashboard in real time as tests complete.

// tip to track lessons you complete and pick up where you left off across devices.