Skip to content

refactor: adopt Dart 3.9 features and migrate the example integration tests to patrol - #268

Merged
spydon merged 21 commits into
mainfrom
fix-analyze
Jul 16, 2026
Merged

spydon merged 21 commits into
mainfrom
fix-analyze

Conversation

@spydon

@spydon spydon commented Jul 16, 2026 •

Copy link
Copy Markdown
Collaborator

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 three fromPlatformType factories in passkeyLoginRequest.dart
  • corbado_auth_component.dart, the screenName builder switch
  • The two availableFallbacks.length switches in passkey_verify_block.dart and passkey_append_block.dart
  • The example router.dart redirect switch, using when guards

Pattern matching with when guards, which removes two // ignore: no_default_cases comments:

  • The alternative.type switches in both block files

Record destructuring with a switch expression:

  • debug_overlay.dart, now final (IconData iconData, Color color) = switch (cp.type) { ... }

Null-aware map entries:

  • authenticate_request.dart and register_request.dart toJson(), where if (x != null) 'k': x becomes 'k': ?x

The switches in authenticator.dart, process_handler.dart, and ExceptionFactory.fromBackendMessage were 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_server development dependency pulled in shelf_plus, which could not co-resolve and broke melos bootstrap. The JavaScript Appium suite is replaced with patrol:

  • Removed appium_flutter_server and the tests/ JavaScript directory, and added patrol with its patrol configuration block.
  • Added integration_test/passkeys_test.dart covering 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 unless RUN_CEREMONIES is set, because patrol cannot match a biometric prompt on its own.
  • Added the Android instrumentation runner and build.gradle setup, and removed the flutter_driver test hook from main.dart.
  • Documented the patrol workflow in the example README.

Pipeline

  • Bumped the Flutter version in the main workflows to 3.44.5. The code generation toolchain (pigeon, source_gen, build_runner) requires analyzer 8 or newer, which needs Dart 3.10 or newer. These are development dependencies only, so the published sdk: ">=3.9.0" support for consumers is unchanged.
  • Disabled Jetifier for the Android example build, since the project is fully AndroidX, and bumped the Android NDK to 28.2.13676358, which integration_test requires.
  • Built the macOS example without code signing, since the runner has no signing identity.
  • Added a separate integration-test workflow 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 with chore(release): (or a manual dispatch), is bounded by a timeout, and is currently non-gating. The passkey ceremonies are opt in through a run_ceremonies dispatch input, backed by a screen-lock PIN and a host side adb emu finger loop.

Verification

  • melos format --set-exit-if-changed passes.
  • melos analyze --fatal-infos passes.
  • melos test passes.
  • The main pipeline is green across all seven jobs. The integration-test job passes on the emulator, with all six deterministic tests passing and the two ceremonies skipped as designed.

spydon added 15 commits July 16, 2026 11:12
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.
@spydon spydon changed the title refactor: adopt Dart 3.9 language features refactor: adopt Dart 3.9 features and migrate the example integration tests to patrol Jul 16, 2026
spydon added 6 commits July 16, 2026 17:19
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.
@spydon
spydon merged commit 7a1bd0d into main Jul 16, 2026
10 checks passed
@spydon
spydon deleted the fix-analyze branch July 16, 2026 19:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant