Skip to content

Commit 0aaa46e

Browse files
denrasemarandaneto
andauthored
Add e2e test for iOS/Android (#1516)
Co-authored-by: Manoel Aranda Neto <[email protected]>
1 parent 95d0636 commit 0aaa46e

File tree

3 files changed

+179
-12
lines changed

3 files changed

+179
-12
lines changed

flutter/example/integration_test/integration_test.dart

Lines changed: 115 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,39 @@
1+
import 'dart:async';
2+
import 'dart:convert';
3+
14
import 'package:flutter/widgets.dart';
25
import 'package:flutter_test/flutter_test.dart';
36
import 'package:sentry_flutter/sentry_flutter.dart';
47
import 'package:sentry_flutter_example/main.dart';
8+
import 'package:http/http.dart';
59

610
void main() {
11+
const org = 'sentry-sdks';
12+
const slug = 'sentry-flutter';
13+
const authToken = String.fromEnvironment('SENTRY_AUTH_TOKEN');
14+
const fakeDsn = 'https://[email protected]/1234567';
15+
716
TestWidgetsFlutterBinding.ensureInitialized();
817

918
tearDown(() async {
1019
await Sentry.close();
1120
});
1221

1322
// Using fake DSN for testing purposes.
14-
Future<void> setupSentryAndApp(WidgetTester tester) async {
15-
await setupSentry(() async {
16-
await tester.pumpWidget(SentryScreenshotWidget(
17-
child: DefaultAssetBundle(
18-
bundle: SentryAssetBundle(enableStructuredDataTracing: true),
19-
child: const MyApp(),
20-
)));
21-
}, 'https://[email protected]/1234567');
23+
Future<void> setupSentryAndApp(WidgetTester tester,
24+
{String? dsn, BeforeSendCallback? beforeSendCallback}) async {
25+
await setupSentry(
26+
() async {
27+
await tester.pumpWidget(SentryScreenshotWidget(
28+
child: DefaultAssetBundle(
29+
bundle: SentryAssetBundle(enableStructuredDataTracing: true),
30+
child: const MyApp(),
31+
)));
32+
},
33+
dsn ?? fakeDsn,
34+
isIntegrationTest: true,
35+
beforeSendCallback: beforeSendCallback,
36+
);
2237
}
2338

2439
// Tests
@@ -123,4 +138,96 @@ void main() {
123138
final transaction = Sentry.startTransactionWithContext(context);
124139
await transaction.finish();
125140
});
141+
142+
group('e2e', () {
143+
var output = find.byKey(const Key('output'));
144+
late Fixture fixture;
145+
146+
setUp(() {
147+
fixture = Fixture();
148+
});
149+
150+
testWidgets('captureException', (tester) async {
151+
await setupSentryAndApp(tester,
152+
dsn: exampleDsn, beforeSendCallback: fixture.beforeSend);
153+
154+
await tester.tap(find.text('captureException'));
155+
await tester.pumpAndSettle();
156+
157+
final text = output.evaluate().single.widget as Text;
158+
final id = text.data!;
159+
160+
final uri = Uri.parse(
161+
'https://sentry.io/api/0/projects/$org/$slug/events/$id/',
162+
);
163+
164+
final event = await fixture.poll(uri, authToken);
165+
expect(event, isNotNull);
166+
167+
final sentEvent = fixture.sentEvent;
168+
expect(sentEvent, isNotNull);
169+
170+
final tags = event!["tags"] as List<dynamic>;
171+
172+
expect(sentEvent!.eventId.toString(), event["id"]);
173+
expect("_Exception: Exception: captureException", event["title"]);
174+
expect(sentEvent.release, event["release"]["version"]);
175+
expect(
176+
2,
177+
(tags.firstWhere((e) => e["value"] == sentEvent.environment) as Map)
178+
.length);
179+
expect(sentEvent.fingerprint, event["fingerprint"] ?? []);
180+
expect(
181+
2,
182+
(tags.firstWhere((e) => e["value"] == SentryLevel.error.name) as Map)
183+
.length);
184+
expect(sentEvent.logger, event["logger"]);
185+
186+
final dist = tags.firstWhere((element) => element['key'] == 'dist');
187+
expect('1', dist['value']);
188+
189+
final environment =
190+
tags.firstWhere((element) => element['key'] == 'environment');
191+
expect('integration', environment['value']);
192+
});
193+
});
194+
}
195+
196+
class Fixture {
197+
SentryEvent? sentEvent;
198+
199+
FutureOr<SentryEvent?> beforeSend(SentryEvent event, {Hint? hint}) async {
200+
sentEvent = event;
201+
return event;
202+
}
203+
204+
Future<Map<String, dynamic>?> poll(Uri url, String authToken) async {
205+
final client = Client();
206+
207+
const maxRetries = 10;
208+
const initialDelay = Duration(seconds: 2);
209+
const factor = 2;
210+
211+
var retries = 0;
212+
var delay = initialDelay;
213+
214+
while (retries < maxRetries) {
215+
try {
216+
final response = await client.get(
217+
url,
218+
headers: <String, String>{'Authorization': 'Bearer $authToken'},
219+
);
220+
if (response.statusCode == 200) {
221+
return jsonDecode(utf8.decode(response.bodyBytes));
222+
}
223+
} catch (e) {
224+
// Do nothing
225+
} finally {
226+
retries++;
227+
await Future.delayed(delay);
228+
delay *= factor;
229+
}
230+
}
231+
return null;
232+
}
126233
}

flutter/example/lib/main.dart

Lines changed: 63 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import 'dart:async';
44
import 'dart:convert';
5+
import 'dart:io' show Platform;
56

67
import 'package:flutter/foundation.dart';
78
import 'package:flutter/material.dart';
@@ -21,10 +22,11 @@ import 'package:sentry_dio/sentry_dio.dart';
2122
import 'package:sentry_logging/sentry_logging.dart';
2223

2324
// ATTENTION: Change the DSN below with your own to see the events in Sentry. Get one at sentry.io
24-
const String _exampleDsn =
25+
const String exampleDsn =
2526
'https://[email protected]/5428562';
2627

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

2931
Future<void> main() async {
3032
await setupSentry(
@@ -38,12 +40,14 @@ Future<void> main() async {
3840
),
3941
),
4042
),
41-
_exampleDsn);
43+
exampleDsn);
4244
}
4345

44-
Future<void> setupSentry(AppRunner appRunner, String dsn) async {
46+
Future<void> setupSentry(AppRunner appRunner, String dsn,
47+
{bool isIntegrationTest = false,
48+
BeforeSendCallback? beforeSendCallback}) async {
4549
await SentryFlutter.init((options) {
46-
options.dsn = _exampleDsn;
50+
options.dsn = exampleDsn;
4751
options.tracesSampleRate = 1.0;
4852
options.reportPackages = false;
4953
options.addInAppInclude('sentry_flutter_example');
@@ -63,6 +67,13 @@ Future<void> setupSentry(AppRunner appRunner, String dsn) async {
6367

6468
options.maxRequestBodySize = MaxRequestBodySize.always;
6569
options.maxResponseBodySize = MaxResponseBodySize.always;
70+
71+
_isIntegrationTest = isIntegrationTest;
72+
if (_isIntegrationTest) {
73+
options.dist = '1';
74+
options.environment = 'integration';
75+
options.beforeSend = beforeSendCallback;
76+
}
6677
},
6778
// Init your App.
6879
appRunner: appRunner);
@@ -136,6 +147,7 @@ class MainScaffold extends StatelessWidget {
136147
body: SingleChildScrollView(
137148
child: Column(
138149
children: [
150+
if (_isIntegrationTest) const IntegrationTestWidget(),
139151
const Center(child: Text('Trigger an action:\n')),
140152
ElevatedButton(
141153
onPressed: () => sqfliteTest(),
@@ -527,6 +539,53 @@ Future<void> asyncThrows() async {
527539
throw StateError('async throws');
528540
}
529541

542+
class IntegrationTestWidget extends StatefulWidget {
543+
const IntegrationTestWidget({super.key});
544+
545+
@override
546+
State<StatefulWidget> createState() {
547+
return _IntegrationTestWidgetState();
548+
}
549+
}
550+
551+
class _IntegrationTestWidgetState extends State<IntegrationTestWidget> {
552+
_IntegrationTestWidgetState();
553+
554+
var _output = "--";
555+
var _isLoading = false;
556+
557+
@override
558+
Widget build(BuildContext context) {
559+
return Column(children: [
560+
Text(
561+
_output,
562+
key: const Key('output'),
563+
),
564+
_isLoading
565+
? const CircularProgressIndicator()
566+
: ElevatedButton(
567+
onPressed: () async => await _captureException(),
568+
child: const Text('captureException'),
569+
)
570+
]);
571+
}
572+
573+
Future<void> _captureException() async {
574+
setState(() {
575+
_isLoading = true;
576+
});
577+
try {
578+
throw Exception('captureException');
579+
} catch (error, stackTrace) {
580+
final id = await Sentry.captureException(error, stackTrace: stackTrace);
581+
setState(() {
582+
_output = id.toString();
583+
_isLoading = false;
584+
});
585+
}
586+
}
587+
}
588+
530589
class CocoaExample extends StatelessWidget {
531590
const CocoaExample({Key? key}) : super(key: key);
532591

flutter/example/pubspec.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ dependencies:
2727
path_provider: ^2.0.0
2828
#sqflite_common_ffi: ^2.0.0
2929
#sqflite_common_ffi_web: ^0.3.0
30+
http: ^1.0.0
3031

3132
dev_dependencies:
3233
flutter_lints: ^2.0.0

0 commit comments

Comments
 (0)