From 5700ad43d8956f25933b6fafc7ecd2a90214afa3 Mon Sep 17 00:00:00 2001 From: Denis Andrasec Date: Tue, 25 Feb 2025 14:24:12 +0100 Subject: [PATCH 1/3] group screenshot options in own class --- .../integration_test/web_sdk_test.dart | 2 +- flutter/example/lib/main.dart | 2 +- .../screenshot_event_processor.dart | 4 +-- .../integrations/screenshot_integration.dart | 2 +- flutter/lib/src/sentry_flutter_options.dart | 35 +++++++++++-------- .../screenshot_event_processor_test.dart | 24 ++++++------- .../screenshot_integration_test.dart | 2 +- min_version_test/lib/main.dart | 2 +- 8 files changed, 39 insertions(+), 34 deletions(-) diff --git a/flutter/example/integration_test/web_sdk_test.dart b/flutter/example/integration_test/web_sdk_test.dart index 48ecc2044c..9de5051f78 100644 --- a/flutter/example/integration_test/web_sdk_test.dart +++ b/flutter/example/integration_test/web_sdk_test.dart @@ -129,7 +129,7 @@ void main() { await restoreFlutterOnErrorAfter(() async { await SentryFlutter.init((options) { options.dsn = fakeDsn; - options.attachScreenshot = true; + options.screenshot.attachScreenshot = true; confOptions = options; }, appRunner: () async { diff --git a/flutter/example/lib/main.dart b/flutter/example/lib/main.dart index 50443cf6e7..2446e0c200 100644 --- a/flutter/example/lib/main.dart +++ b/flutter/example/lib/main.dart @@ -74,7 +74,7 @@ Future setupSentry( options.addIntegration(LoggingIntegration(minEventLevel: Level.INFO)); options.sendDefaultPii = true; options.reportSilentFlutterErrors = true; - options.attachScreenshot = true; + options.screenshot.attachScreenshot = true; options.attachViewHierarchy = true; // We can enable Sentry debug logging during development. This is likely // going to log too much for your app, but can be useful when figuring out diff --git a/flutter/lib/src/event_processor/screenshot_event_processor.dart b/flutter/lib/src/event_processor/screenshot_event_processor.dart index 2526a19db3..8aed12343c 100644 --- a/flutter/lib/src/event_processor/screenshot_event_processor.dart +++ b/flutter/lib/src/event_processor/screenshot_event_processor.dart @@ -16,7 +16,7 @@ class ScreenshotEventProcessor implements EventProcessor { late final Debouncer _debouncer; ScreenshotEventProcessor(this._options) { - final targetResolution = _options.screenshotQuality.targetResolution(); + final targetResolution = _options.screenshot.screenshotQuality.targetResolution(); _recorder = ScreenshotRecorder( ScreenshotRecorderConfig( width: targetResolution, @@ -50,7 +50,7 @@ class ScreenshotEventProcessor implements EventProcessor { // skip capturing in case of debouncing (=too many frequent capture requests) // the BeforeCaptureCallback may overrule the debouncing decision final shouldDebounce = _debouncer.shouldDebounce(); - final beforeCaptureScreenshot = _options.beforeCaptureScreenshot; + final beforeCaptureScreenshot = _options.screenshot.beforeCaptureScreenshot; try { FutureOr? result; diff --git a/flutter/lib/src/integrations/screenshot_integration.dart b/flutter/lib/src/integrations/screenshot_integration.dart index 00782ea4aa..850a72ede7 100644 --- a/flutter/lib/src/integrations/screenshot_integration.dart +++ b/flutter/lib/src/integrations/screenshot_integration.dart @@ -18,7 +18,7 @@ class ScreenshotIntegration implements Integration { ); return; } - if (options.attachScreenshot) { + if (options.screenshot.attachScreenshot) { _options = options; final screenshotEventProcessor = ScreenshotEventProcessor(options); options.addEventProcessor(screenshotEventProcessor); diff --git a/flutter/lib/src/sentry_flutter_options.dart b/flutter/lib/src/sentry_flutter_options.dart index ed39b63143..a37a2fb994 100644 --- a/flutter/lib/src/sentry_flutter_options.dart +++ b/flutter/lib/src/sentry_flutter_options.dart @@ -172,21 +172,8 @@ class SentryFlutterOptions extends SentryOptions { /// Enable auto performance tracking by default. bool enableAutoPerformanceTracing = true; - /// Automatically attaches a screenshot when capturing an error or exception. - /// - /// Requires adding the [SentryWidget] to the widget tree. - /// Example: - /// runApp(SentryWidget(child: App())); - /// The [SentryWidget] has to be the root widget of the app. - bool attachScreenshot = false; - - /// The quality of the attached screenshot - SentryScreenshotQuality screenshotQuality = SentryScreenshotQuality.high; - - /// Sets a callback which is executed before capturing screenshots. Only - /// relevant if `attachScreenshot` is set to true. When false is returned - /// from the function, no screenshot will be attached. - BeforeCaptureCallback? beforeCaptureScreenshot; + /// Collection of screenshot options. + SentryFlutterScreenshotOptions screenshot = SentryFlutterScreenshotOptions(); /// Enable or disable automatic breadcrumbs for User interactions Using [Listener] /// @@ -385,6 +372,24 @@ class _SentryFlutterExperimentalOptions { final privacy = SentryPrivacyOptions(); } +class SentryFlutterScreenshotOptions { + /// Automatically attaches a screenshot when capturing an error or exception. + /// + /// Requires adding the [SentryWidget] to the widget tree. + /// Example: + /// runApp(SentryWidget(child: App())); + /// The [SentryWidget] has to be the root widget of the app. + bool attachScreenshot = false; + + /// The quality of the attached screenshot + SentryScreenshotQuality screenshotQuality = SentryScreenshotQuality.high; + + /// Sets a callback which is executed before capturing screenshots. Only + /// relevant if `attachScreenshot` is set to true. When false is returned + /// from the function, no screenshot will be attached. + BeforeCaptureCallback? beforeCaptureScreenshot; +} + /// A callback which can be used to suppress capturing of screenshots. /// It's called in [ScreenshotEventProcessor] if screenshots are enabled. /// This gives more fine-grained control over when capturing should be performed, diff --git a/flutter/test/event_processor/screenshot_event_processor_test.dart b/flutter/test/event_processor/screenshot_event_processor_test.dart index 515def4fe2..c45f70aec9 100644 --- a/flutter/test/event_processor/screenshot_event_processor_test.dart +++ b/flutter/test/event_processor/screenshot_event_processor_test.dart @@ -83,7 +83,7 @@ void main() { testWidgets('does add screenshot in correct resolution for low', (tester) async { final height = SentryScreenshotQuality.low.targetResolution()!; - fixture.options.screenshotQuality = SentryScreenshotQuality.low; + fixture.options.screenshot.screenshotQuality = SentryScreenshotQuality.low; await _addScreenshotAttachment(tester, null, added: true, isWeb: false, expectedMaxWidthOrHeight: height); }); @@ -91,7 +91,7 @@ void main() { testWidgets('does add screenshot in correct resolution for medium', (tester) async { final height = SentryScreenshotQuality.medium.targetResolution()!; - fixture.options.screenshotQuality = SentryScreenshotQuality.medium; + fixture.options.screenshot.screenshotQuality = SentryScreenshotQuality.medium; await _addScreenshotAttachment(tester, null, added: true, isWeb: false, expectedMaxWidthOrHeight: height); }); @@ -99,7 +99,7 @@ void main() { testWidgets('does add screenshot in correct resolution for high', (tester) async { final widthOrHeight = SentryScreenshotQuality.high.targetResolution()!; - fixture.options.screenshotQuality = SentryScreenshotQuality.high; + fixture.options.screenshot.screenshotQuality = SentryScreenshotQuality.high; await _addScreenshotAttachment(tester, null, added: true, isWeb: false, expectedMaxWidthOrHeight: widthOrHeight); }); @@ -137,7 +137,7 @@ void main() { group('beforeCaptureScreenshot', () { testWidgets('does add screenshot if beforeCapture returns true', (tester) async { - fixture.options.beforeCaptureScreenshot = + fixture.options.screenshot.beforeCaptureScreenshot = (SentryEvent event, Hint hint, bool shouldDebounce) { return true; }; @@ -147,7 +147,7 @@ void main() { testWidgets('does add screenshot if async beforeCapture returns true', (tester) async { - fixture.options.beforeCaptureScreenshot = + fixture.options.screenshot.beforeCaptureScreenshot = (SentryEvent event, Hint hint, bool shouldDebounce) async { await Future.delayed(Duration(milliseconds: 1)); return true; @@ -158,7 +158,7 @@ void main() { testWidgets('does not add screenshot if beforeCapture returns false', (tester) async { - fixture.options.beforeCaptureScreenshot = + fixture.options.screenshot.beforeCaptureScreenshot = (SentryEvent event, Hint hint, bool shouldDebounce) { return false; }; @@ -168,7 +168,7 @@ void main() { testWidgets('does not add screenshot if async beforeCapture returns false', (tester) async { - fixture.options.beforeCaptureScreenshot = + fixture.options.screenshot.beforeCaptureScreenshot = (SentryEvent event, Hint hint, bool shouldDebounce) async { await Future.delayed(Duration(milliseconds: 1)); return false; @@ -179,7 +179,7 @@ void main() { testWidgets('does add screenshot if beforeCapture throws', (tester) async { fixture.options.automatedTestMode = false; - fixture.options.beforeCaptureScreenshot = + fixture.options.screenshot.beforeCaptureScreenshot = (SentryEvent event, Hint hint, bool shouldDebounce) { throw Error(); }; @@ -190,7 +190,7 @@ void main() { testWidgets('does add screenshot if async beforeCapture throws', (tester) async { fixture.options.automatedTestMode = false; - fixture.options.beforeCaptureScreenshot = + fixture.options.screenshot.beforeCaptureScreenshot = (SentryEvent event, Hint hint, bool shouldDebounce) async { await Future.delayed(Duration(milliseconds: 1)); throw Error(); @@ -204,7 +204,7 @@ void main() { await tester.runAsync(() async { var shouldDebounceValues = []; - fixture.options.beforeCaptureScreenshot = + fixture.options.screenshot.beforeCaptureScreenshot = (SentryEvent event, Hint hint, bool shouldDebounce) { shouldDebounceValues.add(shouldDebounce); return true; @@ -241,7 +241,7 @@ void main() { SentryEvent? beforeScreenshotEvent; Hint? beforeScreenshotHint; - fixture.options.beforeCaptureScreenshot = + fixture.options.screenshot.beforeCaptureScreenshot = (SentryEvent event, Hint hint, bool shouldDebounce) { beforeScreenshotEvent = event; beforeScreenshotHint = hint; @@ -342,7 +342,7 @@ class Fixture { SentryFlutterOptions options = defaultTestOptions(); Fixture() { - options.attachScreenshot = true; + options.screenshot.attachScreenshot = true; hub = Hub(options); } diff --git a/flutter/test/integrations/screenshot_integration_test.dart b/flutter/test/integrations/screenshot_integration_test.dart index b45b8d9d80..40827a185d 100644 --- a/flutter/test/integrations/screenshot_integration_test.dart +++ b/flutter/test/integrations/screenshot_integration_test.dart @@ -89,7 +89,7 @@ class Fixture { final options = defaultTestOptions(); ScreenshotIntegration getSut({bool attachScreenshot = true}) { - options.attachScreenshot = attachScreenshot; + options.screenshot.attachScreenshot = attachScreenshot; return ScreenshotIntegration(); } } diff --git a/min_version_test/lib/main.dart b/min_version_test/lib/main.dart index 9900260666..30ff61f6e4 100644 --- a/min_version_test/lib/main.dart +++ b/min_version_test/lib/main.dart @@ -27,7 +27,7 @@ Future setupSentry(AppRunner appRunner) async { options.addIntegration(LoggingIntegration()); options.sendDefaultPii = true; options.reportSilentFlutterErrors = true; - options.attachScreenshot = true; + options.screenshot.attachScreenshot = true; options.attachViewHierarchy = true; // We can enable Sentry debug logging during development. This is likely // going to log too much for your app, but can be useful when figuring out From 2c323c5faa0998b3fc748539010a090d8049a078 Mon Sep 17 00:00:00 2001 From: Denis Andrasec Date: Tue, 25 Feb 2025 14:25:29 +0100 Subject: [PATCH 2/3] rename properties --- .../integration_test/web_sdk_test.dart | 2 +- flutter/example/lib/main.dart | 2 +- .../screenshot_event_processor.dart | 4 ++-- .../integrations/screenshot_integration.dart | 4 ++-- flutter/lib/src/sentry_flutter_options.dart | 10 ++++---- .../screenshot_event_processor_test.dart | 24 +++++++++---------- .../screenshot_integration_test.dart | 2 +- min_version_test/lib/main.dart | 2 +- 8 files changed, 25 insertions(+), 25 deletions(-) diff --git a/flutter/example/integration_test/web_sdk_test.dart b/flutter/example/integration_test/web_sdk_test.dart index 9de5051f78..b278c7a95c 100644 --- a/flutter/example/integration_test/web_sdk_test.dart +++ b/flutter/example/integration_test/web_sdk_test.dart @@ -129,7 +129,7 @@ void main() { await restoreFlutterOnErrorAfter(() async { await SentryFlutter.init((options) { options.dsn = fakeDsn; - options.screenshot.attachScreenshot = true; + options.screenshot.attach = true; confOptions = options; }, appRunner: () async { diff --git a/flutter/example/lib/main.dart b/flutter/example/lib/main.dart index 2446e0c200..875b933534 100644 --- a/flutter/example/lib/main.dart +++ b/flutter/example/lib/main.dart @@ -74,7 +74,7 @@ Future setupSentry( options.addIntegration(LoggingIntegration(minEventLevel: Level.INFO)); options.sendDefaultPii = true; options.reportSilentFlutterErrors = true; - options.screenshot.attachScreenshot = true; + options.screenshot.attach = true; options.attachViewHierarchy = true; // We can enable Sentry debug logging during development. This is likely // going to log too much for your app, but can be useful when figuring out diff --git a/flutter/lib/src/event_processor/screenshot_event_processor.dart b/flutter/lib/src/event_processor/screenshot_event_processor.dart index 8aed12343c..ef9057bc28 100644 --- a/flutter/lib/src/event_processor/screenshot_event_processor.dart +++ b/flutter/lib/src/event_processor/screenshot_event_processor.dart @@ -16,7 +16,7 @@ class ScreenshotEventProcessor implements EventProcessor { late final Debouncer _debouncer; ScreenshotEventProcessor(this._options) { - final targetResolution = _options.screenshot.screenshotQuality.targetResolution(); + final targetResolution = _options.screenshot.quality.targetResolution(); _recorder = ScreenshotRecorder( ScreenshotRecorderConfig( width: targetResolution, @@ -50,7 +50,7 @@ class ScreenshotEventProcessor implements EventProcessor { // skip capturing in case of debouncing (=too many frequent capture requests) // the BeforeCaptureCallback may overrule the debouncing decision final shouldDebounce = _debouncer.shouldDebounce(); - final beforeCaptureScreenshot = _options.screenshot.beforeCaptureScreenshot; + final beforeCaptureScreenshot = _options.screenshot.beforeCapture; try { FutureOr? result; diff --git a/flutter/lib/src/integrations/screenshot_integration.dart b/flutter/lib/src/integrations/screenshot_integration.dart index 850a72ede7..1f6a58ffb0 100644 --- a/flutter/lib/src/integrations/screenshot_integration.dart +++ b/flutter/lib/src/integrations/screenshot_integration.dart @@ -3,7 +3,7 @@ import '../event_processor/screenshot_event_processor.dart'; import '../sentry_flutter_options.dart'; /// Adds [ScreenshotEventProcessor] to options event processors if -/// [SentryFlutterOptions.attachScreenshot] is true +/// [SentryFlutterOptions.attach] is true class ScreenshotIntegration implements Integration { SentryFlutterOptions? _options; ScreenshotEventProcessor? _screenshotEventProcessor; @@ -18,7 +18,7 @@ class ScreenshotIntegration implements Integration { ); return; } - if (options.screenshot.attachScreenshot) { + if (options.screenshot.attach) { _options = options; final screenshotEventProcessor = ScreenshotEventProcessor(options); options.addEventProcessor(screenshotEventProcessor); diff --git a/flutter/lib/src/sentry_flutter_options.dart b/flutter/lib/src/sentry_flutter_options.dart index a37a2fb994..7e82c0728d 100644 --- a/flutter/lib/src/sentry_flutter_options.dart +++ b/flutter/lib/src/sentry_flutter_options.dart @@ -379,15 +379,15 @@ class SentryFlutterScreenshotOptions { /// Example: /// runApp(SentryWidget(child: App())); /// The [SentryWidget] has to be the root widget of the app. - bool attachScreenshot = false; + bool attach = false; /// The quality of the attached screenshot - SentryScreenshotQuality screenshotQuality = SentryScreenshotQuality.high; + SentryScreenshotQuality quality = SentryScreenshotQuality.high; /// Sets a callback which is executed before capturing screenshots. Only - /// relevant if `attachScreenshot` is set to true. When false is returned - /// from the function, no screenshot will be attached. - BeforeCaptureCallback? beforeCaptureScreenshot; + /// relevant if `attach` is set to true. When false is returned from the + /// function, no screenshot will be attached. + BeforeCaptureCallback? beforeCapture; } /// A callback which can be used to suppress capturing of screenshots. diff --git a/flutter/test/event_processor/screenshot_event_processor_test.dart b/flutter/test/event_processor/screenshot_event_processor_test.dart index c45f70aec9..656eb588ca 100644 --- a/flutter/test/event_processor/screenshot_event_processor_test.dart +++ b/flutter/test/event_processor/screenshot_event_processor_test.dart @@ -83,7 +83,7 @@ void main() { testWidgets('does add screenshot in correct resolution for low', (tester) async { final height = SentryScreenshotQuality.low.targetResolution()!; - fixture.options.screenshot.screenshotQuality = SentryScreenshotQuality.low; + fixture.options.screenshot.quality = SentryScreenshotQuality.low; await _addScreenshotAttachment(tester, null, added: true, isWeb: false, expectedMaxWidthOrHeight: height); }); @@ -91,7 +91,7 @@ void main() { testWidgets('does add screenshot in correct resolution for medium', (tester) async { final height = SentryScreenshotQuality.medium.targetResolution()!; - fixture.options.screenshot.screenshotQuality = SentryScreenshotQuality.medium; + fixture.options.screenshot.quality = SentryScreenshotQuality.medium; await _addScreenshotAttachment(tester, null, added: true, isWeb: false, expectedMaxWidthOrHeight: height); }); @@ -99,7 +99,7 @@ void main() { testWidgets('does add screenshot in correct resolution for high', (tester) async { final widthOrHeight = SentryScreenshotQuality.high.targetResolution()!; - fixture.options.screenshot.screenshotQuality = SentryScreenshotQuality.high; + fixture.options.screenshot.quality = SentryScreenshotQuality.high; await _addScreenshotAttachment(tester, null, added: true, isWeb: false, expectedMaxWidthOrHeight: widthOrHeight); }); @@ -137,7 +137,7 @@ void main() { group('beforeCaptureScreenshot', () { testWidgets('does add screenshot if beforeCapture returns true', (tester) async { - fixture.options.screenshot.beforeCaptureScreenshot = + fixture.options.screenshot.beforeCapture = (SentryEvent event, Hint hint, bool shouldDebounce) { return true; }; @@ -147,7 +147,7 @@ void main() { testWidgets('does add screenshot if async beforeCapture returns true', (tester) async { - fixture.options.screenshot.beforeCaptureScreenshot = + fixture.options.screenshot.beforeCapture = (SentryEvent event, Hint hint, bool shouldDebounce) async { await Future.delayed(Duration(milliseconds: 1)); return true; @@ -158,7 +158,7 @@ void main() { testWidgets('does not add screenshot if beforeCapture returns false', (tester) async { - fixture.options.screenshot.beforeCaptureScreenshot = + fixture.options.screenshot.beforeCapture = (SentryEvent event, Hint hint, bool shouldDebounce) { return false; }; @@ -168,7 +168,7 @@ void main() { testWidgets('does not add screenshot if async beforeCapture returns false', (tester) async { - fixture.options.screenshot.beforeCaptureScreenshot = + fixture.options.screenshot.beforeCapture = (SentryEvent event, Hint hint, bool shouldDebounce) async { await Future.delayed(Duration(milliseconds: 1)); return false; @@ -179,7 +179,7 @@ void main() { testWidgets('does add screenshot if beforeCapture throws', (tester) async { fixture.options.automatedTestMode = false; - fixture.options.screenshot.beforeCaptureScreenshot = + fixture.options.screenshot.beforeCapture = (SentryEvent event, Hint hint, bool shouldDebounce) { throw Error(); }; @@ -190,7 +190,7 @@ void main() { testWidgets('does add screenshot if async beforeCapture throws', (tester) async { fixture.options.automatedTestMode = false; - fixture.options.screenshot.beforeCaptureScreenshot = + fixture.options.screenshot.beforeCapture = (SentryEvent event, Hint hint, bool shouldDebounce) async { await Future.delayed(Duration(milliseconds: 1)); throw Error(); @@ -204,7 +204,7 @@ void main() { await tester.runAsync(() async { var shouldDebounceValues = []; - fixture.options.screenshot.beforeCaptureScreenshot = + fixture.options.screenshot.beforeCapture = (SentryEvent event, Hint hint, bool shouldDebounce) { shouldDebounceValues.add(shouldDebounce); return true; @@ -241,7 +241,7 @@ void main() { SentryEvent? beforeScreenshotEvent; Hint? beforeScreenshotHint; - fixture.options.screenshot.beforeCaptureScreenshot = + fixture.options.screenshot.beforeCapture = (SentryEvent event, Hint hint, bool shouldDebounce) { beforeScreenshotEvent = event; beforeScreenshotHint = hint; @@ -342,7 +342,7 @@ class Fixture { SentryFlutterOptions options = defaultTestOptions(); Fixture() { - options.screenshot.attachScreenshot = true; + options.screenshot.attach = true; hub = Hub(options); } diff --git a/flutter/test/integrations/screenshot_integration_test.dart b/flutter/test/integrations/screenshot_integration_test.dart index 40827a185d..79a62daba5 100644 --- a/flutter/test/integrations/screenshot_integration_test.dart +++ b/flutter/test/integrations/screenshot_integration_test.dart @@ -89,7 +89,7 @@ class Fixture { final options = defaultTestOptions(); ScreenshotIntegration getSut({bool attachScreenshot = true}) { - options.screenshot.attachScreenshot = attachScreenshot; + options.screenshot.attach = attachScreenshot; return ScreenshotIntegration(); } } diff --git a/min_version_test/lib/main.dart b/min_version_test/lib/main.dart index 30ff61f6e4..506653a2aa 100644 --- a/min_version_test/lib/main.dart +++ b/min_version_test/lib/main.dart @@ -27,7 +27,7 @@ Future setupSentry(AppRunner appRunner) async { options.addIntegration(LoggingIntegration()); options.sendDefaultPii = true; options.reportSilentFlutterErrors = true; - options.screenshot.attachScreenshot = true; + options.screenshot.attach = true; options.attachViewHierarchy = true; // We can enable Sentry debug logging during development. This is likely // going to log too much for your app, but can be useful when figuring out From 8f529d0becfa1b5bde47fb5df21221b28b06735d Mon Sep 17 00:00:00 2001 From: Denis Andrasec Date: Tue, 25 Feb 2025 17:00:37 +0100 Subject: [PATCH 3/3] add cl entry --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 63263fec85..a45dc95600 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,7 +23,11 @@ - Responses are attached to the `Hint` object, which can be read in `beforeSend`/`beforeSendTransaction` callbacks via `hint.response`. - For now, only the `dio` integration is supported. - Enable privacy masking for screenshots by default ([#2728](https://github.com/getsentry/sentry-dart/pull/2728)) - +- Screenshot Grouping ([#2754](https://github.com/getsentry/sentry-dart/pull/2754)) + - Moved `SentryFlutterOptions.attachScreenshot` to `SentryFlutterOptions.screenshot.attach` + - Moved `SentryFlutterOptions.screenshotQuality` to `SentryFlutterOptions.screenshot.quality` + - Moved `SentryFlutterOptions.beforeCaptureScreenshot` to `SentryFlutterOptions.screenshot.beforeCapture` + ### Enhancements - Replay: improve Android native interop performance by using JNI ([#2670](https://github.com/getsentry/sentry-dart/pull/2670))