/** * 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} */ 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} */ 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} */ 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} */ async waitForPageReady() { // Override in subclasses } /** * Check if element exists * @param {string} selector - CSS selector * @returns {Promise} */ 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} */ 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} */ 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} */ 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} */ 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} */ 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} */ 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} */ 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} */ 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} */ 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} */ 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} */ async getPageTitle() { return await this.page.title(); } /** * Take screenshot of current page * @param {string} filename - Screenshot filename * @returns {Promise} 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} */ 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} */ async scrollToElement(selector) { await this.waitForElement(selector); await this.page.locator(selector).scrollIntoViewIfNeeded(); } /** * Hover over element * @param {string} selector - CSS selector * @returns {Promise} */ async hoverElement(selector) { await this.waitForElement(selector); await this.page.hover(selector); } /** * Double click element * @param {string} selector - CSS selector * @returns {Promise} */ async doubleClickElement(selector) { await this.waitForElement(selector); await this.page.dblclick(selector); } /** * Right click element * @param {string} selector - CSS selector * @returns {Promise} */ async rightClickElement(selector) { await this.waitForElement(selector); await this.page.click(selector, { button: 'right' }); } } module.exports = BasePage;