/** * Copyright since 2025 Mifos Initiative * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { Page, Locator, expect } from '@playwright/test'; import { BasePage } from './BasePage'; /** * LoginPage - Page Object for the Mifos X Login page. * * Encapsulates all login-related interactions and element locators. * Extends BasePage for common functionality. * * Locator Strategy: * - Uses exact selectors from Playwright codegen for maximum stability * - Preserves complex selectors (filter, nth) as generated by codegen * - These selectors match the actual DOM structure of the Angular Material UI */ export class LoginPage extends BasePage { /** * The URL path for the login page. */ readonly url = '/#/login'; /** * Centralized locators for all page elements. * Using exact selectors from Playwright codegen. */ /** * Get the username input wrapper div. * Uses codegen selector: div.filter({ hasText: 'Username' }).nth(5) */ get usernameDivWrapper(): Locator { return this.page.locator('div').filter({ hasText: 'Username' }).nth(5); } /** * Get the username input field. * Uses formcontrolname attribute for reliable selection. */ get usernameInput(): Locator { return this.page.locator('input[formcontrolname="username"]'); } /** * Get the password input wrapper div. * Uses codegen selector: div.filter({ hasText: 'Password' }).nth(5) */ get passwordDivWrapper(): Locator { return this.page.locator('div').filter({ hasText: 'Password' }).nth(5); } /** * Get the password input field. * Uses formcontrolname attribute for reliable selection. */ get passwordInput(): Locator { return this.page.locator('input[formcontrolname="password"]'); } /** * Get the login button. * Uses codegen selector: getByRole('button', { name: 'Login' }) */ get loginButton(): Locator { return this.page.getByRole('button', { name: 'Login' }); } public divLocator(className: string): Locator { return this.page.locator(className); } // Additional helper locators for validation and assertions private readonly locators = { errorMessage: 'mat-error', progressBar: 'mat-progress-bar', loginForm: '#login-form' }; /** * Get all error messages on the page. */ get errorMessages(): Locator { return this.page.locator(this.locators.errorMessage); } /** * Get the progress bar (loading indicator). */ get progressBar(): Locator { return this.page.locator(this.locators.progressBar); } /** * Wait for the login page to be fully loaded. * Overrides BasePage to wait for login form visibility. */ async waitForLoad(): Promise { // Wait for basic page load await this.page.waitForLoadState('domcontentloaded'); // Try to wait for network idle, but don't fail if it takes too long try { await this.page.waitForLoadState('networkidle', { timeout: 10000 }); } catch (e) { console.log('Network idle timeout - proceeding anyway'); } // Wait for the login button to be visible with extended timeout await this.loginButton.waitFor({ state: 'visible', timeout: 60000 }); } /** * Perform login with the given credentials. * * @param username - The username to enter * @param password - The password to enter */ async login(username: string, password: string): Promise { // Fill the username input await this.usernameInput.click(); await this.usernameInput.fill(username); // Fill the password input await this.passwordInput.click(); await this.passwordInput.fill(password); // Click the login button await this.loginButton.click(); } /** * Perform login and wait for successful navigation. * Use this when expecting a successful login. * * @param username - The username to enter * @param password - The password to enter */ async loginAndWaitForDashboard(username: string, password: string): Promise { await this.login(username, password); // Wait for navigation away from login page await this.page.waitForURL(/.*(? { return this.loginButton.isEnabled(); } /** * Get the count of validation errors displayed. * @returns Number of visible error messages */ async getErrorCount(): Promise { return this.errorMessages.count(); } /** * Check if page is showing a loading state. * @returns true if progress bar is visible */ async isLoading(): Promise { return this.progressBar.isVisible(); } /** * Assert that we are on the login page. */ async assertOnLoginPage(): Promise { await expect(this.page).toHaveURL(/.*login/); await expect(this.loginButton).toBeVisible(); } /** * Assert successful login by checking we're no longer on login page. */ async assertLoginSuccess(): Promise { await expect(this.page).not.toHaveURL(/.*login/, { timeout: 30000 }); } /** * Assert that a validation error is displayed. */ async assertValidationError(): Promise { await expect(this.errorMessages.first()).toBeVisible(); } }