Skip to content

Commit 18ccaf8

Browse files
authored
chore: improve tests by adding realtime delays (#477)
* test: improve navigation tests by adding realtime delays * ci: run patrol tests with older iOS version
1 parent a6aef40 commit 18ccaf8

15 files changed

+322
-368
lines changed

.github/workflows/test-and-build.yaml

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ jobs:
226226
env:
227227
MAPS_API_KEY: ${{ secrets.ACTIONS_API_KEY }}
228228
PATROL_ANALYTICS_ENABLED: false
229-
patrol_cli_version: "3.5.0"
229+
patrol_cli_version: "3.10.0"
230230
working_directory: "example"
231231
steps:
232232
- name: Fail if workflow has no access to API key
@@ -311,12 +311,13 @@ jobs:
311311
if: contains(github.base_ref, 'main')
312312
timeout-minutes: 90
313313
runs-on:
314-
labels: macos-latest-xlarge
314+
labels: macos-15-xlarge
315315
env:
316316
MAPS_API_KEY: ${{ secrets.ACTIONS_API_KEY }}
317317
PATROL_ANALYTICS_ENABLED: false
318-
patrol_cli_version: "3.5.0"
318+
patrol_cli_version: "3.10.0"
319319
working_directory: "example"
320+
IOS_VERSION: "18.6"
320321
steps:
321322
- name: Fail if workflow has no access to API key
322323
if: ${{ env.MAPS_API_KEY == '' }}
@@ -337,14 +338,16 @@ jobs:
337338
run: |
338339
SIMULATOR_NAME="iPhone 16 Pro"
339340
340-
# Find the UUID of the existing simulator by name
341-
DEVICE_ID=$(xcrun simctl list devices | grep "$SIMULATOR_NAME (" | grep -Eo "\([A-F0-9-]+\)" | head -n 1 | tr -d "()")
341+
# Find the UUID of the existing simulator by name and iOS version using the correct filter command
342+
DEVICE_ID=$(xcrun simctl list devices $IOS_VERSION | grep "$SIMULATOR_NAME (" | grep -Eo "\([A-F0-9-]+\)" | head -n 1 | tr -d "()")
342343
343344
if [ -z "$DEVICE_ID" ]; then
344-
echo "Simulator $SIMULATOR_NAME not found."
345+
echo "Simulator $SIMULATOR_NAME with iOS $IOS_VERSION not found."
346+
echo "Available simulators for iOS $IOS_VERSION:"
347+
xcrun simctl list devices $IOS_VERSION
345348
exit 1
346349
else
347-
echo "Found existing simulator: $SIMULATOR_NAME ($DEVICE_ID)"
350+
echo "Found existing simulator: $SIMULATOR_NAME iOS $IOS_VERSION ($DEVICE_ID)"
348351
echo "Erasing the simulator to ensure a clean state..."
349352
xcrun simctl erase "$DEVICE_ID"
350353
fi
@@ -381,7 +384,7 @@ jobs:
381384
id: tests_step
382385
working-directory: ${{ env.working_directory }}
383386
run: |
384-
patrol test --dart-define=MAPS_API_KEY="$MAPS_API_KEY" ${{ runner.debug && '--show-flutter-logs --verbose' || '' }} -d "$DEVICE_ID"
387+
patrol test --dart-define=MAPS_API_KEY="$MAPS_API_KEY" --ios=$IOS_VERSION ${{ runner.debug && '--show-flutter-logs --verbose' || '' }} -d "$DEVICE_ID"
385388
- name: Upload test report
386389
uses: actions/upload-artifact@v4
387390
if: ${{ always() }}

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ Specify the device you want to run the tests on with the DEVICE env variable.
132132
Integration tests are responsible for ensuring that the plugin works against the native Navigation SDK for both Android and iOS platforms. Patrol is used for the integration tests to simplify interactions with native elements. To use patrol, you first need to activate the patrol_cli.
133133

134134
```bash
135-
flutter pub global activate patrol_cli 3.5.0
135+
flutter pub global activate patrol_cli 3.10.0
136136
```
137137

138138
To ensure that all necessary dependencies for patrol are properly set up, run the following command:

example/android/app/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ android {
5858
applicationId = "com.google.maps.flutter.navigation_example"
5959
// You can update the following values to match your application needs.
6060
// For more information, see: https://flutter.dev/to/review-gradle-config.
61-
minSdk = flutter.minSdkVersion
61+
minSdk = 24
6262
targetSdk = flutter.targetSdkVersion
6363
versionCode = flutter.versionCode
6464
versionName = flutter.versionName

example/android/gradle/wrapper/gradle-wrapper.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22
//=limitations under the License.
33
distributionBase=GRADLE_USER_HOME
44
distributionPath=wrapper/dists
5-
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip
5+
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-all.zip
66
zipStoreBase=GRADLE_USER_HOME
77
zipStorePath=wrapper/dists

example/android/settings.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ pluginManagement {
3232

3333
plugins {
3434
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
35-
id("com.android.application") version "8.7.3" apply false
35+
id("com.android.application") version "8.13.0" apply false
3636
id("org.jetbrains.kotlin.android") version "2.1.0" apply false
3737
id("com.google.android.libraries.mapsplatform.secrets-gradle-plugin") version "2.0.1" apply false
3838
}

example/integration_test/shared.dart

Lines changed: 79 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,11 @@ Future<void> checkTermsAndConditionsAcceptance(
136136
);
137137

138138
await $.pumpAndSettle();
139+
// Force wait a bit for the dialog to appear.
140+
await $.tester.runAsync(
141+
() => Future.delayed(const Duration(milliseconds: 250)),
142+
);
143+
139144
// Tap accept or cancel.
140145
if (Platform.isAndroid) {
141146
await $.native.tap(Selector(text: "Got It"));
@@ -188,37 +193,17 @@ Future<GoogleNavigationViewController> startNavigation(
188193
void Function(CameraPosition)? onCameraStartedFollowingLocation,
189194
void Function(CameraPosition)? onCameraStoppedFollowingLocation,
190195
}) async {
191-
final ControllerCompleter<GoogleNavigationViewController>
192-
controllerCompleter = ControllerCompleter();
193-
194-
await checkLocationDialogAndTosAcceptance($);
195-
196-
final Key key = GlobalKey();
197-
await pumpNavigationView(
198-
$,
199-
GoogleMapsNavigationView(
200-
key: key,
201-
onViewCreated: (GoogleNavigationViewController viewController) {
202-
controllerCompleter.complete(viewController);
203-
},
204-
onCameraMoveStarted: onCameraMoveStarted,
205-
onCameraMove: onCameraMove,
206-
onCameraIdle: onCameraIdle,
207-
onCameraStartedFollowingLocation: onCameraStartedFollowingLocation,
208-
onCameraStoppedFollowingLocation: onCameraStoppedFollowingLocation,
209-
),
210-
);
211-
212196
final GoogleNavigationViewController controller =
213-
await controllerCompleter.future;
214-
215-
await GoogleMapsNavigator.initializeNavigationSession();
216-
await $.pumpAndSettle();
217-
218-
await GoogleMapsNavigator.simulator.setUserLocation(
219-
const LatLng(latitude: startLocationLat, longitude: startLocationLng),
220-
);
221-
await $.pumpAndSettle(timeout: const Duration(seconds: 1));
197+
await startNavigationWithoutDestination(
198+
$,
199+
initializeNavigation: true,
200+
simulateLocation: true,
201+
onCameraMoveStarted: onCameraMoveStarted,
202+
onCameraMove: onCameraMove,
203+
onCameraIdle: onCameraIdle,
204+
onCameraStartedFollowingLocation: onCameraStartedFollowingLocation,
205+
onCameraStoppedFollowingLocation: onCameraStoppedFollowingLocation,
206+
);
222207

223208
/// Set Destination.
224209
final Destinations destinations = Destinations(
@@ -236,11 +221,9 @@ Future<GoogleNavigationViewController> startNavigation(
236221
final NavigationRouteStatus status =
237222
await GoogleMapsNavigator.setDestinations(destinations);
238223
expect(status, NavigationRouteStatus.statusOk);
239-
await $.pumpAndSettle();
240224

241225
/// Start guidance.
242226
await GoogleMapsNavigator.startGuidance();
243-
await $.pumpAndSettle();
244227

245228
expect(await GoogleMapsNavigator.isGuidanceRunning(), true);
246229

@@ -350,9 +333,13 @@ Future<GoogleNavigationViewController> startNavigationWithoutDestination(
350333
void Function(bool)? onNavigationUIEnabledChanged,
351334
void Function(String)? onPolygonClicked,
352335
void Function(String)? onPolylineClicked,
336+
void Function(CameraPosition, bool)? onCameraMoveStarted,
337+
void Function(CameraPosition)? onCameraMove,
338+
void Function(CameraPosition)? onCameraIdle,
339+
void Function(CameraPosition)? onCameraStartedFollowingLocation,
340+
void Function(CameraPosition)? onCameraStoppedFollowingLocation,
353341
void Function(NavigationViewRecenterButtonClickedEvent)?
354342
onRecenterButtonClicked,
355-
void Function(CameraPosition)? onCameraIdle,
356343
}) async {
357344
final Completer<GoogleNavigationViewController> controllerCompleter =
358345
Completer<GoogleNavigationViewController>();
@@ -383,24 +370,30 @@ Future<GoogleNavigationViewController> startNavigationWithoutDestination(
383370
onPolygonClicked: onPolygonClicked,
384371
onPolylineClicked: onPolylineClicked,
385372
onRecenterButtonClicked: onRecenterButtonClicked,
373+
onCameraMoveStarted: onCameraMoveStarted,
374+
onCameraMove: onCameraMove,
386375
onCameraIdle: onCameraIdle,
376+
onCameraStartedFollowingLocation: onCameraStartedFollowingLocation,
377+
onCameraStoppedFollowingLocation: onCameraStoppedFollowingLocation,
387378
),
388379
);
389380

390381
final GoogleNavigationViewController controller =
391382
await controllerCompleter.future;
392-
await $.pumpAndSettle();
393383

394384
if (initializeNavigation) {
395385
await GoogleMapsNavigator.initializeNavigationSession();
396-
await $.pumpAndSettle();
397386
}
398387

399388
if (simulateLocation) {
400-
await GoogleMapsNavigator.simulator.setUserLocation(
401-
const LatLng(latitude: startLocationLat, longitude: startLocationLng),
389+
const double tolerance = 0.001;
390+
await setSimulatedUserLocationWithCheck(
391+
$,
392+
controller,
393+
startLocationLat,
394+
startLocationLng,
395+
tolerance,
402396
);
403-
await $.pumpAndSettle(timeout: const Duration(seconds: 1));
404397
}
405398

406399
return controller;
@@ -432,8 +425,6 @@ Future<GoogleMapViewController> startMapView(
432425
final ControllerCompleter<GoogleMapViewController> controllerCompleter =
433426
ControllerCompleter();
434427

435-
//await checkLocationDialogAndTosAcceptance($);
436-
437428
final Key key = GlobalKey();
438429
await pumpMapView(
439430
$,
@@ -485,11 +476,58 @@ Future<Value?> waitForValueMatchingPredicate<Value>(
485476
if (predicate(currentValue)) {
486477
return currentValue;
487478
}
488-
await $.pump(Duration(milliseconds: delayMs));
479+
await $.tester.runAsync(
480+
() => Future.delayed(Duration(milliseconds: delayMs)),
481+
);
489482
}
490483
return null;
491484
}
492485

486+
/// Sets the simulated user location to [startLat], [startLng] and checks that
487+
/// the location was set correctly within the given [tolerance].
488+
Future<void> setSimulatedUserLocationWithCheck(
489+
PatrolIntegrationTester $,
490+
GoogleNavigationViewController viewController,
491+
double startLat,
492+
double startLng,
493+
double tolerance,
494+
) async {
495+
// Simulate location
496+
await GoogleMapsNavigator.simulator.setUserLocation(
497+
LatLng(latitude: startLat, longitude: startLng),
498+
);
499+
500+
// Wait a bit for the location to be set, as this always seems to take a bit
501+
// of time on both Android and iOS platforms.
502+
await $.tester.runAsync(
503+
() => Future.delayed(const Duration(milliseconds: 500)),
504+
);
505+
506+
final LatLng? currentLocation = await waitForValueMatchingPredicate<LatLng?>(
507+
$,
508+
viewController.getMyLocation,
509+
(LatLng? location) {
510+
if (location == null) return false;
511+
512+
bool isCloseTo(double a, double b) {
513+
var diff = a - b;
514+
if (diff < 0) diff = -diff;
515+
return diff <= tolerance;
516+
}
517+
518+
return isCloseTo(location.latitude, startLat) &&
519+
isCloseTo(location.longitude, startLng);
520+
},
521+
// Total time before giving up: 100 * 200ms = 20s
522+
maxTries: 100,
523+
delayMs: 200,
524+
);
525+
526+
expect(currentLocation, isNotNull);
527+
expect(currentLocation?.latitude, closeTo(startLat, tolerance));
528+
expect(currentLocation?.longitude, closeTo(startLng, tolerance));
529+
}
530+
493531
// Convert a Color to an integer.
494532
int? colorToInt(Color? color) {
495533
if (color == null) {

0 commit comments

Comments
 (0)