diff --git a/.github/workflows/flutter_integration_test.yml b/.github/workflows/flutter_integration_test.yml new file mode 100644 index 000000000..191115d6f --- /dev/null +++ b/.github/workflows/flutter_integration_test.yml @@ -0,0 +1,81 @@ +name: flutter integration tests +on: + push: + branches: + - main + pull_request: + +jobs: + cancel-previous-workflow: + runs-on: ubuntu-latest + steps: + - name: Cancel Previous Runs + uses: styfle/cancel-workflow-action@b173b6ec0100793626c2d9e6b90435061f4fc3e5 # pin@0.11.0 + with: + access_token: ${{ github.token }} + + test-android: + runs-on: macos-latest + defaults: + run: + working-directory: ./flutter/example + strategy: + matrix: + sdk: ['stable', 'beta'] + steps: + - name: checkout + uses: actions/checkout@v3 + + - uses: actions/setup-java@v3 + with: + distribution: 'adopt' + java-version: '8' + + - uses: subosito/flutter-action@1e6ee87cb840500837bcd50a667fb28815d8e310 # pin@v2 + with: + channel: ${{ matrix.sdk }} + + - name: flutter upgrade + run: flutter upgrade + + - name: flutter pub get + run: flutter pub get + + - name: launch android emulator & run android integration test + uses: reactivecircus/android-emulator-runner@d7b53ddc6e44254e1f4cf4a6ad67345837027a66 #pin@v2.26.0 + with: + working-directory: ./flutter/example + api-level: 21 + arch: x86_64 + profile: Nexus 6 + script: flutter test integration_test/integration_test.dart + test-ios: + runs-on: macos-11 + defaults: + run: + working-directory: ./flutter/example + strategy: + matrix: + sdk: ['stable', 'beta'] + steps: + - name: checkout + uses: actions/checkout@v3 + + - uses: subosito/flutter-action@1e6ee87cb840500837bcd50a667fb28815d8e310 # pin@v2 + with: + channel: ${{ matrix.sdk }} + + - name: flutter upgrade + run: flutter upgrade + + - name: flutter pub get + run: flutter pub get + + - name: launch ios emulator + uses: futureware-tech/simulator-action@ee05c113b79f056b47f354d7b313555f5491e158 #pin@v2 + with: + model: 'iPhone 8' + os_version: '15.2' + + - name: run ios integration test + run: flutter test integration_test/integration_test.dart diff --git a/flutter/example/integration_test/integration_test.dart b/flutter/example/integration_test/integration_test.dart new file mode 100644 index 000000000..9889eb537 --- /dev/null +++ b/flutter/example/integration_test/integration_test.dart @@ -0,0 +1,124 @@ +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; +import 'package:sentry_flutter_example/main.dart'; + +void main() { + final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fullyLive; + + Future setupSentryAndApp(WidgetTester tester) async { + await setupSentry(() async { + await tester.pumpWidget(SentryScreenshotWidget( + child: DefaultAssetBundle( + bundle: SentryAssetBundle(enableStructuredDataTracing: true), + child: MyApp(), + ))); + await tester.pumpAndSettle(); + }); + } + + // Tests + + testWidgets('setup sentry and render app', (tester) async { + await setupSentryAndApp(tester); + + // Find any UI element and verify it is present. + expect(find.text('Open another Scaffold'), findsOneWidget); + }); + + testWidgets('setup sentry and capture event', (tester) async { + await setupSentryAndApp(tester); + + final event = SentryEvent(); + final sentryId = await Sentry.captureEvent(event); + + expect(sentryId != SentryId.empty(), true); + }); + + testWidgets('setup sentry and capture exception', (tester) async { + await setupSentryAndApp(tester); + + try { + throw SentryException( + type: 'StarError', value: 'I have a bad feeling about this...'); + } catch (exception, stacktrace) { + final sentryId = + await Sentry.captureException(exception, stackTrace: stacktrace); + + expect(sentryId != SentryId.empty(), true); + } + }); + + testWidgets('setup sentry and capture message', (tester) async { + await setupSentryAndApp(tester); + + final sentryId = await Sentry.captureMessage('hello world!'); + + expect(sentryId != SentryId.empty(), true); + }); + + testWidgets('setup sentry and capture user feedback', (tester) async { + await setupSentryAndApp(tester); + + final feedback = SentryUserFeedback( + eventId: SentryId.newId(), + name: 'fixture-name', + email: 'fixture@email.com', + comments: 'fixture-comments'); + await Sentry.captureUserFeedback(feedback); + }); + + testWidgets('setup sentry and close', (tester) async { + await setupSentryAndApp(tester); + + await Sentry.close(); + }); + + testWidgets('setup sentry and add breadcrumb', (tester) async { + await setupSentryAndApp(tester); + + final breadcrumb = Breadcrumb(message: 'fixture-message'); + await Sentry.addBreadcrumb(breadcrumb); + }); + + testWidgets('setup sentry and configure scope', (tester) async { + await setupSentryAndApp(tester); + + await Sentry.configureScope((scope) async { + await scope.setContexts('contexts-key', 'contexts-value'); + await scope.removeContexts('contexts-key'); + + final user = SentryUser(id: 'fixture-id'); + await scope.setUser(user); + await scope.setUser(null); + + final breadcrumb = Breadcrumb(message: 'fixture-message'); + await scope.addBreadcrumb(breadcrumb); + await scope.clearBreadcrumbs(); + + await scope.setExtra('extra-key', 'extra-value'); + await scope.removeExtra('extra-key'); + + await scope.setTag('tag-key', 'tag-value'); + await scope.removeTag('tag-key'); + }); + }); + + testWidgets('setup sentry and start transaction', (tester) async { + await setupSentryAndApp(tester); + + final transaction = Sentry.startTransaction('transaction', 'test'); + await transaction.finish(); + }); + + testWidgets('setup sentry and start transaction with context', + (tester) async { + await setupSentryAndApp(tester); + + final context = SentryTransactionContext('transaction', 'test'); + final transaction = Sentry.startTransactionWithContext(context); + await transaction.finish(); + }); +} diff --git a/flutter/example/lib/main.dart b/flutter/example/lib/main.dart index db1d35d90..503b71958 100644 --- a/flutter/example/lib/main.dart +++ b/flutter/example/lib/main.dart @@ -21,32 +21,34 @@ const String _exampleDsn = final _channel = const MethodChannel('example.flutter.sentry.io'); Future main() async { - await SentryFlutter.init( - (options) { - options.dsn = _exampleDsn; - options.tracesSampleRate = 1.0; - options.reportPackages = false; - options.addInAppInclude('sentry_flutter_example'); - options.considerInAppFramesByDefault = false; - options.attachThreads = true; - options.enableWindowMetricBreadcrumbs = true; - options.addIntegration(LoggingIntegration()); - options.attachScreenshot = 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 - // configuration issues, e.g. finding out why your events are not uploaded. - options.debug = true; - }, - // Init your App. - appRunner: () => runApp( - SentryScreenshotWidget( - child: DefaultAssetBundle( - bundle: SentryAssetBundle(enableStructuredDataTracing: true), - child: MyApp(), + await setupSentry(() => runApp( + SentryScreenshotWidget( + child: DefaultAssetBundle( + bundle: SentryAssetBundle(enableStructuredDataTracing: true), + child: MyApp(), + ), ), - ), - ), - ); + )); +} + +Future setupSentry(AppRunner appRunner) async { + await SentryFlutter.init((options) { + options.dsn = _exampleDsn; + options.tracesSampleRate = 1.0; + options.reportPackages = false; + options.addInAppInclude('sentry_flutter_example'); + options.considerInAppFramesByDefault = false; + options.attachThreads = true; + options.enableWindowMetricBreadcrumbs = true; + options.addIntegration(LoggingIntegration()); + options.attachScreenshot = 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 + // configuration issues, e.g. finding out why your events are not uploaded. + options.debug = true; + }, + // Init your App. + appRunner: appRunner); } class MyApp extends StatefulWidget { diff --git a/flutter/example/pubspec.yaml b/flutter/example/pubspec.yaml index 2641c1430..69cfe6c4a 100644 --- a/flutter/example/pubspec.yaml +++ b/flutter/example/pubspec.yaml @@ -25,6 +25,10 @@ dependencies: dev_dependencies: pedantic: ^1.11.1 sentry_dart_plugin: ^1.0.0-beta.1 + integration_test: + sdk: flutter + flutter_test: + sdk: flutter flutter: uses-material-design: true diff --git a/flutter/lib/src/sentry_native_channel.dart b/flutter/lib/src/sentry_native_channel.dart index 3767158ef..1dc1db7af 100644 --- a/flutter/lib/src/sentry_native_channel.dart +++ b/flutter/lib/src/sentry_native_channel.dart @@ -28,7 +28,7 @@ class SentryNativeChannel { Future beginNativeFrames() async { try { - await _channel.invokeMapMethod('beginNativeFrames'); + await _channel.invokeMethod('beginNativeFrames'); } catch (error, stackTrace) { _logError('beginNativeFrames', error, stackTrace); } diff --git a/flutter/test/sentry_native_channel_test.dart b/flutter/test/sentry_native_channel_test.dart index cb145b711..8585d4425 100644 --- a/flutter/test/sentry_native_channel_test.dart +++ b/flutter/test/sentry_native_channel_test.dart @@ -37,8 +37,7 @@ void main() { final sut = fixture.getSut(); await sut.beginNativeFrames(); - verify(fixture.methodChannel - .invokeMapMethod('beginNativeFrames')); + verify(fixture.methodChannel.invokeMethod('beginNativeFrames')); }); test('endNativeFrames', () async {