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>
249 lines
No EOL
6.5 KiB
JavaScript
249 lines
No EOL
6.5 KiB
JavaScript
/**
|
|
* Browser Manager - Centralized browser lifecycle management
|
|
* Provides singleton browser instances with proper cleanup and configuration
|
|
*/
|
|
|
|
const { chromium, firefox, webkit } = require('playwright');
|
|
const path = require('path');
|
|
|
|
class BrowserManager {
|
|
constructor() {
|
|
this.browser = null;
|
|
this.context = null;
|
|
this.page = null;
|
|
this.config = null;
|
|
}
|
|
|
|
/**
|
|
* Initialize browser with configuration
|
|
* @param {Object} config - Browser configuration
|
|
* @returns {Promise<void>}
|
|
*/
|
|
async initialize(config) {
|
|
this.config = {
|
|
browserType: 'chromium',
|
|
headless: true,
|
|
slowMo: 0,
|
|
timeout: 30000,
|
|
viewport: { width: 1280, height: 720 },
|
|
video: false,
|
|
screenshot: 'only-on-failure',
|
|
...config
|
|
};
|
|
|
|
// Launch browser based on type
|
|
const browserType = this._getBrowserType(this.config.browserType);
|
|
|
|
this.browser = await browserType.launch({
|
|
headless: this.config.headless,
|
|
slowMo: this.config.slowMo,
|
|
args: this._getBrowserArgs()
|
|
});
|
|
|
|
// Create context with configuration
|
|
this.context = await this.browser.newContext({
|
|
viewport: this.config.viewport,
|
|
video: this.config.video ? {
|
|
dir: path.join(process.cwd(), 'tests/evidence/videos')
|
|
} : undefined,
|
|
recordVideo: this.config.video ? {
|
|
dir: path.join(process.cwd(), 'tests/evidence/videos'),
|
|
size: this.config.viewport
|
|
} : undefined
|
|
});
|
|
|
|
// Set default timeout
|
|
this.context.setDefaultTimeout(this.config.timeout);
|
|
|
|
// Create initial page
|
|
this.page = await this.context.newPage();
|
|
|
|
// Set up page defaults
|
|
await this._setupPageDefaults();
|
|
|
|
return { browser: this.browser, context: this.context, page: this.page };
|
|
}
|
|
|
|
/**
|
|
* Get new page instance
|
|
* @returns {Promise<Page>}
|
|
*/
|
|
async getNewPage() {
|
|
if (!this.context) {
|
|
throw new Error('Browser not initialized. Call initialize() first.');
|
|
}
|
|
return await this.context.newPage();
|
|
}
|
|
|
|
/**
|
|
* Get current page
|
|
* @returns {Page}
|
|
*/
|
|
getCurrentPage() {
|
|
if (!this.page) {
|
|
throw new Error('No active page. Browser may not be initialized.');
|
|
}
|
|
return this.page;
|
|
}
|
|
|
|
/**
|
|
* Close current page
|
|
* @returns {Promise<void>}
|
|
*/
|
|
async closePage() {
|
|
if (this.page) {
|
|
await this.page.close();
|
|
this.page = null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Clean shutdown of browser
|
|
* @returns {Promise<void>}
|
|
*/
|
|
async cleanup() {
|
|
try {
|
|
if (this.page) {
|
|
await this.page.close();
|
|
this.page = null;
|
|
}
|
|
if (this.context) {
|
|
await this.context.close();
|
|
this.context = null;
|
|
}
|
|
if (this.browser) {
|
|
await this.browser.close();
|
|
this.browser = null;
|
|
}
|
|
} catch (error) {
|
|
console.warn('Error during browser cleanup:', error.message);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Take screenshot of current page
|
|
* @param {string} filename - Screenshot filename
|
|
* @param {Object} options - Screenshot options
|
|
* @returns {Promise<string>} Path to screenshot
|
|
*/
|
|
async takeScreenshot(filename, options = {}) {
|
|
if (!this.page) {
|
|
throw new Error('No active page for screenshot');
|
|
}
|
|
|
|
const screenshotPath = path.join(
|
|
process.cwd(),
|
|
'tests/evidence/screenshots',
|
|
filename
|
|
);
|
|
|
|
await this.page.screenshot({
|
|
path: screenshotPath,
|
|
fullPage: true,
|
|
...options
|
|
});
|
|
|
|
return screenshotPath;
|
|
}
|
|
|
|
/**
|
|
* Wait for page load with network idle
|
|
* @param {number} timeout - Wait timeout
|
|
* @returns {Promise<void>}
|
|
*/
|
|
async waitForPageLoad(timeout = 30000) {
|
|
if (!this.page) return;
|
|
|
|
try {
|
|
await this.page.waitForLoadState('networkidle', { timeout });
|
|
} catch (error) {
|
|
console.warn('Page load wait timeout:', error.message);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get browser type instance
|
|
* @param {string} browserType - Browser type name
|
|
* @returns {BrowserType}
|
|
* @private
|
|
*/
|
|
_getBrowserType(browserType) {
|
|
switch (browserType.toLowerCase()) {
|
|
case 'firefox':
|
|
return firefox;
|
|
case 'webkit':
|
|
case 'safari':
|
|
return webkit;
|
|
case 'chromium':
|
|
case 'chrome':
|
|
default:
|
|
return chromium;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get browser launch arguments
|
|
* @returns {string[]}
|
|
* @private
|
|
*/
|
|
_getBrowserArgs() {
|
|
return [
|
|
'--no-sandbox',
|
|
'--disable-dev-shm-usage',
|
|
'--disable-extensions',
|
|
'--disable-background-timer-throttling',
|
|
'--disable-backgrounding-occluded-windows',
|
|
'--disable-renderer-backgrounding',
|
|
'--disable-features=TranslateUI',
|
|
'--no-default-browser-check',
|
|
'--no-first-run'
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Set up default page configuration
|
|
* @returns {Promise<void>}
|
|
* @private
|
|
*/
|
|
async _setupPageDefaults() {
|
|
if (!this.page) return;
|
|
|
|
// Set user agent
|
|
await this.page.setExtraHTTPHeaders({
|
|
'User-Agent': 'HVAC-Testing-Framework/2.0'
|
|
});
|
|
|
|
// Set up console logging
|
|
this.page.on('console', (msg) => {
|
|
if (msg.type() === 'error') {
|
|
console.error('Browser console error:', msg.text());
|
|
}
|
|
});
|
|
|
|
// Set up page error handling
|
|
this.page.on('pageerror', (error) => {
|
|
console.error('Page error:', error.message);
|
|
});
|
|
|
|
// Set up network error handling
|
|
this.page.on('requestfailed', (request) => {
|
|
console.warn('Request failed:', request.url(), request.failure()?.errorText);
|
|
});
|
|
}
|
|
}
|
|
|
|
// Singleton instance
|
|
let browserManager = null;
|
|
|
|
/**
|
|
* Get singleton BrowserManager instance
|
|
* @returns {BrowserManager}
|
|
*/
|
|
function getBrowserManager() {
|
|
if (!browserManager) {
|
|
browserManager = new BrowserManager();
|
|
}
|
|
return browserManager;
|
|
}
|
|
|
|
module.exports = { BrowserManager, getBrowserManager }; |