Skip to content

fix(feedback): Keep the User Feedback screenshot trigger active after form dismissal#8048

Open
tsushanth wants to merge 8 commits into
getsentry:mainfrom
tsushanth:fix/feedback-screenshot-trigger-only-once
Open

fix(feedback): Keep the User Feedback screenshot trigger active after form dismissal#8048
tsushanth wants to merge 8 commits into
getsentry:mainfrom
tsushanth:fix/feedback-screenshot-trigger-only-once

Conversation

@tsushanth

@tsushanth tsushanth commented Jun 11, 2026

Copy link
Copy Markdown

📜 Description

Fixes showFormForScreenshots = true only working once per app launch.

The screenshot observer now stays registered, and screenshot events are ignored while a feedback form is already displayed. The widget early-return path also registers the screenshot observer.

💡 Motivation and Context

Fixes #7641.

Previously, userCapturedScreenshot() removed the screenshot observer before presenting the form, but it was never re-registered after dismissal.

💚 How did you test it?

Added coverage for screenshot behavior during/after form presentation and observer cleanup on driver deinit.

  • make test-ios IOS_SIMULATOR_OS=18.5 IOS_DEVICE_NAME='iPhone 16e' ONLY_TESTING='SentryTests/UserFeedbackIntegrationTests'

📝 Checklist

  • I added tests to verify the changes.
  • No new PII added or SDK only sends newly added PII if sendDefaultPII is enabled.
  • I updated the docs if needed.
  • I updated the wizard if needed.
  • Review from the native team if needed.
  • No breaking change or entry added to the changelog.
  • No breaking change for hybrid SDKs or communicated to hybrid SDKs.
  • If I added a new public API, I also added it to the SentryObjC wrapper.

@denrase denrase left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for contributing and finding this! 🥳Left some feedback.

Comment thread Sources/Swift/Integrations/UserFeedback/SentryUserFeedbackIntegrationDriver.swift Outdated
Comment thread Tests/SentryTests/Integrations/Feedback/UserFeedbackIntegrationTests.swift Outdated
@tsushanth

Copy link
Copy Markdown
Author

Thanks @denrase, good catches on both. Pushed bfa4978:

  1. userCapturedScreenshot now guards on displayingForm and leaves the observer attached. userFeedbackFormDidClose no longer calls observeScreenshots() and the stopObservingScreenshots() call inside userCapturedScreenshot is gone. That covers the no-presenter / already-presented cases for free and mirrors the existing handleShakeGesture pattern.

  2. The two regression tests are collapsed into one four-step sequence: present, post-during-active (still 1), dismiss, post-after-close (now 2).

The merge conflict on the branch is on .github/workflows/* files I can't update through this PR (my PAT doesn't carry the workflow scope), so I left them as-is rather than force-push a rebased branch that would fail server-side anyway. Happy to resolve via the "update branch" button or however you'd like to handle it once you're ready to merge.

@tsushanth

Copy link
Copy Markdown
Author

Pushed 61dc439 — dropped the stopObservingScreenshots() + re-register from observeScreenshots() per your suggestion. The function is now byte-identical to main; the only behavior change vs main is the displayingForm guard inside userCapturedScreenshot. That single guard covers all three subcases you flagged (active form, missing presenter, already-presented).

For the test side, the file only has one new test in the PR — testScreenshotTrigger_presentsExactlyOncePerScreenshotAcrossDismissal — and it's already shaped exactly as you described: post → present (1), post again with form still up → still 1, dismiss form, post → present (2). So nothing to collapse on the test side, but let me know if you'd like the comment trimmed.

When showFormForScreenshots = true, userCapturedScreenshot() removes the
UIApplication.userDidTakeScreenshotNotification observer before presenting
the form, so holding the form open doesn't keep re-presenting it. The
observer was never re-registered when the form was dismissed, so every
subsequent screenshot was silently ignored — the feedback flow only
worked once per app launch.

Re-register in userFeedbackFormDidClose(_:) and make observeScreenshots()
idempotent (it now removes any existing observer first) so the
re-register call can't end up attaching two observers and presenting
the form twice for a single screenshot.

Adds two regression tests against the previously test-uncovered
SentryUserFeedbackIntegrationDriver:

- testScreenshotTrigger_afterFormDismissal_stillPresentsForm — fails on
  main with presentCallCount stuck at 1; passes after the fix.
- testScreenshotTrigger_afterFormDismissal_presentsFormOnlyOncePerScreenshot
  — guards against a double-fire regression if observeScreenshots() were
  ever called twice in the future.

Closes getsentry#7641
Per maintainer review, simplify the fix: instead of removing the screenshot
observer before presenting and re-registering it after dismissal, guard
userCapturedScreenshot on the existing displayingForm flag and leave the
observer attached for the integration's lifetime. This mirrors how
handleShakeGesture already handles the same race, and covers the cases
where there is no presenter for the form or one is already presented
without having to think about observer lifecycle.

Collapse the two regression tests into one four-step sequence: first
screenshot presents once, second screenshot while form is active still
shows one presentation, dismiss the form, third screenshot presents a
second time. This is the exact scenario reported in getsentry#7641 plus the
double-present guard.
denrase noted observing the screenshot notification once at integration
start, plus the existing displayingForm guard in userCapturedScreenshot,
is enough — no de-register / re-register dance needed. The original
implementation matches main again; the only behavior change vs main is
the guard.
@tsushanth tsushanth force-pushed the fix/feedback-screenshot-trigger-only-once branch from 61dc439 to 647f51a Compare June 14, 2026 16:41
@denrase

denrase commented Jun 15, 2026

Copy link
Copy Markdown
Collaborator

@tsushanth Thank you, I will take a look and push it over the finish line if we need anything else. 🙇

@denrase denrase left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tsushanth Thank you for contibuting! :bow I have added an additional test and cleaned up a bit. Doing general commit now to give other maintainers a change to review (/cc @philprime ) to have a second pair of eyes.

@denrase denrase added the ready-to-merge Use this label to trigger all PR workflows label Jun 15, 2026
@denrase denrase changed the title fix(feedback): re-register screenshot observer after form dismissal fix(feedback): Keep the User Feedback screenshot trigger active after form dismissal Jun 15, 2026
@codecov

codecov Bot commented Jun 15, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 88.88889% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 87.479%. Comparing base (36c25b3) to head (d3f78b6).
⚠️ Report is 7 commits behind head on main.

Files with missing lines Patch % Lines
...Feedback/SentryUserFeedbackIntegrationDriver.swift 88.888% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@              Coverage Diff              @@
##              main     #8048       +/-   ##
=============================================
+ Coverage   87.401%   87.479%   +0.078%     
=============================================
  Files          559       559               
  Lines        32170     32172        +2     
  Branches     13156     13159        +3     
=============================================
+ Hits         28117     28144       +27     
+ Misses        4004      3981       -23     
+ Partials        49        47        -2     
Files with missing lines Coverage Δ
...Feedback/SentryUserFeedbackIntegrationDriver.swift 70.833% <88.888%> (+15.748%) ⬆️

... and 5 files with indirect coverage changes


Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 36c25b3...d3f78b6. Read the comment docs.

…shot-trigger-only-once

# Conflicts:
#	Sources/Swift/Integrations/UserFeedback/SentryUserFeedbackIntegrationDriver.swift
@denrase denrase enabled auto-merge (squash) June 16, 2026 15:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-to-merge Use this label to trigger all PR workflows

Projects

None yet

Development

Successfully merging this pull request may close these issues.

showFormForScreenshots only works once

3 participants