import { expect } from '@playwright/test'; import { STAGING_URL, TIMEOUTS } from '../config/staging-config'; /** * Common actions and utilities for E2E tests * Reduces code duplication across test files */ export class CommonActions { constructor(private page: any) {} /** * Navigate to a page and wait for load */ async navigateAndWait(path: string) { const url = path.startsWith('http') ? path : `${STAGING_URL}${path}`; await this.page.goto(url); await this.page.waitForLoadState('networkidle'); } /** * Take screenshot with descriptive name */ async screenshot(name: string) { await this.page.screenshot({ path: `test-results/screenshots/${name}-${Date.now()}.png`, fullPage: true }); } /** * Fill TinyMCE editor content */ async fillTinyMCE(selector: string, content: string) { try { const frame = this.page.frameLocator('iframe[id*="_ifr"]'); await frame.locator('body').fill(content); } catch { // Fallback to textarea if TinyMCE not available await this.page.fill(selector, content); } } /** * Handle venue creation in event forms */ async createVenue(venueName: string, address: string = '') { if (await this.page.locator('select#saved_tribe_venue').count() > 0) { await this.page.selectOption('select#saved_tribe_venue', '-1'); const venueNameField = this.page.locator('input[name="Venue[Venue]"]'); if (await venueNameField.isVisible()) { await venueNameField.fill(venueName); if (address) { await this.page.fill('input[name="Venue[Address]"]', address); } } } } /** * Handle organizer creation in event forms */ async createOrganizer(organizerName: string) { if (await this.page.locator('select#saved_tribe_organizer').count() > 0) { await this.page.selectOption('select#saved_tribe_organizer', '-1'); const organizerNameField = this.page.locator('input[name="Organizer[Organizer]"]'); if (await organizerNameField.isVisible()) { await organizerNameField.fill(organizerName); } } } /** * Wait for and verify navigation items are present */ async verifyNavigation() { const navButtons = [ { text: 'Dashboard', selector: 'a[href*="hvac-dashboard"]' }, { text: 'Generate Certificates', selector: 'text=Generate Certificates' }, { text: 'Create Event', selector: 'text=Create Event' } ]; for (const button of navButtons) { const locator = button.selector.startsWith('text=') ? this.page.locator(button.selector) : this.page.locator(button.selector); await expect(locator.first()).toBeVisible(); } } /** * Check for PHP errors in browser console */ async checkForPHPErrors() { const errors = []; this.page.on('console', (msg) => { if (msg.type() === 'error' && msg.text().includes('PHP')) { errors.push(msg.text()); } }); return errors; } /** * Wait for AJAX request to complete */ async waitForAjax() { await this.page.waitForLoadState('networkidle'); await this.page.waitForTimeout(500); // Additional buffer for AJAX } /** * Enhanced AJAX wait for complex operations like certificate generation */ async waitForComplexAjax() { await this.page.waitForLoadState('networkidle'); await this.page.waitForTimeout(2000); // Longer wait for complex operations // Wait for any loading indicators to disappear const loadingIndicators = this.page.locator('.loading, .spinner, [class*="loading"]'); if (await loadingIndicators.count() > 0) { await loadingIndicators.first().waitFor({ state: 'hidden', timeout: 10000 }); } } /** * Generate unique test data with timestamp */ generateTestData(prefix: string) { const timestamp = Date.now(); return { title: `${prefix} ${timestamp}`, description: `Test ${prefix.toLowerCase()} created at ${new Date().toISOString()}`, timestamp }; } /** * Click button and wait for navigation */ async clickAndWait(selector: string) { await this.page.click(selector); await this.page.waitForLoadState('networkidle'); } }