fix(core): [PER-9706] sync Automate snapshots fail with "upload is not async iterable"#2314
fix(core): [PER-9706] sync Automate snapshots fail with "upload is not async iterable"#2314Shivanshu-07 wants to merge 2 commits into
Conversation
percy.upload() is the generatePromise-wrapped method and returns a Promise,
not an async iterable. The sync branches of /percy/comparison,
/percy/maestro-screenshot and /percy/automateScreenshot were changed (while
fixing Maestro sync) to `for await (const _ of percy.upload(...))`, which
throws "upload is not async iterable". SDKs surface this as
{"error":"upload is not async iterable"} on every sync snapshot, regressing
Percy-on-Automate sync (sync: true) on @percy/cli 1.32.0-1.32.2.
generatePromise already drives the underlying async generator to completion,
so calling percy.upload() inside the Promise executor enqueues #snapshots and
lets the sync queue resolve/reject the attached callback. Revert all three
sync routes to that proven 1.31.8 pattern (the raw generator remains
available at percy.yield.upload() if direct iteration is ever needed).
The original drain-canary tests passed only because they mocked percy.upload
to return an async generator, contradicting its real shape. Replace them with
regression tests that model the real return shape (a Promise that resolves
the callback asynchronously), so async-iterating the return value would fail.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tests Address PR review on the sync relay fix: - Add a trailing .catch(reject) to percy.upload() at all three sync routes (/percy/comparison, /percy/maestro-screenshot, /percy/automateScreenshot) so a generator error that bypasses the sync-queue callback (e.g. a throw before the queue task runs) is surfaced as data.error via handleSyncJob instead of leaking an unhandled rejection and hanging the request. - Assert percy.client.getComparisonDetails was called in the three sync regression tests, proving handleSyncJob ran to completion rather than the request resolving early. - Add a /percy/comparison test that a rejected upload Promise (no callback invoked) surfaces as data.error, directly covering the new .catch(reject). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude Code PR ReviewPR: #2314 • Head: 61cce14 • Reviewers: stack:code-reviewer (2 rounds) SummaryFixes a regression (@percy/cli 1.32.0–1.32.2) where synchronous Percy-on-Automate snapshots fail with Review Table
FindingsNo Critical or High findings. The first review round raised one High (discarded upload Promise could cause unhandledRejection + hung sync request) and one Medium (tests should assert Remaining observations are non-blocking (Low/Info):
Verdict: PASS |
Problem
Fixes PER-9706 / #2311.
On
@percy/cli1.32.0–1.32.2, synchronous Percy-on-Automate snapshots (sync: true) fail. Instead of comparison JSON, SDKs receive:{"error":"upload is not async iterable"}Screenshot capture on BrowserStack Automate succeeds; the failure is in the CLI sync upload path after capture. Works on 1.31.8.
Root cause
percy.uploadis wrapped inpercy.jsso the public method returns a Promise viageneratePromise():The Maestro-sync change (commit
545d498, PR #2217) rewrote the sync branches of/percy/comparison,/percy/maestro-screenshotand/percy/automateScreenshotto:A
Promiseis not async-iterable, sofor awaitthrowsupload is not async iterable, which propagates to the SDK. The raw async generator ispercy.yield.upload(), notpercy.upload().Fix
generatePromisealready drives the underlying generator to completion, so callingpercy.upload()inside thePromiseexecutor enqueues#snapshotsand lets the sync queue resolve/reject the attached callback. Revert all three sync routes to the proven 1.31.8 pattern:Tests
The original "drain canary" tests passed only because they mocked
percy.uploadto return an async generator — contradicting its real Promise shape, which is why the regression slipped through CI. They're replaced with regression tests that model the real return shape (a Promise that resolves the sync callback asynchronously), so async-iterating the return value would fail. Allapi.test.jssync specs (/percy/comparison,/percy/automateScreenshot,/percy/maestro-screenshot) pass.🤖 Generated with Claude Code