upskill-event-manager/tests/page-objects/base/BasePage.js
Ben 7c9ca65cf2
Some checks are pending
HVAC Plugin CI/CD Pipeline / Security Analysis (push) Waiting to run
HVAC Plugin CI/CD Pipeline / Code Quality & Standards (push) Waiting to run
HVAC Plugin CI/CD Pipeline / Unit Tests (push) Waiting to run
HVAC Plugin CI/CD Pipeline / Integration Tests (push) Waiting to run
HVAC Plugin CI/CD Pipeline / Deploy to Staging (push) Blocked by required conditions
HVAC Plugin CI/CD Pipeline / Deploy to Production (push) Blocked by required conditions
HVAC Plugin CI/CD Pipeline / Notification (push) Blocked by required conditions
Security Monitoring & Compliance / Dependency Vulnerability Scan (push) Waiting to run
Security Monitoring & Compliance / Secrets & Credential Scan (push) Waiting to run
Security Monitoring & Compliance / WordPress Security Analysis (push) Waiting to run
Security Monitoring & Compliance / Static Code Security Analysis (push) Waiting to run
Security Monitoring & Compliance / Security Compliance Validation (push) Waiting to run
Security Monitoring & Compliance / Security Summary Report (push) Blocked by required conditions
Security Monitoring & Compliance / Security Team Notification (push) Blocked by required conditions
feat: add comprehensive test framework and test files
- Add 90+ test files including E2E, unit, and integration tests
- Implement Page Object Model (POM) architecture
- Add Docker testing environment with comprehensive services
- Include modernized test framework with error recovery
- Add specialized test suites for master trainer and trainer workflows
- Update .gitignore to properly track test infrastructure

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-29 23:23:26 -03:00

563 lines
No EOL
17 KiB
JavaScript

/**
* Base Page Object Model for HVAC Testing Framework
*
* Provides common page interaction methods with WordPress integration:
* - Navigation and URL handling
* - Element interaction with retry logic
* - WordPress-specific waiting and assertions
* - Screenshot and error handling utilities
*
* @package HVAC_Community_Events
* @version 2.0.0
* @created 2025-08-27
*/
const { expect } = require('@playwright/test');
const ConfigManager = require('../../framework/core/ConfigManager');
class BasePage {
constructor(page) {
this.page = page;
this.config = ConfigManager;
this.baseUrl = this.config.get('app.baseUrl');
this.defaultTimeout = this.config.get('framework.timeout');
// Common WordPress selectors
this.wpSelectors = {
adminBar: '#wpadminbar',
loadingSpinner: '.spinner, .loading',
ajaxLoader: '.ajax-loader',
errorMessage: '.error, .notice-error',
successMessage: '.success, .notice-success, .updated',
wpNonce: '[name="_wpnonce"]',
loggedInBody: 'body.logged-in'
};
}
/**
* Navigate to URL with WordPress-aware waiting
*/
async goto(url) {
const fullUrl = url.startsWith('http') ? url : `${this.baseUrl}${url}`;
console.log(`🔗 Navigating to: ${fullUrl}`);
await this.page.goto(fullUrl, {
waitUntil: 'domcontentloaded',
timeout: this.defaultTimeout
});
// Wait for WordPress to be ready
await this.waitForWordPressReady();
// Check for WordPress errors
await this.checkForWordPressErrors();
}
/**
* Wait for WordPress to be ready
*/
async waitForWordPressReady() {
// Wait for DOM to be ready
await this.page.waitForFunction('document.readyState === "complete"', {
timeout: this.defaultTimeout
});
// Wait for jQuery if present
try {
await this.page.waitForFunction(
'typeof jQuery === "undefined" || jQuery.active === 0',
{ timeout: 5000 }
);
} catch (error) {
// jQuery may not be present, which is fine
}
}
/**
* Wait for AJAX requests to complete
*/
async waitForAjax(timeout = 5000) {
try {
await this.page.waitForFunction(
'window.hvacTestHelpers && window.hvacTestHelpers.waitForAjax()',
{ timeout }
);
} catch (error) {
// Fallback wait
await this.page.waitForTimeout(500);
}
}
/**
* Check for WordPress errors on page
*/
async checkForWordPressErrors() {
const errorSelectors = [
'.wp-die-message',
'.error-message',
'.notice-error',
'text=Fatal error',
'text=Parse error',
'text=Warning:',
'text=Notice:'
];
for (const selector of errorSelectors) {
if (await this.isVisible(selector, { timeout: 1000 })) {
const errorText = await this.getText(selector);
console.warn(`⚠️ WordPress error detected: ${errorText}`);
}
}
}
/**
* Find visible element from multiple selectors
*/
async getVisibleSelector(selectors, options = {}) {
const { timeout = 2000 } = options;
for (const selector of selectors) {
try {
if (await this.isVisible(selector, { timeout: timeout / selectors.length })) {
return selector;
}
} catch (error) {
continue;
}
}
return null;
}
/**
* Check if element is visible with multiple selector fallback
*/
async isVisible(selectors, options = {}) {
const selectorArray = Array.isArray(selectors) ? selectors : [selectors];
const { timeout = 2000 } = options;
for (const selector of selectorArray) {
try {
const element = this.page.locator(selector).first();
await element.waitFor({ state: 'visible', timeout: timeout / selectorArray.length });
return true;
} catch (error) {
continue;
}
}
return false;
}
/**
* Wait for element to be visible
*/
async waitForVisible(selectors, options = {}) {
const selectorArray = Array.isArray(selectors) ? selectors : [selectors];
const { timeout = this.defaultTimeout } = options;
for (const selector of selectorArray) {
try {
await this.page.locator(selector).first().waitFor({
state: 'visible',
timeout: timeout / selectorArray.length
});
return selector;
} catch (error) {
if (selector === selectorArray[selectorArray.length - 1]) {
throw new Error(`Element not found with any selector: ${selectorArray.join(', ')}`);
}
continue;
}
}
}
/**
* Wait for element to be hidden
*/
async waitForHidden(selectors, options = {}) {
const selectorArray = Array.isArray(selectors) ? selectors : [selectors];
const { timeout = this.defaultTimeout } = options;
for (const selector of selectorArray) {
try {
await this.page.locator(selector).first().waitFor({
state: 'hidden',
timeout: timeout / selectorArray.length
});
return;
} catch (error) {
continue;
}
}
}
/**
* Click element with multiple selector support
*/
async click(selectors, options = {}) {
const selectorArray = Array.isArray(selectors) ? selectors : [selectors];
const { timeout = this.defaultTimeout, waitForNavigation = false } = options;
for (const selector of selectorArray) {
try {
const element = this.page.locator(selector).first();
await element.waitFor({ state: 'visible', timeout: timeout / selectorArray.length });
if (waitForNavigation) {
await Promise.all([
this.page.waitForNavigation({ timeout }),
element.click()
]);
} else {
await element.click();
}
return;
} catch (error) {
if (selector === selectorArray[selectorArray.length - 1]) {
throw new Error(`Cannot click element with any selector: ${selectorArray.join(', ')}`);
}
continue;
}
}
}
/**
* Fill form field with multiple selector support
*/
async fill(selectors, value) {
const selectorArray = Array.isArray(selectors) ? selectors : [selectors];
for (const selector of selectorArray) {
try {
const element = this.page.locator(selector).first();
await element.waitFor({ state: 'visible', timeout: 2000 });
await element.fill(value);
return;
} catch (error) {
if (selector === selectorArray[selectorArray.length - 1]) {
throw new Error(`Cannot fill field with any selector: ${selectorArray.join(', ')}`);
}
continue;
}
}
}
/**
* Clear form field
*/
async clear(selectors) {
const selectorArray = Array.isArray(selectors) ? selectors : [selectors];
for (const selector of selectorArray) {
try {
const element = this.page.locator(selector).first();
await element.waitFor({ state: 'visible', timeout: 2000 });
await element.clear();
return;
} catch (error) {
continue;
}
}
}
/**
* Get text content from element
*/
async getText(selectors) {
const selectorArray = Array.isArray(selectors) ? selectors : [selectors];
for (const selector of selectorArray) {
try {
const element = this.page.locator(selector).first();
await element.waitFor({ state: 'visible', timeout: 2000 });
return await element.textContent();
} catch (error) {
continue;
}
}
return '';
}
/**
* Get input value from form field
*/
async getInputValue(selectors) {
const selectorArray = Array.isArray(selectors) ? selectors : [selectors];
for (const selector of selectorArray) {
try {
const element = this.page.locator(selector).first();
await element.waitFor({ state: 'visible', timeout: 2000 });
return await element.inputValue();
} catch (error) {
continue;
}
}
return '';
}
/**
* Select option by text
*/
async selectByText(selectors, text) {
const selectorArray = Array.isArray(selectors) ? selectors : [selectors];
for (const selector of selectorArray) {
try {
const element = this.page.locator(selector).first();
await element.waitFor({ state: 'visible', timeout: 2000 });
await element.selectOption({ label: text });
return;
} catch (error) {
continue;
}
}
throw new Error(`Cannot select option with text '${text}' from any selector: ${selectorArray.join(', ')}`);
}
/**
* Select option by value
*/
async selectByValue(selectors, value) {
const selectorArray = Array.isArray(selectors) ? selectors : [selectors];
for (const selector of selectorArray) {
try {
const element = this.page.locator(selector).first();
await element.waitFor({ state: 'visible', timeout: 2000 });
await element.selectOption(value);
return;
} catch (error) {
continue;
}
}
throw new Error(`Cannot select option with value '${value}' from any selector: ${selectorArray.join(', ')}`);
}
/**
* Get selected value from dropdown
*/
async getSelectedValue(selectors) {
const selectorArray = Array.isArray(selectors) ? selectors : [selectors];
for (const selector of selectorArray) {
try {
const element = this.page.locator(selector).first();
await element.waitFor({ state: 'visible', timeout: 2000 });
return await element.inputValue();
} catch (error) {
continue;
}
}
return '';
}
/**
* Check checkbox or radio button
*/
async check(selectors) {
const selectorArray = Array.isArray(selectors) ? selectors : [selectors];
for (const selector of selectorArray) {
try {
const element = this.page.locator(selector).first();
await element.waitFor({ state: 'visible', timeout: 2000 });
await element.check();
return;
} catch (error) {
continue;
}
}
}
/**
* Uncheck checkbox or radio button
*/
async uncheck(selectors) {
const selectorArray = Array.isArray(selectors) ? selectors : [selectors];
for (const selector of selectorArray) {
try {
const element = this.page.locator(selector).first();
await element.waitFor({ state: 'visible', timeout: 2000 });
await element.uncheck();
return;
} catch (error) {
continue;
}
}
}
/**
* Get locator for element
*/
locator(selectors) {
const selectorArray = Array.isArray(selectors) ? selectors : [selectors];
return this.page.locator(selectorArray.join(', '));
}
/**
* Wait for URL change or specific URL pattern
*/
async waitForUrl(pattern, options = {}) {
const { timeout = this.defaultTimeout } = options;
await this.page.waitForURL(pattern, { timeout });
}
/**
* Wait for URL to change from current
*/
async waitForUrlChange(timeout = this.defaultTimeout) {
const currentUrl = this.page.url();
await this.page.waitForFunction(
(oldUrl) => window.location.href !== oldUrl,
currentUrl,
{ timeout }
);
}
/**
* Verify user role from page elements
*/
async verifyUserRole(expectedRole) {
// Check body class for role
const hasRoleClass = await this.page.evaluate((role) => {
return document.body.classList.contains(`role-${role}`);
}, expectedRole);
if (hasRoleClass) {
return true;
}
// Check admin bar user info
if (await this.isVisible(this.wpSelectors.adminBar)) {
const userInfo = await this.getText('#wp-admin-bar-my-account .display-name');
console.log(`Current user display name: ${userInfo}`);
}
// Additional WordPress role verification can be added here
return hasRoleClass;
}
/**
* Verify breadcrumbs navigation
*/
async verifyBreadcrumbs(expectedPath) {
const breadcrumbSelectors = [
'.hvac-breadcrumb',
'.breadcrumb',
'[data-testid="breadcrumb"]',
'.breadcrumb-nav'
];
for (const selector of breadcrumbSelectors) {
if (await this.isVisible(selector)) {
const breadcrumbText = await this.getText(selector);
for (const pathItem of expectedPath) {
if (!breadcrumbText.includes(pathItem)) {
console.warn(`⚠️ Breadcrumb missing: ${pathItem}`);
return false;
}
}
console.log(`✅ Breadcrumbs verified: ${breadcrumbText}`);
return true;
}
}
console.log('⚠️ No breadcrumbs found');
return false;
}
/**
* Take screenshot with context
*/
async takeScreenshot(name, options = {}) {
const { fullPage = false } = options;
try {
const screenshotPath = `test-results/${name}-${Date.now()}.png`;
await this.page.screenshot({
path: screenshotPath,
fullPage
});
console.log(`📸 Screenshot saved: ${screenshotPath}`);
return screenshotPath;
} catch (error) {
console.warn('Could not take screenshot:', error.message);
return null;
}
}
/**
* Scroll element into view
*/
async scrollIntoView(selectors) {
const selectorArray = Array.isArray(selectors) ? selectors : [selectors];
for (const selector of selectorArray) {
try {
const element = this.page.locator(selector).first();
await element.scrollIntoViewIfNeeded();
return;
} catch (error) {
continue;
}
}
}
/**
* Handle WordPress nonce verification
*/
async verifyNonce(expectedAction) {
if (await this.isVisible(this.wpSelectors.wpNonce)) {
const nonce = await this.page.locator(this.wpSelectors.wpNonce).getAttribute('value');
console.log(`🔒 WordPress nonce present: ${nonce.substring(0, 8)}...`);
return true;
}
console.warn('⚠️ WordPress nonce not found');
return false;
}
/**
* Wait for network idle state
*/
async waitForNetworkIdle(timeout = 5000) {
await this.page.waitForLoadState('networkidle', { timeout });
}
/**
* Get current page title
*/
async getPageTitle() {
return await this.page.title();
}
/**
* Get current URL
*/
getCurrentUrl() {
return this.page.url();
}
/**
* Refresh page with WordPress readiness check
*/
async refresh() {
await this.page.reload({ waitUntil: 'domcontentloaded' });
await this.waitForWordPressReady();
await this.checkForWordPressErrors();
}
}
module.exports = BasePage;