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=trueMarking 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.