upskill-event-manager/tests/e2e/theme-independence.test.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

775 lines
No EOL
31 KiB
JavaScript

/**
* Theme Independence Test Suite
*
* Tests that the HVAC Community Events plugin works independently of themes:
* - Layout consistency across different themes
* - Asset loading without theme dependencies
* - Template fallback mechanisms
* - CSS isolation verification
* - No hardcoded theme-specific code
* - Responsive design consistency
*
* @package HVAC_Community_Events
* @version 3.0.0
* @created 2025-08-20
*/
const { test, expect, authHelpers } = require('../helpers/auth-fixtures');
const path = require('path');
// Test configuration
const BASE_URL = process.env.UPSKILL_STAGING_URL || 'https://upskill-staging.measurequick.com';
const TEST_TIMEOUT = 90000;
// Theme-independent pages to test
const PLUGIN_PAGES = [
'/trainer/dashboard/',
'/trainer/profile/',
'/trainer/events/',
'/trainer/events/create/',
'/trainer/certificate-reports/',
'/master-trainer/master-dashboard/',
'/find-trainer/'
];
// Expected core layout elements (theme-independent)
const CORE_ELEMENTS = {
navigation: ['.hvac-trainer-nav', '.hvac-nav-menu', 'nav[class*="hvac"]'],
content: ['.hvac-page-wrapper', '.hvac-content', 'main[class*="hvac"]'],
forms: ['.hvac-form-wrapper', 'form[class*="hvac"]'],
buttons: ['.hvac-button', 'button[class*="hvac"]'],
headers: ['.hvac-page-header', '.hvac-dashboard-header']
};
// CSS properties that should be consistent regardless of theme
const LAYOUT_PROPERTIES = [
'display',
'position',
'width',
'max-width',
'padding',
'margin',
'flex-direction',
'grid-template-columns'
];
// Helper functions
async function loginAsTrainer(page) {
await authHelpers.loginAs(page, 'trainer');
}
async function getCurrentTheme(page) {
return await page.evaluate(() => {
// Try to determine active theme from various indicators
const body = document.body;
const bodyClasses = Array.from(body.classList);
// Check for theme-specific body classes
const themeClasses = bodyClasses.filter(cls =>
cls.includes('theme-') ||
cls.includes('astra') ||
cls.includes('twentytwenty') ||
cls.includes('genesis') ||
cls.includes('divi') ||
cls.includes('avada')
);
// Check for theme-specific stylesheets
const stylesheets = Array.from(document.querySelectorAll('link[rel="stylesheet"]'))
.map(link => link.href)
.filter(href => href.includes('themes/'));
const themeFromStylesheet = stylesheets.length > 0 ?
stylesheets[0].match(/themes\/([^\/]+)/)?.[1] : 'unknown';
return {
bodyClasses: themeClasses,
detectedTheme: themeFromStylesheet,
totalStylesheets: stylesheets.length
};
});
}
async function checkElementConsistency(page, selectors) {
const elements = [];
for (const selector of selectors) {
const element = await page.$(selector);
if (element) {
const styles = await element.evaluate((el, properties) => {
const computed = window.getComputedStyle(el);
const result = {};
properties.forEach(prop => {
result[prop] = computed.getPropertyValue(prop);
});
return result;
}, LAYOUT_PROPERTIES);
const boundingBox = await element.boundingBox();
elements.push({
selector,
styles,
boundingBox,
isVisible: await element.isVisible()
});
}
}
return elements;
}
async function analyzePluginAssets(page) {
return await page.evaluate(() => {
const assets = {
hvacCss: [],
hvacJs: [],
themeCss: [],
themeJs: [],
conflicts: []
};
// Analyze CSS files
Array.from(document.querySelectorAll('link[rel="stylesheet"]')).forEach(link => {
if (link.href.includes('hvac') || link.href.includes('community-events')) {
assets.hvacCss.push(link.href);
} else if (link.href.includes('themes/')) {
assets.themeCss.push(link.href);
}
});
// Analyze JS files
Array.from(document.querySelectorAll('script[src]')).forEach(script => {
if (script.src.includes('hvac') || script.src.includes('community-events')) {
assets.hvacJs.push(script.src);
} else if (script.src.includes('themes/')) {
assets.themeJs.push(script.src);
}
});
// Check for CSS conflicts
const hvacElements = document.querySelectorAll('[class*="hvac"]');
hvacElements.forEach(el => {
const computed = window.getComputedStyle(el);
// Check if theme styles are overriding plugin styles
const backgroundImage = computed.backgroundImage;
const fontFamily = computed.fontFamily;
if (backgroundImage !== 'none' && !backgroundImage.includes('hvac')) {
assets.conflicts.push({
element: el.className,
property: 'background-image',
value: backgroundImage
});
}
});
return assets;
});
}
async function checkResponsiveConsistency(page, breakpoints = [1200, 768, 480]) {
const results = [];
for (const breakpoint of breakpoints) {
await page.setViewportSize({ width: breakpoint, height: 720 });
await page.waitForTimeout(1000);
// Check navigation visibility and behavior
const navState = await page.evaluate(() => {
const nav = document.querySelector('.hvac-trainer-nav, .hvac-nav-menu');
const hamburger = document.querySelector('.hvac-menu-toggle, .menu-toggle');
return {
navVisible: nav ? window.getComputedStyle(nav).display !== 'none' : false,
hamburgerVisible: hamburger ? window.getComputedStyle(hamburger).display !== 'none' : false,
navPosition: nav ? window.getComputedStyle(nav).position : 'none'
};
});
// Check content layout
const contentState = await page.evaluate(() => {
const container = document.querySelector('.hvac-page-wrapper, .hvac-content, main');
const sidebar = document.querySelector('.sidebar, .widget-area');
return {
containerWidth: container ? container.offsetWidth : 0,
hasFlexLayout: container ? window.getComputedStyle(container).display.includes('flex') : false,
sidebarVisible: sidebar ? window.getComputedStyle(sidebar).display !== 'none' : false
};
});
results.push({
breakpoint,
navigation: navState,
content: contentState
});
}
return results;
}
async function takeThemeScreenshot(page, name, theme) {
const screenshotDir = path.join(__dirname, '../../screenshots/theme-independence');
await require('fs').promises.mkdir(screenshotDir, { recursive: true });
await page.screenshot({
path: path.join(screenshotDir, `${name}-${theme}-${Date.now()}.png`),
fullPage: true
});
}
test.describe('Theme Independence Tests', () => {
test.setTimeout(TEST_TIMEOUT);
test.beforeEach(async ({ page }) => {
await page.setViewportSize({ width: 1280, height: 720 });
// Login before each test to ensure authenticated access
await authHelpers.loginAs(page, 'trainer');
});
test.describe('Layout Consistency Tests', () => {
test('should maintain consistent layout across plugin pages', async ({ page }) => {
const currentTheme = await getCurrentTheme(page);
console.log('Current theme detected:', currentTheme);
const pageLayouts = {};
// Test each plugin page
for (const pagePath of PLUGIN_PAGES) {
try {
await page.goto(`${BASE_URL}${pagePath}`, { timeout: 15000 });
await page.waitForLoadState('domcontentloaded');
// Check for core plugin elements
const elements = await checkElementConsistency(page, [
'.hvac-page-wrapper',
'.hvac-content',
'.hvac-nav-menu',
'main'
]);
pageLayouts[pagePath] = elements;
// Verify page has plugin content wrapper
const hasWrapper = await page.locator('.hvac-page-wrapper, .hvac-content, main').count() > 0;
expect(hasWrapper).toBeTruthy();
console.log(`Layout check for ${pagePath}: OK`);
} catch (error) {
console.log(`Layout check for ${pagePath} failed:`, error.message);
}
}
// Verify layout consistency
const wrapperElements = Object.values(pageLayouts)
.map(layout => layout.find(el => el.selector === '.hvac-page-wrapper'))
.filter(Boolean);
if (wrapperElements.length > 1) {
const firstLayout = wrapperElements[0];
const inconsistencies = wrapperElements.slice(1).filter(layout => {
return layout.styles.display !== firstLayout.styles.display ||
layout.styles.maxWidth !== firstLayout.styles.maxWidth;
});
expect(inconsistencies.length).toBeLessThan(wrapperElements.length / 2);
}
await takeThemeScreenshot(page, 'layout-consistency', currentTheme.detectedTheme);
});
test('should use theme-independent container widths', async ({ page }) => {
const containerSpecs = [];
for (const pagePath of PLUGIN_PAGES.slice(0, 3)) { // Test subset for speed
await page.goto(`${BASE_URL}${pagePath}`);
const containerInfo = await page.evaluate(() => {
const containers = document.querySelectorAll('.hvac-page-wrapper, .hvac-content, .container');
return Array.from(containers).map(container => ({
className: container.className,
offsetWidth: container.offsetWidth,
clientWidth: container.clientWidth,
computedWidth: window.getComputedStyle(container).width,
computedMaxWidth: window.getComputedStyle(container).maxWidth
}));
});
containerSpecs.push({
page: pagePath,
containers: containerInfo
});
}
// Check for consistent max-width usage
const maxWidths = containerSpecs.flatMap(spec =>
spec.containers.map(c => c.computedMaxWidth)
).filter(w => w !== 'none');
if (maxWidths.length > 0) {
const uniqueMaxWidths = [...new Set(maxWidths)];
console.log('Container max-widths found:', uniqueMaxWidths);
// Should use consistent max-width values
expect(uniqueMaxWidths.length).toBeLessThan(4); // Allow some variation
}
});
test('should handle missing theme support gracefully', async ({ page }) => {
await page.goto(`${BASE_URL}/trainer/dashboard/`);
// Simulate theme support removal
await page.evaluate(() => {
// Remove theme support indicators
const themeElements = document.querySelectorAll('[class*="theme-"], [class*="astra"]');
themeElements.forEach(el => {
el.classList.forEach(cls => {
if (cls.includes('theme-') || cls.includes('astra')) {
el.classList.remove(cls);
}
});
});
// Remove theme stylesheets temporarily
const themeStyles = document.querySelectorAll('link[href*="themes/"]');
themeStyles.forEach(link => {
link.disabled = true;
});
});
await page.waitForTimeout(1000);
// Check if plugin still functions
const pluginFunctional = await page.evaluate(() => {
const navMenu = document.querySelector('.hvac-nav-menu, .hvac-trainer-nav');
const content = document.querySelector('.hvac-content, .hvac-page-wrapper');
return {
hasNavigation: !!navMenu,
hasContent: !!content,
contentVisible: content ? window.getComputedStyle(content).display !== 'none' : false
};
});
expect(pluginFunctional.hasContent).toBeTruthy();
console.log('Plugin graceful degradation:', pluginFunctional);
});
});
test.describe('Asset Independence Tests', () => {
test('should load plugin assets independently', async ({ page }) => {
await page.goto(`${BASE_URL}/trainer/dashboard/`);
const assetAnalysis = await analyzePluginAssets(page);
// Should have plugin-specific assets
expect(assetAnalysis.hvacCss.length).toBeGreaterThan(0);
console.log('HVAC CSS files:', assetAnalysis.hvacCss.length);
console.log('Theme CSS files:', assetAnalysis.themeCss.length);
// Check for asset conflicts
if (assetAnalysis.conflicts.length > 0) {
console.log('Theme conflicts detected:', assetAnalysis.conflicts);
// Should have minimal conflicts
expect(assetAnalysis.conflicts.length).toBeLessThan(5);
}
// Verify plugin assets load before theme assets
const loadOrder = await page.evaluate(() => {
const allStyles = Array.from(document.querySelectorAll('link[rel="stylesheet"]'));
const hvacIndex = allStyles.findIndex(link =>
link.href.includes('hvac') || link.href.includes('community-events')
);
const themeIndex = allStyles.findIndex(link => link.href.includes('themes/'));
return { hvacIndex, themeIndex, total: allStyles.length };
});
console.log('Asset load order:', loadOrder);
});
test('should not depend on theme-specific CSS classes', async ({ page }) => {
await page.goto(`${BASE_URL}/trainer/dashboard/`);
// Check for theme-specific dependencies
const themeDependencies = await page.evaluate(() => {
const hvacElements = document.querySelectorAll('[class*="hvac"]');
const dependencies = [];
hvacElements.forEach(el => {
const classes = Array.from(el.classList);
const themeClasses = classes.filter(cls =>
cls.includes('astra') ||
cls.includes('twentytwenty') ||
cls.includes('genesis') ||
cls.includes('theme-')
);
if (themeClasses.length > 0) {
dependencies.push({
element: el.tagName + '.' + classes.join('.'),
themeClasses
});
}
});
return dependencies;
});
// Should have minimal theme dependencies
expect(themeDependencies.length).toBeLessThan(10);
console.log('Theme class dependencies:', themeDependencies.length);
if (themeDependencies.length > 0) {
console.log('Dependencies found:', themeDependencies.slice(0, 3));
}
});
test('should handle theme stylesheet conflicts', async ({ page }) => {
await page.goto(`${BASE_URL}/trainer/dashboard/`);
// Test critical plugin elements for style conflicts
const criticalElements = [
'.hvac-nav-menu',
'.hvac-button',
'.hvac-form-wrapper',
'.hvac-page-header'
];
const styleConflicts = [];
for (const selector of criticalElements) {
const element = await page.$(selector);
if (element) {
const styles = await element.evaluate(el => {
const computed = window.getComputedStyle(el);
return {
display: computed.display,
position: computed.position,
zIndex: computed.zIndex,
fontSize: computed.fontSize,
color: computed.color
};
});
// Check for unexpected style values that might indicate conflicts
if (styles.display === 'none' && selector.includes('nav')) {
styleConflicts.push({ selector, issue: 'Navigation hidden' });
}
if (styles.zIndex === 'auto' && selector.includes('nav')) {
styleConflicts.push({ selector, issue: 'No z-index set' });
}
}
}
console.log('Style conflicts detected:', styleConflicts.length);
expect(styleConflicts.length).toBeLessThan(3);
});
});
test.describe('Responsive Design Independence Tests', () => {
test('should maintain responsive behavior regardless of theme', async ({ page }) => {
await page.goto(`${BASE_URL}/trainer/dashboard/`);
const responsiveResults = await checkResponsiveConsistency(page);
// Verify navigation adapts correctly at all breakpoints
responsiveResults.forEach(result => {
console.log(`Breakpoint ${result.breakpoint}px:`, {
nav: result.navigation.navVisible,
hamburger: result.navigation.hamburgerVisible,
contentWidth: result.content.containerWidth
});
// At mobile breakpoints, either nav should be hidden or hamburger should be visible
if (result.breakpoint <= 768) {
const mobileNavHandled = !result.navigation.navVisible || result.navigation.hamburgerVisible;
expect(mobileNavHandled).toBeTruthy();
}
// Content should never be zero width
expect(result.content.containerWidth).toBeGreaterThan(0);
});
await takeThemeScreenshot(page, 'responsive-design', 'current');
});
test('should handle mobile navigation independently', async ({ page }) => {
await page.setViewportSize({ width: 375, height: 667 }); // Mobile viewport
await page.goto(`${BASE_URL}/trainer/dashboard/`);
// Check for mobile navigation
const mobileNav = await page.evaluate(() => {
const hamburger = document.querySelector('.hvac-menu-toggle, .menu-toggle, .hamburger');
const navMenu = document.querySelector('.hvac-nav-menu, .hvac-trainer-nav');
return {
hasHamburger: !!hamburger,
hamburgerVisible: hamburger ? window.getComputedStyle(hamburger).display !== 'none' : false,
navHidden: navMenu ? window.getComputedStyle(navMenu).display === 'none' : true
};
});
console.log('Mobile navigation state:', mobileNav);
// Should have mobile navigation solution
const hasMobileNav = mobileNav.hasHamburger || !mobileNav.navHidden;
expect(hasMobileNav).toBeTruthy();
// If hamburger exists, test functionality
if (mobileNav.hasHamburger) {
const hamburger = await page.$('.hvac-menu-toggle, .menu-toggle, .hamburger');
if (hamburger) {
await hamburger.click();
await page.waitForTimeout(500);
const menuOpened = await page.evaluate(() => {
const menu = document.querySelector('.hvac-nav-menu');
return menu ? window.getComputedStyle(menu).display !== 'none' : false;
});
expect(menuOpened).toBeTruthy();
}
}
});
test('should maintain form layout on all devices', async ({ page }) => {
const breakpoints = [1200, 768, 480];
const formResults = [];
for (const breakpoint of breakpoints) {
await page.setViewportSize({ width: breakpoint, height: 720 });
await page.goto(`${BASE_URL}/trainer/profile/edit/`);
await page.waitForTimeout(1000);
const formLayout = await page.evaluate(() => {
const form = document.querySelector('form');
const inputs = document.querySelectorAll('input, select, textarea');
if (!form) return null;
return {
formWidth: form.offsetWidth,
inputCount: inputs.length,
stackedInputs: Array.from(inputs).filter(input => {
const rect = input.getBoundingClientRect();
return rect.width > form.offsetWidth * 0.8; // Nearly full width = stacked
}).length
};
});
if (formLayout) {
formResults.push({
breakpoint,
...formLayout
});
// Form should be usable at all breakpoints
expect(formLayout.formWidth).toBeGreaterThan(200);
// At mobile breakpoints, inputs should be mostly stacked
if (breakpoint <= 768) {
const stackedRatio = formLayout.stackedInputs / formLayout.inputCount;
expect(stackedRatio).toBeGreaterThan(0.5);
}
}
}
console.log('Form responsive results:', formResults);
});
});
test.describe('Template Fallback Tests', () => {
test('should provide default templates when theme lacks support', async ({ page }) => {
// Test pages that might not have theme support
const fallbackPages = [
'/trainer/certificate-reports/',
'/trainer/events/create/',
'/trainer/organizer/manage/'
];
for (const pagePath of fallbackPages) {
await page.goto(`${BASE_URL}${pagePath}`);
// Check if page loads with plugin template
const hasTemplate = await page.evaluate(() => {
return !!(
document.querySelector('.hvac-page-wrapper') ||
document.querySelector('.hvac-template') ||
document.body.className.includes('hvac-page')
);
});
expect(hasTemplate).toBeTruthy();
console.log(`Template fallback for ${pagePath}: OK`);
// Check for WordPress integration
const hasWordPressElements = await page.evaluate(() => {
return !!(
document.querySelector('header, .site-header') &&
document.querySelector('footer, .site-footer')
);
});
expect(hasWordPressElements).toBeTruthy();
}
});
test('should handle missing theme functions gracefully', async ({ page }) => {
await page.goto(`${BASE_URL}/trainer/dashboard/`);
// Simulate missing theme functions
const errorCount = await page.evaluate(() => {
let errors = 0;
// Override console.error to catch theme-related errors
const originalError = console.error;
console.error = function(...args) {
const message = args.join(' ');
if (message.includes('theme') || message.includes('undefined function')) {
errors++;
}
originalError.apply(console, args);
};
// Try to trigger theme function calls
try {
// Simulate missing theme support checks
if (typeof window.themeSupports !== 'undefined') {
window.themeSupports.customLogo();
}
} catch (e) {
if (e.message.includes('theme')) {
errors++;
}
}
return errors;
});
// Should handle missing theme functions without errors
expect(errorCount).toBeLessThan(3);
console.log('Theme function errors:', errorCount);
});
test('should maintain functionality with minimal theme support', async ({ page }) => {
// Test core functionality without theme enhancements
const coreFeatures = [
{
name: 'Navigation',
test: async () => {
await page.goto(`${BASE_URL}/trainer/dashboard/`);
return await page.locator('.hvac-nav-menu, nav').count() > 0;
}
},
{
name: 'Forms',
test: async () => {
await page.goto(`${BASE_URL}/trainer/profile/edit/`);
return await page.locator('form').count() > 0;
}
},
{
name: 'Content Display',
test: async () => {
await page.goto(`${BASE_URL}/trainer/events/`);
return await page.locator('.hvac-content, main').count() > 0;
}
}
];
for (const feature of coreFeatures) {
const works = await feature.test();
expect(works).toBeTruthy();
console.log(`Core feature '${feature.name}' working:`, works);
}
});
});
test.describe('CSS Isolation Tests', () => {
test('should not interfere with theme styles', async ({ page }) => {
await page.goto(`${BASE_URL}/`); // Homepage - theme territory
// Check if plugin CSS affects theme elements
const themeInterference = await page.evaluate(() => {
const themeElements = document.querySelectorAll('header, footer, .site-main, .content-area');
const issues = [];
themeElements.forEach(el => {
const computed = window.getComputedStyle(el);
// Check for unexpected plugin-style properties
if (computed.backgroundColor && computed.backgroundColor.includes('hvac')) {
issues.push('HVAC background color on theme element');
}
if (computed.fontFamily && computed.fontFamily.includes('HVAC')) {
issues.push('HVAC font on theme element');
}
});
return issues;
});
expect(themeInterference.length).toBeLessThan(2);
console.log('Theme interference issues:', themeInterference.length);
});
test('should scope plugin styles correctly', async ({ page }) => {
await page.goto(`${BASE_URL}/trainer/dashboard/`);
// Check CSS specificity and scoping
const scopingAnalysis = await page.evaluate(() => {
const hvacElements = document.querySelectorAll('[class*="hvac"]');
const properlyScoped = [];
const unscoped = [];
hvacElements.forEach(el => {
const classes = Array.from(el.classList);
const hasHvacClass = classes.some(cls => cls.startsWith('hvac-'));
if (hasHvacClass) {
properlyScoped.push(el.tagName);
} else {
unscoped.push(el.tagName);
}
});
return {
properlyScoped: properlyScoped.length,
unscoped: unscoped.length,
scopingRatio: properlyScoped.length / (properlyScoped.length + unscoped.length)
};
});
// Should have good scoping ratio
expect(scopingAnalysis.scopingRatio).toBeGreaterThan(0.7);
console.log('CSS scoping analysis:', scopingAnalysis);
});
});
});
// Export theme independence test configuration
module.exports = {
testDir: __dirname,
timeout: TEST_TIMEOUT,
retries: 1,
workers: 1, // Sequential for consistent testing
use: {
baseURL: BASE_URL,
screenshot: 'only-on-failure',
video: 'retain-on-failure',
trace: 'on-first-retry'
}
};