From 84bc17ab9febaf210ad29edea0216560fe5530c5 Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Thu, 14 May 2026 11:42:17 -0400 Subject: [PATCH] fix(editor): Flip to webauthn via switcher when MFA login defaults to TOTP The seeded test owner has `mfaSecret` set (so `availableMfaMethods` includes 'totp' alongside webauthn) and the previous successful sign-in caches the chosen method in `localStorage.n8n.last2faMethod`. When the user lands back on the MFA prompt with `last = 'totp'`, the TOTP form shows instead of the webauthn prompt. The composable's `loginWithWebAuthn` now flips to the webauthn screen via the switcher when it detects the TOTP form. Also wraps the TOTP branch of `MfaView` in `mfa-totp-screen` so the page object can scope the switcher locator without tripping the janitor's `scope-lockdown` rule. --- .../src/features/core/auth/views/MfaView.vue | 4 ++-- .../testing/playwright/composables/MfaComposer.ts | 9 ++++++++- packages/testing/playwright/pages/MfaLoginPage.ts | 12 ++++++++++-- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/packages/frontend/editor-ui/src/features/core/auth/views/MfaView.vue b/packages/frontend/editor-ui/src/features/core/auth/views/MfaView.vue index 7104768e121..7ffeee6e85c 100644 --- a/packages/frontend/editor-ui/src/features/core/auth/views/MfaView.vue +++ b/packages/frontend/editor-ui/src/features/core/auth/views/MfaView.vue @@ -297,7 +297,7 @@ watch(currentMethod, (next) => { - +
diff --git a/packages/testing/playwright/composables/MfaComposer.ts b/packages/testing/playwright/composables/MfaComposer.ts index 3b6b944465b..54821cc70ab 100644 --- a/packages/testing/playwright/composables/MfaComposer.ts +++ b/packages/testing/playwright/composables/MfaComposer.ts @@ -72,13 +72,20 @@ export class MfaComposer { /** * Complete the MFA login via a WebAuthn ceremony. The browser ceremony is * served by whichever virtual authenticator was attached to the page — - * see `utils/webauthn-virtual-authenticator.ts`. + * see `utils/webauthn-virtual-authenticator.ts`. If the MFA screen lands + * on the TOTP form (which can happen when the seed user has both + * `mfaSecret` and a webauthn credential, with `localStorage.n8n.last2faMethod` + * pointing at `totp`), flip to webauthn via the switcher first. */ async loginWithWebAuthn(email: string, password: string): Promise { await this.n8n.signIn.fillEmail(email); await this.n8n.signIn.fillPassword(password); await this.n8n.signIn.clickSubmit(); + if (await this.n8n.mfaLogin.getForm().isVisible()) { + await this.n8n.mfaLogin.getSwitcherLink().click(); + } + await this.n8n.mfaWebauthnLogin.clickWebAuthnButton(); await expect(this.n8n.page).toHaveURL(/workflows/); } diff --git a/packages/testing/playwright/pages/MfaLoginPage.ts b/packages/testing/playwright/pages/MfaLoginPage.ts index f0634ddf6e5..dfc76bb6b9e 100644 --- a/packages/testing/playwright/pages/MfaLoginPage.ts +++ b/packages/testing/playwright/pages/MfaLoginPage.ts @@ -7,11 +7,11 @@ import { BasePage } from './BasePage'; */ export class MfaLoginPage extends BasePage { get container(): Locator { - return this.page.getByTestId('mfa-login-form'); + return this.page.getByTestId('mfa-totp-screen'); } getForm(): Locator { - return this.container; + return this.container.getByTestId('mfa-login-form'); } getMfaCodeField(): Locator { @@ -52,4 +52,12 @@ export class MfaLoginPage extends BasePage { await this.fillMfaRecoveryCode(recoveryCode); // Form auto-submits } + + /** + * Switcher link shown when the user has both TOTP and a webauthn credential. + * Clicking it flips the screen from TOTP to the webauthn prompt. + */ + getSwitcherLink(): Locator { + return this.container.getByTestId('mfa-switcher').getByRole('button'); + } }