Files
cbsnexora/playwright/pages/login.page.ts
T

199 lines
5.4 KiB
TypeScript

/**
* 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<void> {
// 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<void> {
// 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<void> {
await this.login(username, password);
// Wait for navigation away from login page
await this.page.waitForURL(/.*(?<!login)$/, {
timeout: 30000,
waitUntil: 'networkidle'
});
}
/**
* Check if the login button is enabled.
* @returns true if the login button is enabled
*/
async isLoginButtonEnabled(): Promise<boolean> {
return this.loginButton.isEnabled();
}
/**
* Get the count of validation errors displayed.
* @returns Number of visible error messages
*/
async getErrorCount(): Promise<number> {
return this.errorMessages.count();
}
/**
* Check if page is showing a loading state.
* @returns true if progress bar is visible
*/
async isLoading(): Promise<boolean> {
return this.progressBar.isVisible();
}
/**
* Assert that we are on the login page.
*/
async assertOnLoginPage(): Promise<void> {
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<void> {
await expect(this.page).not.toHaveURL(/.*login/, { timeout: 30000 });
}
/**
* Assert that a validation error is displayed.
*/
async assertValidationError(): Promise<void> {
await expect(this.errorMessages.first()).toBeVisible();
}
}