- Add HVAC_Test_User_Factory class with: * User creation with specific roles * Multiple role support * Persona management system * Account cleanup integration - Create comprehensive test suite in HVAC_Test_User_Factory_Test.php - Update testing improvement plan documentation - Add implementation decisions to project memory bank - Restructure .gitignore with: * Whitelist approach for better file management * Explicit backup exclusions * Specific bin directory inclusions Part of the Account Management component from the testing framework improvement plan.
201 lines
No EOL
7.8 KiB
TypeScript
201 lines
No EOL
7.8 KiB
TypeScript
import { Page, expect } from '@playwright/test';
|
|
import { LogParser } from '../utils/logParser';
|
|
|
|
export class LoginPage {
|
|
readonly page: Page;
|
|
private readonly selectors = {
|
|
usernameInput: '#user_login',
|
|
passwordInput: '#user_pass',
|
|
loginButton: '#wp-submit',
|
|
rememberMeCheckbox: '#rememberme',
|
|
errorMessage: '#login_error, .hvac-login-error, .notice-error',
|
|
resetPasswordLink: '.wp-login-lost-password, .hvac-lostpassword-link',
|
|
registerLink: '.wp-login-register, .hvac-register-link',
|
|
successMessage: '.message, .updated, .success'
|
|
};
|
|
|
|
constructor(page: Page) {
|
|
this.page = page;
|
|
}
|
|
|
|
async navigate() {
|
|
await this.page.goto('/community-login/');
|
|
}
|
|
|
|
async login(username: string, password: string, rememberMe = false) {
|
|
await this.page.fill(this.selectors.usernameInput, username);
|
|
await this.page.fill(this.selectors.passwordInput, password);
|
|
|
|
if (rememberMe) {
|
|
await this.page.check(this.selectors.rememberMeCheckbox);
|
|
}
|
|
|
|
await this.page.click(this.selectors.loginButton);
|
|
}
|
|
|
|
async verifyLoginError(expectedMessage: string) {
|
|
try {
|
|
console.log('Waiting for login failure indicators...');
|
|
|
|
// Check if we're on either the community login page or WordPress login page with error
|
|
const currentUrl = this.page.url();
|
|
console.log('Current URL:', currentUrl);
|
|
|
|
const isErrorPage = currentUrl.includes('login=failed') ||
|
|
currentUrl.includes('wp-login.php');
|
|
expect(isErrorPage).toBe(true);
|
|
|
|
console.log('Waiting for error message to appear...');
|
|
const errorElement = await this.page.waitForSelector(this.selectors.errorMessage, {
|
|
state: 'visible',
|
|
timeout: 20000 // Increased timeout to 20 seconds
|
|
});
|
|
|
|
const actualError = await errorElement.textContent();
|
|
console.log('Found error message:', actualError);
|
|
|
|
if (!actualError) {
|
|
throw new Error('Error message element found but contains no text');
|
|
}
|
|
|
|
// Take screenshot for debugging
|
|
await this.page.screenshot({
|
|
path: `test-results/login-error-${Date.now()}.png`,
|
|
fullPage: true
|
|
});
|
|
|
|
// Clean up and normalize the error message text
|
|
const cleanError = actualError
|
|
.replace(/\s+/g, ' ')
|
|
.trim()
|
|
.toLowerCase();
|
|
const cleanExpected = expectedMessage
|
|
.toLowerCase()
|
|
.trim();
|
|
|
|
console.log('Cleaned error message:', cleanError);
|
|
console.log('Expected to contain:', cleanExpected);
|
|
|
|
// WordPress prefixes errors with "Error:" - handle both formats
|
|
const normalizedError = cleanError.replace(/^error:\s*/i, '');
|
|
const normalizedExpected = cleanExpected.replace(/^error:\s*/i, '');
|
|
|
|
expect(normalizedError).toContain(normalizedExpected);
|
|
|
|
// Additional check for error message visibility
|
|
const isVisible = await errorElement.isVisible();
|
|
expect(isVisible).toBe(true);
|
|
|
|
} catch (error) {
|
|
console.error('Failed to verify login error:', error);
|
|
// Take screenshot on failure
|
|
await this.page.screenshot({
|
|
path: `test-results/login-error-failure-${Date.now()}.png`,
|
|
fullPage: true
|
|
});
|
|
|
|
// Log the page content for debugging
|
|
const content = await this.page.content();
|
|
console.error('Page content at failure:', content);
|
|
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async verifySuccessfulLogin() {
|
|
// After successful login, we should be redirected to the dashboard
|
|
await expect(this.page).toHaveURL(/.*\/wp-admin\/?$/);
|
|
}
|
|
|
|
async clickResetPassword() {
|
|
try {
|
|
console.log('Waiting for password reset link...');
|
|
// Wait for the link to be visible and stable
|
|
await this.page.waitForSelector(this.selectors.resetPasswordLink, {
|
|
state: 'visible',
|
|
timeout: 10000
|
|
});
|
|
|
|
// Get all matching links and click the first one that contains relevant text
|
|
const links = await this.page.$$(this.selectors.resetPasswordLink);
|
|
for (const link of links) {
|
|
const text = await link.textContent();
|
|
if (text && (text.toLowerCase().includes('lost') || text.toLowerCase().includes('forgot'))) {
|
|
console.log('Found password reset link with text:', text);
|
|
await link.click();
|
|
return;
|
|
}
|
|
}
|
|
|
|
throw new Error('No password reset link with expected text found');
|
|
} catch (error) {
|
|
console.error('Failed to click password reset link:', error);
|
|
await this.page.screenshot({
|
|
path: `test-results/reset-password-failure-${Date.now()}.png`,
|
|
fullPage: true
|
|
});
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async checkLogEntries(logParser: LogParser) {
|
|
try {
|
|
const logs = await logParser.parseLogFile('wordpress.log');
|
|
const loginAttempt = logs.find(log =>
|
|
log.component === 'auth' &&
|
|
log.message.includes('login_attempt')
|
|
);
|
|
expect(loginAttempt).toBeTruthy();
|
|
} catch (error) {
|
|
console.warn('Warning: Could not check log entries:', error);
|
|
// Skip log verification if we can't access the logs
|
|
return;
|
|
}
|
|
}
|
|
|
|
async verifyRememberMeCookie() {
|
|
try {
|
|
console.log('Checking for WordPress authentication cookies...');
|
|
|
|
// Wait briefly for cookies to be set
|
|
await this.page.waitForTimeout(1000);
|
|
|
|
const cookies = await this.page.context().cookies();
|
|
console.log('Found cookies:', cookies.map(c => c.name).join(', '));
|
|
|
|
// WordPress sets multiple cookies for persistent login
|
|
const requiredCookies = [
|
|
'wordpress_logged_in_', // Main login cookie
|
|
'wordpress_sec_' // Security cookie
|
|
];
|
|
|
|
const foundCookies = requiredCookies.map(cookiePrefix => {
|
|
const cookie = cookies.find(c => c.name.startsWith(cookiePrefix));
|
|
if (!cookie) {
|
|
console.warn(`Missing expected cookie with prefix: ${cookiePrefix}`);
|
|
}
|
|
return cookie;
|
|
});
|
|
|
|
// Verify both cookies exist and have appropriate expiration
|
|
foundCookies.forEach(cookie => {
|
|
expect(cookie, `Cookie with prefix ${cookie?.name} should exist`).toBeTruthy();
|
|
if (cookie) {
|
|
// WordPress "Remember Me" cookies typically expire in 14 days
|
|
const twoWeeksFromNow = Date.now() + (14 * 24 * 60 * 60 * 1000);
|
|
expect(cookie.expires * 1000).toBeGreaterThan(Date.now());
|
|
expect(cookie.expires * 1000).toBeLessThanOrEqual(twoWeeksFromNow);
|
|
}
|
|
});
|
|
|
|
console.log('Successfully verified WordPress authentication cookies');
|
|
} catch (error) {
|
|
console.error('Failed to verify WordPress authentication cookies:', error);
|
|
await this.page.screenshot({
|
|
path: `test-results/cookie-check-failure-${Date.now()}.png`,
|
|
fullPage: true
|
|
});
|
|
throw error;
|
|
}
|
|
}
|
|
} |