Skip to content

Commit

Permalink
Add e2e test for iOS/Android (#1516)
Browse files Browse the repository at this point in the history
Co-authored-by: Manoel Aranda Neto <5731772+marandaneto@users.noreply.github.com>
  • Loading branch information
denrase and marandaneto authored Jul 18, 2023
1 parent 95d0636 commit 0aaa46e
Show file tree
Hide file tree
Showing 3 changed files with 179 additions and 12 deletions.
123 changes: 115 additions & 8 deletions flutter/example/integration_test/integration_test.dart
Original file line number Diff line number Diff line change
@@ -1,24 +1,39 @@
import 'dart:async';
import 'dart:convert';

import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:sentry_flutter_example/main.dart';
import 'package:http/http.dart';

void main() {
const org = 'sentry-sdks';
const slug = 'sentry-flutter';
const authToken = String.fromEnvironment('SENTRY_AUTH_TOKEN');
const fakeDsn = 'https://abc@def.ingest.sentry.io/1234567';

TestWidgetsFlutterBinding.ensureInitialized();

tearDown(() async {
await Sentry.close();
});

// Using fake DSN for testing purposes.
Future<void> setupSentryAndApp(WidgetTester tester) async {
await setupSentry(() async {
await tester.pumpWidget(SentryScreenshotWidget(
child: DefaultAssetBundle(
bundle: SentryAssetBundle(enableStructuredDataTracing: true),
child: const MyApp(),
)));
}, 'https://abc@def.ingest.sentry.io/1234567');
Future<void> setupSentryAndApp(WidgetTester tester,
{String? dsn, BeforeSendCallback? beforeSendCallback}) async {
await setupSentry(
() async {
await tester.pumpWidget(SentryScreenshotWidget(
child: DefaultAssetBundle(
bundle: SentryAssetBundle(enableStructuredDataTracing: true),
child: const MyApp(),
)));
},
dsn ?? fakeDsn,
isIntegrationTest: true,
beforeSendCallback: beforeSendCallback,
);
}

// Tests
Expand Down Expand Up @@ -123,4 +138,96 @@ void main() {
final transaction = Sentry.startTransactionWithContext(context);
await transaction.finish();
});

group('e2e', () {
var output = find.byKey(const Key('output'));
late Fixture fixture;

setUp(() {
fixture = Fixture();
});

testWidgets('captureException', (tester) async {
await setupSentryAndApp(tester,
dsn: exampleDsn, beforeSendCallback: fixture.beforeSend);

await tester.tap(find.text('captureException'));
await tester.pumpAndSettle();

final text = output.evaluate().single.widget as Text;
final id = text.data!;

final uri = Uri.parse(
'https://sentry.io/api/0/projects/$org/$slug/events/$id/',
);

final event = await fixture.poll(uri, authToken);
expect(event, isNotNull);

final sentEvent = fixture.sentEvent;
expect(sentEvent, isNotNull);

final tags = event!["tags"] as List<dynamic>;

expect(sentEvent!.eventId.toString(), event["id"]);
expect("_Exception: Exception: captureException", event["title"]);
expect(sentEvent.release, event["release"]["version"]);
expect(
2,
(tags.firstWhere((e) => e["value"] == sentEvent.environment) as Map)
.length);
expect(sentEvent.fingerprint, event["fingerprint"] ?? []);
expect(
2,
(tags.firstWhere((e) => e["value"] == SentryLevel.error.name) as Map)
.length);
expect(sentEvent.logger, event["logger"]);

final dist = tags.firstWhere((element) => element['key'] == 'dist');
expect('1', dist['value']);

final environment =
tags.firstWhere((element) => element['key'] == 'environment');
expect('integration', environment['value']);
});
});
}

class Fixture {
SentryEvent? sentEvent;

FutureOr<SentryEvent?> beforeSend(SentryEvent event, {Hint? hint}) async {
sentEvent = event;
return event;
}

Future<Map<String, dynamic>?> poll(Uri url, String authToken) async {
final client = Client();

const maxRetries = 10;
const initialDelay = Duration(seconds: 2);
const factor = 2;

var retries = 0;
var delay = initialDelay;

while (retries < maxRetries) {
try {
final response = await client.get(
url,
headers: <String, String>{'Authorization': 'Bearer $authToken'},
);
if (response.statusCode == 200) {
return jsonDecode(utf8.decode(response.bodyBytes));
}
} catch (e) {
// Do nothing
} finally {
retries++;
await Future.delayed(delay);
delay *= factor;
}
}
return null;
}
}
67 changes: 63 additions & 4 deletions flutter/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import 'dart:async';
import 'dart:convert';
import 'dart:io' show Platform;

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
Expand All @@ -21,10 +22,11 @@ import 'package:sentry_dio/sentry_dio.dart';
import 'package:sentry_logging/sentry_logging.dart';

// ATTENTION: Change the DSN below with your own to see the events in Sentry. Get one at sentry.io
const String _exampleDsn =
const String exampleDsn =
'https://e85b375ffb9f43cf8bdf9787768149e0@o447951.ingest.sentry.io/5428562';

const _channel = MethodChannel('example.flutter.sentry.io');
var _isIntegrationTest = false;

Future<void> main() async {
await setupSentry(
Expand All @@ -38,12 +40,14 @@ Future<void> main() async {
),
),
),
_exampleDsn);
exampleDsn);
}

Future<void> setupSentry(AppRunner appRunner, String dsn) async {
Future<void> setupSentry(AppRunner appRunner, String dsn,
{bool isIntegrationTest = false,
BeforeSendCallback? beforeSendCallback}) async {
await SentryFlutter.init((options) {
options.dsn = _exampleDsn;
options.dsn = exampleDsn;
options.tracesSampleRate = 1.0;
options.reportPackages = false;
options.addInAppInclude('sentry_flutter_example');
Expand All @@ -63,6 +67,13 @@ Future<void> setupSentry(AppRunner appRunner, String dsn) async {

options.maxRequestBodySize = MaxRequestBodySize.always;
options.maxResponseBodySize = MaxResponseBodySize.always;

_isIntegrationTest = isIntegrationTest;
if (_isIntegrationTest) {
options.dist = '1';
options.environment = 'integration';
options.beforeSend = beforeSendCallback;
}
},
// Init your App.
appRunner: appRunner);
Expand Down Expand Up @@ -136,6 +147,7 @@ class MainScaffold extends StatelessWidget {
body: SingleChildScrollView(
child: Column(
children: [
if (_isIntegrationTest) const IntegrationTestWidget(),
const Center(child: Text('Trigger an action:\n')),
ElevatedButton(
onPressed: () => sqfliteTest(),
Expand Down Expand Up @@ -527,6 +539,53 @@ Future<void> asyncThrows() async {
throw StateError('async throws');
}

class IntegrationTestWidget extends StatefulWidget {
const IntegrationTestWidget({super.key});

@override
State<StatefulWidget> createState() {
return _IntegrationTestWidgetState();
}
}

class _IntegrationTestWidgetState extends State<IntegrationTestWidget> {
_IntegrationTestWidgetState();

var _output = "--";
var _isLoading = false;

@override
Widget build(BuildContext context) {
return Column(children: [
Text(
_output,
key: const Key('output'),
),
_isLoading
? const CircularProgressIndicator()
: ElevatedButton(
onPressed: () async => await _captureException(),
child: const Text('captureException'),
)
]);
}

Future<void> _captureException() async {
setState(() {
_isLoading = true;
});
try {
throw Exception('captureException');
} catch (error, stackTrace) {
final id = await Sentry.captureException(error, stackTrace: stackTrace);
setState(() {
_output = id.toString();
_isLoading = false;
});
}
}
}

class CocoaExample extends StatelessWidget {
const CocoaExample({Key? key}) : super(key: key);

Expand Down
1 change: 1 addition & 0 deletions flutter/example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ dependencies:
path_provider: ^2.0.0
#sqflite_common_ffi: ^2.0.0
#sqflite_common_ffi_web: ^0.3.0
http: ^1.0.0

dev_dependencies:
flutter_lints: ^2.0.0
Expand Down

0 comments on commit 0aaa46e

Please sign in to comment.