/** * Base Test Class for HVAC Testing Framework * * Provides standardized test foundation with: * - WordPress-aware setup and teardown * - Screenshot and video management * - Database isolation and cleanup * - Error handling and recovery * - Consistent test reporting * * @package HVAC_Community_Events * @version 2.0.0 * @created 2025-08-27 */ const { test, expect } = require('@playwright/test'); const fs = require('fs').promises; const path = require('path'); const ConfigManager = require('./ConfigManager'); const AuthManager = require('./AuthManager'); const WordPressUtils = require('../utils/WordPressUtils'); const ScreenshotManager = require('../utils/ScreenshotManager'); class BaseTest { constructor() { this.config = ConfigManager; this.authManager = AuthManager; this.wpUtils = new WordPressUtils(); this.screenshotManager = new ScreenshotManager(); this.testStartTime = null; this.testContext = null; this.testMetadata = {}; } /** * Configure test with metadata and options */ configure(metadata = {}) { this.testMetadata = { category: metadata.category || 'general', priority: metadata.priority || 'medium', tags: metadata.tags || [], requirements: metadata.requirements || [], author: metadata.author || 'automated', ...metadata }; return this; } /** * Standard test setup - call this in beforeEach */ async setup(page, testInfo) { this.testStartTime = Date.now(); this.testContext = { page, testInfo, testId: this.generateTestId(testInfo), screenshotPath: this.getScreenshotPath(testInfo) }; // Set up error handling this.setupErrorHandling(page); // Set up WordPress environment await this.setupWordPressEnvironment(page); // Initialize test data if needed if (this.config.get('testData.seedData')) { await this.setupTestData(); } // Take initial screenshot if configured if (this.config.get('media.screenshotMode') === 'all') { await this.screenshotManager.capture(page, 'test-start', this.testContext.screenshotPath); } console.log(`๐Ÿงช Test setup complete: ${testInfo.title}`); } /** * Standard test teardown - call this in afterEach */ async teardown(page, testInfo) { const testDuration = Date.now() - this.testStartTime; const testPassed = testInfo.status === 'passed'; try { // Take final screenshot if test failed or if configured for all tests const screenshotMode = this.config.get('media.screenshotMode'); if (!testPassed || screenshotMode === 'all') { await this.screenshotManager.capture( page, testPassed ? 'test-end' : 'test-failure', this.testContext.screenshotPath ); } // Clean up test data if configured if (this.config.get('testData.cleanupAfterTests')) { await this.cleanupTestData(); } // Log test completion const status = testPassed ? 'โœ… PASSED' : 'โŒ FAILED'; console.log(`${status} ${testInfo.title} (${testDuration}ms)`); // Record test metrics await this.recordTestMetrics(testInfo, testDuration, testPassed); } catch (error) { console.error('Error during test teardown:', error.message); } } /** * Set up error handling for the page */ setupErrorHandling(page) { // Handle console errors page.on('console', msg => { if (msg.type() === 'error') { console.error(`๐Ÿ”ด Console Error: ${msg.text()}`); } }); // Handle page errors page.on('pageerror', error => { console.error(`๐Ÿ”ด Page Error: ${error.message}`); }); // Handle failed requests (optional) page.on('requestfailed', request => { if (request.url().includes(this.config.get('app.baseUrl'))) { console.warn(`โš ๏ธ Request Failed: ${request.method()} ${request.url()}`); } }); } /** * Set up WordPress environment for test */ async setupWordPressEnvironment(page) { // Set WordPress-specific headers and context await page.setExtraHTTPHeaders({ 'User-Agent': 'HVAC-Testing-Framework/2.0 Playwright', 'X-Test-Framework': 'HVAC-WordPress-Tests' }); // Add WordPress helper scripts await page.addInitScript(` window.HVAC_TEST_MODE = true; window.HVAC_TEST_ID = '${this.testContext.testId}'; window.HVAC_TEST_START = ${this.testStartTime}; // WordPress-specific test helpers window.hvacTestHelpers = { waitForAjax: function() { return new Promise(resolve => { if (typeof jQuery !== 'undefined' && jQuery.active === 0) { resolve(); } else if (typeof jQuery !== 'undefined') { jQuery(document).ajaxStop(resolve); } else { // Fallback for non-jQuery AJAX setTimeout(resolve, 100); } }); }, waitForWordPress: function() { return new Promise(resolve => { if (document.readyState === 'complete') { resolve(); } else { window.addEventListener('load', resolve); } }); }, triggerWordPressEvent: function(eventName, data) { if (typeof jQuery !== 'undefined') { jQuery(document).trigger(eventName, data); } } }; `); // Flush rewrite rules if configured if (this.config.get('wordpress.flushRewriteRules')) { await this.wpUtils.flushRewriteRules(); } // Clear cache if configured if (this.config.get('wordpress.clearCache')) { await this.wpUtils.clearCache(); } } /** * Set up test data for the test */ async setupTestData() { // This method can be overridden by specific test classes // to set up role-specific test data console.log('๐Ÿ“‹ Setting up test data...'); // Load fixtures if available const fixtures = this.config.get('testData.fixtures'); if (fixtures && fixtures.users) { await this.loadUserFixtures(fixtures.users); } } /** * Clean up test data after test */ async cleanupTestData() { console.log('๐Ÿงน Cleaning up test data...'); // Clean up test-specific data await this.wpUtils.cleanupTestData(this.testContext.testId); } /** * Load user fixtures for testing */ async loadUserFixtures(fixturesPath) { try { const fixtures = JSON.parse(await fs.readFile(fixturesPath, 'utf8')); // Process user fixtures as needed console.log(`๐Ÿ“‚ Loaded user fixtures: ${Object.keys(fixtures).length} users`); } catch (error) { console.warn('Could not load user fixtures:', error.message); } } /** * Authenticate as specific user role */ async authenticateAs(page, role, options = {}) { console.log(`๐Ÿ” Authenticating as: ${role}`); return await this.authManager.authenticate(page, role, options); } /** * Wait for WordPress-specific conditions */ async waitForWordPress(page, condition = 'ready') { switch (condition) { case 'ready': await page.waitForFunction('document.readyState === "complete"'); break; case 'ajax': await page.waitForFunction(` window.hvacTestHelpers && window.hvacTestHelpers.waitForAjax() `); break; case 'dom-ready': await page.waitForFunction('document.readyState !== "loading"'); break; case 'network-idle': await page.waitForLoadState('networkidle'); break; default: throw new Error(`Unknown WordPress condition: ${condition}`); } } /** * Take screenshot with context */ async takeScreenshot(page, name = 'screenshot', fullPage = false) { return await this.screenshotManager.capture( page, name, this.testContext.screenshotPath, { fullPage } ); } /** * Assert WordPress-specific conditions */ async assertWordPressState(page, assertions) { if (assertions.authenticated) { // Check if user is authenticated const isLoggedIn = await page.evaluate(() => { return document.body.classList.contains('logged-in') || document.querySelector('#wpadminbar') !== null || window.location.pathname.includes('/wp-admin/'); }); expect(isLoggedIn).toBe(true); } if (assertions.role) { // Verify user has expected role const hasRole = await page.evaluate((expectedRole) => { return document.body.classList.contains(`role-${expectedRole}`) || window.HVAC_AUTH_ROLE === expectedRole; }, assertions.role); expect(hasRole).toBe(true); } if (assertions.capability) { // Check if user has specific capability await expect(page.locator(`[data-capability="${assertions.capability}"]`)).toBeVisible(); } if (assertions.nonce) { // Verify WordPress nonce is present const nonceExists = await page.evaluate((nonceName) => { return document.querySelector(`[name="_wpnonce"]`) !== null || document.querySelector(`[name="${nonceName}"]`) !== null; }, assertions.nonce); expect(nonceExists).toBe(true); } } /** * Generate unique test ID */ generateTestId(testInfo) { const timestamp = Date.now(); const testName = testInfo.title.replace(/[^a-zA-Z0-9]/g, '-').toLowerCase(); return `hvac-test-${testName}-${timestamp}`; } /** * Get screenshot path for test */ getScreenshotPath(testInfo) { const screenshotDir = this.config.get('media.screenshotDir'); const testName = testInfo.title.replace(/[^a-zA-Z0-9]/g, '-').toLowerCase(); const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); return path.join(screenshotDir, `${testName}-${timestamp}`); } /** * Record test metrics for reporting */ async recordTestMetrics(testInfo, duration, passed) { const metrics = { testId: this.testContext.testId, title: testInfo.title, category: this.testMetadata.category, priority: this.testMetadata.priority, tags: this.testMetadata.tags, duration, passed, timestamp: new Date().toISOString(), environment: this.config.getEnvironment(), metadata: this.testMetadata }; // Write metrics to results directory const resultsDir = this.config.get('reporting.outputDir'); const metricsFile = path.join(resultsDir, 'test-metrics.jsonl'); try { await fs.mkdir(resultsDir, { recursive: true }); await fs.appendFile(metricsFile, JSON.stringify(metrics) + '\n'); } catch (error) { console.warn('Could not record test metrics:', error.message); } } /** * Helper method to create WordPress-aware test */ static create(title, testFn, metadata = {}) { return test(title, async ({ page }, testInfo) => { const baseTest = new BaseTest(); baseTest.configure(metadata); try { await baseTest.setup(page, testInfo); await testFn(page, testInfo, baseTest); } finally { await baseTest.teardown(page, testInfo); } }); } /** * Helper method to create WordPress-aware describe block */ static describe(title, testSuite, metadata = {}) { return test.describe(title, () => { // Set up common hooks for the test suite test.beforeAll(async () => { console.log(`๐Ÿ“ฆ Starting test suite: ${title}`); }); test.afterAll(async () => { console.log(`๐Ÿ“ฆ Completed test suite: ${title}`); }); // Run the test suite testSuite(); }); } } module.exports = BaseTest;