Commit Graph

2 Commits

Author SHA1 Message Date
Ignacy Łątka aa13120abf fix(screenshot): wait for first frame so multi-sim captures don't fail (#391) (#399)
After more investigation i've found a much better fix for #391. 
I'm merging this either way, because it blocks 0.13.0 release and we
already have this fix tested and approved.
By the next release, I want to revert this patch in favour of fixing
core of the issue in simserver.

Regression introduced by
https://github.com/software-mansion/radon/pull/101 adding the streaming
feature to the argent simulator-server build, which made
`/api/screenshot` serve the last streamed frame async instead of
capturing on demand.

<details>

## Summary

Fixes #391 — `screenshot` failing with `no image to export` when more
than one simulator is booted, even with an explicit `--udid`. Regression
in 0.12.x.

## Root cause

The simulator-server captures screenshots from its **live frame
stream**, so a capture only succeeds once the first frame has arrived.
Immediately after the server starts streaming, `POST /api/screenshot`
replies `HTTP 200 { error: "no image to export" }` until that first
frame lands (~0.5–1s). The tool-server requested the capture right after
the server became ready, **losing that race**.

- With a single, foreground simulator the first frame arrives fast
enough that the race is usually won — hiding the bug.
- With more than one simulator booted, the target is often backgrounded
and its first frame is delayed, so the race is lost **reliably** → `no
image to export`.

This became reachable when streaming was enabled for the argent
simulator-server build in 0.12.x (radon
[#101](https://github.com/software-mansion/radon/pull/101)); the 0.11.0
non-streaming build captured frames on demand, so it was immune. Device
selection itself is correct — each device's stream captures the right
device (verified: Pro Max → 1320×2868, iPhone 15 → 1179×2556).

## Fix

`httpScreenshot` now polls past that specific transient (every 250ms, up
to 6s) instead of failing immediately, while still surfacing every other
error (non-2xx, Android `wrong data size`, missing url/path) right away.

## Verification

End-to-end against the **shipped** simulator-server binary with
**three** booted simulators, forcing a fresh sim-server spawn before
each capture:

| | result |
|---|---|
| **unfixed** (current `dist`) | `no image to export` ~2/3 of
fresh-spawn attempts |
| **fixed** | 7/7 success (~800ms), returns a correct PNG (1320×2868) |

- `npm test` (tool-server): **1199 passed**, including new retry /
give-up cases for `httpScreenshot`
- `npx tsc --build`, `prettier --check`, `typecheck:tests`: clean

</details>
2026-06-24 21:00:54 +02:00
Ignacy Łątka 8c080c9189 fix(screenshot-diff): make live capture work on Android + surface real capture errors (#307)
## Problem

`screenshot-diff` with `baselinePath` + `captureCurrent: true` — the
tool's own
documented *"common visual-regression flow"* — fails reproducibly on
Android with:

> Screenshot failed: server response missing url or path. The
simulator-server may be misconfigured. Try restarting it.

That message is misleading (the server is healthy), and the flow is 100%
broken on Android.

## Root cause (verified against a live emulator)

`captureLiveInput` requests a **full-resolution** capture (`scale:
1.0`). The Android
simulator-server can't stream some framebuffers at full res and returns
**HTTP 200**
with an `error` field. Confirmed directly against the running
simulator-server:

| request | response |
|---|---|
| `{"scale":0.5}` | `{"path":…,"url":…}` ✅ |
| `{}` (full res) | `{"error":"wrong data size, expected 7853760 got
17627328"}` ❌ |
| `{"scale":1.0}` | `{"error":"wrong data size, expected 7853760 got
17627328"}` ❌ |

(`7853760 = 1080×2424×3`; the emulator returns ~2.24× that.)

Two compounding bugs:
1. **`httpScreenshot`** only reads `error` on a non-2xx status. On the
`200 + {error}` path it falls through to the generic *"missing url or
path … restart it"*, hiding the real cause.
2. **`captureLiveInput`** hard-codes `scale: 1.0` with no fallback, so
the whole diff fails on Android.

## Fix (TS-only)

- **`httpScreenshot`**: when `url`/`path` are missing, surface
`resBody.error` if present (so *"wrong data size …"* reaches the caller)
before the generic hint.
- **`captureLiveInput`**: attempt full-res, and on any failure **retry
at the server's default scale**, which captures reliably. #285's
same-aspect normalization already makes a scaled capture diff-compatible
with a baseline saved at any scale, and full-res is preserved wherever
it works (iOS). If the fallback also fails, its error propagates.

## Tests

`screenshot-diff-tool.test.ts` — full-res→default-scale fallback
(asserts the `1.0` call then a default-scale retry), and error
propagation when both fail.
`simulator-client.test.ts` (new) — `httpScreenshot` surfaces the server
`error` on a `200+{error}` response, still uses the generic hint when
there's genuinely no info, and returns `url`/`path` on success.

All 21 screenshot-diff/simulator-client tests pass; `tsc` clean on the
changed files.

## Scope note

The underlying inability to capture a full-resolution Android frame is a
**simulator-server (native)** issue — the *"wrong data size"*
framebuffer mismatch. This PR makes the tool **degrade gracefully**
(works at the default scale) and **diagnose clearly** instead of failing
outright; a proper native fix for full-res Android capture is a separate
follow-up.
2026-06-09 10:55:21 +02:00