Skip to content

feat(provider-webdriver): add TestMu virtual-device cloud provider - #3112

Closed
gautam-jain-dev wants to merge 1 commit into
callstack:mainfrom
gautam-jain-dev:feat/testmu-webdriver-provider
Closed

gautam-jain-dev wants to merge 1 commit into
callstack:mainfrom
gautam-jain-dev:feat/testmu-webdriver-provider

Conversation

@gautam-jain-dev

@gautam-jain-dev gautam-jain-dev commented Oct 2, 2026 •

Copy link
Copy Markdown

Summary

Adds testmu as a cloud WebDriver provider beside BrowserStack and AWS Device Farm. TestMu (formerly LambdaTest) fronts Android emulators and iOS simulators with the same Appium hub as its real devices, so the provider sets isRealMobile: false in lt:options and otherwise reuses the shared WebDriver runtime. First emulator/simulator vendor on this seam.

  • connect testmu verifies the device/OS pair against the public virtual-device catalog and the credentials plus lt:// app against the authenticated uploaded-app listing; no session is created.
  • Local paths and public URLs are uploaded through the virtual-device upload API at session creation. appiumVersion: latest is requested unless pinned so mobile: extensions exist.
  • Device-feature flags project onto lt:options; BrowserStack-only network/resign flags are refused by name.
  • artifacts returns video, Appium, device, network and command log URLs plus the dashboard link.
  • Shared helpers (verification fetch, session-details fetch, OS-version compare, orientation validation, URL artifacts, one hub-profile builder) replace what would otherwise be BrowserStack copies. TestMu modules load lazily so the package entry's eager closure does not grow.
LT_USERNAME=... LT_ACCESS_KEY=...
agent-device connect testmu --platform android --device "Pixel 5" --provider-os-version 14 --provider-app lt://APP-id
agent-device open com.example.app && agent-device snapshot -i && agent-device close && agent-device artifacts --json

29 files touched; docs page website/docs/docs/testmu.md.

Validation

Commit 44fc427: pnpm typecheck, pnpm lint, pnpm check:fallow --base origin/main, and pnpm check:affected --run (449 files / 3454 tests) pass locally.

Live, production TestMu, Android emulator Pixel 5 / 14: connect → open → snapshot -i → click 'label="…"' (settle diff observed) → screenshot → back → close → artifacts (7 ready). Provider session ids bf384a9f-9af4-465d-9830-f3e59b923272, 9d92f504-1dbc-43d6-88dc-4806ea6510e7. iOS simulator path verified through connect only.

Risk: no iOS simulator session has been opened yet; the WebDriver runtime path is shared with BrowserStack iOS.

🤖 Generated with Claude Code

Review in cubic

Add `testmu` as a cloud WebDriver provider beside BrowserStack and AWS
Device Farm. TestMu (formerly LambdaTest) fronts Android emulators and
iOS simulators with the same Appium hub as its real devices, so the
provider sets `isRealMobile: false` in `lt:options` and otherwise reuses
the shared WebDriver runtime.

- `connect testmu` verifies the device/OS pair against the public
  virtual-device catalog and the credentials plus `lt://` app against the
  authenticated uploaded-app listing, without creating a session.
- Local app paths and public URLs are uploaded through the virtual-device
  upload API when the session is created; `appiumVersion: latest` is
  requested unless pinned so `mobile:` extensions are available.
- Device-feature flags project onto `lt:options` (orientation upper-cased);
  BrowserStack-only network and resign flags are rejected by name.
- `artifacts` reads video, Appium, device, network, and command log URLs
  from the session-details API and adds the dashboard link.
- Credentials: LT_USERNAME / LT_ACCESS_KEY. Endpoints override via
  TESTMU_WEBDRIVER_ENDPOINT, TESTMU_APP_UPLOAD_ENDPOINT, TESTMU_API_ENDPOINT.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@gautam-jain-dev
gautam-jain-dev marked this pull request as draft October 2, 2026 11:24

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

12 issues found across 29 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/provider-webdriver/src/testmu.ts">

<violation number="1" location="packages/provider-webdriver/src/testmu.ts:111">
P2: `postTestMuUpload` parses the body before checking `response.ok`, so an HTML or empty error response throws `SyntaxError` instead of the typed `COMMAND_FAILED` with status. Catch body-parsing failures and preserve the upload error contract.</violation>

<violation number="2" location="packages/provider-webdriver/src/testmu.ts:196">
P2: `fetchTestMuSessionDetails` treats any object as valid session details without checking the JSend status or rejecting an invalid `data` shape, so provider errors can be reported as pending artifacts. Validate the envelope before mapping URLs.</violation>

<violation number="3" location="packages/provider-webdriver/src/testmu.ts:234">
P2: `readTestMuAppReference` accepts arbitrary `app_url` values even though this helper promises an `lt://` reference and sends it directly to the hub. Require `isTestMuAppReference(record.app_url)` so malformed upload responses fail before session creation.</violation>
</file>

<file name="packages/provider-webdriver/src/browserstack-connection-verification.ts">

<violation number="1" location="packages/provider-webdriver/src/browserstack-connection-verification.ts:113">
P2: This hint now covers HTTP failures as well as transport failures. A 500 or 429 response therefore tells users to check network access and omits the previous BrowserStack service-status guidance; add a separate HTTP-failure hint to the shared helper.</violation>
</file>

<file name="src/commands/schema/cli-help.ts">

<violation number="1" location="src/commands/schema/cli-help.ts:627">
P3: The new TestMu virtual-device flow block ends at `artifacts --json` and omits `agent-device disconnect`, while the topic's lifecycle (`connect -> install/open -> commands -> close -> disconnect`) and every other flow block (Cloud, BrowserStack, AWS Device Farm, Local, Script) end with disconnect. Add it so the example matches the documented lifecycle.</violation>
</file>

<file name="src/__tests__/cloud-connect-profile.test.ts">

<violation number="1" location="src/__tests__/cloud-connect-profile.test.ts:73">
P3: The testmu branch in this mock is unreachable: no test in the file invokes `connect testmu` (or calls `verifyConnection` with `provider: 'testmu'`), and the mock is file-scoped. Add a `connect testmu` test that mirrors the browserstack case (assert the options passed to the mock and the generated state), or the branch should be removed. This also leaves the CLI-side TestMu connect path exercised only by nothing.</violation>
</file>

<file name="src/cli/connection/cloud-webdriver-profile.ts">

<violation number="1" location="src/cli/connection/cloud-webdriver-profile.ts:128">
P2: TestMu’s profile builder accepts BrowserStack-only feature flags and defers rejection until session preparation. As a result, `connect testmu` can report a verified connection and persist state, then fail on the first session; reject these flags in `testMuProfileFields` before generating the profile while retaining the runtime guard for hand-authored profiles.</violation>
</file>

<file name="packages/provider-webdriver/src/testmu-connection-verification.ts">

<violation number="1" location="packages/provider-webdriver/src/testmu-connection-verification.ts:31">
P2: This probe always uses the bundled production URL. `connect testmu` therefore checks the wrong device catalog when `TESTMU_API_ENDPOINT` targets a staging or private deployment; pass the configured endpoint into verification.</violation>

<violation number="2" location="packages/provider-webdriver/src/testmu-connection-verification.ts:76">
P2: This URL construction breaks `appsEndpoint` overrides that already contain query parameters. Build a `URL` and set `searchParams` so existing parameters and the required TestMu filters are composed correctly.</violation>
</file>

<file name="packages/provider-webdriver/src/webdriver-utils.ts">

<violation number="1" location="packages/provider-webdriver/src/webdriver-utils.ts:81">
P2: This session-details request has no deadline, so a stalled provider response can hang artifact retrieval indefinitely. Add the same bounded timeout used by connection verification.</violation>

<violation number="2" location="packages/provider-webdriver/src/webdriver-utils.ts:123">
P2: Session-details transport and JSON parsing failures escape as raw `TypeError` or `SyntaxError`, especially for released-session artifact queries. Wrap the fetch/parse path in `AppError('COMMAND_FAILED', ...)` and preserve the cause.</violation>

<violation number="3" location="packages/provider-webdriver/src/webdriver-utils.ts:124">
P2: The record-shape check admits JSON arrays, so malformed session responses can be reported as pending or ready artifacts instead of failing. Reject arrays alongside other non-object bodies.</violation>
</file>

Tip: cubic used a learning from your PR history. Let your coding agent read cubic learnings directly with the cubic MCP.

Re-trigger cubic

function readTestMuAppReference(value: unknown): string | undefined {
if (!value || typeof value !== 'object') return undefined;
const record = value as { app_url?: unknown; app_id?: unknown };
if (typeof record.app_url === 'string' && record.app_url.length > 0) return record.app_url;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: readTestMuAppReference accepts arbitrary app_url values even though this helper promises an lt:// reference and sends it directly to the hub. Require isTestMuAppReference(record.app_url) so malformed upload responses fail before session creation.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At packages/provider-webdriver/src/testmu.ts, line 234:

<comment>`readTestMuAppReference` accepts arbitrary `app_url` values even though this helper promises an `lt://` reference and sends it directly to the hub. Require `isTestMuAppReference(record.app_url)` so malformed upload responses fail before session creation.</comment>

<file context>
@@ -0,0 +1,239 @@
+function readTestMuAppReference(value: unknown): string | undefined {
+  if (!value || typeof value !== 'object') return undefined;
+  const record = value as { app_url?: unknown; app_id?: unknown };
+  if (typeof record.app_url === 'string' && record.app_url.length > 0) return record.app_url;
+  if (typeof record.app_id === 'string' && record.app_id.length > 0) {
+    return isTestMuAppReference(record.app_id) ? record.app_id : `lt://${record.app_id}`;
</file context>

Comment on lines +196 to +197
const details = (json as { data?: unknown }).data ?? json;
return details && typeof details === 'object' ? (details as Record<string, unknown>) : {};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: fetchTestMuSessionDetails treats any object as valid session details without checking the JSend status or rejecting an invalid data shape, so provider errors can be reported as pending artifacts. Validate the envelope before mapping URLs.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At packages/provider-webdriver/src/testmu.ts, line 196:

<comment>`fetchTestMuSessionDetails` treats any object as valid session details without checking the JSend status or rejecting an invalid `data` shape, so provider errors can be reported as pending artifacts. Validate the envelope before mapping URLs.</comment>

<file context>
@@ -0,0 +1,239 @@
+    service: 'TestMu',
+  });
+  // The API wraps the session in a jsend envelope: `{ status, data: {...}, message }`.
+  const details = (json as { data?: unknown }).data ?? json;
+  return details && typeof details === 'object' ? (details as Record<string, unknown>) : {};
+}
</file context>
Suggested change
const details = (json as { data?: unknown }).data ?? json;
return details && typeof details === 'object' ? (details as Record<string, unknown>) : {};
const envelope = asRecord(json);
if (envelope.status !== undefined && envelope.status !== 'success') {
throw new AppError('COMMAND_FAILED', 'TestMu session details lookup failed.', {
response: json,
});
}
const details = Object.prototype.hasOwnProperty.call(envelope, 'data') ? envelope.data : json;
if (!details || typeof details !== 'object' || Array.isArray(details)) {
throw new AppError('COMMAND_FAILED', 'TestMu session details response was not an object.', {
response: json,
});
}
return details as Record<string, unknown>;

body: form,
signal,
});
const json = (await response.json()) as unknown;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: postTestMuUpload parses the body before checking response.ok, so an HTML or empty error response throws SyntaxError instead of the typed COMMAND_FAILED with status. Catch body-parsing failures and preserve the upload error contract.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At packages/provider-webdriver/src/testmu.ts, line 111:

<comment>`postTestMuUpload` parses the body before checking `response.ok`, so an HTML or empty error response throws `SyntaxError` instead of the typed `COMMAND_FAILED` with status. Catch body-parsing failures and preserve the upload error contract.</comment>

<file context>
@@ -0,0 +1,239 @@
+    body: form,
+    signal,
+  });
+  const json = (await response.json()) as unknown;
+  const appUrl = readTestMuAppReference(json);
+  if (!response.ok || !appUrl) {
</file context>
Suggested change
const json = (await response.json()) as unknown;
let json: unknown;
try {
json = (await response.json()) as unknown;
} catch (error) {
throw new AppError('COMMAND_FAILED', 'TestMu app upload failed.', {
status: response.status,
}, error);
}

hints: {
service: 'BrowserStack',
unauthorizedHint: 'Check BROWSERSTACK_USERNAME and BROWSERSTACK_ACCESS_KEY.',
networkHint: 'Check network access to api-cloud.browserstack.com and retry connect.',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: This hint now covers HTTP failures as well as transport failures. A 500 or 429 response therefore tells users to check network access and omits the previous BrowserStack service-status guidance; add a separate HTTP-failure hint to the shared helper.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At packages/provider-webdriver/src/browserstack-connection-verification.ts, line 113:

<comment>This hint now covers HTTP failures as well as transport failures. A 500 or 429 response therefore tells users to check network access and omits the previous BrowserStack service-status guidance; add a separate HTTP-failure hint to the shared helper.</comment>

<file context>
@@ -105,42 +104,15 @@ async function fetchBrowserStackJson(
+    hints: {
+      service: 'BrowserStack',
+      unauthorizedHint: 'Check BROWSERSTACK_USERNAME and BROWSERSTACK_ACCESS_KEY.',
+      networkHint: 'Check network access to api-cloud.browserstack.com and retry connect.',
+    },
+  });
</file context>

env?: EnvMap;
cwd: string;
}): RemoteConfigProfile {
return hubProviderProfileFields(TESTMU_HUB_PROFILE, options);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: TestMu’s profile builder accepts BrowserStack-only feature flags and defers rejection until session preparation. As a result, connect testmu can report a verified connection and persist state, then fail on the first session; reject these flags in testMuProfileFields before generating the profile while retaining the runtime guard for hand-authored profiles.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/cli/connection/cloud-webdriver-profile.ts, line 128:

<comment>TestMu’s profile builder accepts BrowserStack-only feature flags and defers rejection until session preparation. As a result, `connect testmu` can report a verified connection and persist state, then fail on the first session; reject these flags in `testMuProfileFields` before generating the profile while retaining the runtime guard for hand-authored profiles.</comment>

<file context>
@@ -82,29 +86,67 @@ function requireConnectProfileBuilder(
+  env?: EnvMap;
+  cwd: string;
+}): RemoteConfigProfile {
+  return hubProviderProfileFields(TESTMU_HUB_PROFILE, options);
+}
+
</file context>

Comment on lines +81 to +86
const response = await fetch(endpoint, {
headers: {
...agentDeviceRequestHeaders(options.clientVersion),
...(options.auth ? { Authorization: basicAuthHeader(options.auth) } : {}),
},
signal: AbortSignal.timeout(15_000),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: This session-details request has no deadline, so a stalled provider response can hang artifact retrieval indefinitely. Add the same bounded timeout used by connection verification.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At packages/provider-webdriver/src/webdriver-utils.ts, line 81:

<comment>This session-details request has no deadline, so a stalled provider response can hang artifact retrieval indefinitely. Add the same bounded timeout used by connection verification.</comment>

<file context>
@@ -50,3 +55,97 @@ export function withTrailingSlash(url: URL): URL {
+): Promise<unknown> {
+  const { service, unauthorizedHint, networkHint } = options.hints;
+  try {
+    const response = await fetch(endpoint, {
+      headers: {
+        ...agentDeviceRequestHeaders(options.clientVersion),
</file context>
Suggested change
const response = await fetch(endpoint, {
headers: {
...agentDeviceRequestHeaders(options.clientVersion),
...(options.auth ? { Authorization: basicAuthHeader(options.auth) } : {}),
},
signal: AbortSignal.timeout(15_000),
const response = await fetch(endpoint, {
headers: {
...agentDeviceRequestHeaders(options.clientVersion),
Authorization: basicAuthHeader(options.auth),
},
signal: AbortSignal.timeout(15_000),
});

Authorization: basicAuthHeader(options.auth),
},
});
const json = (await response.json()) as unknown;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Session-details transport and JSON parsing failures escape as raw TypeError or SyntaxError, especially for released-session artifact queries. Wrap the fetch/parse path in AppError('COMMAND_FAILED', ...) and preserve the cause.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At packages/provider-webdriver/src/webdriver-utils.ts, line 123:

<comment>Session-details transport and JSON parsing failures escape as raw `TypeError` or `SyntaxError`, especially for released-session artifact queries. Wrap the fetch/parse path in `AppError('COMMAND_FAILED', ...)` and preserve the cause.</comment>

<file context>
@@ -50,3 +55,97 @@ export function withTrailingSlash(url: URL): URL {
+      Authorization: basicAuthHeader(options.auth),
+    },
+  });
+  const json = (await response.json()) as unknown;
+  if (!response.ok || !json || typeof json !== 'object') {
+    throw new AppError('COMMAND_FAILED', `${options.service} session details lookup failed.`, {
</file context>

const auth = { username: options.username, accessKey: options.accessKey };
const catalog = await fetchTestMuJson(
options.devicesEndpoint ??
`${trimTrailingSlash(TESTMU_API_ENDPOINT)}/capability/generator?isVirtualDevice=true`,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: This probe always uses the bundled production URL. connect testmu therefore checks the wrong device catalog when TESTMU_API_ENDPOINT targets a staging or private deployment; pass the configured endpoint into verification.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At packages/provider-webdriver/src/testmu-connection-verification.ts, line 31:

<comment>This probe always uses the bundled production URL. `connect testmu` therefore checks the wrong device catalog when `TESTMU_API_ENDPOINT` targets a staging or private deployment; pass the configured endpoint into verification.</comment>

<file context>
@@ -0,0 +1,181 @@
+  const auth = { username: options.username, accessKey: options.accessKey };
+  const catalog = await fetchTestMuJson(
+    options.devicesEndpoint ??
+      `${trimTrailingSlash(TESTMU_API_ENDPOINT)}/capability/generator?isVirtualDevice=true`,
+    undefined,
+    clientVersion,
</file context>

agent-device open com.example.app
agent-device snapshot -i
agent-device close
agent-device artifacts --json

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The new TestMu virtual-device flow block ends at artifacts --json and omits agent-device disconnect, while the topic's lifecycle (connect -> install/open -> commands -> close -> disconnect) and every other flow block (Cloud, BrowserStack, AWS Device Farm, Local, Script) end with disconnect. Add it so the example matches the documented lifecycle.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/commands/schema/cli-help.ts, line 627:

<comment>The new TestMu virtual-device flow block ends at `artifacts --json` and omits `agent-device disconnect`, while the topic's lifecycle (`connect -> install/open -> commands -> close -> disconnect`) and every other flow block (Cloud, BrowserStack, AWS Device Farm, Local, Script) end with disconnect. Add it so the example matches the documented lifecycle.</comment>

<file context>
@@ -617,6 +618,14 @@ Cloud profile flow:
+  agent-device open com.example.app
+  agent-device snapshot -i
+  agent-device close
+  agent-device artifacts --json
+
 BrowserStack hosted-device flow:
</file context>
Suggested change
agent-device artifacts --json
agent-device artifacts --json
agent-device disconnect

status: 'missing',
message:
'No app upload is attached; AWS Device Farm does not support install after allocation.',
: options.provider === 'testmu'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The testmu branch in this mock is unreachable: no test in the file invokes connect testmu (or calls verifyConnection with provider: 'testmu'), and the mock is file-scoped. Add a connect testmu test that mirrors the browserstack case (assert the options passed to the mock and the generated state), or the branch should be removed. This also leaves the CLI-side TestMu connect path exercised only by nothing.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/__tests__/cloud-connect-profile.test.ts, line 73:

<comment>The testmu branch in this mock is unreachable: no test in the file invokes `connect testmu` (or calls `verifyConnection` with `provider: 'testmu'`), and the mock is file-scoped. Add a `connect testmu` test that mirrors the browserstack case (assert the options passed to the mock and the generated state), or the branch should be removed. This also leaves the CLI-side TestMu connect path exercised only by nothing.</comment>

<file context>
@@ -70,24 +70,37 @@ beforeEach(() => {
-            status: 'missing',
-            message:
-              'No app upload is attached; AWS Device Farm does not support install after allocation.',
+      : options.provider === 'testmu'
+        ? {
+            provider: 'testmu',
</file context>

@thymikee

thymikee commented Oct 2, 2026

Copy link
Copy Markdown
Member

At 44fc427, connect testmu accepts device-feature flags that TestMu cannot use, and the TestMu code copies much of the BrowserStack code. I would fix both before merge.

connect testmu --provider-network-profile 4g-lte-good (also --provider-custom-network and --provider-no-resign-app) verifies, prints success and saves the profile. testMuProfileFields goes through hubProviderProfileFields, which spreads the device-feature fields into the profile and never rejects them. The flag is refused only later, at open, by rejectUnsupportedTestMuDeviceFeatures in prepareSession. So connect reports a ready connection, and every open then fails with INVALID_ARGS. The docstring in testmu-device-features.ts says the CLI profile builder calls this check, but it does not. The AWS branch already rejects these flags at connect. The rule: every non-BrowserStack hub provider refuses the device-feature flags it cannot act on, both at connect and in prepareSession. Could the hub profile builder take a per-hub reject hook, as AWS does? Please add a connect testmu --provider-network-profile x test through the connect adapter. It should assert INVALID_ARGS and that no profile is written.

createTestMuUploadApp is a verbatim copy of createBrowserStackUploadApp, apart from the function it calls. The same goes for postTestMuUpload against uploadBrowserStackApp, resolveTestMuAppReference against resolveBrowserStackAppReference, and the unsupported-flag table and reject function against their BrowserStack twins. asRecord is now defined in four files. Each later fix to upload error typing, app-reference rules or feature rejection must be made twice. This also conflicts with the PR's claim that shared helpers replace the BrowserStack copies. The rule: the hub mechanics live once in webdriver-utils.ts, as fetchProviderVerificationJson already does. That means one upload POST helper, one upload-app factory, one app-reference resolver and one asRecord, and both providers call them.

Would it be simpler to treat BrowserStack and TestMu as two configs of one hosted-hub provider factory? A HubProviderConfig would hold the credential env names, app scheme, upload endpoint and response reader, the vendor options key (bstack:options or lt:options), the supported feature fields and the artifact field map. The factory would produce the runtime, prepareSession, the upload adapter, app-reference resolution and the reject check. The device-feature table would become one spec table with a per-provider support trait. TestMu would then be a config of about 80 lines plus its verification parsers, not about 835 lines. This would mean extracting BrowserStack into the factory first as a behavior-preserving move, then adding TestMu as the second config. HubProviderProfile already gives the CLI side a seam to extend with the reject hook.

Not blocking, and you can take or leave it: no test drives connect testmu, so testMuProfileFields, the lt:// normalization and the verifyTestMu adapter are not exercised through the CLI route. One adapter test would cover required-flag errors, the saved profile fields and an unsupported flag, and it can be the same test as the one above.

The PR body states only an Android emulator lt:// run, with no output attached. Please attach the output of connect testmu, open, snapshot -i, close and artifacts --json on the Android emulator. It should show the TestMu providerSessionId, cloudArtifacts with a video or appium-log URL, and the dashboard link. Please also run the same open and snapshot sequence on an iOS simulator, because the docs and help advertise iOS simulator support and iOS has only reached connect. Finally, run one open with --provider-app set to a local .apk or zipped .app. It should show that the upload to /app/upload/virtualDevice returned an lt:// reference and that the session started. No live run has reached that upload route.

I did not run the code or the local gates. I did not measure the eager closure or bundle size; I checked the lazy-loading claim by import grep only. I could not verify the TestMu API shapes (capability layout, /app/data with type=ios, upload response app_id versus app_url, session details, hub orientation casing), so the tests rest on hand-written fixtures. The live-run claims and session ids in the PR body are unverified.

The only check not passing is the third-party cubic review, which is still in progress. It runs no test route this diff could affect, and no repository job (unit, size, smoke) is listed yet. Before merge, connect testmu must reject the unsupported flags, the upload, app-reference and reject helpers must be shared with BrowserStack, and the live output above must be attached.

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.

2 participants