Skip to content

Commit f0e18bc

Browse files
committed
test: Improve reliability of widget tests with custom wait function
1 parent 439a140 commit f0e18bc

1 file changed

Lines changed: 24 additions & 9 deletions

File tree

client/integration_test/app_test.dart

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import 'package:borneo_app/core/services/devices/device_module_registry.dart';
66
import 'package:flutter/material.dart';
77
import 'package:flutter_test/flutter_test.dart';
88
import 'package:integration_test/integration_test.dart';
9-
import 'package:sembast/sembast_memory.dart';
9+
import 'package:sembast/sembast_memory.dart' hide Finder;
1010
import 'package:shared_preferences/shared_preferences.dart';
1111

1212
import 'package:borneo_app/main.dart' as app;
@@ -41,6 +41,21 @@ Future<Widget> _buildTestApp({IDeviceModuleRegistry? registry}) async {
4141
// Tests
4242
// ---------------------------------------------------------------------------
4343

44+
/// Repeatedly pumps the tester until [finder] is found or the [timeout]
45+
/// expires. This is more reliable than `pumpAndSettle` when the UI shows an
46+
/// indefinite animation (e.g. a loading spinner) that would otherwise keep
47+
/// scheduling frames.
48+
Future<void> _waitFor(WidgetTester tester, Finder finder, {Duration timeout = const Duration(seconds: 5)}) async {
49+
final end = DateTime.now().add(timeout);
50+
while (DateTime.now().isBefore(end)) {
51+
await tester.pump(const Duration(milliseconds: 100));
52+
if (tester.any(finder)) return;
53+
}
54+
// Final check to produce a sensible failure message
55+
await tester.pump();
56+
expect(tester.any(finder), true, reason: 'Expected $finder to appear within $timeout');
57+
}
58+
4459
void main() {
4560
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
4661

@@ -62,6 +77,11 @@ void main() {
6277
await tester.tap(find.byIcon(Icons.device_hub_outlined));
6378
await tester.pumpAndSettle();
6479

80+
// Devices screen should be fully initialized before we continue. The title
81+
// contains the current scene name which is "My Home" in the fresh
82+
// in-memory database.
83+
await _waitFor(tester, find.text('Devices in My Home'));
84+
6585
// open add menu and select Add Devices Group
6686
await tester.tap(find.byIcon(Icons.add_outlined));
6787
await tester.pumpAndSettle();
@@ -71,12 +91,9 @@ void main() {
7191
// fill in group name and submit
7292
await tester.enterText(find.byKey(const Key('field_group_name')), 'Test Group');
7393
await tester.tap(find.byKey(const Key('btn_submit')));
74-
// Give DB write + EventBus async delivery + reload time to complete.
94+
// give database and event bus a moment to complete, then wait for list update
7595
await tester.pump(const Duration(milliseconds: 500));
76-
await tester.pumpAndSettle();
77-
78-
// verify group shows up in list
79-
expect(find.text('Test Group'), findsOneWidget);
96+
await _waitFor(tester, find.text('Test Group'));
8097

8198
// edit the group
8299
await tester.tap(find.byKey(const Key('btn_edit_group_Test Group')));
@@ -86,7 +103,7 @@ void main() {
86103
await tester.enterText(find.byKey(const Key('field_group_name')), 'Updated Group');
87104
await tester.tap(find.byKey(const Key('btn_submit')));
88105
await tester.pump(const Duration(milliseconds: 500));
89-
await tester.pumpAndSettle();
106+
await _waitFor(tester, find.text('Updated Group'));
90107

91108
expect(find.text('Updated Group'), findsOneWidget);
92109

@@ -101,8 +118,6 @@ void main() {
101118
// confirm deletion
102119
await tester.tap(find.byKey(const Key('btn_confirm_delete')));
103120
await tester.pump(const Duration(milliseconds: 500));
104-
await tester.pumpAndSettle();
105-
106121
// group should no longer be present
107122
expect(find.text('Updated Group'), findsNothing);
108123
});

0 commit comments

Comments
 (0)