- Remove `playwright.config.js` to clean up configuration. - Add new E2E test suites (`admin-pos-orders`, `admin-module-goals`, `admin-module-pos-mobile-order-flow`) to structure Admin module tests. - Introduce reusable authentication and data seeding utilities in `fixtures` for streamlined test flows and maintainability.
241 lines
8.3 KiB
TypeScript
241 lines
8.3 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;
|
|
|
|
/**
|
|
* 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', { 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', { 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', { 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', { 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', { 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}`);
|
|
}
|