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
- 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>
345 lines
No EOL
9.5 KiB
JavaScript
345 lines
No EOL
9.5 KiB
JavaScript
/**
|
|
* Base Page Object - Foundation for all page objects
|
|
* Provides common functionality for WordPress pages
|
|
*/
|
|
|
|
const { getBrowserManager } = require('../browser/BrowserManager');
|
|
|
|
class BasePage {
|
|
constructor(page = null) {
|
|
this.page = page || getBrowserManager().getCurrentPage();
|
|
this.url = '';
|
|
this.title = '';
|
|
this.selectors = {};
|
|
}
|
|
|
|
/**
|
|
* Navigate to this page
|
|
* @param {Object} options - Navigation options
|
|
* @returns {Promise<void>}
|
|
*/
|
|
async navigate(options = {}) {
|
|
if (!this.url) {
|
|
throw new Error('Page URL not defined');
|
|
}
|
|
|
|
await this.page.goto(this.url, {
|
|
waitUntil: 'networkidle',
|
|
timeout: 30000,
|
|
...options
|
|
});
|
|
|
|
await this.waitForPageLoad();
|
|
}
|
|
|
|
/**
|
|
* Wait for page to be loaded and ready
|
|
* @returns {Promise<void>}
|
|
*/
|
|
async waitForPageLoad() {
|
|
// Wait for document ready
|
|
await this.page.waitForLoadState('domcontentloaded');
|
|
|
|
// Wait for any WordPress-specific loading
|
|
await this.waitForWordPressReady();
|
|
|
|
// Wait for any page-specific loading
|
|
await this.waitForPageReady();
|
|
}
|
|
|
|
/**
|
|
* Wait for WordPress-specific elements to be ready
|
|
* @returns {Promise<void>}
|
|
*/
|
|
async waitForWordPressReady() {
|
|
try {
|
|
// Wait for WordPress admin bar or body class
|
|
await this.page.waitForSelector('body.logged-in, body.wp-core-ui, #wpadminbar', {
|
|
timeout: 10000
|
|
});
|
|
} catch (error) {
|
|
// Not a WordPress page or user not logged in - continue
|
|
}
|
|
|
|
// Wait for jQuery if present
|
|
try {
|
|
await this.page.waitForFunction(() =>
|
|
window.jQuery !== undefined || window.$ !== undefined,
|
|
{ timeout: 5000 }
|
|
);
|
|
} catch (error) {
|
|
// jQuery not present - continue
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Wait for page-specific elements to be ready
|
|
* Override in subclasses for page-specific waiting
|
|
* @returns {Promise<void>}
|
|
*/
|
|
async waitForPageReady() {
|
|
// Override in subclasses
|
|
}
|
|
|
|
/**
|
|
* Check if element exists
|
|
* @param {string} selector - CSS selector
|
|
* @returns {Promise<boolean>}
|
|
*/
|
|
async hasElement(selector) {
|
|
try {
|
|
const element = await this.page.$(selector);
|
|
return element !== null;
|
|
} catch (error) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Wait for element to be visible
|
|
* @param {string} selector - CSS selector
|
|
* @param {number} timeout - Timeout in milliseconds
|
|
* @returns {Promise<void>}
|
|
*/
|
|
async waitForElement(selector, timeout = 30000) {
|
|
await this.page.waitForSelector(selector, {
|
|
visible: true,
|
|
timeout
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Wait for element to be hidden/removed
|
|
* @param {string} selector - CSS selector
|
|
* @param {number} timeout - Timeout in milliseconds
|
|
* @returns {Promise<void>}
|
|
*/
|
|
async waitForElementHidden(selector, timeout = 30000) {
|
|
await this.page.waitForSelector(selector, {
|
|
hidden: true,
|
|
timeout
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Click element with retry and wait
|
|
* @param {string} selector - CSS selector
|
|
* @param {Object} options - Click options
|
|
* @returns {Promise<void>}
|
|
*/
|
|
async clickElement(selector, options = {}) {
|
|
await this.waitForElement(selector);
|
|
|
|
// Scroll element into view
|
|
await this.page.locator(selector).scrollIntoViewIfNeeded();
|
|
|
|
// Wait for element to be stable
|
|
await this.page.locator(selector).waitFor({ state: 'visible' });
|
|
|
|
// Click with retry
|
|
await this.page.click(selector, {
|
|
timeout: 10000,
|
|
...options
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Fill input field with validation
|
|
* @param {string} selector - CSS selector
|
|
* @param {string} value - Value to fill
|
|
* @param {Object} options - Fill options
|
|
* @returns {Promise<void>}
|
|
*/
|
|
async fillField(selector, value, options = {}) {
|
|
await this.waitForElement(selector);
|
|
|
|
// Clear existing content
|
|
await this.page.fill(selector, '', { timeout: 10000 });
|
|
|
|
// Fill with new value
|
|
await this.page.fill(selector, value, {
|
|
timeout: 10000,
|
|
...options
|
|
});
|
|
|
|
// Verify the value was set
|
|
const actualValue = await this.page.inputValue(selector);
|
|
if (actualValue !== value) {
|
|
throw new Error(`Failed to fill field ${selector}. Expected: ${value}, Got: ${actualValue}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Select option from dropdown
|
|
* @param {string} selector - CSS selector
|
|
* @param {string|Object} option - Option value or object with label/value
|
|
* @returns {Promise<void>}
|
|
*/
|
|
async selectOption(selector, option) {
|
|
await this.waitForElement(selector);
|
|
|
|
if (typeof option === 'string') {
|
|
await this.page.selectOption(selector, option);
|
|
} else {
|
|
await this.page.selectOption(selector, {
|
|
label: option.label,
|
|
value: option.value
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get text content of element
|
|
* @param {string} selector - CSS selector
|
|
* @returns {Promise<string>}
|
|
*/
|
|
async getElementText(selector) {
|
|
await this.waitForElement(selector);
|
|
return await this.page.textContent(selector);
|
|
}
|
|
|
|
/**
|
|
* Get attribute value of element
|
|
* @param {string} selector - CSS selector
|
|
* @param {string} attribute - Attribute name
|
|
* @returns {Promise<string|null>}
|
|
*/
|
|
async getElementAttribute(selector, attribute) {
|
|
await this.waitForElement(selector);
|
|
return await this.page.getAttribute(selector, attribute);
|
|
}
|
|
|
|
/**
|
|
* Check if element is visible
|
|
* @param {string} selector - CSS selector
|
|
* @returns {Promise<boolean>}
|
|
*/
|
|
async isElementVisible(selector) {
|
|
try {
|
|
return await this.page.isVisible(selector);
|
|
} catch (error) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if element is enabled
|
|
* @param {string} selector - CSS selector
|
|
* @returns {Promise<boolean>}
|
|
*/
|
|
async isElementEnabled(selector) {
|
|
try {
|
|
return await this.page.isEnabled(selector);
|
|
} catch (error) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Wait for page URL to match pattern
|
|
* @param {RegExp|string} pattern - URL pattern to match
|
|
* @param {number} timeout - Timeout in milliseconds
|
|
* @returns {Promise<void>}
|
|
*/
|
|
async waitForUrl(pattern, timeout = 30000) {
|
|
await this.page.waitForURL(pattern, { timeout });
|
|
}
|
|
|
|
/**
|
|
* Get current page URL
|
|
* @returns {string}
|
|
*/
|
|
getCurrentUrl() {
|
|
return this.page.url();
|
|
}
|
|
|
|
/**
|
|
* Get page title
|
|
* @returns {Promise<string>}
|
|
*/
|
|
async getPageTitle() {
|
|
return await this.page.title();
|
|
}
|
|
|
|
/**
|
|
* Take screenshot of current page
|
|
* @param {string} filename - Screenshot filename
|
|
* @returns {Promise<string>} Path to screenshot
|
|
*/
|
|
async takeScreenshot(filename) {
|
|
const browserManager = getBrowserManager();
|
|
return await browserManager.takeScreenshot(filename);
|
|
}
|
|
|
|
/**
|
|
* Execute JavaScript on page
|
|
* @param {string|Function} script - JavaScript to execute
|
|
* @param {...*} args - Arguments to pass to script
|
|
* @returns {Promise<*>}
|
|
*/
|
|
async executeScript(script, ...args) {
|
|
return await this.page.evaluate(script, ...args);
|
|
}
|
|
|
|
/**
|
|
* Wait for AJAX requests to complete
|
|
* @param {number} timeout - Timeout in milliseconds
|
|
* @returns {Promise<void>}
|
|
*/
|
|
async waitForAjaxComplete(timeout = 10000) {
|
|
try {
|
|
await this.page.waitForFunction(() => {
|
|
// Check if jQuery is available and has no active requests
|
|
if (window.jQuery) {
|
|
return window.jQuery.active === 0;
|
|
}
|
|
// Fallback: check if no pending fetch requests
|
|
return !window.fetch || window.fetch.length === 0;
|
|
}, { timeout });
|
|
} catch (error) {
|
|
console.warn('AJAX wait timeout - continuing anyway');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Scroll to element
|
|
* @param {string} selector - CSS selector
|
|
* @returns {Promise<void>}
|
|
*/
|
|
async scrollToElement(selector) {
|
|
await this.waitForElement(selector);
|
|
await this.page.locator(selector).scrollIntoViewIfNeeded();
|
|
}
|
|
|
|
/**
|
|
* Hover over element
|
|
* @param {string} selector - CSS selector
|
|
* @returns {Promise<void>}
|
|
*/
|
|
async hoverElement(selector) {
|
|
await this.waitForElement(selector);
|
|
await this.page.hover(selector);
|
|
}
|
|
|
|
/**
|
|
* Double click element
|
|
* @param {string} selector - CSS selector
|
|
* @returns {Promise<void>}
|
|
*/
|
|
async doubleClickElement(selector) {
|
|
await this.waitForElement(selector);
|
|
await this.page.dblclick(selector);
|
|
}
|
|
|
|
/**
|
|
* Right click element
|
|
* @param {string} selector - CSS selector
|
|
* @returns {Promise<void>}
|
|
*/
|
|
async rightClickElement(selector) {
|
|
await this.waitForElement(selector);
|
|
await this.page.click(selector, { button: 'right' });
|
|
}
|
|
}
|
|
|
|
module.exports = BasePage; |