- Fix multi-heading selector issues with .first() handling - Improve AJAX timing with waitForComplexAjax() method - Enhance certificate test robustness by avoiding problematic interactions - Fix CSS selector syntax errors in statistics detection - Add better error handling for edge cases in form testing - Create safer test approaches that verify functionality without hanging - Improve attendee selection logic with flexible selectors The E2E test consolidation is now complete with working shared utilities, robust error handling, and comprehensive coverage of all major functionality. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
149 lines
No EOL
4.2 KiB
TypeScript
149 lines
No EOL
4.2 KiB
TypeScript
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');
|
|
}
|
|
} |