From 54818c5fff738b93ae81f284af2826529ea4d9c7 Mon Sep 17 00:00:00 2001 From: Danny Morabito Date: Wed, 20 May 2026 22:22:23 +0200 Subject: [PATCH 1/6] test(integration): support Android via public relays and adb device detection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extends `just int-test` to work on Android emulators/devices in addition to iOS simulators. `_resolve-device` now falls back to `adb devices` when no booted iOS simulator is found (and skips xcrun on hosts where it's missing, so Linux works too). `int-test` auto-injects public Nostr relays via `--dart-define` when the resolved device is an Android one โ€” the emulator can't reach host localhost, so the local docker relays aren't usable there. iOS keeps its local-relay path. Users can override on either platform with `WHITENOISE_INTEGRATION_RELAYS=...`. `integration_test/_support/app_setup.dart` reads the relay list from the new dart-define (default: the same two localhost relays as before) and the TCP precheck now no-ops for non-localhost URLs. --- integration_test/_support/app_setup.dart | 27 +++++++++--- justfile | 52 +++++++++++++++++------- 2 files changed, 58 insertions(+), 21 deletions(-) diff --git a/integration_test/_support/app_setup.dart b/integration_test/_support/app_setup.dart index 81e61097..d7aafae3 100644 --- a/integration_test/_support/app_setup.dart +++ b/integration_test/_support/app_setup.dart @@ -16,7 +16,15 @@ import 'package:whitenoise/src/rust/frb_generated.dart'; import '../../test/mocks/mock_secure_storage.dart'; import 'tester_helpers.dart'; -const _relayUrls = ['ws://localhost:8080', 'ws://localhost:7777']; +const _relayUrlsEnv = String.fromEnvironment( + 'WHITENOISE_INTEGRATION_RELAYS', + defaultValue: 'ws://localhost:8080,ws://localhost:7777', +); +final List _relayUrls = _relayUrlsEnv + .split(',') + .map((s) => s.trim()) + .where((s) => s.isNotEmpty) + .toList(); bool _rustBridgeInitialized = false; Directory? _backendRoot; @@ -78,21 +86,28 @@ Future mountApp(WidgetTester tester) async { } Future expectLocalRelaysAvailable() async { - await _expectLocalRelayAvailable(8080); - await _expectLocalRelayAvailable(7777); + for (final url in _relayUrls) { + final uri = Uri.tryParse(url); + if (uri == null) continue; + final host = uri.host; + final isLocal = host == 'localhost' || host == '127.0.0.1'; + if (!isLocal) continue; + final port = uri.hasPort ? uri.port : 80; + await _expectLocalRelayAvailable(host, port); + } } -Future _expectLocalRelayAvailable(int port) async { +Future _expectLocalRelayAvailable(String host, int port) async { try { final socket = await Socket.connect( - '127.0.0.1', + host, port, timeout: const Duration(seconds: 1), ); socket.destroy(); } catch (error) { fail( - 'Expected a local Nostr relay on 127.0.0.1:$port before running this integration test. ' + 'Expected a local Nostr relay on $host:$port before running this integration test. ' 'Run `docker compose up -d`, then run the test again. ' 'Connection error: $error', ); diff --git a/justfile b/justfile index bfe4c40e..386df49b 100644 --- a/justfile +++ b/justfile @@ -168,35 +168,57 @@ test-flutter-quiet: echo "No test directory found."; \ fi -# Resolves the integration-test device: the given id, else the one booted simulator. +# Resolves the integration-test device: the given id, else the one booted iOS simulator or Android device. _resolve-device device: @device="{{ device }}"; \ if [ -n "$device" ]; then echo "$device"; exit 0; fi; \ - booted=$(xcrun simctl list devices booted 2>/dev/null | grep -oiE '[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}'); \ - count=$(printf '%s' "$booted" | grep -c .); \ - if [ "$count" -eq 1 ]; then \ - echo "Using booted simulator $booted" >&2; \ - echo "$booted"; \ - elif [ "$count" -eq 0 ]; then \ - echo "No device id given and no booted simulator found. Boot one, pass a device id, or set WHITENOISE_INTEGRATION_DEVICE." >&2; \ + if command -v xcrun >/dev/null 2>&1; then \ + booted=$(xcrun simctl list devices booted 2>/dev/null | grep -oiE '[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}'); \ + else \ + booted=""; \ + fi; \ + ios_count=$(printf '%s' "$booted" | grep -c .); \ + if command -v adb >/dev/null 2>&1; then \ + android=$(adb devices 2>/dev/null | awk 'NR>1 && $2=="device" {print $1}'); \ + else \ + android=""; \ + fi; \ + android_count=$(printf '%s' "$android" | grep -c .); \ + total=$((ios_count + android_count)); \ + if [ "$total" -eq 1 ]; then \ + picked="${booted}${android}"; \ + echo "Using device $picked" >&2; \ + echo "$picked"; \ + elif [ "$total" -eq 0 ]; then \ + echo "No device id given and no booted iOS simulator or Android device found. Boot one, pass a device id, or set WHITENOISE_INTEGRATION_DEVICE." >&2; \ exit 1; \ else \ - echo "Multiple booted simulators โ€” pass a device id or set WHITENOISE_INTEGRATION_DEVICE:" >&2; \ - xcrun simctl list devices booted >&2; \ + echo "Multiple devices found โ€” pass a device id or set WHITENOISE_INTEGRATION_DEVICE:" >&2; \ + [ -n "$booted" ] && echo "iOS simulators:" >&2 && echo "$booted" >&2; \ + [ -n "$android" ] && echo "Android devices:" >&2 && echo "$android" >&2; \ exit 1; \ fi -# Run Flutter integration tests. Requires local Nostr relays on ports 8080 and 7777. +# Run Flutter integration tests. iOS uses local Nostr relays on ports 8080 and 7777; +# Android auto-uses public relays (emulator can't reach host localhost). # Run one file by passing its path: `just int-test integration_test/messaging_interactions_test.dart`. -# Device: WHITENOISE_INTEGRATION_DEVICE, else the one booted simulator. -int-test target="integration_test/all_tests.dart" device=env("WHITENOISE_INTEGRATION_DEVICE", "") flavor="staging": +# Device: WHITENOISE_INTEGRATION_DEVICE, else the one booted simulator/emulator. +# Relays: WHITENOISE_INTEGRATION_RELAYS (comma-separated) overrides defaults. +int-test target="integration_test/all_tests.dart" device=env("WHITENOISE_INTEGRATION_DEVICE", "") flavor="staging" relays=env("WHITENOISE_INTEGRATION_RELAYS", ""): @echo "๐Ÿงช Testing Flutter integration flows..." @device=$(just _resolve-device "{{ device }}") || exit 1; \ + relays="{{ relays }}"; \ + if [ -z "$relays" ] && command -v adb >/dev/null 2>&1 && adb devices 2>/dev/null | awk 'NR>1 && $2=="device" {print $1}' | grep -qx "$device"; then \ + relays="wss://nos.lol,wss://relay.primal.net,wss://relay.damus.io"; \ + echo "๐Ÿ“ก Android device detected โ€” using public relays: $relays" >&2; \ + fi; \ + define=""; \ + [ -n "$relays" ] && define="--dart-define=WHITENOISE_INTEGRATION_RELAYS=$relays"; \ if [ -n "{{ flavor }}" ]; then \ - flutter test -d "$device" --flavor {{ flavor }} {{ target }}; \ + flutter test -d "$device" --flavor {{ flavor }} $define {{ target }}; \ else \ - flutter test -d "$device" {{ target }}; \ + flutter test -d "$device" $define {{ target }}; \ fi # Run Flutter integration tests with minimal output. Requires local Nostr relays on ports 8080 and 7777. From f0eb22df9fdba6122661fd10065001c232b2760f Mon Sep 17 00:00:00 2001 From: Danny Morabito Date: Wed, 20 May 2026 22:49:52 +0200 Subject: [PATCH 2/6] review: scheme-aware port default, shared relay resolver, safe define quoting - app_setup.dart: when a local relay URL omits its port, pick 443 for wss and 80 for everything else (Dart's `Uri.port` returns 0 for unknown schemes including ws/wss). Localhost relays normally carry explicit ports, so this is purely defensive. - justfile: extract `_resolve-relays device` so `int-test` and `int-test-quiet` share one relay-selection path. `int-test-quiet` previously inherited the Android emulator from `_resolve-device` but never injected `--dart-define=WHITENOISE_INTEGRATION_RELAYS=...`, so it would try to reach unreachable `localhost` from the emulator. - justfile: expand the dart-define flag as `${define:+"$define"}` in both recipes so no empty `""` arg is passed to `flutter test` when relays is unset, while keeping a single argument when set. --- integration_test/_support/app_setup.dart | 4 ++- justfile | 36 ++++++++++++++++-------- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/integration_test/_support/app_setup.dart b/integration_test/_support/app_setup.dart index d7aafae3..0ed7bf21 100644 --- a/integration_test/_support/app_setup.dart +++ b/integration_test/_support/app_setup.dart @@ -92,7 +92,9 @@ Future expectLocalRelaysAvailable() async { final host = uri.host; final isLocal = host == 'localhost' || host == '127.0.0.1'; if (!isLocal) continue; - final port = uri.hasPort ? uri.port : 80; + final port = uri.port != 0 + ? uri.port + : (uri.scheme == 'wss' ? 443 : 80); await _expectLocalRelayAvailable(host, port); } } diff --git a/justfile b/justfile index 386df49b..341c01ac 100644 --- a/justfile +++ b/justfile @@ -199,42 +199,54 @@ _resolve-device device: exit 1; \ fi +# Resolves relay URLs: WHITENOISE_INTEGRATION_RELAYS env, else public relays when the +# device is Android (emulator can't reach host localhost), else empty (= the localhost +# defaults baked into integration_test/_support/app_setup.dart). +_resolve-relays device: + @relays="${WHITENOISE_INTEGRATION_RELAYS:-}"; \ + if [ -z "$relays" ] && command -v adb >/dev/null 2>&1 && adb devices 2>/dev/null | awk 'NR>1 && $2=="device" {print $1}' | grep -qx "{{ device }}"; then \ + relays="wss://nos.lol,wss://relay.primal.net,wss://relay.damus.io"; \ + echo "๐Ÿ“ก Android device detected โ€” using public relays: $relays" >&2; \ + fi; \ + echo "$relays" + # Run Flutter integration tests. iOS uses local Nostr relays on ports 8080 and 7777; # Android auto-uses public relays (emulator can't reach host localhost). # Run one file by passing its path: `just int-test integration_test/messaging_interactions_test.dart`. # Device: WHITENOISE_INTEGRATION_DEVICE, else the one booted simulator/emulator. # Relays: WHITENOISE_INTEGRATION_RELAYS (comma-separated) overrides defaults. -int-test target="integration_test/all_tests.dart" device=env("WHITENOISE_INTEGRATION_DEVICE", "") flavor="staging" relays=env("WHITENOISE_INTEGRATION_RELAYS", ""): +int-test target="integration_test/all_tests.dart" device=env("WHITENOISE_INTEGRATION_DEVICE", "") flavor="staging": @echo "๐Ÿงช Testing Flutter integration flows..." @device=$(just _resolve-device "{{ device }}") || exit 1; \ - relays="{{ relays }}"; \ - if [ -z "$relays" ] && command -v adb >/dev/null 2>&1 && adb devices 2>/dev/null | awk 'NR>1 && $2=="device" {print $1}' | grep -qx "$device"; then \ - relays="wss://nos.lol,wss://relay.primal.net,wss://relay.damus.io"; \ - echo "๐Ÿ“ก Android device detected โ€” using public relays: $relays" >&2; \ - fi; \ + relays=$(just _resolve-relays "$device") || exit 1; \ define=""; \ [ -n "$relays" ] && define="--dart-define=WHITENOISE_INTEGRATION_RELAYS=$relays"; \ if [ -n "{{ flavor }}" ]; then \ - flutter test -d "$device" --flavor {{ flavor }} $define {{ target }}; \ + flutter test -d "$device" --flavor {{ flavor }} ${define:+"$define"} {{ target }}; \ else \ - flutter test -d "$device" $define {{ target }}; \ + flutter test -d "$device" ${define:+"$define"} {{ target }}; \ fi -# Run Flutter integration tests with minimal output. Requires local Nostr relays on ports 8080 and 7777. +# Run Flutter integration tests with minimal output. iOS uses local Nostr relays on +# ports 8080 and 7777; Android auto-uses public relays. # Run one file by passing its path: `just int-test-quiet integration_test/messaging_interactions_test.dart`. -# Device: WHITENOISE_INTEGRATION_DEVICE, else the one booted simulator. +# Device: WHITENOISE_INTEGRATION_DEVICE, else the one booted simulator/emulator. +# Relays: WHITENOISE_INTEGRATION_RELAYS (comma-separated) overrides defaults. int-test-quiet target="integration_test/all_tests.dart" device=env("WHITENOISE_INTEGRATION_DEVICE", "") flavor="staging": @if [ ! -e "{{ target }}" ]; then \ echo "No integration test target found at {{ target }}."; \ exit 1; \ fi; \ device=$(just _resolve-device "{{ device }}") || exit 1; \ + relays=$(just _resolve-relays "$device") || exit 1; \ + define=""; \ + [ -n "$relays" ] && define="--dart-define=WHITENOISE_INTEGRATION_RELAYS=$relays"; \ if [ -n "{{ flavor }}" ]; then \ - flutter test -d "$device" --flavor {{ flavor }} --no-pub --reporter=failures-only {{ target }}; \ + flutter test -d "$device" --flavor {{ flavor }} --no-pub --reporter=failures-only ${define:+"$define"} {{ target }}; \ else \ - flutter test -d "$device" --no-pub --reporter=failures-only {{ target }}; \ + flutter test -d "$device" --no-pub --reporter=failures-only ${define:+"$define"} {{ target }}; \ fi coverage min="99": From 8559119a1b67d277088480c729be0cf2f576c42b Mon Sep 17 00:00:00 2001 From: Danny Morabito Date: Wed, 20 May 2026 22:56:31 +0200 Subject: [PATCH 3/6] review: warn on unparseable relay URL, use hasPort idiom - app_setup.dart: log via debugPrint when a relay URL fails to parse so a misconfigured WHITENOISE_INTEGRATION_RELAYS doesn't silently drop relays. - app_setup.dart: switch the port check from `uri.port != 0` to the more idiomatic `uri.hasPort`. Equivalent for ws/wss (the only schemes used in this precheck); diverges only for http/https schemes, which never reach this code path. Skipped: refactoring `_resolve-device`/`_resolve-relays` to share a single adb-devices probe. The race window between the two calls is sub-millisecond, and encoding the platform into the resolved device id would force both callers to strip a prefix before passing it to `flutter test`. Reviewer marked this low priority for a reason. --- integration_test/_support/app_setup.dart | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/integration_test/_support/app_setup.dart b/integration_test/_support/app_setup.dart index 0ed7bf21..100e2369 100644 --- a/integration_test/_support/app_setup.dart +++ b/integration_test/_support/app_setup.dart @@ -88,11 +88,14 @@ Future mountApp(WidgetTester tester) async { Future expectLocalRelaysAvailable() async { for (final url in _relayUrls) { final uri = Uri.tryParse(url); - if (uri == null) continue; + if (uri == null) { + debugPrint('[app_setup] Skipping unparseable relay URL: $url'); + continue; + } final host = uri.host; final isLocal = host == 'localhost' || host == '127.0.0.1'; if (!isLocal) continue; - final port = uri.port != 0 + final port = uri.hasPort ? uri.port : (uri.scheme == 'wss' ? 443 : 80); await _expectLocalRelayAvailable(host, port); From 9cfe2d9dd5633b422228577fd73ec5012d1d6abf Mon Sep 17 00:00:00 2001 From: Danny Morabito Date: Wed, 20 May 2026 23:05:01 +0200 Subject: [PATCH 4/6] review: fail-fast on unparseable relay URL `debugPrint` + continue is too quiet in CI output: a malformed entry in `WHITENOISE_INTEGRATION_RELAYS` could slip through and tests would fail later with confusing connection errors. Switch to `fail()` so the bad URL surfaces immediately, with the env var named in the message. Matches the existing `fail()` pattern used by `_expectLocalRelayAvailable` for TCP precheck failures. --- integration_test/_support/app_setup.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/integration_test/_support/app_setup.dart b/integration_test/_support/app_setup.dart index 100e2369..02a7f4d0 100644 --- a/integration_test/_support/app_setup.dart +++ b/integration_test/_support/app_setup.dart @@ -89,8 +89,10 @@ Future expectLocalRelaysAvailable() async { for (final url in _relayUrls) { final uri = Uri.tryParse(url); if (uri == null) { - debugPrint('[app_setup] Skipping unparseable relay URL: $url'); - continue; + fail( + 'Unparseable relay URL "$url" in WHITENOISE_INTEGRATION_RELAYS. ' + 'Fix the relay list (comma-separated) and run the test again.', + ); } final host = uri.host; final isLocal = host == 'localhost' || host == '127.0.0.1'; From b0bff840d387c94c2d93ce1132ed7984cabb420c Mon Sep 17 00:00:00 2001 From: Danny Morabito Date: Wed, 20 May 2026 23:17:59 +0200 Subject: [PATCH 5/6] review: use grep -F for fixed-string device matching `grep -qx` treats the pattern as a basic regex, so a device ID containing regex metacharacters (e.g. an `adb connect 192.168.1.100:5555` network device) could over-match. Switch to `grep -Fqx` so device IDs are matched literally. --- justfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/justfile b/justfile index 341c01ac..285ba95d 100644 --- a/justfile +++ b/justfile @@ -204,7 +204,7 @@ _resolve-device device: # defaults baked into integration_test/_support/app_setup.dart). _resolve-relays device: @relays="${WHITENOISE_INTEGRATION_RELAYS:-}"; \ - if [ -z "$relays" ] && command -v adb >/dev/null 2>&1 && adb devices 2>/dev/null | awk 'NR>1 && $2=="device" {print $1}' | grep -qx "{{ device }}"; then \ + if [ -z "$relays" ] && command -v adb >/dev/null 2>&1 && adb devices 2>/dev/null | awk 'NR>1 && $2=="device" {print $1}' | grep -Fqx "{{ device }}"; then \ relays="wss://nos.lol,wss://relay.primal.net,wss://relay.damus.io"; \ echo "๐Ÿ“ก Android device detected โ€” using public relays: $relays" >&2; \ fi; \ From 646c37061a84629d07d1b61b667a2e8c326a45fd Mon Sep 17 00:00:00 2001 From: "Javier G. Montoya S" Date: Mon, 25 May 2026 06:44:15 -0400 Subject: [PATCH 6/6] test(integration): have android integration tests use local relays --- android/app/src/debug/AndroidManifest.xml | 6 +++- .../debug/res/xml/network_security_config.xml | 7 ++++ justfile | 36 +++++++++---------- 3 files changed, 30 insertions(+), 19 deletions(-) create mode 100644 android/app/src/debug/res/xml/network_security_config.xml diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml index 399f6981..b6cf5a6b 100644 --- a/android/app/src/debug/AndroidManifest.xml +++ b/android/app/src/debug/AndroidManifest.xml @@ -1,7 +1,11 @@ - + + diff --git a/android/app/src/debug/res/xml/network_security_config.xml b/android/app/src/debug/res/xml/network_security_config.xml new file mode 100644 index 00000000..6da0e74a --- /dev/null +++ b/android/app/src/debug/res/xml/network_security_config.xml @@ -0,0 +1,7 @@ + + + + localhost + 127.0.0.1 + + diff --git a/justfile b/justfile index 285ba95d..28a9dfb6 100644 --- a/justfile +++ b/justfile @@ -199,19 +199,19 @@ _resolve-device device: exit 1; \ fi -# Resolves relay URLs: WHITENOISE_INTEGRATION_RELAYS env, else public relays when the -# device is Android (emulator can't reach host localhost), else empty (= the localhost -# defaults baked into integration_test/_support/app_setup.dart). -_resolve-relays device: - @relays="${WHITENOISE_INTEGRATION_RELAYS:-}"; \ - if [ -z "$relays" ] && command -v adb >/dev/null 2>&1 && adb devices 2>/dev/null | awk 'NR>1 && $2=="device" {print $1}' | grep -Fqx "{{ device }}"; then \ - relays="wss://nos.lol,wss://relay.primal.net,wss://relay.damus.io"; \ - echo "๐Ÿ“ก Android device detected โ€” using public relays: $relays" >&2; \ - fi; \ - echo "$relays" +# If device is an Android adb device, tunnel host ports 8080/7777 to its loopback via +# `adb reverse` so `ws://localhost:` from the device hits the host's docker relays. +# No-op for iOS simulators and desktop targets. +_setup-android-relays device: + @if command -v adb >/dev/null 2>&1 && adb devices 2>/dev/null | awk 'NR>1 && $2=="device" {print $1}' | grep -Fqx "{{ device }}"; then \ + adb -s "{{ device }}" reverse tcp:8080 tcp:8080 >/dev/null || { echo "โŒ adb reverse tcp:8080 failed for {{ device }}" >&2; exit 1; }; \ + adb -s "{{ device }}" reverse tcp:7777 tcp:7777 >/dev/null || { echo "โŒ adb reverse tcp:7777 failed for {{ device }}" >&2; exit 1; }; \ + echo "๐Ÿ“ก Android device {{ device }} โ€” adb reverse set for ports 8080, 7777 โ†’ host docker relays" >&2; \ + fi -# Run Flutter integration tests. iOS uses local Nostr relays on ports 8080 and 7777; -# Android auto-uses public relays (emulator can't reach host localhost). +# Run Flutter integration tests against local Nostr relays on ports 8080 and 7777 +# (`docker compose up -d`). On Android, `adb reverse` tunnels the device's localhost +# to the host so the same URLs work on iOS simulators, desktop, and Android. # Run one file by passing its path: `just int-test integration_test/messaging_interactions_test.dart`. # Device: WHITENOISE_INTEGRATION_DEVICE, else the one booted simulator/emulator. @@ -219,17 +219,17 @@ _resolve-relays device: int-test target="integration_test/all_tests.dart" device=env("WHITENOISE_INTEGRATION_DEVICE", "") flavor="staging": @echo "๐Ÿงช Testing Flutter integration flows..." @device=$(just _resolve-device "{{ device }}") || exit 1; \ - relays=$(just _resolve-relays "$device") || exit 1; \ + just _setup-android-relays "$device" || exit 1; \ define=""; \ - [ -n "$relays" ] && define="--dart-define=WHITENOISE_INTEGRATION_RELAYS=$relays"; \ + [ -n "${WHITENOISE_INTEGRATION_RELAYS:-}" ] && define="--dart-define=WHITENOISE_INTEGRATION_RELAYS=$WHITENOISE_INTEGRATION_RELAYS"; \ if [ -n "{{ flavor }}" ]; then \ flutter test -d "$device" --flavor {{ flavor }} ${define:+"$define"} {{ target }}; \ else \ flutter test -d "$device" ${define:+"$define"} {{ target }}; \ fi -# Run Flutter integration tests with minimal output. iOS uses local Nostr relays on -# ports 8080 and 7777; Android auto-uses public relays. +# Run Flutter integration tests with minimal output against local Nostr relays on +# ports 8080 and 7777; Android tunnels via `adb reverse`. # Run one file by passing its path: `just int-test-quiet integration_test/messaging_interactions_test.dart`. # Device: WHITENOISE_INTEGRATION_DEVICE, else the one booted simulator/emulator. @@ -240,9 +240,9 @@ int-test-quiet target="integration_test/all_tests.dart" device=env("WHITENOISE_I exit 1; \ fi; \ device=$(just _resolve-device "{{ device }}") || exit 1; \ - relays=$(just _resolve-relays "$device") || exit 1; \ + just _setup-android-relays "$device" || exit 1; \ define=""; \ - [ -n "$relays" ] && define="--dart-define=WHITENOISE_INTEGRATION_RELAYS=$relays"; \ + [ -n "${WHITENOISE_INTEGRATION_RELAYS:-}" ] && define="--dart-define=WHITENOISE_INTEGRATION_RELAYS=$WHITENOISE_INTEGRATION_RELAYS"; \ if [ -n "{{ flavor }}" ]; then \ flutter test -d "$device" --flavor {{ flavor }} --no-pub --reporter=failures-only ${define:+"$define"} {{ target }}; \ else \