Skip to content

Commit 2630144

Browse files
committed
Merge branch 'main' into feat/app-context-screen
2 parents bacf003 + 0aaa46e commit 2630144

File tree

7 files changed

+226
-22
lines changed

7 files changed

+226
-22
lines changed

.github/workflows/flutter_integration_test.yml

+36-8
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,42 @@ jobs:
4646
- name: flutter pub get
4747
run: flutter pub get
4848

49-
- name: launch android emulator & run android integration test
50-
uses: reactivecircus/android-emulator-runner@50986b1464923454c95e261820bc626f38490ec0 #[email protected]
51-
with:
52-
working-directory: ./flutter/example
53-
api-level: 21
54-
arch: x86_64
55-
profile: Nexus 6
56-
script: flutter test integration_test/integration_test.dart --verbose
49+
- name: Gradle cache
50+
uses: gradle/gradle-build-action@v2
51+
52+
- name: AVD cache
53+
uses: actions/cache@v3
54+
id: avd-cache
55+
with:
56+
path: |
57+
~/.android/avd/*
58+
~/.android/adb*
59+
key: avd-21
60+
61+
- name: create AVD and generate snapshot for caching
62+
if: steps.avd-cache.outputs.cache-hit != 'true'
63+
uses: reactivecircus/android-emulator-runner@d94c3fbe4fe6a29e4a5ba47c12fb47677c73656b #[email protected]
64+
with:
65+
working-directory: ./flutter/example
66+
api-level: 21
67+
force-avd-creation: false
68+
emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
69+
disable-animations: false
70+
arch: x86_64
71+
profile: Nexus 6
72+
script: echo "Generated AVD snapshot for caching."
73+
74+
- name: launch android emulator & run android integration test
75+
uses: reactivecircus/android-emulator-runner@d94c3fbe4fe6a29e4a5ba47c12fb47677c73656b #[email protected]
76+
with:
77+
working-directory: ./flutter/example
78+
api-level: 21
79+
force-avd-creation: false
80+
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
81+
disable-animations: true
82+
arch: x86_64
83+
profile: Nexus 6
84+
script: flutter test integration_test/integration_test.dart --verbose
5785

5886
test-ios:
5987
runs-on: macos-13

CHANGELOG.md

+9
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,15 @@
99

1010
[Trace origin](https://develop.sentry.dev/sdk/performance/trace-origin/) indicates what created a trace or a span. Not all transactions and spans contain enough information to tell whether the user or what precisely in the SDK created it. Origin solves this problem. The SDK now sends origin for transactions and spans.
1111

12+
### Dependencies
13+
14+
- Bump Cocoa SDK from v8.8.0 to v8.9.1 ([#1553](https://github.com/getsentry/sentry-dart/pull/1553))
15+
- [changelog](https://github.com/getsentry/sentry-cocoa/blob/main/CHANGELOG.md#891)
16+
- [diff](https://github.com/getsentry/sentry-cocoa/compare/8.8.0...8.9.1)
17+
- Bump Android SDK from v6.23.0 to v6.25.2 ([#1554](https://github.com/getsentry/sentry-dart/pull/1554))
18+
- [changelog](https://github.com/getsentry/sentry-java/blob/main/CHANGELOG.md#6252)
19+
- [diff](https://github.com/getsentry/sentry-java/compare/6.23.0...6.25.2)
20+
1221
## 7.8.0
1322

1423
### Enhancements

flutter/android/build.gradle

+1-1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,6 @@ android {
6060
}
6161

6262
dependencies {
63-
api 'io.sentry:sentry-android:6.23.0'
63+
api 'io.sentry:sentry-android:6.25.2'
6464
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
6565
}

flutter/example/integration_test/integration_test.dart

+115-8
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

+63-4
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

+1
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

flutter/ios/sentry_flutter.podspec

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ Sentry SDK for Flutter with support to native through sentry-cocoa.
1212
:tag => s.version.to_s }
1313
s.source_files = 'Classes/**/*'
1414
s.public_header_files = 'Classes/**/*.h'
15-
s.dependency 'Sentry/HybridSDK', '8.8.0'
15+
s.dependency 'Sentry/HybridSDK', '8.9.1'
1616
s.ios.dependency 'Flutter'
1717
s.osx.dependency 'FlutterMacOS'
1818
s.ios.deployment_target = '11.0'

0 commit comments

Comments
 (0)