BrowserStack App Automate Tutorial: Run Mobile Tests in the Cloud
BrowserStack App Automate lets you run Appium and other mobile test frameworks against real iOS and Android devices in the cloud. No managing physical devices, no iOS provisioning pain, no Android fragmentation across 15 different devices in a drawer.
This tutorial covers the full setup: uploading your app, configuring Appium capabilities, running tests, and analyzing results.
What BrowserStack App Automate Is
App Automate is BrowserStack's mobile test automation platform. You write tests using standard frameworks (Appium, XCUITest, Espresso, Detox), upload your app binary, and run against real devices in BrowserStack's device lab.
Key capabilities:
- Real devices: Physical iOS and Android hardware, not simulators
- Framework support: Appium, XCUITest, Espresso, Flutter, Detox, React Native
- Scale: Parallel test execution across multiple devices simultaneously
- Debugging: Video recordings, device logs, screenshots, network traffic logs
- CI integration: Direct integrations with Jenkins, CircleCI, GitHub Actions, and others
Prerequisites
Before starting, you need:
- A BrowserStack account (free trial available)
- Your
BROWSERSTACK_USERNAMEandBROWSERSTACK_ACCESS_KEYfrom the App Automate dashboard - A compiled app binary (
.apkfor Android,.ipafor iOS) - Your test suite written in Appium or another supported framework
Step 1: Upload Your App
Upload your app binary to BrowserStack to get an app_url for use in test capabilities.
Using the REST API:
curl -u "YOUR_USERNAME:YOUR_ACCESS_KEY" \
-X POST <span class="hljs-string">"https://api-cloud.browserstack.com/app-automate/upload" \
-F <span class="hljs-string">"file=@/path/to/your/app.apk"Response:
{
"app_url": "bs://a1b2c3d4e5f6...",
"custom_id": null,
"shareable_id": "USERNAME/app.apk"
}Save the app_url — you'll use it in your capabilities. App URLs are valid for 30 days.
Upload with a custom ID (for consistent reference):
curl -u "YOUR_USERNAME:YOUR_ACCESS_KEY" \
-X POST <span class="hljs-string">"https://api-cloud.browserstack.com/app-automate/upload" \
-F <span class="hljs-string">"file=@app.apk" \
-F <span class="hljs-string">"custom_id=MyAndroidApp"Using a custom ID lets you reference the latest upload by name instead of tracking the app_url in every test run.
Step 2: Configure Appium Capabilities
BrowserStack extends standard Appium capabilities with bstack:options for BrowserStack-specific configuration.
Android example:
// Node.js / WebdriverIO
const capabilities = {
platformName: 'Android',
'appium:app': 'bs://a1b2c3d4e5f6...',
'appium:deviceName': 'Samsung Galaxy S24',
'appium:platformVersion': '14.0',
'appium:automationName': 'UiAutomator2',
'bstack:options': {
userName: process.env.BROWSERSTACK_USERNAME,
accessKey: process.env.BROWSERSTACK_ACCESS_KEY,
projectName: 'My Mobile App',
buildName: `Build ${process.env.BUILD_NUMBER || 'local'}`,
sessionName: 'Login Flow Test',
debug: true, // Enable screenshots
networkLogs: true, // Capture network traffic
appiumVersion: '2.0.1'
}
};iOS example:
const capabilities = {
platformName: 'iOS',
'appium:app': 'bs://your-ios-app-url...',
'appium:deviceName': 'iPhone 15 Pro',
'appium:platformVersion': '17',
'appium:automationName': 'XCUITest',
'bstack:options': {
userName: process.env.BROWSERSTACK_USERNAME,
accessKey: process.env.BROWSERSTACK_ACCESS_KEY,
projectName: 'My Mobile App',
buildName: `Build ${process.env.BUILD_NUMBER || 'local'}`,
sessionName: 'iOS Login Test',
debug: true,
networkLogs: true
}
};Step 3: Connect to BrowserStack
The BrowserStack remote WebDriver URL replaces your local Appium server URL:
const { remote } = require('webdriverio');
const driver = await remote({
protocol: 'https',
hostname: 'hub.browserstack.com',
path: '/wd/hub',
port: 443,
capabilities: capabilities
});Step 4: Write a Test
Once connected, Appium commands work identically to a local session. The device is remote but the API is the same.
const { remote } = require('webdriverio');
describe('Login flow', () => {
let driver;
before(async () => {
driver = await remote({
protocol: 'https',
hostname: 'hub.browserstack.com',
path: '/wd/hub',
port: 443,
capabilities: capabilities
});
});
after(async () => {
if (driver) await driver.deleteSession();
});
it('should login with valid credentials', async () => {
// Wait for app to load
const usernameField = await driver.$('~username-input');
await usernameField.setValue('testuser@example.com');
const passwordField = await driver.$('~password-input');
await passwordField.setValue('SecurePassword123');
const loginButton = await driver.$('~login-button');
await loginButton.click();
// Verify login success
const homeScreen = await driver.$('~home-screen');
await homeScreen.waitForDisplayed({ timeout: 10000 });
expect(await homeScreen.isDisplayed()).toBe(true);
});
});Step 5: Run Tests in Parallel
Parallel execution is one of App Automate's key advantages. Run the same test against multiple devices simultaneously.
Using WebdriverIO's parallel capability:
// wdio.conf.js
exports.config = {
user: process.env.BROWSERSTACK_USERNAME,
key: process.env.BROWSERSTACK_ACCESS_KEY,
hostname: 'hub.browserstack.com',
port: 443,
path: '/wd/hub',
maxInstances: 5, // 5 parallel sessions
capabilities: [
{
platformName: 'Android',
'appium:deviceName': 'Samsung Galaxy S24',
'appium:platformVersion': '14.0',
'appium:automationName': 'UiAutomator2',
'appium:app': process.env.APP_URL,
'bstack:options': {
projectName: 'Mobile App',
buildName: process.env.BUILD_NAME
}
},
{
platformName: 'Android',
'appium:deviceName': 'Google Pixel 8',
'appium:platformVersion': '14.0',
'appium:automationName': 'UiAutomator2',
'appium:app': process.env.APP_URL,
'bstack:options': {
projectName: 'Mobile App',
buildName: process.env.BUILD_NAME
}
},
{
platformName: 'iOS',
'appium:deviceName': 'iPhone 15 Pro',
'appium:platformVersion': '17',
'appium:automationName': 'XCUITest',
'appium:app': process.env.APP_URL,
'bstack:options': {
projectName: 'Mobile App',
buildName: process.env.BUILD_NAME
}
}
],
specs: ['./tests/**/*.test.js']
};Step 6: GitHub Actions Integration
# .github/workflows/mobile-tests.yml
name: Mobile Tests
on:
push:
branches: [main, staging]
jobs:
mobile-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Upload app to BrowserStack
id: upload
run: |
response=$(curl -s -u "${{ secrets.BROWSERSTACK_USERNAME }}:${{ secrets.BROWSERSTACK_ACCESS_KEY }}" \
-X POST "https://api-cloud.browserstack.com/app-automate/upload" \
-F "file=@app/build/outputs/apk/debug/app-debug.apk" \
-F "custom_id=MyApp-${{ github.sha }}")
echo "app_url=$(echo $response | jq -r '.app_url')" >> $GITHUB_OUTPUT
- name: Run mobile tests
env:
BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }}
BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }}
APP_URL: ${{ steps.upload.outputs.app_url }}
BUILD_NAME: "GitHub Actions - ${{ github.run_number }}"
run: npm run test:mobileDebugging Failed Tests
BrowserStack App Automate captures extensive debug information for every session.
Video recordings: Every session has a full video recording. In the dashboard, scrub to the failure point.
Device logs: Logcat (Android) and device console logs (iOS) are captured and searchable.
Appium logs: Complete Appium server logs for diagnosing command failures.
Network logs: If networkLogs: true is set, all HTTP requests made during the test are captured. Useful for API timing and request/response debugging.
Screenshots: If debug: true is set, screenshots are captured at each Appium command. The dashboard shows a filmstrip view.
Best Practices
Use accessibility IDs for selectors. XPath selectors are fragile and slow. Accessibility IDs (~element-id in WebdriverIO) are stable across OS versions and work on both iOS and Android with the same selector string.
Set explicit timeouts. Cloud devices can be slightly slower than local simulators. Set waitForExist and waitForDisplayed timeouts to 10-15 seconds.
Manage app uploads. Don't re-upload the app for every test run — use custom IDs and upload only when the build changes.
Use build naming. Set meaningful buildName values (include build number, git SHA, or branch name) so the dashboard shows useful information.
Test on actual target devices. Check your analytics for the most common device models and OS versions your users have, and prioritize those for your test matrix.
Handle session initialization time. App Automate sessions take 10-30 seconds to initialize (device allocation, app install). Don't write tests that fail on the first Appium command because the session isn't ready.
Cost Optimization
BrowserStack charges per parallel session. Optimize:
- Run tests in parallel to reduce total wall-clock time (you pay per session-minute, not per total test time)
- Skip devices that aren't in your target user base
- Use the
uploadMediacapability to cache app uploads instead of re-uploading every run
Conclusion
BrowserStack App Automate removes the painful parts of mobile testing: device procurement, provisioning, fragmentation management, and maintenance. You write standard Appium code, point it at BrowserStack, and run on real hardware.
The key workflow: upload app → configure capabilities → run parallel → review video/log for failures. Once set up in CI, mobile testing on real devices becomes as routine as running a desktop browser test suite.
For continuous monitoring of your mobile application — including production health checks that complement your App Automate test suite — HelpMeTest provides 24/7 monitoring with CI/CD integration.