Automate Android and iOS App Tests in the Cloud

Run Your Appium, Espresso, XCUITest, and Maestro Suites as They Are, in Parallel on 50,000+ Real Devices.

AppiumMobile App TestingAutomation

How to Migrate Appium Tests From BrowserStack App Automate to TestMu AI

Move Appium tests from BrowserStack App Automate to TestMu AI real devices: app upload, capability mapping, SDK removal, CI secrets and common errors.

Published on:

Most of an Appium migration is configuration rather than test code. Your locators, your page objects and your assertions are all completely agnostic as to which device cloud they're running on. The only things that change are five things around the test itself: the endpoint, the credentials, the app reference, the vendor options block, and the runner/CI setup.

This guide maps each one from BrowserStack App Automate to TestMu AI real devices, with copy-and-paste code for both Android and iOS. If you're still in the stage of comparing vendors on price, devices, and features, then start with our BrowserStack alternative comparison. This guide assumes you've already made your decision and it's time to do the Appium move.

BrowserStack details checked against BrowserStack's public docs as of November 2026

TL;DR

A BrowserStack App Automate migration to TestMu AI keeps your Appium test steps, locators, page objects and assertions as they are. Only the setup around the tests changes: the hub endpoint, the credentials, the app reference, the vendor options block, and the runner and CI configuration.

To migrate Appium tests from BrowserStack App Automate to TestMu AI:

  • Re-upload your app.
  • Point the driver at https://mobile-hub.lambdatest.com/wd/hub
  • Rename bstack:options to LT:Options and add isRealMobile: true.
  • Just remove the BrowserStack SDK and the browserstack.yml.
  • Swap your CI secrets.

Key Facts for a BrowserStack App Automate Migration

  • BrowserStack to LambdaTest migration: LambdaTest is now TestMu AI, so a BrowserStack to LambdaTest migration follows these same steps. The Appium endpoint and the app upload API still use lambdatest.com hosts: mobile-hub.lambdatest.com and manual-api.lambdatest.com.
  • BrowserStack SDK and browserstack.yml: Remove both before the first TestMu AI run. BrowserStack documents that SDK config capabilities override the webdriver URL and capabilities, so a leftover SDK can keep sending sessions to BrowserStack.
  • bs:// app URL: A BrowserStack bs:// app URL does not carry over to TestMu AI. Upload the same binary to the TestMu AI upload API and pass the returned lt:// value, which the response carries in an app_url field, as the app capability.
  • Legacy device and os_version capabilities: Map device to deviceName and os_version to platformVersion inside LT:Options, then drop every browserstack.* key, including browserstack.appium_version.
  • Real devices: TestMu AI App Automation runs the migrated Appium suite on 50,000+ real iOS and Android devices once isRealMobile is set as a boolean in LT:Options.

What Changes When You Take Your Appium Tests off of BrowserStack App Automate?

Five things change, and none of them touch your test steps. Use this 5-Swap checklist to track the migration:

  • Endpoint swap. Change hub.browserstack.com to mobile-hub.lambdatest.com.
  • Credential swap. Update the BROWSERSTACK_* variables to LT_USERNAME and LT_ACCESS_KEY.
  • App swap. Re-upload the binary and then change bs:// to the lt:// app URL that the upload returns.
  • Options swap. Change bstack:options to LT:Options and rename the build, project and session keys.
  • Runner swap. Remove the BrowserStack SDK and browserstack.yml and then set your parallelism and secrets in your own runner and CI.
Five swaps to migrate Appium tests from BrowserStack App Automate to TestMu AI: endpoint, credentials, app reference, options block, runner.

Here is the full mapping. For the short version, see the Appium migration reference in the docs:

What changesBrowserStack App AutomateTestMu AI
Appium hub URLhttps://hub.browserstack.com/wd/hubhttps://mobile-hub.lambdatest.com/wd/hub. Don't use hub.lambdatest.com, which serves Selenium browser sessions.
Credential env varsBROWSERSTACK_USERNAME, BROWSERSTACK_ACCESS_KEYLT_USERNAME, LT_ACCESS_KEY
Where credentials gouserName and accessKey inside bstack:optionsuser and accessKey inside LT:Options
App upload APIPOST https://api-cloud.browserstack.com/app-automate/uploadPOST https://manual-api.lambdatest.com/app/upload/realDevice
App referencebs://<app-id>lt://APP<id>, returned at upload. The App Testing APIs doc also lets you use a custom_id (see below).
Vendor options blockbstack:optionsLT:Options
Device (W3C caps)deviceName, platformVersiondeviceName, platformVersion (copy exact strings from the TestMu AI device list)
Device (legacy JSON Wire caps)device, os_versiondeviceName, platformVersion
Legacy vendor keysbrowserstack.user, browserstack.key, browserstack.appium_versionDrop them. Credentials go in LT:Options. TestMu AI's Appium examples set no Appium-version capability.
Real device vs virtualn/a (App Automate runs on real devices)isRealMobile: true for real devices; false for emulators/simulators
Grouping namesprojectName, buildName, sessionNameproject, build, name
Local or staging backendsBrowserStack Local (managed by the SDK)tunnel: true, plus tunnelName when you run several tunnels
Config file and SDKbrowserstack.yml plus the BrowserStack SDKNone required. Use the plain Appium client.
Idle timeoutCheck your BrowserStack plan settingsidleTimeout, default 120 seconds

Both vendors have their options tucked away in a key with a colon, as per the W3C WebDriver spec, which requires it. Extension capability keys "must contain a ':' (colon) character" to denote a vendor namespace. That's why this is a rename rather than a rewrite.

On legacy capabilities? Map device to deviceName and os_version to platformVersion and drop every browserstack.* key.

How Do You Upload Your App and Then Reference It in the Tests?

Upload the same app binary that you're uploading to BrowserStack right now and use the lt:// app URL from the upload response as your app capability. That's exactly what TestMu AI's Appium troubleshooting guide walks you through.

On BrowserStack, the upload goes to api-cloud.browserstack.com/app-automate/upload and returns {"app_url":"bs://<app-id>"} (BrowserStack docs). On TestMu AI (App Testing APIs):

curl -u "$LT_USERNAME:$LT_ACCESS_KEY" -X POST \
  "https://manual-api.lambdatest.com/app/upload/realDevice" \
  -F "appFile=@app/build/outputs/apk/release/app-release.apk" \
  -F "name=checkout-app" \
  -F "visibility=team"

The response contains the app URL in the format lt://APP123456789123456789. Pass that as the 'app'. The docs don't actually name the JSON field that contains it, so just copy the app URL that's in the response rather than trying to parse some field name that you've guessed.

Alternative: custom_id.

  • You can add -F "custom_id=checkout-app" to the upload. The App Testing APIs doc states that you can use the custom_id to run your automation on the same app without having to remember its app URL.
  • LambdaTest sample projects on GitHub will pass that ID as the app value.
  • The docs don't show that capability line, and they don't say what happens if you upload again with an existing custom_id. This guide therefore uses the lt:// URL everywhere.

Three practical notes:

  • Re-upload after any binary change and take the lt:// value from that upload. The troubleshooting guide lists a stale or missing app value as one of the most common causes of 'app not found' errors.
  • Use visibility=team so the whole team can run against the same upload. The default is individual.
  • Check the size limit. The App Management docs state the upload size limit is 1GB. The App Testing APIs docs reference .ipa files and link to a sample iOS app (proverbial_ios.ipa). The real device upload examples in that document are for .apk files.

Prefer to watch first? This TestMu AI walkthrough covers getting the credentials, generating the capabilities, uploading your APK or IPA, and then reviewing the results on the App Automation dashboard: How To Perform App Automation On Real Devices.

Youtube thumbnail

How Do You Rewrite bstack:options as LT:Options?

Keep the driver code and just change the capabilities. Keep the credentials in the environment variables within LT:Options (not in the URL) so they don't end up in the CI logs. The keys are the same in Java, JavaScript, and C#.

Before: BrowserStack App Automate (Android)

import os
from appium import webdriver
from appium.options.android import UiAutomator2Options

options = UiAutomator2Options().load_capabilities({
    "platformName": "android",
    "deviceName": "Google Pixel 3",
    "platformVersion": "9.0",
    "app": os.environ["BROWSERSTACK_APP_URL"],          # bs://<app-id>
    "bstack:options": {
        "userName": os.environ["BROWSERSTACK_USERNAME"],
        "accessKey": os.environ["BROWSERSTACK_ACCESS_KEY"],
        "projectName": "Checkout app",
        "buildName": "nightly-regression",
        "sessionName": "login smoke",
    },
})

driver = webdriver.Remote("https://hub.browserstack.com/wd/hub", options=options)

After: TestMu AI real devices (Android)

import os
from appium import webdriver
from appium.options.android import UiAutomator2Options

options = UiAutomator2Options().load_capabilities({
    "platformName": "Android",
    "LT:Options": {
        "w3c": True,
        "user": os.environ["LT_USERNAME"],
        "accessKey": os.environ["LT_ACCESS_KEY"],
        "platformName": "Android",
        "deviceName": "Galaxy S20",        # copy exact strings from the TestMu AI device list
        "platformVersion": "10",
        "isRealMobile": True,              # boolean, not the string "true"
        "app": os.environ["LT_APP_URL"],   # lt://APP... from the upload response
        "project": "Checkout app",
        "build": "nightly-regression",
        "name": "login smoke",
        "video": True,
        "devicelog": True,
    },
})

driver = webdriver.Remote("https://mobile-hub.lambdatest.com/wd/hub", options=options)

After: TestMu AI real devices (iOS)

import os
from appium import webdriver
from appium.options.ios import XCUITestOptions

options = XCUITestOptions().load_capabilities({
    "platformName": "ios",
    "LT:Options": {
        "w3c": True,
        "user": os.environ["LT_USERNAME"],
        "accessKey": os.environ["LT_ACCESS_KEY"],
        "platformName": "ios",
        "deviceName": "iPhone 12",         # copy exact strings from the TestMu AI device list
        "platformVersion": "14",
        "isRealMobile": True,
        "app": os.environ["LT_IOS_APP_URL"],  # lt://APP... from your iOS app upload
        "build": "nightly-regression",
        "name": "login smoke",
    },
})

driver = webdriver.Remote("https://mobile-hub.lambdatest.com/wd/hub", options=options)

The TestMu AI Appium troubleshooting guide states that omitting any of deviceName, platformVersion, platformName, isRealMobile, or app will result in a failure to create the session. The TestMu AI docs 'Desired Capabilities in Appium' reference lists the rest as well:

  • network
  • queueTimeout (default 600, range 300-900)
  • region (EU, US, AP)
  • autoGrantPermissions

Upload Response and Real-Device Run Output

The upload response is JSON. Upload TestMu AI's sample Proverbial APK with the curl command from the upload section, and the app URL comes back in an app_url field:

{
  "app_id": "APP10160181461794296645519116",
  "name": "checkout-app",
  "type": "android",
  "url": "https://prod-mobile-artefacts.lambdatest.com/assets/docs/proverbial_android.apk",
  "app_url": "lt://APP10160181461794296645519116",
  "min_version": "4.0",
  "max_version": "12.0",
  "custom_id": null
}

Set LT_APP_URL to that value and the Android sample above opens a session on a real Galaxy S20 running Android 10, with the uploaded app already launched:

The Android Appium sample running the uploaded Proverbial app on a real Galaxy S20 with Android 10 on TestMu AI

The session is filed under the build and name values from LT:Options. The App Automation sessions API returns its record; here it is trimmed to the fields that matter, with the long URLs shortened:

{
  "build_name": "nightly-regression",
  "name": "login smoke",
  "device_name": "Galaxy S20",
  "platform": "android",
  "os_version": "10",
  "status_ind": "completed",
  "video_url": "https://prod-mobile-artefacts.lambdatest.com/.../video/video.mp4",
  "device_logs_url": "https://mobile-api.lambdatest.com/mobile-automation/api/v1/sessions/<session_id>/log/devicelog"
}

The iOS sample behaves the same way on a real iPhone 12 running iOS 14, once TestMu AI's sample .ipa is uploaded and its lt:// value is set in LT_IOS_APP_URL.

Note

Run your first migrated test on a real device.

Note: Do a smoke test, apply the 5-Swap checklist, and run it with TestMu AI App Automation on real iOS and Android devices. To try it, start free.

What Do You Do With the BrowserStack SDK and the browserstack.yml?

Remove both of them before you run anything on TestMu AI; otherwise, your tests may just carry on to BrowserStack.

BrowserStack's docs state that SDK config capabilities will override your existing webdriver URL and capabilities and that the config file wins if both the config and the code set a value (BrowserStack SDK core concepts). So a hub URL that you've changed in your code can be silently overridden by an SDK that's still installed.

Work through this checklist:

  • Remove the BrowserStack SDK package from your dependency file (pom.xml, build.gradle, package.json or requirements.txt).
  • Remove the SDK wrapper from your test run command (i.e., run your tests with your normal runner).
  • Delete browserstack.yml.
  • Move the platforms from the yml into your own device matrix (a parametrized fixture, a TestNG data provider or WebdriverIO capabilities).
  • Replace parallelsPerPlatform with your runner's own parallelism (e.g. pytest -n 4 with pytest-xdist, or TestNG parallel="methods" thread-count="4"). Size it to the number of parallel sessions you have on your TestMu AI plan.
  • Replace the SDK-managed BrowserStack Local with the TestMu AI tunnel (see next section).
Test your website on the TestMu AI real device cloud

How Do You Test Apps That Call Into a Local or Staging Backend?

Set tunnel: true in LT:Options and then start the TestMu AI tunnel on a machine that can reach your backend.

Add tunnelName only if you're running multiple named tunnels. TestMu AI's Desired Capabilities in Appium reference describes a tunnel as "the way to test local apps with TestMu AI" and has a link to the tunnel setup docs.

How Do You Actually Run Both Clouds Side by Side During the Migration?

Run a pilot slice of your suite on both clouds for one sprint, switching providers with an environment variable:

import os
from appium import webdriver
from appium.options.android import UiAutomator2Options

def capabilities_for(cloud: str) -> tuple[str, dict]:
    if cloud == "testmu":
        return ("https://mobile-hub.lambdatest.com/wd/hub",
                {"platformName": "Android",
                 "LT:Options": {"w3c": True,
                                "user": os.environ["LT_USERNAME"],
                                "accessKey": os.environ["LT_ACCESS_KEY"],
                                "platformName": "Android", "deviceName": "Galaxy S20",
                                "platformVersion": "10", "isRealMobile": True,
                                "app": os.environ["LT_APP_URL"],
                                "build": "migration-pilot", "name": "login smoke"}})
    return ("https://hub.browserstack.com/wd/hub",
            {"platformName": "android", "deviceName": "Google Pixel 3",
             "platformVersion": "9.0", "app": os.environ["BROWSERSTACK_APP_URL"],
             "bstack:options": {"userName": os.environ["BROWSERSTACK_USERNAME"],
                                "accessKey": os.environ["BROWSERSTACK_ACCESS_KEY"],
                                "buildName": "migration-pilot", "sessionName": "login smoke"}})

hub_url, caps = capabilities_for(os.getenv("TEST_CLOUD", "testmu"))
driver = webdriver.Remote(hub_url, options=UiAutomator2Options().load_capabilities(caps))

A Pilot That Gives You a Clean Decision

  • Pick 10 to 20 tests: smoke and your flakiest flows.
  • Run them nightly on both clouds for one sprint.
  • Compare four numbers: pass rate, median session duration, queue wait and failures that reproduce on only one cloud.
  • Look at every one-cloud-only failure. It's almost invariably a device string, OS version, or app build mismatch rather than a test bug.
  • Cut the whole suite over well in advance of your BrowserStack renewal date and plan your parallel sessions with the TestMu AI team in advance.

CI: Swap the Secrets and the Upload Step (GitHub Actions Example)

- name: Upload app to TestMu AI
  env:
    LT_USERNAME: ${{ secrets.LT_USERNAME }}
    LT_ACCESS_KEY: ${{ secrets.LT_ACCESS_KEY }}
  run: |
    curl -sf -u "$LT_USERNAME:$LT_ACCESS_KEY" -X POST \
      "https://manual-api.lambdatest.com/app/upload/realDevice" \
      -F "appFile=@app/build/outputs/apk/release/app-release.apk" \
      -F "name=checkout-app" \
      -F "visibility=team"
    # The response prints the lt://APP... app URL. Copy it into the LT_APP_URL repository variable.

- name: Run Appium suite on TestMu AI
  env:
    LT_USERNAME: ${{ secrets.LT_USERNAME }}
    LT_ACCESS_KEY: ${{ secrets.LT_ACCESS_KEY }}
    LT_APP_URL: ${{ vars.LT_APP_URL }}
    TEST_CLOUD: testmu
  run: pytest tests/mobile -n 4

TestMu AI's docs don't actually name the response field that contains the app URL, so this pipeline doesn't parse it. You'll need to copy the lt:// value that appears after the upload and pass that into the pipeline as a variable (in this case LT_APP_URL).

You'll need to update that variable every time you upload a new build. Once you've actually confirmed what the field name is on a real upload, you can then automate that hand-off.

What Errors Will You Encounter After the Migration and How Do You Resolve Them?

Almost every first-day error is a capability or endpoint mismatch. These come from TestMu AI's Appium troubleshooting guide:

ErrorLikely causeFix
SessionNotCreatedExceptionMissing or misspelled capability, missing LT:Options wrapper for a W3C client, or an unavailable device/OS pairSend the complete LT:Options block. Copy device strings exactly.
401 UnauthorizedLT_USERNAME or LT_ACCESS_KEY unset, stale, or not passed throughExport both. Confirm your IDE or CI job inherits them.
Device never allocatedPointing at the Selenium browser hubUse mobile-hub.lambdatest.com/wd/hub.
App not found / invalid app URLapp is empty, a local path, or still a bs:// valueUpload to TestMu AI, then use the returned lt:// value.
Session lands on an emulatorisRealMobile is false or sent as a stringUse the boolean true.
Device or OS unavailabledeviceName/platformVersion combination not offered or spelled differentlyCopy the exact strings from the device list.
Session ends mid-testIdle longer than idleTimeout (default 120 s)Raise it only to your longest real gap. Remove breakpoints.
TerminateApp returns 500Older Appium versionUse Appium 2.12.1 or later.

What's the TestMu AI Score After the Move?

After the move, the same Appium suite runs on TestMu AI's real devices with the mobile-specific controls set from the capabilities. The App Automation page shows:

  • 50,000+ real iOS and Android devices
  • Appium, Espresso, XCUITest and Maestro support
  • Network throttling profiles (slow 3G to 5G and offline)
  • IP or GPS geolocation in 170+ countries
  • Face ID, Touch ID and Android fingerprint prompts
  • Camera image and video injection for QR, barcode and photo flows

For a broader market view, have a look at our roundup of the best real device cloud platforms. If a locator fails on a new device following the move, use Appium Inspector to inspect the element tree on real devices.

New to running Appium on the TestMu AI cloud? Have a look at the full tutorial: How To Perform Mobile App Testing Using Appium.

Youtube thumbnail

Conclusion

Move your first Appium test today. Apply the 5-Swap checklist to one smoke test and run it on real devices.

Start with the app upload, since every other change needs the lt:// value it returns. Then rename bstack:options to LT:Options in one capabilities file, delete the BrowserStack SDK and browserstack.yml so nothing reroutes the session, and point the driver at the mobile hub.

If the session does not start, check the error table before you touch any test code, because a capability or endpoint mismatch causes almost every first-day failure. Once the smoke test passes, run the pilot slice nightly on both clouds for one sprint and move the full suite before your BrowserStack renewal date. For a first run from a fresh project, see getting started with Appium testing in the TestMu AI docs.

Testing from the terminal? Kane CLI by TestMu AI lets SDETs and developers plan, write, run and debug tests from the command line.

To run your first test, start free, or
for a full walkthrough of the product.

Author

...

Srinivasan Sekar

Blogs: 20

  • Twitter
  • Linkedin

Srinivasan Sekar is Director of Engineering at TestMu AI, where he leads engineering and open-source initiatives behind the Selenium and Appium automation grid and owns TestMu AI's MCP Server. A committer to Appium and a contributor to Selenium, WebdriverIO, Taiko, and AppiumTestDistribution, he brings over 15 years of experience in quality engineering and open-source technologies. He is the author of the Apress book 'The MCP Standard: A Developer's Guide to Building Universal AI Tools with the Model Context Protocol,' a Certified Kubernetes and Cloud Native Associate, and an international conference speaker. Before TestMu AI he spent over eight years at Thoughtworks as a Principal Consultant and Quality Architect. Srinivasan holds a B.Tech in Information Technology from Anna University.

Reviewer

...

Sai Krishna

Reviewer

  • Linkedin

Sai Krishna is Director of Engineering at TestMu AI, where he leads agentic AI for quality engineering, building AI agents that autonomously drive mobile and conversational test automation. His current focus is Agent Testing and Model Context Protocol (MCP) support for mobile. He is a core contributor and member of the Appium open-source project and the creator of AppiumTestDistribution and appium-device-farm. With over 14 years of experience including more than 9 years at Thoughtworks as a Principal Consultant, he holds a BSc in Electronics and speaks regularly at TestMu and Appium Conf on Appium, mobile automation, and agentic AI in testing.

Add to Google preferred sources

Summarise with AI

Copied to Clipboard!
...

3000+ Browsers. One Platform.

See exactly how your site performs everywhere.

Try it free
...

Write Tests in Plain English with KaneAI

Create, debug, and evolve tests using natural language.

Try for free

BrowserStack to TestMu AI Appium Migration FAQs

Did you find this page helpful?

More Related Blogs

TestMu AI forEnterprise

Get access to solutions built on Enterprise
grade security, privacy, & compliance

  • Advanced access controls
  • Advanced data retention rules
  • Advanced Local Testing
  • Premium Support options
  • Early access to beta features
  • Private Slack Channel
  • Unlimited Manual Accessibility DevTools Tests