feat(lambdatest): Integrate TestMU hub connection with tunnel, mirroring SauceLabs/BrowserStack#116
Conversation
Greptile SummaryThis PR integrates LambdaTest (exposed as the
Confidence Score: 5/5Safe to merge — the new provider is well-isolated, mirrors established patterns, and the two findings are minor defensive-fallback improvements that do not affect the happy path. The core provider logic (capability building, tunnel lifecycle, REST status reporting) is correct and covered by a thorough test suite. The two findings relate to edge-case error fallbacks (an unexpected upload API response producing a bare 'unknown' string) and a resource description that misstates the manual-setup requirement for auto-started tunnels; neither affects normal operation. src/tools/cloud-provider.tool.ts (parseUploadResponse fallback) and src/resources/testmu-local.resource.ts (requirement wording)
|
| Filename | Overview |
|---|---|
| src/providers/cloud/testmu.provider.ts | New LambdaTest provider with browser/mobile browser/native app capability modes, tunnel lifecycle via @lambdatest/node-tunnel, and REST API session status reporting. REST response is now checked before logging success. |
| src/tools/cloud-provider.tool.ts | Adds TestMu to list_apps and upload_app tools. TestMu list_apps fetches both android and ios endpoints and merges; upload_app targets manual-api.lambdatest.com. The parseUploadResponse fallback produces a bare 'unknown' string (no lt:// prefix) if neither app_id nor app_url is present in the response. |
| src/resources/testmu-local.resource.ts | New MCP resource providing LambdaTest Tunnel binary download URLs and setup instructions per platform. Resource requirement text says MUST manually start before tunnel:true, but tunnel:true auto-starts via @lambdatest/node-tunnel without a manual binary download. |
| src/recording/code-generator.ts | Adds LambdaTest code generation branch detecting lt:options in capabilities; generates correct hub hostname, env var references, and tunnel setup code using @lambdatest/node-tunnel. |
| src/tools/session.tool.ts | Adds testmu to provider enum and testmuLocal as deprecated tunnel param; region description correctly scoped to 'saucelabs' only. Tunnel lifecycle correctly gated on effectiveTunnel === true. |
| src/providers/registry.ts | Registers testMuProvider in the provider map alongside browserstack and saucelabs. |
| tests/providers/testmu.provider.test.ts | Comprehensive unit tests covering all three capability modes, tunnel, reporting labels, credential handling, and onSessionClose REST/execute paths. |
Sequence Diagram
sequenceDiagram
participant C as MCP Client
participant S as session.tool.ts
participant P as TestMuProvider
participant LT as LambdaTest API
participant T as @lambdatest/node-tunnel
C->>S: start_session(provider:'testmu', tunnel:true)
S->>S: compute tunnelName (auto-generated)
S->>P: buildCapabilities(options + tunnelName)
P-->>S: "caps with lt:options.tunnel=true"
S->>P: "startTunnel({tunnelName})"
P->>T: "tunnel.start({user,key,tunnelName})"
T-->>P: tunnel handle
P-->>S: tunnelHandle
S->>LT: remote(hub.lambdatest.com, caps)
LT-->>S: sessionId
S-->>C: session started
C->>S: close_session()
S->>P: onSessionClose(sessionId, 'browser', result)
P->>LT: "PATCH /automation/api/v1/sessions/{id}"
LT-->>P: 200 OK
S->>P: stopTunnel(tunnelHandle)
P->>T: tunnel.stop()
T-->>P: stopped
S-->>C: session closed
Reviews (4): Last reviewed commit: "fix(mobile): Allow mobile browser sessio..." | Re-trigger Greptile
…ing SauceLabs/BrowserStack - Adding LambdaTunnel declaration to types - Intorducing resources for tunnel setup
6ec8417 to
c76e4da
Compare
|
|
||
| // Mobile browser/emulator mode (e.g. Chrome on Android emulator) | ||
| if (mobileBrowser) { | ||
| ltOptions.appiumVersion = '2.11.0'; |
There was a problem hiding this comment.
Don't hardcode appiumVersion for the virtual mobile-browser path. TestMu AI rejects a pinned version here with The Device/Appium version combination is not supported, I got this while testing this PR locally. Both 2.11.0 and latest fail, while omitting it succeeds (hub auto-selects).
Though, native real-device mode below accepts latest.
There was a problem hiding this comment.
Thank you! Could not get a trial yet, so this code was "best effort guessing" based on the other 2
| const body = { status_ind: result.status === 'passed' ? 'passed' : 'failed' }; | ||
| const apiUrl = `https://api.lambdatest.com/automation/api/v1/sessions/${sessionId}`; |
There was a problem hiding this comment.
This REST PATCH only works for web sessions. For mobile, the WebDriver sessionId isn't addressable here - it 404s (Either resource not found or already deleted); mobile sessions live under mobile-api.lambdatest.com keyed by an internal test_id, so status is silently never recorded.
Branch by sessionType: web keeps this PATCH; mobile (ios/android) should use the live handle - await browser.execute('lambda-status=' + status) (the hub intercepts it server-side; verified). The hook doesn't work for web, hence the split.
| " const ltAuth = Buffer.from(`${process.env.TESTMU_USERNAME}:${process.env.TESTMU_ACCESS_KEY}`).toString('base64');", | ||
| " await fetch('https://api.lambdatest.com/automation/api/v1/sessions/' + browser.sessionId, {", | ||
| " method: 'PATCH',", | ||
| " headers: { Authorization: 'Basic ' + ltAuth, 'Content-Type': 'application/json' },", | ||
| ' body: JSON.stringify({ status_ind: ltStatus })', |
There was a problem hiding this comment.
Same issue as the provider's onSessionClose, in the generated script: this REST PATCH 404s for real-device mobile sessions. Branch on history.type - emit await browser.execute('lambda-status=' + ltStatus) for ios/android, and keep the REST PATCH only for browser.
Something like:
const isMobile = history.type !== 'browser';
const statusUpdate = isMobile
? " await browser.execute('lambda-status=' + ltStatus);"
: [
" const ltAuth = Buffer.from(`${process.env.TESTMU_USERNAME}:${process.env.TESTMU_ACCESS_KEY}`).toString('base64');",
" await fetch('https://api.lambdatest.com/automation/api/v1/sessions/' + browser.sessionId, {",
" method: 'PATCH',",
" headers: { Authorization: 'Basic ' + ltAuth, 'Content-Type': 'application/json' },",
' body: JSON.stringify({ status_ind: ltStatus })',
' });',
].join('\n');| if (process.env.TESTMU_USERNAME) ltOptions.username = process.env.TESTMU_USERNAME; | ||
| if (process.env.TESTMU_ACCESS_KEY) ltOptions.accessKey = process.env.TESTMU_ACCESS_KEY; |
There was a problem hiding this comment.
I think we can drop these 2 as getConnectionConfig() already passes user/key at the connection level, I verified that it works fine without passing them as caps and just having them as env vars.
It could also leak the creds wherever the capabilities is parsed.
…tatus, credential deduplication
…s in case of Testmu and SauceLabs providers
|
I addressed the issues and the greptile comments. |
|
Thank you @Winify ! Above changes worked well in my testing |
Proposed changes
New Provider (src/providers/cloud/testmu.provider.ts)
TestMuProvider implements the SessionProvider interface with three capability modes (browser, mobile browser/emulator, mobile native app), tunnel lifecycle via
@lambdatest/node-tunnel, and session status reporting via LambdaTest REST API. UsesTESTMU_USERNAME/TESTMU_ACCESS_KEYenvironment variables, credentials never appear in tool call parameters.App Management (src/tools/cloud-provider.tool.ts)
Tunnel Binary Resource (src/resources/testmu-local.resource.ts)
wdio://testmu/local-binaryresource with platform-specific LambdaTest Tunnel binary download URLs and setup commands, matching the BrowserStack/Sauce Labs pattern.Closing #104
Types of changes
Checklist
Further comments
Reviewers: @webdriverio/project-committers