Skip to content

fix(provider-webdriver): upload the file the materializer names for a hosted install - #3167

Closed
amankansal-lt wants to merge 5 commits into
callstack:mainfrom
LambdaTest:split/materialized-upload
Closed

amankansal-lt wants to merge 5 commits into
callstack:mainfrom
LambdaTest:split/materialized-upload

Conversation

@amankansal-lt

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

Copy link
Copy Markdown
Contributor

Summary

Split out of #3118 and stacked on #3164. Please review only the top two commits.

  • A hosted install of a materialized iOS build (an artifact zip or a zipped simulator .app) now uploads the file named by the Apple materializer's new uploadPath. Before, it uploaded the extracted .app directory, which failed with EISDIR.
  • Review nit [4] from feat(provider-webdriver): add TestMu AI device cloud provider #3118: the "upload must be a regular file" rule is now enforced once, in appFileUploadForm. That covers both install-from-source and --provider-app.
    • A missing, directory or FIFO path is now INVALID_ARGS before any request is made.
    • appFileUploadForm takes a third argument, { provider, service }. The error details carry the provider id, and the message names the service.

Validation

  • pnpm check:affected --base split/hub-helpers --run passes, and eager-closure-budgets passes.
  • Unit coverage only. Not run live against BrowserStack.

🤖 Generated with Claude Code

Review in cubic

amankansal-lt and others added 4 commits October 3, 2026 11:09
BrowserStack's app upload, install adapter, --provider-app resolution,
session-details URL artifacts, and orientation check lived inline in
the BrowserStack modules. Move the vendor-neutral mechanics into shared
helpers that BrowserStack calls with its own form field, reference
scheme, and response reader. This is groundwork for a WebDriver host
port that accepts external provider definitions.

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

BrowserStack gains two fixes from 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.
The test holds the fetch open until the armed signal aborts and checks
that the signal is the 15 second deadline.

Connection verification gets the same treatment through
fetchProviderVerificationJson, which BrowserStack now uses in place of
its private fetch, so one helper owns the 15 second provider API
deadline. 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>
… hosted install

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.

Materialization now records the archive an installable was extracted
from directly, and the Apple materializer declares the file a hosted
provider uploads: the .ipa itself, or the zip a simulator .app was
extracted from. An outer archive that merely wrapped either, such as a
URL .zip around an .ipa, is never it, and nothing is named when no such
file exists (a .tar, for instance).

The deployment runtime uploads the declared file and otherwise the
installable path, with no inference of its own. A provider without an
uploader still installs the extracted bundle path, and the bundle id and
launch target hints are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…y hosted upload

A materialized build that names no uploadable file falls back to its
installable path, which for iOS is the extracted .app directory. The
deployment runtime handed that to the hub's uploader, and BrowserStack's
read it as a file and failed with a raw EISDIR. A --provider-app that
points at a directory failed the same way.

appFileUploadForm, which every hub upload builds its form with, now
refuses any path that is not an existing regular file with INVALID_ARGS
before any request is sent. The message names the hub's display label,
the details carry the provider id and path, and the hint lists the files
a hosted upload takes. Both the materialized install and --provider-app
routes go through it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@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.

All reported issues were addressed across 16 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/provider-webdriver/src/webdriver-utils.test.ts
…d read

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@thymikee

thymikee commented Oct 3, 2026

Copy link
Copy Markdown
Member

The change in 8ee4364 reads well, but it is not proven to fix the reported failure, because the BrowserStack install-from-source route now uploads a different file and nothing has run it against BrowserStack. CI is green, with one check reported and passing, and no conflicts.

The install-from-source upload choice now sends a zip or .ipa instead of the extracted .app directory, and both upload routes gain a new refusal. The evidence is unit tests with stubbed fetch and stubbed uploaders, and the PR body says it was not run live. If BrowserStack rejects the uploaded file, for example an outer GitHub artifact zip holding a bare .app, the user sees a hub COMMAND_FAILED where they used to see EISDIR. Please run install-from-source against a live BrowserStack iOS session for (a) a URL or GitHub artifact zip containing a simulator .app and (b) a zip that wraps an .ipa. Show output naming the uploaded file, the bs:// reference that came back, and a successful install or launch. Also run --provider-app <dir> and show the INVALID_ARGS refusal before any request. If no account is available, please say so and note the remaining risk.

Could one shared hosted-upload step own the choice of upload file instead? The change adds containingArchivePath to the MaterializedAppSource contract and threads it through provision-kit, platform-apple and provider-webdriver, while prepareLimrunIosAsset already uploads a file as given and otherwise zips the .app directory. Lifting that into a shared step would make a bare .app or a tar.gz-wrapped .app uploadable instead of refused, with no new contract field. As written, two providers decide what to upload by different rules. Please decide whether the materializer (the uploadPath contract) or a shared provider-side preparer owns that choice, then move Limrun onto the same owner.

Not blocking: when a GitHub artifact zip holds a bare Demo.app under subdirectories or next to other files such as dSYMs, iosUploadPath names the whole artifact zip, and BrowserStack expects the .app at the zip root. The new test only covers Demo.app at the root. Please confirm this case in the live run. If the hub rejects it, name an uploadPath only when the .app is at the zip root, or zip the resolved .app the way Limrun does. These can be taken or left.

I read the code and did not run the tests. I also could not check how BrowserStack's upload API treats a nested or crowded outer zip. No test covers the handoff from prepareIosInstallArtifact into the webdriver deployMaterializedApp, but that handoff is a typed passthrough that I checked by reading. I reviewed only the work beyond #3164.

On the open thread from the other reviewer, the mkfifo refusal test thread no longer applies, because 8ee4364 adds that test in webdriver-utils.test.ts, so you can resolve it.

Before merge, a live BrowserStack iOS install-from-source run must show that the zip or .ipa named by uploadPath is accepted and the app installs.

@amankansal-lt

Copy link
Copy Markdown
Contributor Author

This landed on main through the squash merge of #3169, which was stacked on it. The review-round test commits that came later are in 3221 (#3221). Closing as superseded.

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