feat(pairing): complete the v2 pairing flow and add functional tests - #21091
Open
vbudhram wants to merge 1 commit into
Open
feat(pairing): complete the v2 pairing flow and add functional tests#21091vbudhram wants to merge 1 commit into
vbudhram wants to merge 1 commit into
Conversation
vbudhram
force-pushed
the
fxa-13870-v2
branch
6 times, most recently
from
August 24, 2026 18:41
46e110b to
b0f7a01
Compare
vbudhram
marked this pull request as ready for review
August 24, 2026 18:44
Because: - FXA-13870 needs functional tests for the v2 pairing flow, and the v2 supplicant flow did not complete end to end on Firefox for Android. - /pair never negotiated v2, so Settings "Connect a device" and the post-signin handoffs stranded v2-capable pairs on the v1 choice screen. This commit: - wires the Pair2 authority and supplicant containers to the pairing channel and the pair_oauth_start / pair_oauth_finish web channels. - negotiates pairing version 2 on both /connect_another_device and /pair, and forwards the v2 supplicant entry from /pair and /pair/supp. - falls back to the known mobile pairing client id when fxa_status omits it, which Firefox for Android does. - adds v2 functional tests for desktop, Android and the pair choice screen, covering every entry path into v2 and the version gate. - resolves the Marionette authority to Firefox Nightly, which carries the v2 chrome commands, so the v2 specs skip rather than fail without it.
Contributor
There was a problem hiding this comment.
Pull request overview
Completes the Pairing v2 authority/supplicant flow and adds supporting functional-test infrastructure.
Changes:
- Wires Pair2 routing, channel state, OAuth authorization, and mobile client fallback.
- Adds v1/v2 capability negotiation and lifecycle handling.
- Adds Nightly, Android, and Playwright functional coverage and diagnostics.
Reviewed changes
Copilot reviewed 36 out of 37 changed files in this pull request and generated 17 comments.
Show a summary per file
| File | Description |
|---|---|
packages/fxa-settings/src/react-app-env.d.ts |
Extends pairing-channel types. |
packages/fxa-settings/src/pages/Pair2/Supplicant/SyncSuccess/index.tsx |
Updates success-screen markup. |
packages/fxa-settings/src/pages/Pair2/Supplicant/ConnectThisDevice/index.tsx |
Adds functional-test selectors. |
packages/fxa-settings/src/pages/Pair2/Supplicant/ConnectThisDevice/container.tsx |
Implements supplicant authorization completion. |
packages/fxa-settings/src/pages/Pair2/Supplicant/ApproveSignIn/index.tsx |
Updates approval-screen markup. |
packages/fxa-settings/src/pages/Pair2/Supplicant/ApproveSignIn/container.tsx |
Joins the channel and gathers OAuth parameters. |
packages/fxa-settings/src/pages/Pair2/Authority/TimeoutAndCancel/container.tsx |
Implements retry and exit actions. |
packages/fxa-settings/src/pages/Pair2/Authority/ScanQR/index.tsx |
Exposes the QR region to tests. |
packages/fxa-settings/src/pages/Pair2/Authority/ScanQR/container.tsx |
Creates and preserves the authority channel. |
packages/fxa-settings/src/pages/Pair2/Authority/ScanQR/container.test.tsx |
Updates channel-lifecycle tests. |
packages/fxa-settings/src/pages/Pair2/Authority/ContinueOnMobile/container.tsx |
Implements authority cancellation. |
packages/fxa-settings/src/pages/Pair2/Authority/ApproveSignIn/index.tsx |
Adds an approval test selector. |
packages/fxa-settings/src/pages/Pair2/Authority/ApproveSignIn/container.tsx |
Implements metadata exchange and v2 authorization. |
packages/fxa-settings/src/pages/Pair/Supp/index.tsx |
Forwards v2 supplicant entries. |
packages/fxa-settings/src/pages/Pair/Index/index.tsx |
Negotiates and routes v2 authorities. |
packages/fxa-settings/src/pages/Pair/Index/index.test.tsx |
Tests pairing-version negotiation. |
packages/fxa-settings/src/pages/ConnectAnotherDevice/index.tsx |
Documents required hard navigation. |
packages/fxa-settings/src/models/integrations/pairing-authority-integration.ts |
Adds shared v2 authority session behavior. |
packages/fxa-settings/src/models/integrations/pairing-authority-integration.test.ts |
Updates shared-session setup and listeners. |
packages/fxa-settings/src/lib/channels/pairing-flow.ts |
Adds the supplicant flow controller. |
packages/fxa-settings/src/lib/channels/pairing-channel.ts |
Contains a formatting-only adjustment. |
packages/fxa-settings/src/lib/channels/firefox.ts |
Extends pairing OAuth and status WebChannels. |
packages/fxa-settings/src/components/App/index.tsx |
Routes Pair2 pages through new containers. |
packages/functional-tests/tests/pairing/pairingFlowV2Android.spec.ts |
Adds Android v2 end-to-end coverage. |
packages/functional-tests/tests/pairing/pairingFlowV2.spec.ts |
Adds browser-based v2 pairing coverage. |
packages/functional-tests/tests/pairing/pairingFlowiOS.spec.ts |
Removes obsolete screenshot calls. |
packages/functional-tests/tests/pairing/pairingFlow.spec.ts |
Pins legacy scenarios to pairing v1. |
packages/functional-tests/tests/pairing/pairChoice.spec.ts |
Adds entry-routing and negotiation tests. |
packages/functional-tests/tests/pairing/CLAUDE.md |
Documents Nightly and supplicant options. |
packages/functional-tests/lib/pairing-supplicant-harness.ts |
Adds PKCE/ECDH supplicant emulation. |
packages/functional-tests/lib/pairing-helpers.ts |
Adds v2 navigation, QR, and diagnostics helpers. |
packages/functional-tests/lib/pairing-constants.ts |
Defines v2 routes and browser preference. |
packages/functional-tests/lib/marionette.ts |
Adds element screenshot support. |
packages/functional-tests/lib/marionette-firefox.ts |
Adds v2 preferences and optional logging. |
packages/functional-tests/lib/fixtures/pairing.ts |
Resolves the authority Firefox binary. |
packages/functional-tests/lib/firefox-binary.ts |
Locates Nightly with bundled fallback. |
packages/functional-tests/lib/android-supplicant.ts |
Adds cold reset and v2 URL handling. |
Suppressed comments (1)
packages/fxa-settings/src/pages/Pair2/Authority/ApproveSignIn/container.tsx:92
- “Change your password” abandons pairing but navigates away without closing the channel or resetting the shared authority session. Clean up first, as the other cancel paths do, so the pending supplicant cannot continue against a page the user left.
const onChangePassword = () => {
navigateWithQuery('/settings/change_password');
};
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| setQrCodeValue(integration.getPairUrl('2')); | ||
| await authority.createChannel(); | ||
| const pairUrl = authority.getPairUrl('2'); | ||
| plog('auth QR minted', pairUrl.split('#')[1] ?? ''); |
Comment on lines
+328
to
+332
| const req = data as unknown as Partial<SupplicantOAuthRequest>; | ||
| if (req?.client_id && req?.state) { | ||
| this._supplicantOAuth = { | ||
| client_id: req.client_id, | ||
| state: req.state, |
Comment on lines
+365
to
+368
| // The authority already approved and was waiting on this. | ||
| if (authoritySession.pendingGrant) { | ||
| this.grantOAuthCode().catch((err) => Sentry.captureException(err)); | ||
| } |
Comment on lines
+661
to
+664
| this._channel.removeEventListener( | ||
| 'remote:pair:supp:complete', | ||
| this.handleSuppComplete | ||
| ); |
Comment on lines
+57
to
+64
| pairingFlow | ||
| .send('pair:supp:complete') | ||
| .catch(() => { | ||
| // Best effort; the supplicant is done regardless. | ||
| }) | ||
| .finally(() => { | ||
| navigateWithQuery('/pair/supplicant/sync_success'); | ||
| }); |
| return <AppLayout> | ||
| <div className="flex flex-col items-center text-center"> | ||
| <FirefoxWordmarkImage className="h-8 w-24 text-black dark:text-white" /> | ||
| // TODO: Wire up props |
Comment on lines
+21
to
+22
| const FENIX_CLIENT_ID = 'a2270f727f45f648'; | ||
| const FIREFOX_IOS_CLIENT_ID = '1b1a3e44c54fbb58'; |
Comment on lines
+32
to
+33
| const onSyncSettings = () => navigateWithQuery('/settings'); | ||
| const onCancel = () => navigateWithQuery('/settings'); |
Comment on lines
+24
to
+31
| const onTryAgain = () => { | ||
| if (integration instanceof PairingAuthorityIntegration) { | ||
| // Ends the flow, so close the channel. `destroy` is async and nothing | ||
| // awaits it here; catch so a failed close is not an unhandled rejection. | ||
| integration.destroy().catch((err) => Sentry.captureException(err)); | ||
| } | ||
| navigateWithQuery('/pair/authority/scan_qr'); | ||
| }; |
Comment on lines
+73
to
+80
| // Best-effort until a shared UA parser is wired; the channel only gives a | ||
| // raw UA string. deviceFamily/deviceOS drive DeviceInfoBlock display only. | ||
| deviceFamily: /Android/i.test(ua) | ||
| ? 'Android' | ||
| : /iPhone|iPad|iOS/i.test(ua) | ||
| ? 'iOS' | ||
| : 'Mobile', | ||
| deviceOS: ua, |
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.
Because
fxa_statusfrom Firefox for Android omitsclientId, leaving the supplicant with no client to authorize.This pull request
pair_oauth_start/pair_oauth_finishweb channels, via a newpairing-flow.tsand an extendedpairing-authority-integration.ts.ConnectAnotherDevice/index.tsxandPair/Index/index.tsx, and forwards the v2 supplicant entry from/pairand/pair/supp.fxa_statusomits it.firefox-binary.ts, so the v2 specs skip rather than fail where it is absent.Issue that this pull request solves
Closes: https://mozilla-hub.atlassian.net/browse/FXA-13870
Checklist
Other information
nx test-unit fxa-settings(4 changed suites): 127 passed, 0 failed.playwright test tests/pairing: 17 passed, 9 skipped, 0 failed.The v2 specs do not run in CI. They need Firefox Nightly for the Marionette authority, which the runner image does not carry, so they skip. To run locally:
The three version-negotiation tests in
pairChoice.spec.tsadditionally need the content server onPAIRING_VERSION=2; they skip on a default stack, which serves version 1.For the Android spec: boot an emulator with a Fenix debug build,
yarn adb-reverse, thenANDROID_PAIRING_V2_ENABLED=1 npx playwright test tests/pairing/pairingFlowV2Android.spec.ts --project=local.