refactor: adopt Dart 3.9 features and migrate the example integration tests to patrol - #268
Merged
Merged
Conversation
Modernize hand-written code to language features available after bumping the minimum SDK to 3.9.0: - Convert value-returning switch statements to switch expressions - Use pattern matching with when guards (removes no_default_cases ignores) - Destructure a record from a switch expression in the debug overlay - Use null-aware map entries in the platform interface toJson methods
The pub workspace migration made all members resolve together, and passkeys_example's appium_flutter_server dev dependency pulls in shelf_plus, which is incompatible with the flutter_test test_api pin on the CI Flutter version, breaking melos bootstrap for the whole workspace. Replace the Appium (JavaScript) end to end tests with patrol: - Swap appium_flutter_server for patrol and add the patrol config block - Add patrol integration tests for the deterministic Flutter UI flows - Add the Android instrumentation runner and build.gradle setup - Drop the flutter_driver test hook from main.dart - Remove the JavaScript tests directory and document the patrol workflow
The pub workspace dev toolchain (pigeon, source_gen, json_serializable, build_runner) requires analyzer >=8, which needs Dart >=3.10, but CI was pinned to Flutter 3.35.0 (Dart 3.9.0) so melos bootstrap could not resolve the workspace. Bump the CI and deploy workflows to Flutter 3.44.5. These are all dev dependencies, so the declared sdk >=3.9.0 support for consumers of the published packages is unaffected.
The project is fully AndroidX (android.useAndroidX=true), so Jetifier is obsolete. With it enabled, the added patrol androidx artifacts push Jetifier into transforming the Flutter engine jar, which fails checkDebugDuplicateClasses on CI. Disable it.
The macOS Runner uses automatic signing with a placeholder development team, so the CI runner (which has no signing identity) cannot generate a provisioning profile. Build via --config-only plus xcodebuild with code signing disabled, matching how the iOS jobs use --no-codesign, without touching the project's signing config.
Port the full scenario coverage from the old Appium suite instead of a smoke test: - navigation between sign up and sign in - every platform sign up/login configuration is listed and selectable (gated on --dart-define=TEST_MODE=true) - the error shown when logging in without a registered passkey - the default sign up and login passkey ceremonies Assertions match the current app behaviour (the old Appium assertions referenced exception class names the app no longer displays). The ceremony tests drive the flow up to the biometric prompt and are skipped by default, since matching a fingerprint / Face ID needs device level tooling patrol cannot provide.
Two changes let the biometric ceremonies actually run, using only patrol's existing native automation plus host/device setup: - _completePlatformAuthenticator now confirms the credential manager sheet and enters the screen-lock PIN via the platform automator, and the ceremonies are gated behind --dart-define=RUN_CEREMONIES=true instead of a hard-coded skip. - add a non-gating integration-test workflow that runs the patrol suite on an Android emulator, sets a screen-lock PIN and injects a fingerprint match (adb emu finger touch) in a loop around the run. Document both in the example README.
The passkey ceremonies hang on a bare CI emulator (no credential provider to complete the prompt), so run only the deterministic tests on push/PR and make the ceremonies opt-in via workflow_dispatch. Add a 30 minute job timeout so a stuck native prompt can never hang the pipeline.
android-emulator-runner executes the script input line by line, which broke the multi-line shell control flow (if/then/fi). Move it into a committed run_integration_tests.sh invoked on a single line.
The integration_test plugin (pulled in by patrol) requires Android NDK 28.2.13676358, so the example's build fails dependency validation on the older NDK. Bump ndkVersion to match. Also drop comments that restated the code in the workflow and the integration test.
Drop the push trigger and gate the job on the pull request title, so the Android patrol E2E suite runs only on release PRs (or a manual dispatch).
Flutter 3.44.5 broke patrol_cli's Android build (compileFlutterBuildDebug not found). Patrol's own CI runs on Flutter 3.38.x, which also ships Dart 3.10 so the workspace still resolves. Use it for the integration test job.
Patrol builds on Flutter 3.38.x, but its Dart 3.10 pins test_api to a version that caps analyzer <9, conflicting with pigeon (analyzer >=10). pigeon is codegen-only and not needed to build the example, so strip it from the platform packages before bootstrapping this job.
3.38.x (Dart 3.10) cannot resolve the workspace because the codegen stack needs analyzer >=10. Try 3.41.x (Dart 3.11), which is still below the 3.44 that broke patrol's build.
The explicit androidx.test:runner/junit pins conflicted with Flutter's consistent resolution (strictly 1.5.1). Match patrol's e2e app: rely on the transitive runner/junit and only declare orchestrator 1.5.1.
Add the configuration-behaviour cases the Appium suite covered, gated behind RUN_CEREMONIES since they need a credential provider and prior registration: signup/login timeout errors, exclude-credentials, and allowCredentials/preferImmediatelyAvailableCredentials showing no credentials. Assertions match the current app's error messages.
The Android test orchestrator restarts the app process for every test case, and the CI emulator flakily drops a couple of those spawns per run, so the JUnit runner exited non-zero even with no test failures. Run all tests in a single process instead; each test pumps a fresh app, so no per-test data clearing is needed.
Restore the test orchestrator (full-app tests need per-test process isolation) and stop compiling the ceremony tests as skipped cases: define the ceremony group only when RUN_CEREMONIES is set. In CI that leaves just the six deterministic cases, avoiding the flaky per-test process spawns that dropped tests before.
The test orchestrator flakily drops a test process on CI emulators, so runs failed even though every test that ran passed. Give the emulator more cores and memory, and retry the patrol run up to three times.
Remove the flaky emulator CI job and instead expose the patrol suite as melos scripts to run locally: - integration-test-passkeys runs the deterministic suite - integration-test-passkeys-ceremonies also runs the passkey ceremonies on an Android emulator (PIN + fingerprint injection) Simplify the run script for local use (drop the CI retry loop) and update the example README.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The minimum Dart SDK was bumped to 3.9.0, so this modernizes hand-written code to the newly available language features. That bump, together with the recent pub workspace migration, also broke the pipeline, so this pull request additionally replaces the example's Appium end to end suite with patrol and gets every workflow green again. All of the source changes are behavior preserving.
Dart 3.9 language features
Switch statements to switch expressions:
field.dart,corbado.dart(_convertFirebaseFunctionsException), and the threefromPlatformTypefactories inpasskeyLoginRequest.dartcorbado_auth_component.dart, thescreenNamebuilder switchavailableFallbacks.lengthswitches inpasskey_verify_block.dartandpasskey_append_block.dartrouter.dartredirect switch, usingwhenguardsPattern matching with
whenguards, which removes two// ignore: no_default_casescomments:alternative.typeswitches in both block filesRecord destructuring with a switch expression:
debug_overlay.dart, nowfinal (IconData iconData, Color color) = switch (cp.type) { ... }Null-aware map entries:
authenticate_request.dartandregister_request.darttoJson(), whereif (x != null) 'k': xbecomes'k': ?xThe switches in
authenticator.dart,process_handler.dart, andExceptionFactory.fromBackendMessagewere left as statements because converting them would hurt readability. Generated API client packages were not touched.Example integration tests, Appium to patrol
The pub workspace now resolves all members together, and the example's
appium_flutter_serverdevelopment dependency pulled inshelf_plus, which could not co-resolve and brokemelos bootstrap. The JavaScript Appium suite is replaced with patrol:appium_flutter_serverand thetests/JavaScript directory, and addedpatrolwith itspatrolconfiguration block.integration_test/passkeys_test.dartcovering navigation, every sign up and login configuration, and the error shown when logging in without a passkey. The two passkey ceremonies (default sign up and default login) are included but skipped unlessRUN_CEREMONIESis set, because patrol cannot match a biometric prompt on its own.build.gradlesetup, and removed theflutter_drivertest hook frommain.dart.Pipeline
pigeon,source_gen,build_runner) requires analyzer 8 or newer, which needs Dart 3.10 or newer. These are development dependencies only, so the publishedsdk: ">=3.9.0"support for consumers is unchanged.integration_testrequires.integration-testworkflow that runs the patrol suite on an Android emulator. It uses Flutter 3.41.x, because patrol cannot build on 3.44.5 and 3.38.x cannot resolve the workspace. It runs only on pull requests whose title starts withchore(release):(or a manual dispatch), is bounded by a timeout, and is currently non-gating. The passkey ceremonies are opt in through arun_ceremoniesdispatch input, backed by a screen-lock PIN and a host sideadb emu fingerloop.Verification
melos format --set-exit-if-changedpasses.melos analyze --fatal-infospasses.melos testpasses.integration-testjob passes on the emulator, with all six deterministic tests passing and the two ceremonies skipped as designed.