diff --git a/test/Sentry.Unity.Tests/ScreenshotEventProcessorTests.cs b/test/Sentry.Unity.Tests/ScreenshotEventProcessorTests.cs
index 81891bc47..de781814f 100644
--- a/test/Sentry.Unity.Tests/ScreenshotEventProcessorTests.cs
+++ b/test/Sentry.Unity.Tests/ScreenshotEventProcessorTests.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections;
using System.IO;
+using System.Text.RegularExpressions;
using NUnit.Framework;
using Sentry.Unity.Tests.Stubs;
using UnityEngine;
@@ -10,6 +11,26 @@ namespace Sentry.Unity.Tests;
public class ScreenshotEventProcessorTests
{
+ ///
+ /// Subclass that mocks screenshot capture and WaitForEndOfFrame but uses the REAL
+ /// CaptureAttachment implementation (via Hub.CaptureAttachment), allowing us to verify
+ /// that the attachment envelope actually reaches the HTTP transport.
+ ///
+ private class RealCaptureScreenshotEventProcessor : ScreenshotEventProcessor
+ {
+ public RealCaptureScreenshotEventProcessor(SentryUnityOptions options, ISentryMonoBehaviour sentryMonoBehaviour)
+ : base(options, sentryMonoBehaviour) { }
+
+ internal override Texture2D CreateNewScreenshotTexture2D(SentryUnityOptions options)
+ => new Texture2D(1, 1);
+
+ internal override YieldInstruction WaitForEndOfFrame()
+ => new YieldInstruction();
+
+ // CaptureAttachment is intentionally NOT overridden — the base implementation
+ // calls Hub.CaptureAttachment which sends a standalone attachment envelope.
+ }
+
private class TestScreenshotEventProcessor : ScreenshotEventProcessor
{
public Func CreateScreenshotFunc { get; set; }
@@ -348,6 +369,100 @@ public IEnumerator Process_BeforeCaptureScreenshotCallbackReturnsTrue_CapturesSc
Assert.AreEqual(1, screenshotCaptureCallCount);
}
+ [UnityTest]
+ public IEnumerator Process_EventCapturedSuccessfully_ScreenshotAttachmentIsSent()
+ {
+ // Positive control: when the event IS captured, the screenshot coroutine should send
+ // the attachment. This validates the test infrastructure so the negative test below
+ // is meaningful — if this test passes but the next one doesn't, the WasCaptured flag
+ // is doing its job.
+
+ var httpHandler = new TestHttpClientHandler("ScreenshotSuccessTest");
+ var sentryMonoBehaviour = GetTestMonoBehaviour();
+
+ var options = new SentryUnityOptions(application: new TestApplication())
+ {
+ Dsn = SentryTests.TestDsn,
+ CreateHttpMessageHandler = () => httpHandler
+ };
+
+ // Register test screenshot processor as an event processor — it will be called
+ // during DoSendEvent → ProcessEvent, just like the real ScreenshotEventProcessor.
+ options.AddEventProcessor(new RealCaptureScreenshotEventProcessor(options, sentryMonoBehaviour));
+
+ SentrySdk.Init(options);
+
+ try
+ {
+ // Event goes through the full DoSendEvent pipeline and is captured successfully.
+ // DoSendEvent sets @event.WasCaptured = true after CaptureEnvelope succeeds.
+ var capturedId = SentrySdk.CaptureMessage("test message");
+ Assert.AreNotEqual(SentryId.Empty, capturedId, "Sanity check: event should be captured");
+
+ // Wait for the screenshot coroutine to complete
+ yield return null;
+ yield return null;
+
+ // Screenshot envelope should reach the transport
+ var screenshotRequest = httpHandler.GetEvent("screenshot.jpg", TimeSpan.FromSeconds(2));
+ Assert.IsNotEmpty(screenshotRequest,
+ "Screenshot attachment should be sent when the event is captured successfully");
+ }
+ finally
+ {
+ SentrySdk.Close();
+ }
+ }
+
+ [UnityTest]
+ public IEnumerator Process_EventDroppedByBeforeSend_ScreenshotAttachmentIsNotSent()
+ {
+ // Full pipeline test: the event goes through DoSendEvent where before_send drops it.
+ // The screenshot coroutine (queued during ProcessEvent, before the drop decision)
+ // must check WasCaptured and skip — no orphaned attachment envelope.
+
+ var httpHandler = new TestHttpClientHandler("ScreenshotBeforeSendTest");
+ var sentryMonoBehaviour = GetTestMonoBehaviour();
+
+ var options = new SentryUnityOptions(application: new TestApplication())
+ {
+ Dsn = SentryTests.TestDsn,
+ CreateHttpMessageHandler = () => httpHandler
+ };
+
+ // Register test screenshot processor — called during DoSendEvent → ProcessEvent
+ options.AddEventProcessor(new RealCaptureScreenshotEventProcessor(options, sentryMonoBehaviour));
+
+ // Drop all events via before_send
+ options.SetBeforeSend((_, _) => null);
+
+ SentrySdk.Init(options);
+
+ try
+ {
+ // CaptureMessage goes through the full DoSendEvent pipeline:
+ // ProcessEvent → screenshot processor queues coroutine with @event in closure
+ // DoBeforeSend → returns null → event dropped, WasCaptured stays false
+ var capturedId = SentrySdk.CaptureMessage("test message");
+ Assert.AreEqual(SentryId.Empty, capturedId, "Sanity check: before_send should drop events");
+
+ // Wait for the screenshot coroutine to complete
+ yield return null;
+ yield return null;
+
+ // No screenshot envelope should reach the transport.
+ // GetEvent logs Debug.LogError on timeout — tell the test runner this is expected.
+ LogAssert.Expect(LogType.Error, new Regex("timed out"));
+ var screenshotRequest = httpHandler.GetEvent("screenshot.jpg", TimeSpan.FromSeconds(2));
+ Assert.IsEmpty(screenshotRequest,
+ "Screenshot attachment should not be sent when before_send drops the event");
+ }
+ finally
+ {
+ SentrySdk.Close();
+ }
+ }
+
private static TestSentryMonoBehaviour GetTestMonoBehaviour()
{
var gameObject = new GameObject("ScreenshotProcessorTest");