Setting Up Testing on Real Devices via BrowserStack
BrowserStack App Automate is cloud farm of real devices with Appium, Espresso, and XCUITest support. Differs from Firebase Test Lab in that devices available in interactive mode (App Live), and automated tests run via standard WebDriver protocol — your existing Appium code connects to BrowserStack with minimal capability changes.
Connecting Existing Appium Tests
If Appium tests already written, moving to BrowserStack is changing capabilities and server URL:
const capabilities = {
platformName: 'Android',
'appium:deviceName': 'Samsung Galaxy S24',
'appium:platformVersion': '14.0',
'bstack:options': {
userName: process.env.BROWSERSTACK_USERNAME,
accessKey: process.env.BROWSERSTACK_ACCESS_KEY,
appiumVersion: '2.6.0',
projectName: 'MyApp E2E Tests',
buildName: `Build ${process.env.BUILD_NUMBER}`,
sessionName: 'Login Flow',
},
'appium:app': 'bs://app_hash_from_upload', // hash of uploaded APK
};
const driver = await remote({
protocol: 'https',
hostname: 'hub.browserstack.com',
path: '/wd/hub',
port: 443,
capabilities,
});
APK upload before tests:
curl -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" \
-X POST "https://api-cloud.browserstack.com/app-automate/upload" \
-F "[email protected]"
# Returns: {"app_url":"bs://abc123..."}
Hash bs://abc123 substituted in capabilities as 'appium:app'. APK stored 30 days, re-upload not needed if build unchanged.
Native Integration: Espresso and XCUITest
BrowserStack supports native execution without Appium server — directly via Espresso (Android) and XCUITest (iOS). Faster and more stable.
Espresso via BrowserStack CLI:
browserstack-sdk ./gradlew connectedAndroidTest
Or via REST API:
curl -u "$USER:$KEY" \
-X POST "https://api-cloud.browserstack.com/app-automate/espresso/v2/build" \
-d '{
"app": "bs://app_hash",
"testSuite": "bs://test_suite_hash",
"devices": ["Samsung Galaxy S24-14.0", "Google Pixel 8-14.0"],
"class": ["com.example.LoginTest"],
"networkLogs": true,
"deviceLogs": true
}'
testSuite is uploaded APK with androidTest code. Results available in BrowserStack console and via API.
Parallel Testing
BrowserStack charges for parallel sessions. Standard plan — 5 parallel devices, Team — 25. WebdriverIO setup for parallel execution:
// wdio.conf.ts
export const config = {
maxInstances: 5,
capabilities: [
{ 'appium:deviceName': 'Samsung Galaxy S24', 'appium:platformVersion': '14.0' },
{ 'appium:deviceName': 'Google Pixel 8', 'appium:platformVersion': '14.0' },
{ 'appium:deviceName': 'iPhone 15 Pro', platformName: 'iOS', 'appium:platformVersion': '17' },
],
};
Each capability object — separate thread. Test files distributed automatically.
Local Testing
If app calls local backend (staging on 192.168.x.x or localhost), BrowserStack can't reach it directly. Solution — BrowserStack Local:
./BrowserStackLocal --key $BROWSERSTACK_ACCESS_KEY --local-identifier my-tunnel
In capabilities add:
'bstack:options': {
local: true,
localIdentifier: 'my-tunnel',
}
Traffic from device on BrowserStack routed through encrypted tunnel to your machine. Works in CI — run BrowserStackLocal as background process before tests.
CI Integration
- name: Upload app to BrowserStack
id: upload
run: |
RESPONSE=$(curl -s -u "${{ secrets.BS_USER }}:${{ secrets.BS_KEY }}" \
-X POST "https://api-cloud.browserstack.com/app-automate/upload" \
-F "[email protected]")
echo "app_url=$(echo $RESPONSE | jq -r '.app_url')" >> $GITHUB_OUTPUT
- name: Run tests
run: npx wdio run wdio.conf.ts
env:
BROWSERSTACK_APP_ID: ${{ steps.upload.outputs.app_url }}
BROWSERSTACK_USERNAME: ${{ secrets.BS_USER }}
BROWSERSTACK_ACCESS_KEY: ${{ secrets.BS_KEY }}
What We Configure
- Build upload and auto
app_urlupdate in CI - Capabilities for target device matrix (iOS + Android)
- Parallel execution within plan limits
- BrowserStack Local for testing against staging backend
- Allure or HTML reporter integration for reporting
Timeline
2–3 days — connection setup, device matrix configuration, CI integration, first run and platform-specific fixes. Cost is calculated individually.







