- Introduced `.prettierrc.json` to enforce consistent code formatting across the project. - Updated unit and e2e test files to address formatting issues, improve readability, and ensure alignment with the new Prettier configuration.
237 lines
8.5 KiB
TypeScript
237 lines
8.5 KiB
TypeScript
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<boolean> {
|
|
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}`);
|
|
}
|