mirror of
https://github.com/dani-garcia/vaultwarden.git
synced 2025-06-14 06:40:08 +00:00
Fix and improve Playwright tests
This commit is contained in:
parent
e3d66216f6
commit
3730355434
15 changed files with 300 additions and 168 deletions
|
@ -40,6 +40,7 @@ DUMMY_AUTHORITY=http://${KC_HTTP_HOST}:${KC_HTTP_PORT}/realms/${DUMMY_REALM}
|
||||||
ROCKET_ADDRESS=0.0.0.0
|
ROCKET_ADDRESS=0.0.0.0
|
||||||
ROCKET_PORT=8000
|
ROCKET_PORT=8000
|
||||||
DOMAIN=http://127.0.0.1:${ROCKET_PORT}
|
DOMAIN=http://127.0.0.1:${ROCKET_PORT}
|
||||||
|
LOG_LEVEL=info,oidcwarden::sso=debug
|
||||||
I_REALLY_WANT_VOLATILE_STORAGE=true
|
I_REALLY_WANT_VOLATILE_STORAGE=true
|
||||||
|
|
||||||
SSO_ENABLED=true
|
SSO_ENABLED=true
|
||||||
|
|
|
@ -2,8 +2,8 @@ FROM playwright_oidc_vaultwarden_prebuilt AS vaultwarden
|
||||||
|
|
||||||
FROM node:18-bookworm AS build
|
FROM node:18-bookworm AS build
|
||||||
|
|
||||||
arg REPO_URL
|
ARG REPO_URL
|
||||||
arg COMMIT_HASH
|
ARG COMMIT_HASH
|
||||||
|
|
||||||
ENV REPO_URL=$REPO_URL
|
ENV REPO_URL=$REPO_URL
|
||||||
ENV COMMIT_HASH=$COMMIT_HASH
|
ENV COMMIT_HASH=$COMMIT_HASH
|
||||||
|
|
|
@ -24,10 +24,12 @@ services:
|
||||||
environment:
|
environment:
|
||||||
- DATABASE_URL
|
- DATABASE_URL
|
||||||
- I_REALLY_WANT_VOLATILE_STORAGE
|
- I_REALLY_WANT_VOLATILE_STORAGE
|
||||||
|
- LOG_LEVEL
|
||||||
- LOGIN_RATELIMIT_MAX_BURST
|
- LOGIN_RATELIMIT_MAX_BURST
|
||||||
- SMTP_HOST
|
- SMTP_HOST
|
||||||
- SMTP_FROM
|
- SMTP_FROM
|
||||||
- SMTP_DEBUG
|
- SMTP_DEBUG
|
||||||
|
- SSO_DEBUG_TOKENS
|
||||||
- SSO_FRONTEND
|
- SSO_FRONTEND
|
||||||
- SSO_ENABLED
|
- SSO_ENABLED
|
||||||
- SSO_ONLY
|
- SSO_ONLY
|
||||||
|
|
|
@ -189,7 +189,7 @@ export async function startVaultwarden(browser: Browser, testInfo: TestInfo, env
|
||||||
|
|
||||||
console.log(`Starting Vaultwarden`);
|
console.log(`Starting Vaultwarden`);
|
||||||
execSync(`docker compose --profile playwright --env-file test.env up -d Vaultwarden`, {
|
execSync(`docker compose --profile playwright --env-file test.env up -d Vaultwarden`, {
|
||||||
env: { LOGIN_RATELIMIT_MAX_BURST: 100, ...env, ...dbConfig(testInfo) },
|
env: { ...env, ...dbConfig(testInfo) },
|
||||||
});
|
});
|
||||||
await waitFor("/", browser);
|
await waitFor("/", browser);
|
||||||
console.log(`Vaultwarden running on: ${process.env.DOMAIN}`);
|
console.log(`Vaultwarden running on: ${process.env.DOMAIN}`);
|
||||||
|
@ -210,3 +210,14 @@ export async function checkNotification(page: Page, hasText: string) {
|
||||||
await page.locator('bit-toast').filter({ hasText }).getByRole('button').click();
|
await page.locator('bit-toast').filter({ hasText }).getByRole('button').click();
|
||||||
await expect(page.locator('bit-toast').filter({ hasText })).toHaveCount(0);
|
await expect(page.locator('bit-toast').filter({ hasText })).toHaveCount(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function cleanLanding(page: Page) {
|
||||||
|
await page.goto('/', { waitUntil: 'domcontentloaded' });
|
||||||
|
await expect(page.getByRole('button').nth(0)).toBeVisible();
|
||||||
|
|
||||||
|
const logged = await page.getByRole('button', { name: 'Log out' }).count();
|
||||||
|
if( logged > 0 ){
|
||||||
|
await page.getByRole('button', { name: 'Log out' }).click();
|
||||||
|
await page.getByRole('button', { name: 'Log out' }).click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -53,6 +53,9 @@ DUMMY_AUTHORITY=http://${KC_HTTP_HOST}:${KC_HTTP_PORT}/realms/${DUMMY_REALM}
|
||||||
######################
|
######################
|
||||||
ROCKET_PORT=8003
|
ROCKET_PORT=8003
|
||||||
DOMAIN=http://127.0.0.1:${ROCKET_PORT}
|
DOMAIN=http://127.0.0.1:${ROCKET_PORT}
|
||||||
|
LOG_LEVEL=info,oidcwarden::sso=debug
|
||||||
|
LOGIN_RATELIMIT_MAX_BURST=100
|
||||||
|
|
||||||
SMTP_SECURITY=off
|
SMTP_SECURITY=off
|
||||||
SMTP_PORT=${MAILDEV_SMTP_PORT}
|
SMTP_PORT=${MAILDEV_SMTP_PORT}
|
||||||
SMTP_FROM_NAME=Vaultwarden
|
SMTP_FROM_NAME=Vaultwarden
|
||||||
|
@ -61,6 +64,7 @@ SMTP_TIMEOUT=5
|
||||||
SSO_CLIENT_ID=VaultWarden
|
SSO_CLIENT_ID=VaultWarden
|
||||||
SSO_CLIENT_SECRET=VaultWarden
|
SSO_CLIENT_SECRET=VaultWarden
|
||||||
SSO_AUTHORITY=http://${KC_HTTP_HOST}:${KC_HTTP_PORT}/realms/${TEST_REALM}
|
SSO_AUTHORITY=http://${KC_HTTP_HOST}:${KC_HTTP_PORT}/realms/${TEST_REALM}
|
||||||
|
SSO_DEBUG_TOKENS=true
|
||||||
|
|
||||||
###########################
|
###########################
|
||||||
# Docker MariaDb container#
|
# Docker MariaDb container#
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { MailDev } from 'maildev';
|
||||||
|
|
||||||
const utils = require('../global-utils');
|
const utils = require('../global-utils');
|
||||||
import { createAccount, logUser } from './setups/user';
|
import { createAccount, logUser } from './setups/user';
|
||||||
|
import { activateEmail, retrieveEmailCode, disableEmail } from './setups/2fa';
|
||||||
|
|
||||||
let users = utils.loadEnv();
|
let users = utils.loadEnv();
|
||||||
|
|
||||||
|
@ -67,24 +68,7 @@ test('Activaite 2fa', async ({ context, page }) => {
|
||||||
|
|
||||||
await logUser(test, page, users.user1);
|
await logUser(test, page, users.user1);
|
||||||
|
|
||||||
await page.getByRole('button', { name: users.user1.name }).click();
|
await activateEmail(test, page, users.user1, emails);
|
||||||
await page.getByRole('menuitem', { name: 'Account settings' }).click();
|
|
||||||
await page.getByRole('link', { name: 'Security' }).click();
|
|
||||||
await page.getByRole('link', { name: 'Two-step login' }).click();
|
|
||||||
await page.locator('li').filter({ hasText: 'Email' }).getByRole('button').click();
|
|
||||||
await page.getByLabel('Master password (required)').fill(users.user1.password);
|
|
||||||
await page.getByRole('button', { name: 'Continue' }).click();
|
|
||||||
await page.getByRole('button', { name: 'Send email' }).click();
|
|
||||||
|
|
||||||
const codeMail = await emails.next((mail) => mail.subject === "Vaultwarden Login Verification Code");
|
|
||||||
const page2 = await context.newPage();
|
|
||||||
await page2.setContent(codeMail.html);
|
|
||||||
const code = await page2.getByTestId("2fa").innerText();
|
|
||||||
await page2.close();
|
|
||||||
|
|
||||||
await page.getByLabel('2. Enter the resulting 6').fill(code);
|
|
||||||
await page.getByRole('button', { name: 'Turn on' }).click();
|
|
||||||
await page.getByRole('heading', { name: 'Turned on', exact: true });
|
|
||||||
|
|
||||||
emails.close();
|
emails.close();
|
||||||
});
|
});
|
||||||
|
@ -112,19 +96,7 @@ test('2fa', async ({ context, page }) => {
|
||||||
await expect(page).toHaveTitle(/Vaultwarden Web/);
|
await expect(page).toHaveTitle(/Vaultwarden Web/);
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step('disable', async () => {
|
await disableEmail(test, page, users.user1);
|
||||||
await page.getByRole('button', { name: 'Test' }).click();
|
|
||||||
await page.getByRole('menuitem', { name: 'Account settings' }).click();
|
|
||||||
await page.getByRole('link', { name: 'Security' }).click();
|
|
||||||
await page.getByRole('link', { name: 'Two-step login' }).click();
|
|
||||||
await page.locator('li').filter({ hasText: 'Email' }).getByRole('button').click();
|
|
||||||
await page.getByLabel('Master password (required)').click();
|
|
||||||
await page.getByLabel('Master password (required)').fill(users.user1.password);
|
|
||||||
await page.getByRole('button', { name: 'Continue' }).click();
|
|
||||||
await page.getByRole('button', { name: 'Turn off' }).click();
|
|
||||||
await page.getByRole('button', { name: 'Yes' }).click();
|
|
||||||
await utils.checkNotification(page, 'Two-step login provider turned off');
|
|
||||||
});
|
|
||||||
|
|
||||||
emails.close();
|
emails.close();
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,6 +3,7 @@ import * as OTPAuth from "otpauth";
|
||||||
|
|
||||||
import * as utils from "../global-utils";
|
import * as utils from "../global-utils";
|
||||||
import { createAccount, logUser } from './setups/user';
|
import { createAccount, logUser } from './setups/user';
|
||||||
|
import { activateTOTP, disableTOTP } from './setups/2fa';
|
||||||
|
|
||||||
let users = utils.loadEnv();
|
let users = utils.loadEnv();
|
||||||
let totp;
|
let totp;
|
||||||
|
@ -24,33 +25,14 @@ test('Master password login', async ({ page }) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Authenticator 2fa', async ({ context, page }) => {
|
test('Authenticator 2fa', async ({ context, page }) => {
|
||||||
let totp;
|
await logUser(test, page, users.user1);
|
||||||
|
|
||||||
await test.step('Login', async () => {
|
let totp = await activateTOTP(test, page, users.user1);
|
||||||
await logUser(test, page, users.user1);
|
|
||||||
});
|
|
||||||
|
|
||||||
await test.step('Activate', async () => {
|
|
||||||
await page.getByRole('button', { name: users.user1.name }).click();
|
|
||||||
await page.getByRole('menuitem', { name: 'Account settings' }).click();
|
|
||||||
await page.getByRole('link', { name: 'Security' }).click();
|
|
||||||
await page.getByRole('link', { name: 'Two-step login' }).click();
|
|
||||||
await page.locator('li').filter({ hasText: 'TOTP Authenticator' }).getByRole('button').click();
|
|
||||||
await page.getByLabel('Master password (required)').fill(users.user1.password);
|
|
||||||
await page.getByRole('button', { name: 'Continue' }).click();
|
|
||||||
|
|
||||||
const secret = await page.getByLabel('Key').innerText();
|
|
||||||
totp = new OTPAuth.TOTP({ secret, period: 30 });
|
|
||||||
|
|
||||||
await page.getByLabel('Verification code (required)').fill(totp.generate());
|
|
||||||
await page.getByRole('button', { name: 'Turn on' }).click();
|
|
||||||
await page.getByRole('heading', { name: 'Turned on', exact: true });
|
|
||||||
await page.getByLabel('Close').click();
|
|
||||||
})
|
|
||||||
|
|
||||||
await test.step('logout', async () => {
|
await test.step('logout', async () => {
|
||||||
await page.getByRole('button', { name: users.user1.name }).click();
|
await page.getByRole('button', { name: users.user1.name }).click();
|
||||||
await page.getByRole('menuitem', { name: 'Log out' }).click();
|
await page.getByRole('menuitem', { name: 'Log out' }).click();
|
||||||
|
await expect(page.getByRole('heading', { name: 'Log in' })).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
await test.step('login', async () => {
|
await test.step('login', async () => {
|
||||||
|
@ -68,17 +50,5 @@ test('Authenticator 2fa', async ({ context, page }) => {
|
||||||
await expect(page).toHaveTitle(/Vaultwarden Web/);
|
await expect(page).toHaveTitle(/Vaultwarden Web/);
|
||||||
});
|
});
|
||||||
|
|
||||||
await test.step('disable', async () => {
|
await disableTOTP(test, page, users.user1);
|
||||||
await page.getByRole('button', { name: 'Test' }).click();
|
|
||||||
await page.getByRole('menuitem', { name: 'Account settings' }).click();
|
|
||||||
await page.getByRole('link', { name: 'Security' }).click();
|
|
||||||
await page.getByRole('link', { name: 'Two-step login' }).click();
|
|
||||||
await page.locator('li').filter({ hasText: 'TOTP Authenticator' }).getByRole('button').click();
|
|
||||||
await page.getByLabel('Master password (required)').click();
|
|
||||||
await page.getByLabel('Master password (required)').fill(users.user1.password);
|
|
||||||
await page.getByRole('button', { name: 'Continue' }).click();
|
|
||||||
await page.getByRole('button', { name: 'Turn off' }).click();
|
|
||||||
await page.getByRole('button', { name: 'Yes' }).click();
|
|
||||||
await utils.checkNotification(page, 'Two-step login provider turned off');
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -57,7 +57,7 @@ test('invited with new account', async ({ page }) => {
|
||||||
await expect(page).toHaveTitle(/Create account | Vaultwarden Web/);
|
await expect(page).toHaveTitle(/Create account | Vaultwarden Web/);
|
||||||
|
|
||||||
//await page.getByLabel('Name').fill(users.user2.name);
|
//await page.getByLabel('Name').fill(users.user2.name);
|
||||||
await page.getByLabel('Master password (required)', { exact: true }).fill(users.user2.password);
|
await page.getByLabel('New master password (required)', { exact: true }).fill(users.user2.password);
|
||||||
await page.getByLabel('Confirm master password (').fill(users.user2.password);
|
await page.getByLabel('Confirm master password (').fill(users.user2.password);
|
||||||
await page.getByRole('button', { name: 'Create account' }).click();
|
await page.getByRole('button', { name: 'Create account' }).click();
|
||||||
await utils.checkNotification(page, 'Your new account has been created');
|
await utils.checkNotification(page, 'Your new account has been created');
|
||||||
|
|
|
@ -11,8 +11,8 @@ test.beforeAll('Setup', async ({ browser }, testInfo: TestInfo) => {
|
||||||
await utils.startVaultwarden(browser, testInfo);
|
await utils.startVaultwarden(browser, testInfo);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.afterAll('Teardown', async ({}, testInfo: TestInfo) => {
|
test.afterAll('Teardown', async ({}) => {
|
||||||
utils.stopVaultwarden(testInfo);
|
utils.stopVaultwarden();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Invite', async ({ page }) => {
|
test('Invite', async ({ page }) => {
|
||||||
|
|
92
playwright/tests/setups/2fa.ts
Normal file
92
playwright/tests/setups/2fa.ts
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
import { expect, type Page, Test } from '@playwright/test';
|
||||||
|
import { type MailBuffer } from 'maildev';
|
||||||
|
import * as OTPAuth from "otpauth";
|
||||||
|
|
||||||
|
import * as utils from '../../global-utils';
|
||||||
|
|
||||||
|
export async function activateTOTP(test: Test, page: Page, user: { name: string, password: string }): OTPAuth.TOTP {
|
||||||
|
return await test.step('Activate TOTP 2FA', async () => {
|
||||||
|
await page.getByRole('button', { name: user.name }).click();
|
||||||
|
await page.getByRole('menuitem', { name: 'Account settings' }).click();
|
||||||
|
await page.getByRole('link', { name: 'Security' }).click();
|
||||||
|
await page.getByRole('link', { name: 'Two-step login' }).click();
|
||||||
|
await page.locator('bit-item').filter({ hasText: /Authenticator app/ }).getByRole('button').click();
|
||||||
|
await page.getByLabel('Master password (required)').fill(user.password);
|
||||||
|
await page.getByRole('button', { name: 'Continue' }).click();
|
||||||
|
|
||||||
|
const secret = await page.getByLabel('Key').innerText();
|
||||||
|
let totp = new OTPAuth.TOTP({ secret, period: 30 });
|
||||||
|
|
||||||
|
await page.getByLabel(/Verification code/).fill(totp.generate());
|
||||||
|
await page.getByRole('button', { name: 'Turn on' }).click();
|
||||||
|
await page.getByRole('heading', { name: 'Turned on', exact: true });
|
||||||
|
await page.getByLabel('Close').click();
|
||||||
|
|
||||||
|
return totp;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function disableTOTP(test: Test, page: Page, user: { password: string }) {
|
||||||
|
await test.step('Disable TOTP 2FA', async () => {
|
||||||
|
await page.getByRole('button', { name: 'Test' }).click();
|
||||||
|
await page.getByRole('menuitem', { name: 'Account settings' }).click();
|
||||||
|
await page.getByRole('link', { name: 'Security' }).click();
|
||||||
|
await page.getByRole('link', { name: 'Two-step login' }).click();
|
||||||
|
await page.locator('bit-item').filter({ hasText: /Authenticator app/ }).getByRole('button').click();
|
||||||
|
await page.getByLabel('Master password (required)').click();
|
||||||
|
await page.getByLabel('Master password (required)').fill(user.password);
|
||||||
|
await page.getByRole('button', { name: 'Continue' }).click();
|
||||||
|
await page.getByRole('button', { name: 'Turn off' }).click();
|
||||||
|
await page.getByRole('button', { name: 'Yes' }).click();
|
||||||
|
await utils.checkNotification(page, 'Two-step login provider turned off');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function activateEmail(test: Test, page: Page, user: { name: string, password: string }, mailBuffer: MailBuffer) {
|
||||||
|
await test.step('Activate Email 2FA', async () => {
|
||||||
|
await page.getByRole('button', { name: user.name }).click();
|
||||||
|
await page.getByRole('menuitem', { name: 'Account settings' }).click();
|
||||||
|
await page.getByRole('link', { name: 'Security' }).click();
|
||||||
|
await page.getByRole('link', { name: 'Two-step login' }).click();
|
||||||
|
await page.locator('bit-item').filter({ hasText: 'Email Email Enter a code sent' }).getByRole('button').click();
|
||||||
|
await page.getByLabel('Master password (required)').fill(user.password);
|
||||||
|
await page.getByRole('button', { name: 'Continue' }).click();
|
||||||
|
await page.getByRole('button', { name: 'Send email' }).click();
|
||||||
|
});
|
||||||
|
|
||||||
|
let code = await retrieveEmailCode(test, page, mailBuffer);
|
||||||
|
|
||||||
|
await test.step('input code', async () => {
|
||||||
|
await page.getByLabel('2. Enter the resulting 6').fill(code);
|
||||||
|
await page.getByRole('button', { name: 'Turn on' }).click();
|
||||||
|
await page.getByRole('heading', { name: 'Turned on', exact: true });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function retrieveEmailCode(test: Test, page: Page, mailBuffer: MailBuffer): string {
|
||||||
|
return await test.step('retrieve code', async () => {
|
||||||
|
const codeMail = await mailBuffer.next((mail) => mail.subject.includes("Login Verification Code"));
|
||||||
|
const page2 = await page.context().newPage();
|
||||||
|
await page2.setContent(codeMail.html);
|
||||||
|
const code = await page2.getByTestId("2fa").innerText();
|
||||||
|
await page2.close();
|
||||||
|
return code;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function disableEmail(test: Test, page: Page, user: { password: string }) {
|
||||||
|
await test.step('Disable Email 2FA', async () => {
|
||||||
|
await page.getByRole('button', { name: 'Test' }).click();
|
||||||
|
await page.getByRole('menuitem', { name: 'Account settings' }).click();
|
||||||
|
await page.getByRole('link', { name: 'Security' }).click();
|
||||||
|
await page.getByRole('link', { name: 'Two-step login' }).click();
|
||||||
|
await page.locator('bit-item').filter({ hasText: 'Email' }).getByRole('button').click();
|
||||||
|
await page.getByLabel('Master password (required)').click();
|
||||||
|
await page.getByLabel('Master password (required)').fill(user.password);
|
||||||
|
await page.getByRole('button', { name: 'Continue' }).click();
|
||||||
|
await page.getByRole('button', { name: 'Turn off' }).click();
|
||||||
|
await page.getByRole('button', { name: 'Yes' }).click();
|
||||||
|
|
||||||
|
await utils.checkNotification(page, 'Two-step login provider turned off');
|
||||||
|
});
|
||||||
|
}
|
|
@ -1,7 +1,9 @@
|
||||||
import { expect, type Page, Test } from '@playwright/test';
|
import { expect, type Page, Test } from '@playwright/test';
|
||||||
import { type MailBuffer, MailServer } from 'maildev';
|
import { type MailBuffer, MailServer } from 'maildev';
|
||||||
|
import * as OTPAuth from "otpauth";
|
||||||
|
|
||||||
import * as utils from '../../global-utils';
|
import * as utils from '../../global-utils';
|
||||||
|
import { retrieveEmailCode } from './2fa';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If a MailBuffer is passed it will be used and consume the expected emails
|
* If a MailBuffer is passed it will be used and consume the expected emails
|
||||||
|
@ -10,50 +12,52 @@ export async function logNewUser(
|
||||||
test: Test,
|
test: Test,
|
||||||
page: Page,
|
page: Page,
|
||||||
user: { email: string, name: string, password: string },
|
user: { email: string, name: string, password: string },
|
||||||
options: { mailBuffer?: MailBuffer, mailServer?: MailServer } = {}
|
options: { mailBuffer?: MailBuffer, override?: boolean } = {}
|
||||||
) {
|
) {
|
||||||
let mailBuffer = options.mailBuffer ?? options.mailServer?.buffer(user.email);
|
await test.step(`Create user ${user.name}`, async () => {
|
||||||
try {
|
await page.context().clearCookies();
|
||||||
await test.step('Create user', async () => {
|
|
||||||
await page.context().clearCookies();
|
|
||||||
|
|
||||||
await test.step('Landing page', async () => {
|
await test.step('Landing page', async () => {
|
||||||
await page.goto('/');
|
await utils.cleanLanding(page);
|
||||||
|
|
||||||
|
if( options.override ) {
|
||||||
|
await page.getByRole('button', { name: 'Continue' }).click();
|
||||||
|
} else {
|
||||||
await page.getByLabel(/Email address/).fill(user.email);
|
await page.getByLabel(/Email address/).fill(user.email);
|
||||||
await page.getByRole('button', { name: /Use single sign-on/ }).click();
|
await page.getByRole('button', { name: /Use single sign-on/ }).click();
|
||||||
});
|
|
||||||
|
|
||||||
await test.step('Keycloak login', async () => {
|
|
||||||
await expect(page.getByRole('heading', { name: 'Sign in to your account' })).toBeVisible();
|
|
||||||
await page.getByLabel(/Username/).fill(user.name);
|
|
||||||
await page.getByLabel('Password', { exact: true }).fill(user.password);
|
|
||||||
await page.getByRole('button', { name: 'Sign In' }).click();
|
|
||||||
});
|
|
||||||
|
|
||||||
await test.step('Create Vault account', async () => {
|
|
||||||
await expect(page.getByRole('heading', { name: 'Join organisation' })).toBeVisible();
|
|
||||||
await page.getByLabel('Master password (required)', { exact: true }).fill(user.password);
|
|
||||||
await page.getByLabel('Confirm master password (').fill(user.password);
|
|
||||||
await page.getByRole('button', { name: 'Create account' }).click();
|
|
||||||
});
|
|
||||||
|
|
||||||
await test.step('Default vault page', async () => {
|
|
||||||
await expect(page).toHaveTitle(/Vaultwarden Web/);
|
|
||||||
await expect(page.getByTitle('All vaults', { exact: true })).toBeVisible();
|
|
||||||
});
|
|
||||||
|
|
||||||
if( mailBuffer ){
|
|
||||||
await test.step('Check emails', async () => {
|
|
||||||
await expect(mailBuffer.next((m) => m.subject === "Welcome")).resolves.toBeDefined();
|
|
||||||
await expect(mailBuffer.next((m) => m.subject.includes("New Device Logged"))).resolves.toBeDefined();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} finally {
|
|
||||||
if( options.mailServer ){
|
await test.step('Keycloak login', async () => {
|
||||||
mailBuffer.close();
|
await expect(page.getByRole('heading', { name: 'Sign in to your account' })).toBeVisible();
|
||||||
|
await page.getByLabel(/Username/).fill(user.name);
|
||||||
|
await page.getByLabel('Password', { exact: true }).fill(user.password);
|
||||||
|
await page.getByRole('button', { name: 'Sign In' }).click();
|
||||||
|
});
|
||||||
|
|
||||||
|
await test.step('Create Vault account', async () => {
|
||||||
|
await expect(page.getByRole('heading', { name: 'Join organisation' })).toBeVisible();
|
||||||
|
await page.getByLabel('New master password (required)', { exact: true }).fill(user.password);
|
||||||
|
await page.getByLabel('Confirm master password (').fill(user.password);
|
||||||
|
await page.getByRole('button', { name: 'Create account' }).click();
|
||||||
|
});
|
||||||
|
|
||||||
|
await test.step('Default vault page', async () => {
|
||||||
|
await expect(page).toHaveTitle(/Vaultwarden Web/);
|
||||||
|
await expect(page.getByTitle('All vaults', { exact: true })).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
await utils.checkNotification(page, 'Account successfully created!');
|
||||||
|
await utils.checkNotification(page, 'Invitation accepted');
|
||||||
|
|
||||||
|
if( options.mailBuffer ){
|
||||||
|
let mailBuffer = options.mailBuffer;
|
||||||
|
await test.step('Check emails', async () => {
|
||||||
|
await expect(mailBuffer.next((m) => m.subject === "Welcome")).resolves.toBeDefined();
|
||||||
|
await expect(mailBuffer.next((m) => m.subject.includes("New Device Logged"))).resolves.toBeDefined();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -63,47 +67,72 @@ export async function logUser(
|
||||||
test: Test,
|
test: Test,
|
||||||
page: Page,
|
page: Page,
|
||||||
user: { email: string, password: string },
|
user: { email: string, password: string },
|
||||||
options: { mailBuffer ?: MailBuffer, mailServer?: MailServer} = {}
|
options: {
|
||||||
|
mailBuffer ?: MailBuffer,
|
||||||
|
override?: boolean,
|
||||||
|
totp?: OTPAuth.TOTP,
|
||||||
|
mail2fa?: boolean,
|
||||||
|
} = {}
|
||||||
) {
|
) {
|
||||||
let mailBuffer = options.mailBuffer ?? options.mailServer?.buffer(user.email);
|
let mailBuffer = options.mailBuffer;
|
||||||
try {
|
|
||||||
await test.step('Log user', async () => {
|
|
||||||
await page.context().clearCookies();
|
|
||||||
|
|
||||||
await test.step('Landing page', async () => {
|
await test.step(`Log user ${user.email}`, async () => {
|
||||||
await page.goto('/');
|
await page.context().clearCookies();
|
||||||
|
|
||||||
|
await test.step('Landing page', async () => {
|
||||||
|
await utils.cleanLanding(page);
|
||||||
|
|
||||||
|
if( options.override ) {
|
||||||
|
await page.getByRole('button', { name: 'Continue' }).click();
|
||||||
|
} else {
|
||||||
await page.getByLabel(/Email address/).fill(user.email);
|
await page.getByLabel(/Email address/).fill(user.email);
|
||||||
await page.getByRole('button', { name: /Use single sign-on/ }).click();
|
await page.getByRole('button', { name: /Use single sign-on/ }).click();
|
||||||
});
|
|
||||||
|
|
||||||
await test.step('Keycloak login', async () => {
|
|
||||||
await expect(page.getByRole('heading', { name: 'Sign in to your account' })).toBeVisible();
|
|
||||||
await page.getByLabel(/Username/).fill(user.name);
|
|
||||||
await page.getByLabel('Password', { exact: true }).fill(user.password);
|
|
||||||
await page.getByRole('button', { name: 'Sign In' }).click();
|
|
||||||
});
|
|
||||||
|
|
||||||
await test.step('Unlock vault', async () => {
|
|
||||||
await expect(page).toHaveTitle('Vaultwarden Web');
|
|
||||||
await expect(page.getByRole('heading', { name: 'Your vault is locked' })).toBeVisible();
|
|
||||||
await page.getByLabel('Master password').fill(user.password);
|
|
||||||
await page.getByRole('button', { name: 'Unlock' }).click();
|
|
||||||
});
|
|
||||||
|
|
||||||
await test.step('Default vault page', async () => {
|
|
||||||
await expect(page).toHaveTitle(/Vaultwarden Web/);
|
|
||||||
await expect(page.getByTitle('All vaults', { exact: true })).toBeVisible();
|
|
||||||
});
|
|
||||||
|
|
||||||
if( options.emails ){
|
|
||||||
await test.step('Check email', async () => {
|
|
||||||
await expect(mailBuffer.next((m) => m.subject.includes("New Device Logged"))).resolves.toBeDefined();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} finally {
|
|
||||||
if( options.mailServer ){
|
await test.step('Keycloak login', async () => {
|
||||||
mailBuffer.close();
|
await expect(page.getByRole('heading', { name: 'Sign in to your account' })).toBeVisible();
|
||||||
|
await page.getByLabel(/Username/).fill(user.name);
|
||||||
|
await page.getByLabel('Password', { exact: true }).fill(user.password);
|
||||||
|
await page.getByRole('button', { name: 'Sign In' }).click();
|
||||||
|
});
|
||||||
|
|
||||||
|
if( options.totp || options.mail2fa ){
|
||||||
|
let code;
|
||||||
|
|
||||||
|
await test.step('2FA check', async () => {
|
||||||
|
await expect(page.getByRole('heading', { name: 'Verify your Identity' })).toBeVisible();
|
||||||
|
|
||||||
|
if( options.totp ) {
|
||||||
|
const totp = options.totp;
|
||||||
|
let timestamp = Date.now(); // Needed to use the next token
|
||||||
|
timestamp = timestamp + (totp.period - (Math.floor(timestamp / 1000) % totp.period) + 1) * 1000;
|
||||||
|
code = totp.generate({timestamp});
|
||||||
|
} else if( options.mail2fa ){
|
||||||
|
code = await retrieveEmailCode(test, page, mailBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
await page.getByLabel(/Verification code/).fill(code);
|
||||||
|
await page.getByRole('button', { name: 'Continue' }).click();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
await test.step('Unlock vault', async () => {
|
||||||
|
await expect(page).toHaveTitle('Vaultwarden Web');
|
||||||
|
await expect(page.getByRole('heading', { name: 'Your vault is locked' })).toBeVisible();
|
||||||
|
await page.getByLabel('Master password').fill(user.password);
|
||||||
|
await page.getByRole('button', { name: 'Unlock' }).click();
|
||||||
|
});
|
||||||
|
|
||||||
|
await test.step('Default vault page', async () => {
|
||||||
|
await expect(page).toHaveTitle(/Vaultwarden Web/);
|
||||||
|
await expect(page.getByTitle('All vaults', { exact: true })).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
if( mailBuffer ){
|
||||||
|
await test.step('Check email', async () => {
|
||||||
|
await expect(mailBuffer.next((m) => m.subject.includes("New Device Logged"))).resolves.toBeDefined();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,16 +4,8 @@ import { type MailBuffer } from 'maildev';
|
||||||
import * as utils from '../../global-utils';
|
import * as utils from '../../global-utils';
|
||||||
|
|
||||||
export async function createAccount(test, page: Page, user: { email: string, name: string, password: string }, mailBuffer?: MailBuffer) {
|
export async function createAccount(test, page: Page, user: { email: string, name: string, password: string }, mailBuffer?: MailBuffer) {
|
||||||
await test.step('Create user', async () => {
|
await test.step(`Create user ${user.name}`, async () => {
|
||||||
// Landing page
|
await utils.cleanLanding(page);
|
||||||
await page.goto('/', { waitUntil: 'domcontentloaded' });
|
|
||||||
await expect(page.getByRole('button').nth(0)).toBeVisible();
|
|
||||||
|
|
||||||
const logged = await page.getByRole('button', { name: 'Log out' }).count();
|
|
||||||
if( logged > 0 ){
|
|
||||||
await page.getByRole('button', { name: 'Log out' }).click();
|
|
||||||
await page.getByRole('button', { name: 'Log out' }).click();
|
|
||||||
}
|
|
||||||
|
|
||||||
await page.getByRole('link', { name: 'Create account' }).click();
|
await page.getByRole('link', { name: 'Create account' }).click();
|
||||||
|
|
||||||
|
@ -24,13 +16,15 @@ export async function createAccount(test, page: Page, user: { email: string, nam
|
||||||
await page.getByRole('button', { name: 'Continue' }).click();
|
await page.getByRole('button', { name: 'Continue' }).click();
|
||||||
|
|
||||||
// Vault finish Creation
|
// Vault finish Creation
|
||||||
await page.getByLabel('Master password (required)', { exact: true }).fill(user.password);
|
await page.getByLabel('New master password (required)', { exact: true }).fill(user.password);
|
||||||
await page.getByLabel('Confirm master password (').fill(user.password);
|
await page.getByLabel('Confirm master password (').fill(user.password);
|
||||||
await page.getByRole('button', { name: 'Create account' }).click();
|
await page.getByRole('button', { name: 'Create account' }).click();
|
||||||
|
|
||||||
|
await utils.checkNotification(page, 'Your new account has been created')
|
||||||
|
|
||||||
// We are now in the default vault page
|
// We are now in the default vault page
|
||||||
await expect(page).toHaveTitle('Vaults | Vaultwarden Web');
|
await expect(page).toHaveTitle('Vaults | Vaultwarden Web');
|
||||||
await utils.checkNotification(page, 'Your new account has been created');
|
await utils.checkNotification(page, 'You have been logged in!');
|
||||||
|
|
||||||
if( mailBuffer ){
|
if( mailBuffer ){
|
||||||
await expect(mailBuffer.next((m) => m.subject === "Welcome")).resolves.toBeDefined();
|
await expect(mailBuffer.next((m) => m.subject === "Welcome")).resolves.toBeDefined();
|
||||||
|
@ -39,16 +33,8 @@ export async function createAccount(test, page: Page, user: { email: string, nam
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function logUser(test, page: Page, user: { email: string, password: string }, mailBuffer?: MailBuffer) {
|
export async function logUser(test, page: Page, user: { email: string, password: string }, mailBuffer?: MailBuffer) {
|
||||||
await test.step('Log user', async () => {
|
await test.step(`Log user ${user.email}`, async () => {
|
||||||
// Landing page
|
await utils.cleanLanding(page);
|
||||||
await page.goto('/', { waitUntil: 'domcontentloaded' });
|
|
||||||
await expect(page.getByRole('button').nth(0)).toBeVisible();
|
|
||||||
|
|
||||||
const logged = await page.getByRole('button', { name: 'Log out' }).count();
|
|
||||||
if( logged > 0 ){
|
|
||||||
await page.getByRole('button', { name: 'Log out' }).click();
|
|
||||||
await page.getByRole('button', { name: 'Log out' }).click();
|
|
||||||
}
|
|
||||||
|
|
||||||
await page.getByLabel(/Email address/).fill(user.email);
|
await page.getByLabel(/Email address/).fill(user.email);
|
||||||
await page.getByRole('button', { name: 'Continue' }).click();
|
await page.getByRole('button', { name: 'Continue' }).click();
|
||||||
|
|
53
playwright/tests/sso_login.smtp.spec.ts
Normal file
53
playwright/tests/sso_login.smtp.spec.ts
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
import { test, expect, type TestInfo } from '@playwright/test';
|
||||||
|
import { MailDev } from 'maildev';
|
||||||
|
|
||||||
|
import { logNewUser, logUser } from './setups/sso';
|
||||||
|
import { activateEmail, disableEmail } from './setups/2fa';
|
||||||
|
import * as utils from "../global-utils";
|
||||||
|
|
||||||
|
let users = utils.loadEnv();
|
||||||
|
|
||||||
|
let mailserver;
|
||||||
|
|
||||||
|
test.beforeAll('Setup', async ({ browser }, testInfo: TestInfo) => {
|
||||||
|
mailserver = new MailDev({
|
||||||
|
port: process.env.MAILDEV_SMTP_PORT,
|
||||||
|
web: { port: process.env.MAILDEV_HTTP_PORT },
|
||||||
|
})
|
||||||
|
|
||||||
|
await mailserver.listen();
|
||||||
|
|
||||||
|
await utils.startVaultwarden(browser, testInfo, {
|
||||||
|
SSO_ENABLED: true,
|
||||||
|
SSO_ONLY: false,
|
||||||
|
SMTP_HOST: process.env.MAILDEV_HOST,
|
||||||
|
SMTP_FROM: process.env.VAULTWARDEN_SMTP_FROM,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test.afterAll('Teardown', async ({}) => {
|
||||||
|
utils.stopVaultwarden();
|
||||||
|
if( mailserver ){
|
||||||
|
await mailserver.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Create and activate 2FA', async ({ page }) => {
|
||||||
|
const mailBuffer = mailserver.buffer(users.user1.email);
|
||||||
|
|
||||||
|
await logNewUser(test, page, users.user1, {mailBuffer: mailBuffer});
|
||||||
|
|
||||||
|
await activateEmail(test, page, users.user1, mailBuffer);
|
||||||
|
|
||||||
|
mailBuffer.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Log and disable', async ({ page }) => {
|
||||||
|
const mailBuffer = mailserver.buffer(users.user1.email);
|
||||||
|
|
||||||
|
await logUser(test, page, users.user1, {mailBuffer: mailBuffer, mail2fa: true});
|
||||||
|
|
||||||
|
await disableEmail(test, page, users.user1);
|
||||||
|
|
||||||
|
mailBuffer.close();
|
||||||
|
});
|
|
@ -1,5 +1,7 @@
|
||||||
import { test, expect, type TestInfo } from '@playwright/test';
|
import { test, expect, type TestInfo } from '@playwright/test';
|
||||||
|
|
||||||
import { logNewUser, logUser } from './setups/sso';
|
import { logNewUser, logUser } from './setups/sso';
|
||||||
|
import { activateTOTP, disableTOTP } from './setups/2fa';
|
||||||
import * as utils from "../global-utils";
|
import * as utils from "../global-utils";
|
||||||
|
|
||||||
let users = utils.loadEnv();
|
let users = utils.loadEnv();
|
||||||
|
@ -38,6 +40,16 @@ test('Non SSO login', async ({ page }) => {
|
||||||
await expect(page).toHaveTitle(/Vaultwarden Web/);
|
await expect(page).toHaveTitle(/Vaultwarden Web/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('SSO login with TOTP 2fa', async ({ page }) => {
|
||||||
|
await logUser(test, page, users.user1);
|
||||||
|
|
||||||
|
let totp = await activateTOTP(test, page, users.user1);
|
||||||
|
|
||||||
|
await logUser(test, page, users.user1, { totp });
|
||||||
|
|
||||||
|
await disableTOTP(test, page, users.user1);
|
||||||
|
});
|
||||||
|
|
||||||
test('Non SSO login impossible', async ({ page, browser }, testInfo: TestInfo) => {
|
test('Non SSO login impossible', async ({ page, browser }, testInfo: TestInfo) => {
|
||||||
await utils.restartVaultwarden(page, testInfo, {
|
await utils.restartVaultwarden(page, testInfo, {
|
||||||
SSO_ENABLED: true,
|
SSO_ENABLED: true,
|
||||||
|
|
|
@ -90,7 +90,7 @@ test('invited with new account', async ({ page }) => {
|
||||||
|
|
||||||
await test.step('Create Vault account', async () => {
|
await test.step('Create Vault account', async () => {
|
||||||
await expect(page.getByRole('heading', { name: 'Join organisation' })).toBeVisible();
|
await expect(page.getByRole('heading', { name: 'Join organisation' })).toBeVisible();
|
||||||
await page.getByLabel('Master password (required)', { exact: true }).fill(users.user2.password);
|
await page.getByLabel('New master password (required)', { exact: true }).fill(users.user2.password);
|
||||||
await page.getByLabel('Confirm master password (').fill(users.user2.password);
|
await page.getByLabel('Confirm master password (').fill(users.user2.password);
|
||||||
await page.getByRole('button', { name: 'Create account' }).click();
|
await page.getByRole('button', { name: 'Create account' }).click();
|
||||||
});
|
});
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue