/** * Public Pages Object Models * * Page objects for public-facing pages including authentication flows * and public access pages for comprehensive E2E testing. * * @package HVAC_Community_Events * @version 2.0.0 * @created 2025-08-27 */ const BasePage = require('../../framework/base/BasePage'); /** * Training Login Page Object */ class TrainingLoginPage extends BasePage { constructor() { super(); this.url = '/training-login/'; this.selectors = { loginForm: 'form#loginform, form.login-form, form[name="loginform"]', usernameField: '#user_login, input[name="log"], input[name="username"]', passwordField: '#user_pass, input[name="pwd"], input[name="password"]', submitButton: '#wp-submit, input[type="submit"], button[type="submit"]', errorMessage: '.login_error, .error, .notice-error, .warning', forgotPasswordLink: 'a[href*="lostpassword"], a[href*="forgot"], a:has-text("Forgot")', rememberMe: '#rememberme, input[name="rememberme"]' }; } async waitForPageReady() { await this.waitForElement(this.selectors.loginForm, 30000); } async login(username, password) { await this.fillField(this.selectors.usernameField, username); await this.fillField(this.selectors.passwordField, password); await this.clickElement(this.selectors.submitButton); // Wait for redirect or error await this.page.waitForTimeout(3000); } async getErrorMessage() { try { return await this.getElementText(this.selectors.errorMessage); } catch (error) { return null; } } async clickForgotPassword() { await this.clickElement(this.selectors.forgotPasswordLink); } async hasRememberMeOption() { return await this.hasElement(this.selectors.rememberMe); } } /** * Trainer Registration Page Object */ class TrainerRegistrationPage extends BasePage { constructor() { super(); this.url = '/trainer-registration/'; this.selectors = { registrationForm: 'form.registration-form, form#registerform, form[name="registerform"]', usernameField: '#user_login, input[name="user_login"], input[name="username"]', emailField: '#user_email, input[name="user_email"], input[name="email"]', passwordField: '#user_pass, input[name="user_pass"], input[name="password"]', confirmPasswordField: '#user_pass_confirm, input[name="user_pass_confirm"]', firstNameField: 'input[name="first_name"], input[name="user_firstname"]', lastNameField: 'input[name="last_name"], input[name="user_lastname"]', phoneField: 'input[name="phone"], input[name="user_phone"]', companyField: 'input[name="company"], input[name="user_company"]', submitButton: 'input[type="submit"], button[type="submit"]', errorMessages: '.error, .notice-error, .registration-errors', successMessage: '.success, .notice-success, .registration-success', requiredFieldIndicators: '.required, .asterisk, span[title*="required"]' }; } async waitForPageReady() { await this.waitForElement(this.selectors.registrationForm, 15000); } async fillRegistrationForm(userData) { if (await this.hasElement(this.selectors.usernameField)) { await this.fillField(this.selectors.usernameField, userData.username); } if (await this.hasElement(this.selectors.emailField)) { await this.fillField(this.selectors.emailField, userData.email); } if (await this.hasElement(this.selectors.passwordField)) { await this.fillField(this.selectors.passwordField, userData.password); } if (await this.hasElement(this.selectors.confirmPasswordField)) { await this.fillField(this.selectors.confirmPasswordField, userData.password); } if (await this.hasElement(this.selectors.firstNameField)) { await this.fillField(this.selectors.firstNameField, userData.firstName); } if (await this.hasElement(this.selectors.lastNameField)) { await this.fillField(this.selectors.lastNameField, userData.lastName); } if (await this.hasElement(this.selectors.phoneField)) { await this.fillField(this.selectors.phoneField, userData.phone || '555-0123'); } if (await this.hasElement(this.selectors.companyField)) { await this.fillField(this.selectors.companyField, userData.company || 'Test Company'); } } async submitRegistration() { await this.clickElement(this.selectors.submitButton); await this.page.waitForTimeout(5000); // Wait for submission processing } async getRegistrationErrors() { try { const errorElements = await this.page.locator(this.selectors.errorMessages).all(); const errors = []; for (const element of errorElements) { const text = await element.textContent(); if (text && text.trim()) { errors.push(text.trim()); } } return errors; } catch (error) { return []; } } async hasSuccessMessage() { return await this.hasElement(this.selectors.successMessage); } async getRequiredFields() { const requiredFields = []; const indicators = await this.page.locator(this.selectors.requiredFieldIndicators).all(); for (const indicator of indicators) { const parentForm = await indicator.locator('..').first(); const inputField = await parentForm.locator('input').first(); const fieldName = await inputField.getAttribute('name'); if (fieldName) { requiredFields.push(fieldName); } } return requiredFields; } } /** * Registration Pending Page Object */ class RegistrationPendingPage extends BasePage { constructor() { super(); this.url = '/registration-pending/'; this.selectors = { pendingMessage: '.pending-message, .approval-notice, .registration-pending', statusIndicator: '.status, [data-status="pending"]', contactInfo: '.contact-info, .admin-contact', nextStepsInfo: '.next-steps, .what-happens-next', timeframeInfo: '.timeframe, .approval-time' }; } async getPendingMessage() { try { return await this.getElementText(this.selectors.pendingMessage); } catch (error) { return await this.getElementText('body'); // Fallback to body content } } async hasContactInformation() { return await this.hasElement(this.selectors.contactInfo); } async hasNextStepsInformation() { return await this.hasElement(this.selectors.nextStepsInfo); } async getApprovalTimeframe() { try { return await this.getElementText(this.selectors.timeframeInfo); } catch (error) { return null; } } } /** * Account Pending Page Object */ class AccountPendingPage extends BasePage { constructor() { super(); this.url = '/trainer-account-pending/'; this.selectors = { pendingMessage: '.account-pending, .approval-pending', statusDisplay: '.account-status, [data-account-status]', adminContact: '.admin-contact, .contact-administrator', submittedDate: '.submission-date, .request-date', accountDetails: '.account-details, .pending-account-info' }; } async getAccountStatus() { try { return await this.getElementText(this.selectors.statusDisplay); } catch (error) { return 'pending'; // Default status } } async getSubmissionDate() { try { return await this.getElementText(this.selectors.submittedDate); } catch (error) { return null; } } async hasAdminContactInfo() { return await this.hasElement(this.selectors.adminContact); } } /** * Account Disabled Page Object */ class AccountDisabledPage extends BasePage { constructor() { super(); this.url = '/trainer-account-disabled/'; this.selectors = { disabledMessage: '.account-disabled, .account-suspended, .disabled-notice', reasonDisplay: '.disable-reason, .suspension-reason', reactivationInfo: '.reactivation-info, .restore-account', contactInfo: '.contact-admin, .appeal-info', disabledDate: '.disabled-date, .suspension-date' }; } async getDisabledReason() { try { return await this.getElementText(this.selectors.reasonDisplay); } catch (error) { return 'Account has been disabled'; } } async hasReactivationInstructions() { return await this.hasElement(this.selectors.reactivationInfo); } async getDisabledDate() { try { return await this.getElementText(this.selectors.disabledDate); } catch (error) { return null; } } async hasAppealProcess() { return await this.hasElement(this.selectors.contactInfo); } } /** * Find Trainer Page Object (Public Directory) */ class FindTrainerPage extends BasePage { constructor() { super(); this.url = '/find-trainer/'; this.selectors = { searchForm: '.trainer-search, .directory-search, form[role="search"]', searchField: 'input[type="search"], input[name="search"], .search-input', searchButton: 'button[type="submit"], input[type="submit"], .search-submit', trainerCards: '.trainer-card, .trainer-item, .trainer-listing, article.trainer', trainerName: '.trainer-name, .trainer-title, h3, h2', trainerLocation: '.trainer-location, .location', trainerSpecialty: '.trainer-specialty, .specialties', contactButton: '.contact-trainer, .trainer-contact', filterOptions: '.filter-options, .directory-filters', locationFilter: 'select[name="location"], input[name="location"]', specialtyFilter: 'select[name="specialty"], input[name="specialty"]', sortOptions: '.sort-options, select[name="sort"]', resultsCount: '.results-count, .trainer-count', noResultsMessage: '.no-results, .no-trainers-found', paginationLinks: '.pagination, .page-numbers' }; } async waitForPageReady() { await this.waitForElement('body', 10000); // Wait for dynamic content to load await this.page.waitForTimeout(2000); } async searchTrainers(searchTerm) { if (await this.hasElement(this.selectors.searchField)) { await this.fillField(this.selectors.searchField, searchTerm); if (await this.hasElement(this.selectors.searchButton)) { await this.clickElement(this.selectors.searchButton); } else { await this.page.press(this.selectors.searchField, 'Enter'); } // Wait for search results await this.page.waitForTimeout(3000); } } async getTrainerCount() { return await this.page.locator(this.selectors.trainerCards).count(); } async getTrainerDetails(index = 0) { const trainerCard = this.page.locator(this.selectors.trainerCards).nth(index); if (!(await trainerCard.isVisible())) { return null; } const details = {}; try { details.name = await trainerCard.locator(this.selectors.trainerName).textContent(); } catch (error) { details.name = 'Unknown'; } try { details.location = await trainerCard.locator(this.selectors.trainerLocation).textContent(); } catch (error) { details.location = ''; } try { details.specialty = await trainerCard.locator(this.selectors.trainerSpecialty).textContent(); } catch (error) { details.specialty = ''; } return details; } async filterByLocation(location) { if (await this.hasElement(this.selectors.locationFilter)) { await this.selectOption(this.selectors.locationFilter, location); await this.page.waitForTimeout(2000); } } async filterBySpecialty(specialty) { if (await this.hasElement(this.selectors.specialtyFilter)) { await this.selectOption(this.selectors.specialtyFilter, specialty); await this.page.waitForTimeout(2000); } } async sortResults(sortOption) { if (await this.hasElement(this.selectors.sortOptions)) { await this.selectOption(this.selectors.sortOptions, sortOption); await this.page.waitForTimeout(2000); } } async hasNoResults() { return await this.hasElement(this.selectors.noResultsMessage); } async getResultsCount() { try { const countText = await this.getElementText(this.selectors.resultsCount); const match = countText.match(/\d+/); return match ? parseInt(match[0]) : 0; } catch (error) { return await this.getTrainerCount(); } } } /** * Documentation Page Object */ class DocumentationPage extends BasePage { constructor() { super(); this.url = '/documentation/'; this.selectors = { docNavigation: '.doc-nav, .documentation-nav, .help-nav, nav', searchField: '.doc-search, .help-search, input[type="search"]', searchButton: '.search-submit, button[type="submit"]', articleLinks: '.doc-link, .help-link, .article-link, a[href*="help"], a[href*="doc"]', articleTitle: '.doc-title, .help-title, .article-title, h1, h2', articleContent: '.doc-content, .help-content, .article-content, .content', breadcrumbs: '.breadcrumbs, .breadcrumb', categoryLinks: '.category-link, .doc-category', tableOfContents: '.toc, .table-of-contents', relatedArticles: '.related-articles, .see-also', backToTop: '.back-to-top, a[href="#top"]', printButton: '.print-doc, .print-page', lastUpdated: '.last-updated, .updated-date' }; } async waitForPageReady() { await this.waitForElement('body', 10000); await this.page.waitForTimeout(1000); } async searchDocumentation(searchTerm) { if (await this.hasElement(this.selectors.searchField)) { await this.fillField(this.selectors.searchField, searchTerm); if (await this.hasElement(this.selectors.searchButton)) { await this.clickElement(this.selectors.searchButton); } else { await this.page.press(this.selectors.searchField, 'Enter'); } await this.page.waitForTimeout(3000); } } async getArticleLinks() { const links = await this.page.locator(this.selectors.articleLinks).all(); const linkData = []; for (const link of links) { const text = await link.textContent(); const href = await link.getAttribute('href'); if (text && href) { linkData.push({ text: text.trim(), href }); } } return linkData; } async navigateToArticle(linkText) { const link = this.page.locator(this.selectors.articleLinks).filter({ hasText: linkText }); if (await link.isVisible()) { await link.click(); await this.page.waitForLoadState('networkidle'); } } async hasTableOfContents() { return await this.hasElement(this.selectors.tableOfContents); } async getArticleTitle() { try { return await this.getElementText(this.selectors.articleTitle); } catch (error) { return await this.getPageTitle(); } } async hasSearchFunctionality() { return await this.hasElement(this.selectors.searchField); } async getLastUpdatedDate() { try { return await this.getElementText(this.selectors.lastUpdated); } catch (error) { return null; } } async hasRelatedArticles() { return await this.hasElement(this.selectors.relatedArticles); } } module.exports = { TrainingLoginPage, TrainerRegistrationPage, RegistrationPendingPage, AccountPendingPage, AccountDisabledPage, FindTrainerPage, DocumentationPage };