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.
This commit is contained in:
Ricardo Espinoza 2026-05-14 11:42:17 -04:00
parent 9c8f1000a7
commit 84bc17ab9f
No known key found for this signature in database
3 changed files with 20 additions and 5 deletions

View File

@ -297,7 +297,7 @@ watch(currentMethod, (next) => {
</div>
<!-- TOTP flow -->
<template v-if="currentMethod === 'totp'">
<div v-if="currentMethod === 'totp'" data-test-id="mfa-totp-screen">
<div :class="[$style.formContainer, reportError ? $style.formError : '']">
<N8nFormInputs
v-if="formInputs"
@ -362,7 +362,7 @@ watch(currentMethod, (next) => {
{{ i18.baseText(switcherLabelKey) }}
</button>
</div>
</template>
</div>
<!-- WebAuthn flow (passkey or security key) -->
<div v-else data-test-id="mfa-webauthn-screen">

View File

@ -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<void> {
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/);
}

View File

@ -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');
}
}