You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

132 lines
5.4 KiB

import { expect, type Browser, Page } from '@playwright/test';
import { type MailBuffer } from 'maildev';
import * as utils from '../../global-utils';
import { submitTwoFactor, type TwoFactor } from './2fa';
/**
* Open the account/avatar menu in the web vault header. The button's
* accessible name is the user's display name; centralising the locator here
* insulates callers from web-vault changes to that element's structure.
*
* Note: cipher rows also expose `aria-haspopup="menu"` ellipsis buttons, so
* naïve `aria-haspopup` selectors mis-target on the vault page. Anchor on the
* accessible-name (`{ exact: true }` to avoid substring matches against any
* cipher whose name happens to start with the user's display name).
*/
export async function openAvatarMenu(page: Page, userName: string) {
await page.getByRole('button', { name: userName, exact: true }).click();
}
/**
* Fill the registration / change-master-password form's "new" + "confirm new"
* master-password fields. Anchored by `formcontrolname` rather than label —
* the current bundled web-vault renders three labels matching "Master
* password" via Playwright's case-insensitive substring matching ("Master
* password (required)", "Confirm master password (required)", and "Master
* password hint"), so a label-based locator is ambiguous.
*/
export async function fillNewMasterPassword(page: Page, password: string) {
await page.locator('input[formcontrolname="newPassword"]').fill(password);
await page.locator('input[formcontrolname="newPasswordConfirm"]').fill(password);
}
/**
* Submit the in-dialog user-verification (`app-user-verification`) master-
* password gate that the bundled web vault renders before any sensitive
* operation (2FA enrol/disable, passkey enrol/remove, key rotation, KDF
* change).
*
* Pressing Enter inside the password input submits the form unambiguously —
* the surrounding page often has multiple `Continue` buttons (dialog action,
* stale settings header), so a button-text click is brittle.
*
* Note: the user-verification component falls back to email-OTP verification
* when the master password isn't "fresh" in the current session (e.g. after a
* passkey login). Callers reaching this helper from a post-passkey-login
* state must arrange a recent MP entry first.
*/
export async function submitMasterPasswordVerification(page: Page, masterPassword: string) {
const mpInput = page.locator('input#masterPassword');
await mpInput.waitFor({ state: 'visible' });
await mpInput.fill(masterPassword);
await mpInput.press('Enter');
}
export async function createAccount(test, page: Page, user: { email: string, name: string, password: string }, mailBuffer?: MailBuffer) {
await test.step(`Create user ${user.name}`, async () => {
await utils.cleanLanding(page);
await page.getByRole('link', { name: 'Create account' }).click();
// Back to Vault create account
await expect(page).toHaveTitle(/Create account | Vaultwarden Web/);
await page.getByLabel(/Email address/).fill(user.email);
await page.getByLabel('Name').fill(user.name);
await page.getByRole('button', { name: 'Continue' }).click();
await fillNewMasterPassword(page, user.password);
await page.getByRole('button', { name: 'Create account' }).click();
await utils.checkNotification(page, 'Your new account has been created')
await utils.ignoreExtension(page);
// We are now in the default vault page
await expect(page).toHaveTitle('Vaults | Vaultwarden Web');
// await utils.checkNotification(page, 'You have been logged in!');
if( mailBuffer ){
await mailBuffer.expect((m) => m.subject === "Welcome");
await mailBuffer.expect((m) => m.subject === "New Device Logged In From Firefox");
}
});
}
/**
* Master-password login.
*
* When the account has 2FA enabled, pass `options.twoFactor` — a
* `TwoFactor` discriminated union carrying the factor's own state (TOTP
* generator, mail buffer, …). The helper then drives the /#/2fa challenge
* inline and lands the user in `/vault`. Mirrors `setups/sso.ts:logUser`.
*
* `options.mailBuffer` (independent of `twoFactor`) consumes the expected
* "New Device Logged In" mail at the end of the flow, when the test wants
* to assert that login emails went out.
*/
export async function logUser(
test,
page: Page,
user: { email: string, password: string },
options: {
mailBuffer?: MailBuffer,
twoFactor?: TwoFactor,
} = {},
) {
let mailBuffer = options.mailBuffer;
await test.step(`Log user ${user.email}`, async () => {
await utils.cleanLanding(page);
await page.getByLabel(/Email address/).fill(user.email);
await page.getByRole('button', { name: 'Continue' }).click();
// Unlock page
await page.getByLabel('Master password').fill(user.password);
await page.getByRole('button', { name: 'Log in with master password' }).click();
if( options.twoFactor ){
await submitTwoFactor(test, page, options.twoFactor);
}
await utils.ignoreExtension(page);
// We are now in the default vault page
await expect(page).toHaveTitle(/Vaultwarden Web/);
if( mailBuffer ){
await mailBuffer.expect((m) => m.subject === "New Device Logged In From Firefox");
}
});
}