import { Page, expect } from "@playwright/test"; import { userCredentials, subuserPhoneCredentials, subuserUsernameCredentials, operatorCredentials, user2FACredentials, subuser2FACredentials, qrAuthToken, } from "./testData"; import { generateOTP } from "./otp"; // Auth timeout in milliseconds (15 seconds) const AUTH_TIMEOUT = 15000; const USER_HOME_URL = /\/user(?:\/)?(?:[?#].*)?$/; const ADMIN_HOME_URL = /\/admin(?:\/)?(?:[?#].*)?$/; /** * Reusable authentication helpers for tests * Provides login functions for different user types */ /** * Login as a customer user (by customer number) * @param page - Playwright page object * @param credentials - Optional custom credentials (defaults to userCredentials) */ export async function loginAsUser( page: Page, credentials?: { customerNumber: string; password: string; twoFactorAuthentication?: boolean; otpSecret?: string } ) { const creds = credentials || userCredentials; await page.goto("/login"); await page.fill('input[name="customer_number"]', creds.customerNumber); await page.fill('input[name="password"]', creds.password); await page.click('button[id="login-button"]'); // If two-factor authentication is enabled, wait for OTP input if (creds.twoFactorAuthentication) { await page.waitForSelector('input[name="2fa_code"]'); // Generate OTP using secret await page.fill('input[name="2fa_code"]', generateOTP(creds.otpSecret!)); await page.click('button[id="2fa-verify-button"]'); } await expect(page).toHaveURL(USER_HOME_URL, { timeout: AUTH_TIMEOUT }); } /** * Login as a subuser/driver (by phone number) * @param page - Playwright page object * @param credentials - Optional custom credentials (defaults to subuserPhoneCredentials) */ export async function loginAsSubuserByPhone( page: Page, credentials?: { phoneCountryCode: string; phone: string; password: string; twoFactorAuthentication?: boolean; otpSecret?: string; } ) { const creds = credentials || subuserPhoneCredentials; await page.goto("/login/driver"); await page.fill('input[name="phone_country_code"]', creds.phoneCountryCode); await page.fill('input[name="phone"]', creds.phone); await page.fill('input[name="password"]', creds.password); await page.click('button[id="subuser-login-button"]'); // If two-factor authentication is enabled, wait for OTP input if (creds.twoFactorAuthentication) { await page.waitForSelector('input[name="2fa_code"]'); // Generate OTP using secret await page.fill('input[name="2fa_code"]', generateOTP(creds.otpSecret!)); await page.click('button[id="2fa-verify-button"]'); } await expect(page).toHaveURL(USER_HOME_URL, { timeout: AUTH_TIMEOUT }); } /** * Login as a subuser/driver (by username) * @param page - Playwright page object * @param credentials - Optional custom credentials (defaults to subuserUsernameCredentials) */ export async function loginAsSubuserByUsername( page: Page, credentials?: { username: string; password: string; twoFactorAuthentication?: boolean; otpSecret?: string } ) { const creds = credentials || subuserUsernameCredentials; await page.goto("/login/driver"); await page.click('button[id="subuser_login_method_username_button"]'); await page.waitForSelector('input[name="username"]'); await page.waitForSelector('input[name="password"]'); await page.fill('input[name="username"]', creds.username); await page.fill('input[name="password"]', creds.password); await page.click('button[id="subuser-login-button"]'); // If two-factor authentication is enabled, wait for OTP input if (creds.twoFactorAuthentication) { await page.waitForSelector('input[name="2fa_code"]'); // Generate OTP using secret await page.fill('input[name="2fa_code"]', generateOTP(creds.otpSecret!)); await page.click('button[id="2fa-verify-button"]'); } await expect(page).toHaveURL(USER_HOME_URL, { timeout: AUTH_TIMEOUT }); } /** * Login as an operator (by user ID) * @param page - Playwright page object * @param credentials - Optional custom credentials (defaults to operatorCredentials) */ export async function loginAsOperator(page: Page, credentials?: { userId: string; password: string }) { const creds = credentials || operatorCredentials; await page.goto("/admin/login"); await page.fill('input[name="user_id"]', creds.userId); await page.fill('input[name="password"]', creds.password); await page.click('button[id="operator_login_button"]'); await expect(page).toHaveURL(ADMIN_HOME_URL, { timeout: AUTH_TIMEOUT }); } /** * Navigate to user login page without logging in * Useful for testing login failures * @param page - Playwright page object */ export async function goToUserLogin(page: Page) { await page.goto("/login"); } /** * Navigate to subuser/driver login page without logging in * @param page - Playwright page object */ export async function goToSubuserLogin(page: Page) { await page.goto("/login/driver"); } /** * Navigate to operator login page without logging in * @param page - Playwright page object */ export async function goToOperatorLogin(page: Page) { await page.goto("/admin/login"); } /** * Login as user and trigger 2FA verification screen * @param page - Playwright page object * @param credentials - Optional custom credentials (defaults to user2FACredentials) */ export async function loginAsUserWith2FA(page: Page, credentials?: { customerNumber: string; password: string }) { const creds = credentials || user2FACredentials; await page.goto("/login"); await page.fill('input[name="customer_number"]', creds.customerNumber); await page.fill('input[name="password"]', creds.password); await page.click('button[id="login-button"]'); // Wait for 2FA verification screen await expect(page.locator('input[name="2fa_code"]')).toBeVisible({ timeout: AUTH_TIMEOUT }); } /** * Complete 2FA verification with a code * @param page - Playwright page object * @param code - The 6-digit 2FA code */ export async function verify2FACode(page: Page, code: string) { await page.fill('input[name="2fa_code"]', code); await page.click('button[id="2fa-verify-button"]'); } /** * Cancel 2FA verification and return to login * @param page - Playwright page object */ export async function cancel2FA(page: Page) { await page.click('button:has-text("Back to login"), button:has-text("Tilbage til login")'); } /** * Login as subuser and trigger 2FA verification screen * @param page - Playwright page object * @param credentials - Optional custom credentials (defaults to subuser2FACredentials) */ export async function loginAsSubuserWith2FA( page: Page, credentials?: { phoneCountryCode: string; phone: string; password: string } ) { const creds = credentials || subuser2FACredentials; await page.goto("/login/driver"); await page.fill('input[name="phone_country_code"]', creds.phoneCountryCode); await page.fill('input[name="phone"]', creds.phone); await page.fill('input[name="password"]', creds.password); await page.click('button[id="subuser-login-button"]'); // Wait for 2FA verification screen await expect(page.locator('input[name="2fa_code"]')).toBeVisible({ timeout: AUTH_TIMEOUT }); } /** * Check if passkey login button is visible * @param page - Playwright page object * @returns boolean - true if passkey button is visible */ export async function isPasskeyButtonVisible(page: Page): Promise { return await page.locator('button[id="passkey-login-button"]').isVisible(); } /** * Login using QR code authentication token * @param page - Playwright page object * @param token - Optional QR auth token (defaults to qrAuthToken from testData) */ export async function loginWithQRCode(page: Page, token?: string) { const authToken = token || qrAuthToken; if (!authToken) { throw new Error("QR auth token is not set. Set PLENO_QR_TOKEN environment variable or pass token directly."); } await page.goto(`/login/qr?token=${authToken}`); await expect(page).toHaveURL(USER_HOME_URL, { timeout: AUTH_TIMEOUT }); } /** * Navigate to QR code login page without authenticating * Useful for testing QR code scanning UI * @param page - Playwright page object */ export async function goToQRCodeLogin(page: Page) { await page.goto("/login/qr"); } /** * Attempt QR code login with invalid token * Useful for testing error handling * @param page - Playwright page object * @param invalidToken - Invalid token to test with */ export async function attemptInvalidQRCodeLogin(page: Page, invalidToken: string = "invalid-token") { await page.goto(`/login/qr?token=${invalidToken}`); }