feat: establish Playwright E2E testing infrastructure with configuration and initial tests
This commit is contained in:
@@ -0,0 +1,189 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user