Skip to content

feat(provider-webdriver): add TestMu AI device cloud provider - #3117

Closed
amankansal-lt wants to merge 6 commits into
callstack:mainfrom
amankansal-lt:feat/testmu-ai-provider
Closed

amankansal-lt wants to merge 6 commits into
callstack:mainfrom
amankansal-lt:feat/testmu-ai-provider

Conversation

@amankansal-lt

@amankansal-lt amankansal-lt commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Adds TestMu AI (formerly LambdaTest) as a hosted device cloud alongside BrowserStack and AWS Device Farm. agent-device can drive TestMu AI Android emulators, iOS simulators and real devices through its existing cloud WebDriver runtime:

export LT_USERNAME=... LT_ACCESS_KEY=...
agent-device connect testmu --platform ios --device "iPhone 16" --provider-os-version 18.0 --provider-app ./MyApp.zip
agent-device open com.example.app && agent-device snapshot -i && agent-device close

From Node, client.leases.allocate({ leaseProvider: 'testmu', ... }) gives the same session to frameworks built on agent-device.

Commits:

  1. Shared hub helpers: app upload, install adapter, --provider-app resolution and URL artifacts, extracted from the BrowserStack code so a second hub reuses them. Two small BrowserStack changes: typed errors for non-JSON upload bodies, and a case-insensitive URL scheme.
  2. Session-details lookups: a 15 s deadline, typed COMMAND_FAILED errors, and an object check for every provider.
  3. testmu provider for emulators and simulators:
    • connect checks credentials, device and exact OS version against the public catalogue, and the lt:// app against the right app list.
    • Sessions put vendor capabilities in lt:options, with w3c and the device pool enforced.
    • Apps can be uploaded from a local file or a URL.
    • Device features map onto lt:options; unsupported flags are refused at connect.
    • Artifacts include video, logs, screenshots and the dashboard link.
  4. Real devices: --provider-device-type real|virtual, default virtual, carried through contracts, lease scope, remote config and leases.allocate. Real devices use their own upload API and catalogue. Other providers refuse the flag.
  5. Materialized iOS builds: install from a remote source uploads the original .zip/.ipa instead of the extracted .app directory, which hosted upload APIs cannot accept.
  6. Docs: a TestMu AI guide in the same layout as the BrowserStack one, plus the device clouds overview, README, client API and command reference.

Endpoints can be overridden with TESTMU_WEBDRIVER_ENDPOINT, TESTMU_APP_UPLOAD_ENDPOINT, TESTMU_REAL_DEVICE_APP_UPLOAD_ENDPOINT and TESTMU_API_ENDPOINT.

Validation

Every commit passes format:check, check:quick, provider-webdriver tests, test:integration:provider, test:unit, check:layering and check:fallow on its own. On the head:

  • test:unit: 12082 passed
  • test:integration:provider: 228 passed
  • build and check:package: pass

Live against TestMu AI production. The local .apk and local zipped .app runs used this exact code; the other three used earlier revisions of the same work. Each run covered connect → open → snapshot -i → screenshot → close → artifacts --json, and every run returned its artifacts ready (video, Appium/device/network/command logs, screenshots, dashboard link):

Device Device type App open
Galaxy S22 Ultra 5G, Android 14 emulator local .apk upload 60 s
Galaxy S22 Ultra 5G, Android 14 emulator .apk by URL 59 s
iPhone 16, iOS 18.0 simulator local zipped .app 72 s
iPhone 16, iOS 18 real device signed .ipa 35 s
Pixel 6, Android 14 real device .apk by URL 29 s

The hub rejects platformVersion: "18" for a simulator the catalogue lists as 18.0, which is why connect matches versions exactly.

Known limits are those of the cloud WebDriver runtime: settings, alert, record, logs and portReverse are unsupported. For local services, use TestMu AI Tunnel.

🤖 Generated with Claude Code

Review in cubic

BrowserStack's app upload, install adapter, --provider-app resolution,
session-details URL artifacts, and orientation check were written for
one vendor. A second hosted Appium hub needs the same mechanics with a
different form field, reference scheme, and response shape, so move
them into shared helpers and have BrowserStack use them:

- webdriver-utils.ts: postHubAppUpload, createHubUploadApp,
  resolveHubAppReference, appFileUploadForm, asRecord,
  readProviderJsonBody, and requireProviderDeviceOrientation.
- artifact-results.ts: urlArtifactFromDetails.
- browserstack.ts: resolveBrowserStackAppReference, moved out of
  provider-definitions.ts.

Two small BrowserStack behaviour changes come with the shared code:

- An upload response that is not JSON (a gateway error page, an empty
  body) now fails with a typed COMMAND_FAILED that carries the HTTP
  status, instead of a raw JSON SyntaxError.
- The http(s) scheme of a --provider-app URL is matched
  case-insensitively, so HTTPS://... is passed through to the hub rather
  than being treated as a local path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The BrowserStack session-details lookup behind `artifacts` had no
deadline, so a stalled API call could hang the command indefinitely. A
transport failure or a body that was not JSON surfaced as an untyped
fetch or SyntaxError, and a JSON array passed the object check and was
read as session details.

Add fetchProviderSessionDetails to webdriver-utils.ts and use it for
BrowserStack. It sends basic auth with a 15 second deadline and reports
every failure as COMMAND_FAILED: a timeout or network error with a retry
hint and the original error as its cause, and a non-2xx answer or a body
that is not a JSON object with the HTTP status and the parsed response.

Connection verification gets the same treatment through
fetchProviderVerificationJson, which BrowserStack now uses in place of
its private fetch. Behaviour is unchanged: 401/403 is UNAUTHORIZED with
a credential hint, any other HTTP failure points at the provider's
service status, and a transport failure points at network access. A
new test pins the two non-credential hints. sameOsVersion moves
alongside it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add `testmu`, a hosted WebDriver provider for TestMu AI (formerly
LambdaTest) virtual devices: Android emulators and iOS simulators behind
the TestMu AI Appium hub. It follows the BrowserStack shape:
`connect testmu` verifies and saves a local profile, and `open` creates
the hosted session. The service hostnames still carry the lambdatest.com
domain.

connect
- Reads LT_USERNAME and LT_ACCESS_KEY, the variables TestMu AI SDKs use.
- Checks the device and OS version against the public virtual-device
  catalog. The match is exact because the hub rejects `18` for a device
  listed as `18.0`; the error lists the versions the device offers.
- Uses the authenticated app listing as the credential check, and looks
  an lt:// id up in the list for the session's runtime (`emulator` or
  `simulator`). An id that is not listed is reported as configured,
  since TestMu AI validates it at session creation.
- Verifies against TESTMU_API_ENDPOINT when it is set, as the runtime
  does, and composes catalog and listing URLs with URL so a base or
  override that carries a query keeps it.
- Never creates a session.

Sessions
- Standard Appium keys stay `appium:`-prefixed; everything vendor-
  specific goes in `lt:options`, merged per key with any configured
  `lt:options`. `isRealMobile: false` and `w3c: true` are applied last,
  so configuration cannot move the session to another device pool or
  off the W3C dialect agent-device speaks.
- `appiumVersion` is sent only when --provider-appium-version pins one;
  otherwise TestMu AI starts its default server for the device.
- Orientation, geo-location, timezone, Appium version, language, and
  locale map onto `lt:options` through a table. The BrowserStack-only
  network-profile, custom-network, and no-resign flags are refused by
  flag name at connect and at session preparation, instead of being
  silently dropped.

Apps
- --provider-app takes an lt:// id, an http(s) URL, or a local path. The
  hub only accepts lt:// references, so a URL is handed to the upload
  API to fetch (`storage=url`) and a local file is uploaded.
- An unzipped iOS `.app` directory is rejected before any request, with
  a hint to zip it.
- Only a well-formed lt:// reference or app id in the upload response
  counts as success.

Artifacts
- Read from the session-details API through the shared bounded lookup.
  A 404 reads as pending until TestMu AI publishes the details.
  `console_logs_url` is the device log on virtual devices.
- Session video, Appium, network, and command logs, screenshots, and
  the dashboard link are returned once at least one artifact URL exists.

TESTMU_WEBDRIVER_ENDPOINT, TESTMU_APP_UPLOAD_ENDPOINT, and
TESTMU_API_ENDPOINT redirect the endpoints.

The session, upload, verification, and device-feature modules are loaded
with dynamic import, so the package entry's eager module graph does not
grow. The CLI reaches the device-feature checks through a new
`./testmu-device-features` subpath export. A .fallowrc entry covers the
exports that are read only through the dynamic import. `connect testmu`
is listed in the connect usage, the remote help topic, and the artifacts
provider description.

Co-authored-by: gautam-jain-dev <gautamj@lambdatest.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TestMu AI serves real devices and virtual devices from the same Appium
hub; `lt:options.isRealMobile` selects the pool. Add
`--provider-device-type real|virtual` to choose it. The default is
`virtual`, so existing `testmu` profiles and sessions keep their current
behaviour.

The value is a cloud provider profile field like the others. It is
defined in contracts as PROVIDER_DEVICE_TYPES and carried through the
lease_allocate projection, request overrides, the remote-config schema,
`connect`, the CLI request flags, and the session doctor's provider
keys. It reaches session preparation from a `connect testmu` profile and
from `client.leases.allocate({ providerDeviceType })`.

For `real`, TestMu AI sessions:
- Set `isRealMobile: true`. It is applied after any configured
  `lt:options`, so configuration cannot switch pools.
- Upload through the real-device upload API. It has its own override,
  TESTMU_REAL_DEVICE_APP_UPLOAD_ENDPOINT; TESTMU_APP_UPLOAD_ENDPOINT
  keeps redirecting virtual-device uploads only. An `.app` directory is
  refused with a hint to pass a signed .ipa.
- Verify against the real-device catalog
  (`capability/generator?isVirtualDevice=false`). It lists devices
  directly under the platform key rather than under `app.devices`. The
  OS version must still match exactly, and real iOS devices are listed
  by major version, such as `18`. A catalog response without the
  expected pool shape fails with a typed error.
- Look an lt:// id up in the real-device app list (`type=android` or
  `type=ios`), the same way virtual uploads are looked up under
  `emulator` or `simulator`.

BrowserStack, AWS Device Farm, and Limrun refuse the flag at connect.
BrowserStack and AWS Device Farm also refuse it at session preparation,
which the typed client and hand-written profiles reach without
`connect`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Install from a remote source materializes an iOS build by extracting the
`.app` bundle from a zipped simulator build or an .ipa. The WebDriver
deployment runtime handed that extracted `.app` directory to the
provider's uploader. Hosted upload APIs take a file, not a directory, so
the upload could not succeed.

When the materialized artifact is an iOS `.app` extracted from a `.zip`
or `.ipa` archive, upload the archive it came from. Every other case
uploads the installable path as before: no archive, an archive of
another type, or an Android build. A provider without an uploader still
installs the extracted bundle path. The bundle id and launch target
hints are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add a TestMu AI guide next to the BrowserStack and AWS Device Farm ones
and link it from the sidebar. It covers credentials and `connect`, the
exact device and OS version match, app references and uploads, device
features, real devices with `--provider-device-type real`, the CLI and
Node.js client workflows, artifacts, and endpoint overrides.

List TestMu AI among the device clouds in the README, the device clouds
overview, the client API page, and the command reference.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant