190 lines
5.2 KiB
TypeScript
190 lines
5.2 KiB
TypeScript
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 codegen selector for the actual input.
|
|
*/
|
|
get usernameInput(): Locator {
|
|
return this.page.getByRole('textbox', { name: '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 codegen selector for the actual input.
|
|
*/
|
|
get passwordInput(): Locator {
|
|
return this.page.getByRole('textbox', { name: 'Password' });
|
|
}
|
|
|
|
/**
|
|
* Get the login button.
|
|
* Uses codegen selector: getByRole('button', { name: 'Login' })
|
|
*/
|
|
get loginButton(): Locator {
|
|
return this.page.getByRole('button', { name: 'Login' });
|
|
}
|
|
|
|
// 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.
|
|
* Uses the exact interaction sequence from codegen.
|
|
*
|
|
* @param username - The username to enter
|
|
* @param password - The password to enter
|
|
*/
|
|
async login(username: string, password: string): Promise<void> {
|
|
// Click the username wrapper div (as per codegen)
|
|
await this.usernameDivWrapper.click();
|
|
// Fill the username input
|
|
await this.usernameInput.fill(username);
|
|
|
|
// Click the password wrapper div (as per codegen)
|
|
await this.passwordDivWrapper.click();
|
|
// Fill the password input
|
|
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();
|
|
}
|
|
}
|