Repository navigation
fix: upload default replay screenshots at one size whether or not they are masked - #967
Merged
Merged
Conversation
Contributor
posthog-ios Compliance ReportDate: 2026-10-27T11:54:01.274652+00:00
|
| Test | Status | Duration |
|---|---|---|
| Format Validation.Event Has Required Fields | ✅ | 3001ms |
| Format Validation.Event Has Uuid | ✅ | 2856ms |
| Format Validation.Event Has Lib Properties | ✅ | 2930ms |
| Format Validation.Distinct Id Is String | ✅ | 2946ms |
| Format Validation.Token Is Present | ✅ | 428ms |
| Format Validation.Custom Properties Preserved | ✅ | 2867ms |
| Format Validation.Event Has Timestamp | ✅ | 2841ms |
| Format Validation.Non Utc Event Timestamp Is Converted To Utc | ✅ | 2919ms |
| Retry Behavior.Retries On 503 | ✅ | 11956ms |
| Retry Behavior.Does Not Retry On 400 | ✅ | 4922ms |
| Retry Behavior.Does Not Retry On 401 | ✅ | 2471ms |
| Retry Behavior.Respects Retry After Header | ✅ | 7962ms |
| Retry Behavior.Implements Backoff | ✅ | 15419ms |
| Retry Behavior.Retries On 500 | ✅ | 5528ms |
| Retry Behavior.Retries On 502 | ✅ | 9239ms |
| Retry Behavior.Retries On 504 | ✅ | 9189ms |
| Retry Behavior.Max Retries Respected | ❌ | 21406ms |
| Deduplication.Generates Unique Uuids | ✅ | 3279ms |
| Deduplication.Preserves Uuid On Retry | ✅ | 9270ms |
| Deduplication.Preserves Uuid And Timestamp On Retry | ✅ | 16240ms |
| Deduplication.Preserves Uuid And Timestamp On Batch Retry | ✅ | 7895ms |
| Deduplication.No Duplicate Events In Batch | ✅ | 3094ms |
| Deduplication.Different Events Have Different Uuids | ✅ | 3383ms |
| Compression.Sends Gzip When Enabled | ✅ | 2785ms |
| Batch Format.Uses Proper Batch Structure | ✅ | 513ms |
| Batch Format.Flush With No Events Sends Nothing | ✅ | 296ms |
| Batch Format.Multiple Events Batched Together | ✅ | 2962ms |
| Error Handling.Does Not Retry On 403 | ✅ | 4823ms |
| Error Handling.Does Not Retry On 413 | ✅ | 4790ms |
| Error Handling.Retries On 408 | ✅ | 9232ms |
Failures
retry_behavior.max_retries_respected
Expected 4 requests, got 6
Feature_Flags Tests
✅ 17/17 tests passed
View Details
| Test | Status | Duration |
|---|---|---|
| Request Payload.Request With Person Properties Device Id | ✅ | 2820ms |
| Request Payload.Flags Request Uses V2 Query Param | ✅ | 2828ms |
| Request Payload.Flags Request Hits Flags Path Not Decide | ✅ | 2998ms |
| Request Payload.Flags Request Omits Authorization Header | ✅ | 2906ms |
| Request Payload.Token In Flags Body Matches Init | ✅ | 2958ms |
| Request Payload.Groups Round Trip | ✅ | 2912ms |
| Request Payload.Groups Default To Empty Object | ✅ | 2987ms |
| Request Payload.Disable Geoip False Propagates As Geoip Disable False | ✅ | 2967ms |
| Request Payload.Disable Geoip Omitted Defaults To False | ✅ | 2841ms |
| Request Payload.Flag Keys To Evaluate Contains Only Requested Key | ✅ | 2926ms |
| Request Lifecycle.No Flags Request On Init Alone | ✅ | 90ms |
| Request Lifecycle.No Flags Request On Normal Capture | ✅ | 2747ms |
| Request Lifecycle.Two Flag Calls Produce Two Remote Requests | ✅ | 5868ms |
| Request Lifecycle.Mock Response Value Is Returned To Caller | ✅ | 3505ms |
| Retry Behavior.Retries Flags On 502 | ✅ | 2993ms |
| Retry Behavior.Retries Flags On 504 | ✅ | 3176ms |
| Side Effect Events.Get Feature Flag Captures Feature Flag Called Event | ✅ | 3183ms |
dustinbyrne
approved these changes
Oct 27, 2026
This was referenced Oct 30, 2026
Merged
5 of 6 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
💡 Motivation and Context
In screenshot mode, the size of an uploaded frame depended on whether it had masks.
UIView.toImagerenders at the screen's native scale, and only the mask pass inRRWireframe.maskImageredrew the image at scale 1. So on a 3x iPhone an unmasked frame went up at 1206x2622 and a masked one at 402x874. Nobody picked that on purpose, it just fell out of where the redraw happened.Every screenshot frame now goes through the same scale-1 redraw before encoding, masks or not, so it always uploads at the window size in points. No new config.
The capture itself doesn't change:
drawHierarchystill renders at native scale on the main thread. The resize happens inRRWireframe.toDict, which runs on the replay dispatch queue, with the same.noneinterpolation the mask pass already used. So unmasked frames now look exactly like masked frames already did. Wireframe-mode images are left alone, the redraw only applies toscreenshotwireframes.Cross-SDK:
view.width/height * screenshotScaleand paints masks onto it). Its default size is different though:screenshotScaledefaults to1.0, which is full physical pixels, while iOS uploads at points.session-replay-privacycovers when masks get painted but says nothing about screenshot size, so this doesn't diverge from any spec.💚 How did you test it?
New suite
PostHogReplayScreenshotSizeTest(Swift Testing, iOS simulator):render(in:),drawHierarchy,afterScreenUpdates)The first two fail on
mainand pass here. The last one guards the scope, so it passes on both.Real pipeline (
toImage→toDict→ WebP) with the example app as test host, iPhone 17 Pro simulator, a 402x874 settings-style list:Masked frames come out the same size in bytes as before. Encoding an unmasked frame on the replay queue also gets a lot cheaper, since WebP now gets a ninth of the pixels.
I also ran it on a physical iPhone 17 Pro against a local mock server, with a screen that switches every 3 s between nothing maskable and a manual mask + text field + image:
All 29 frames from the PR build uploaded and decoded, and in the masked ones the manual mask, the text field and the image are all covered.
Full iOS simulator suite (1,603 tests) and the hosted presentation-masking suites (31 tests) pass, plus
make lintandmake buildSdkfor iOS, macOS and Mac Catalyst.📝 Checklist
If releasing new changes
pnpm changesetto generate a changeset file🤖 Agent context
DRI: @ioannisj
Autonomy: Human-driven (agent-assisted)