Skip to content

feat(pairing): complete the v2 pairing flow and add functional tests - #21091

Open
vbudhram wants to merge 1 commit into
mainfrom
fxa-13870-v2
Open

feat(pairing): complete the v2 pairing flow and add functional tests#21091
vbudhram wants to merge 1 commit into
mainfrom
fxa-13870-v2

Conversation

@vbudhram

@vbudhram vbudhram commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Because

  • The v2 supplicant flow did not complete end to end on Firefox for Android, the Pair2 containers were never wired to the pairing channel or the OAuth web channels.
  • fxa_status from Firefox for Android omits clientId, leaving the supplicant with no client to authorize.

This pull request

  • Wires the Pair2 authority and supplicant containers to the pairing channel and the pair_oauth_start / pair_oauth_finish web channels, via a new pairing-flow.ts and an extended pairing-authority-integration.ts.
  • Negotiates pairing version 2 in ConnectAnotherDevice/index.tsx and Pair/Index/index.tsx, 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.
  • Resolves the Marionette authority to Firefox Nightly via a new 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

  • My commit is GPG signed.
  • If applicable, I have modified or added tests which pass locally.
  • I have added necessary documentation (if appropriate).
  • I have verified that my changes render correctly in RTL (if appropriate).
  • I have manually reviewed all AI generated code.

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:

yarn start mza
cd packages/functional-tests
npx playwright test tests/pairing/pairingFlowV2.spec.ts --project=local

The three version-negotiation tests in pairChoice.spec.ts additionally need the content server on PAIRING_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, then ANDROID_PAIRING_V2_ENABLED=1 npx playwright test tests/pairing/pairingFlowV2Android.spec.ts --project=local.

@vbudhram
vbudhram force-pushed the fxa-13870-v2 branch 6 times, most recently from 46e110b to b0f7a01 Compare August 24, 2026 18:41
@vbudhram
vbudhram marked this pull request as ready for review August 24, 2026 18:44
@vbudhram
vbudhram requested a review from a team as a code owner August 24, 2026 18:44
Base automatically changed from FXA-13868 to main August 24, 2026 20:37
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.
Copilot AI balanced review requested due to automatic review settings August 24, 2026 21:36

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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,
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.

2 participants