Files
blackcanyontickets/test-ticket-purchasing-comprehensive.cjs
dzinesco aa81eb5adb feat: add advanced analytics and territory management system
- Add comprehensive analytics components with export functionality
- Implement territory management with manager performance tracking
- Add seatmap components for venue layout management
- Create customer management features with modal interface
- Add advanced hooks for dashboard flags and territory data
- Implement seat selection and venue management utilities
- Add type definitions for ticketing and seatmap systems

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-26 09:25:10 -06:00

844 lines
29 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// @ts-check
const { test, expect, devices } = require('@playwright/test');
/**
* Comprehensive Ticket Purchasing Test Suite
*
* This test suite covers:
* - End-to-end ticket purchasing flow
* - Multiple ticket types and quantities
* - Mobile responsive design
* - Form validation and error handling
* - Presale code functionality
* - Inventory management and reservations
* - Accessibility compliance
* - Visual regression testing
*/
// Page Object Models
class TicketPurchasePage {
constructor(page) {
this.page = page;
// Main containers
this.eventContainer = page.locator('[data-test="event-container"], .max-w-5xl');
this.ticketCheckout = page.locator('[data-test="ticket-checkout"]');
// Event details
this.eventTitle = page.locator('h1').first();
this.eventDate = page.locator('[data-test="event-date"]').or(page.getByText(/Event Date/));
this.eventVenue = page.locator('[data-test="venue"]').or(page.getByText(/Venue/));
this.eventDescription = page.locator('[data-test="event-description"]');
// Presale code section
this.presaleCodeSection = page.locator('[data-test="presale-code-section"]');
this.presaleCodeInput = page.locator('input#presale-code, input[placeholder*="presale"]');
this.presaleCodeButton = page.getByRole('button', { name: /apply code/i });
this.presaleCodeError = page.locator('[data-test="presale-error"]');
this.presaleCodeSuccess = page.locator('[data-test="presale-success"]');
// Ticket selection
this.ticketTypes = page.locator('[data-test="ticket-type"]').or(page.locator('.border-2.rounded-2xl'));
this.quantityDecrease = page.locator('button:has-text("")');
this.quantityIncrease = page.locator('button:has-text("+")');
this.quantityDisplay = page.locator('[data-test="quantity-display"]');
// Order summary
this.orderSummary = page.locator('[data-test="order-summary"]').or(page.getByText(/Order Summary/));
this.subtotalAmount = page.locator('[data-test="subtotal"]');
this.platformFeeAmount = page.locator('[data-test="platform-fee"]');
this.totalAmount = page.locator('[data-test="total"]');
// Customer information form
this.emailInput = page.locator('input#email, input[type="email"]');
this.nameInput = page.locator('input#name, input[placeholder*="name"]');
this.purchaseButton = page.getByRole('button', { name: /complete purchase|purchase|checkout/i });
// Timer and reservations
this.reservationTimer = page.locator('[data-test="reservation-timer"]');
this.timeRemaining = page.locator('[data-test="time-remaining"]');
// Loading and error states
this.loadingIndicator = page.getByText(/loading/i);
this.errorMessage = page.locator('[data-test="error-message"], .text-red-500');
this.successMessage = page.locator('[data-test="success-message"], .text-green-500');
}
// Navigation methods
async goto(eventSlug) {
await this.page.goto(`/e/${eventSlug}`);
await this.page.waitForLoadState('networkidle');
}
// Presale code methods
async enterPresaleCode(code) {
await this.presaleCodeInput.fill(code);
await this.presaleCodeButton.click();
await this.page.waitForTimeout(1000); // Wait for validation
}
async waitForPresaleValidation() {
await expect(this.presaleCodeSuccess.or(this.presaleCodeError)).toBeVisible({ timeout: 5000 });
}
// Ticket selection methods
async selectTicketQuantity(ticketTypeIndex, quantity) {
const ticketType = this.ticketTypes.nth(ticketTypeIndex);
const increaseButton = ticketType.locator('button:has-text("+")');
const currentQuantity = await this.getCurrentQuantity(ticketTypeIndex);
const difference = quantity - currentQuantity;
if (difference > 0) {
for (let i = 0; i < difference; i++) {
await increaseButton.click();
await this.page.waitForTimeout(500); // Wait for reservation
}
} else if (difference < 0) {
const decreaseButton = ticketType.locator('button:has-text("")');
for (let i = 0; i < Math.abs(difference); i++) {
await decreaseButton.click();
await this.page.waitForTimeout(500);
}
}
}
async getCurrentQuantity(ticketTypeIndex) {
const ticketType = this.ticketTypes.nth(ticketTypeIndex);
const quantityText = await ticketType.locator('.text-lg.font-semibold').textContent();
return parseInt(quantityText || '0');
}
async getTicketTypePrice(ticketTypeIndex) {
const ticketType = this.ticketTypes.nth(ticketTypeIndex);
const priceText = await ticketType.locator('.text-xl, .text-2xl').filter({ hasText: '$' }).textContent();
return parseFloat((priceText || '$0').replace('$', ''));
}
async getTicketTypeName(ticketTypeIndex) {
const ticketType = this.ticketTypes.nth(ticketTypeIndex);
return await ticketType.locator('h3').textContent();
}
// Form submission methods
async fillCustomerInfo(email, name) {
await this.emailInput.fill(email);
await this.nameInput.fill(name);
}
async submitPurchase() {
await this.purchaseButton.click();
}
// Validation methods
async waitForOrderSummary() {
await expect(this.orderSummary).toBeVisible({ timeout: 5000 });
}
async getTotalPrice() {
const totalText = await this.totalAmount.textContent();
return parseFloat((totalText || '$0').replace('$', ''));
}
async waitForReservationTimer() {
await expect(this.reservationTimer).toBeVisible({ timeout: 5000 });
}
// Screenshot helpers
async captureEventPage(filename) {
await this.page.screenshot({
path: `screenshots/${filename}`,
fullPage: true
});
}
async captureTicketSelection(filename) {
await this.ticketCheckout.screenshot({
path: `screenshots/${filename}`
});
}
}
// Test data setup
const testEvents = {
sample: {
slug: 'sample-event',
title: 'Sample Event',
venue: 'Test Venue',
ticketTypes: [
{ name: 'General Admission', price: 25.00 },
{ name: 'VIP', price: 75.00 }
]
}
};
const testCustomers = {
valid: {
email: 'test@blackcanyontickets.com',
name: 'Test Customer'
},
invalid: {
email: 'invalid-email',
name: ''
}
};
// Test Configuration
const viewports = {
desktop: { width: 1920, height: 1080 },
tablet: { width: 768, height: 1024 },
mobile: { width: 375, height: 667 }
};
// Utility functions
async function createScreenshotsDirectory() {
const fs = require('fs');
if (!fs.existsSync('screenshots')) {
fs.mkdirSync('screenshots', { recursive: true });
}
}
// Main Test Suite
test.describe('Ticket Purchasing - Comprehensive Tests', () => {
test.beforeEach(async ({ page }) => {
await createScreenshotsDirectory();
// Set up consistent test environment
await page.setViewportSize(viewports.desktop);
// Mock API responses for testing
await page.route('**/api/inventory/availability/*', async route => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
success: true,
availability: {
available: 50,
total: 100,
reserved: 5,
sold: 45,
is_available: true
}
})
});
});
});
test.describe('Basic Ticket Purchasing Flow', () => {
test('should display event information correctly', async ({ page }) => {
const ticketPage = new TicketPurchasePage(page);
// Navigate to a test event (we'll use a mock or create one)
await page.goto('/e/sample-event');
// Take initial screenshot
await ticketPage.captureEventPage('event-page-initial.png');
// Verify event details are displayed
await expect(ticketPage.eventTitle).toBeVisible();
await expect(ticketPage.eventDate).toBeVisible();
// Verify ticket checkout section is present
await expect(ticketPage.ticketTypes.first()).toBeVisible();
console.log('✓ Event information displayed correctly');
});
test('should handle ticket selection and quantity changes', async ({ page }) => {
const ticketPage = new TicketPurchasePage(page);
await page.goto('/e/sample-event');
// Wait for ticket types to load
await expect(ticketPage.ticketTypes.first()).toBeVisible();
// Select 2 tickets of the first type
await ticketPage.selectTicketQuantity(0, 2);
// Verify quantity is updated
const quantity = await ticketPage.getCurrentQuantity(0);
expect(quantity).toBe(2);
// Take screenshot of ticket selection
await ticketPage.captureTicketSelection('ticket-selection-2-tickets.png');
// Verify order summary appears
await ticketPage.waitForOrderSummary();
// Verify reservation timer appears
await ticketPage.waitForReservationTimer();
console.log('✓ Ticket selection and quantity changes work correctly');
});
test('should calculate pricing correctly', async ({ page }) => {
const ticketPage = new TicketPurchasePage(page);
await page.goto('/e/sample-event');
await expect(ticketPage.ticketTypes.first()).toBeVisible();
// Get the price of the first ticket type
const ticketPrice = await ticketPage.getTicketTypePrice(0);
// Select 3 tickets
await ticketPage.selectTicketQuantity(0, 3);
await ticketPage.waitForOrderSummary();
// Verify total calculation (should include platform fees)
const totalPrice = await ticketPage.getTotalPrice();
expect(totalPrice).toBeGreaterThan(ticketPrice * 3);
console.log(`✓ Pricing calculated correctly: ${ticketPrice} × 3 = ${totalPrice} (including fees)`);
});
test('should complete purchase flow with valid customer information', async ({ page }) => {
const ticketPage = new TicketPurchasePage(page);
await page.goto('/e/sample-event');
// Select tickets
await expect(ticketPage.ticketTypes.first()).toBeVisible();
await ticketPage.selectTicketQuantity(0, 1);
await ticketPage.waitForOrderSummary();
// Fill customer information
await ticketPage.fillCustomerInfo(testCustomers.valid.email, testCustomers.valid.name);
// Take screenshot before purchase
await ticketPage.captureTicketSelection('pre-purchase-form-filled.png');
// Mock the purchase attempt API
await page.route('**/api/inventory/purchase-attempt', async route => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
success: true,
purchase_attempt: {
id: 'pa_test123',
total_amount: 25.00,
status: 'pending'
}
})
});
});
// Submit purchase
await ticketPage.submitPurchase();
// Verify success or expected behavior
// Note: The current implementation shows an alert, so we'll handle that
await page.on('dialog', dialog => dialog.accept());
console.log('✓ Purchase flow completed successfully');
});
});
test.describe('Multiple Ticket Types and Quantities', () => {
test('should handle multiple different ticket types', async ({ page }) => {
const ticketPage = new TicketPurchasePage(page);
await page.goto('/e/sample-event');
await expect(ticketPage.ticketTypes.first()).toBeVisible();
// Select tickets from multiple types
await ticketPage.selectTicketQuantity(0, 2); // General admission
// If there's a second ticket type, select from it too
const ticketCount = await ticketPage.ticketTypes.count();
if (ticketCount > 1) {
await ticketPage.selectTicketQuantity(1, 1); // VIP
}
await ticketPage.waitForOrderSummary();
// Take screenshot of mixed ticket selection
await ticketPage.captureTicketSelection('mixed-ticket-types-selection.png');
console.log('✓ Multiple ticket types handled correctly');
});
test('should enforce maximum quantity limits', async ({ page }) => {
const ticketPage = new TicketPurchasePage(page);
await page.goto('/e/sample-event');
await expect(ticketPage.ticketTypes.first()).toBeVisible();
// Try to select many tickets (should be limited by availability)
const increaseButton = ticketPage.ticketTypes.first().locator('button:has-text("+")');
// Click increase button multiple times
for (let i = 0; i < 60; i++) {
await increaseButton.click();
await page.waitForTimeout(100);
// Check if button becomes disabled
if (await increaseButton.isDisabled()) {
break;
}
}
const finalQuantity = await ticketPage.getCurrentQuantity(0);
expect(finalQuantity).toBeLessThanOrEqual(50); // Based on our mock availability
console.log(`✓ Quantity limits enforced: max ${finalQuantity} tickets`);
});
});
test.describe('Mobile Responsive Design', () => {
test('should work correctly on mobile devices', async ({ page }) => {
await page.setViewportSize(viewports.mobile);
const ticketPage = new TicketPurchasePage(page);
await page.goto('/e/sample-event');
// Take mobile screenshot
await ticketPage.captureEventPage('mobile-event-page.png');
// Verify elements are visible and accessible on mobile
await expect(ticketPage.eventTitle).toBeVisible();
await expect(ticketPage.ticketTypes.first()).toBeVisible();
// Test ticket selection on mobile
await ticketPage.selectTicketQuantity(0, 1);
await ticketPage.waitForOrderSummary();
// Take mobile ticket selection screenshot
await ticketPage.captureTicketSelection('mobile-ticket-selection.png');
// Test form filling on mobile
await ticketPage.fillCustomerInfo(testCustomers.valid.email, testCustomers.valid.name);
// Verify purchase button is accessible
await expect(ticketPage.purchaseButton).toBeVisible();
console.log('✓ Mobile responsive design works correctly');
});
test('should work correctly on tablet devices', async ({ page }) => {
await page.setViewportSize(viewports.tablet);
const ticketPage = new TicketPurchasePage(page);
await page.goto('/e/sample-event');
await ticketPage.captureEventPage('tablet-event-page.png');
// Test core functionality on tablet
await expect(ticketPage.ticketTypes.first()).toBeVisible();
await ticketPage.selectTicketQuantity(0, 2);
await ticketPage.waitForOrderSummary();
await ticketPage.captureTicketSelection('tablet-ticket-selection.png');
console.log('✓ Tablet responsive design works correctly');
});
});
test.describe('Form Validation and Error Handling', () => {
test('should validate email format', async ({ page }) => {
const ticketPage = new TicketPurchasePage(page);
await page.goto('/e/sample-event');
await expect(ticketPage.ticketTypes.first()).toBeVisible();
await ticketPage.selectTicketQuantity(0, 1);
await ticketPage.waitForOrderSummary();
// Enter invalid email
await ticketPage.fillCustomerInfo(testCustomers.invalid.email, testCustomers.valid.name);
// Try to submit
await ticketPage.submitPurchase();
// Verify HTML5 validation prevents submission
const isValidEmail = await ticketPage.emailInput.evaluate(el => el.validity.valid);
expect(isValidEmail).toBe(false);
console.log('✓ Email validation works correctly');
});
test('should require customer name', async ({ page }) => {
const ticketPage = new TicketPurchasePage(page);
await page.goto('/e/sample-event');
await expect(ticketPage.ticketTypes.first()).toBeVisible();
await ticketPage.selectTicketQuantity(0, 1);
await ticketPage.waitForOrderSummary();
// Enter valid email but empty name
await ticketPage.fillCustomerInfo(testCustomers.valid.email, '');
await ticketPage.submitPurchase();
// Verify required field validation
const isValidName = await ticketPage.nameInput.evaluate(el => el.validity.valid);
expect(isValidName).toBe(false);
console.log('✓ Name requirement validation works correctly');
});
test('should handle sold out tickets gracefully', async ({ page }) => {
// Mock sold out response
await page.route('**/api/inventory/availability/*', async route => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
success: true,
availability: {
available: 0,
total: 100,
reserved: 5,
sold: 95,
is_available: false
}
})
});
});
const ticketPage = new TicketPurchasePage(page);
await page.goto('/e/sample-event');
// Verify sold out state is displayed
await expect(page.getByText(/sold out/i)).toBeVisible({ timeout: 5000 });
// Verify increase button is disabled
const increaseButton = ticketPage.ticketTypes.first().locator('button:has-text("+")');
await expect(increaseButton).toBeDisabled();
await ticketPage.captureTicketSelection('sold-out-state.png');
console.log('✓ Sold out state handled correctly');
});
test('should handle network errors gracefully', async ({ page }) => {
const ticketPage = new TicketPurchasePage(page);
// Mock network failure for availability
await page.route('**/api/inventory/availability/*', async route => {
await route.abort('failed');
});
await page.goto('/e/sample-event');
// Should show loading or error state
await expect(ticketPage.loadingIndicator.or(ticketPage.errorMessage)).toBeVisible({ timeout: 5000 });
console.log('✓ Network errors handled gracefully');
});
});
test.describe('Presale Code Functionality', () => {
test('should show presale code input when required', async ({ page }) => {
// Mock event with presale requirement
await page.route('**/e/presale-event', async route => {
const mockEventPage = `
<!-- Mock event page with presale requirement -->
<!DOCTYPE html>
<html>
<head><title>Presale Event</title></head>
<body>
<h1>Presale Event</h1>
<div id="presale-code-section">
<input id="presale-code" placeholder="Enter your presale code" />
<button>Apply Code</button>
</div>
<div>Presale access required</div>
</body>
</html>
`;
await route.fulfill({
status: 200,
contentType: 'text/html',
body: mockEventPage
});
});
await page.goto('/e/presale-event');
const ticketPage = new TicketPurchasePage(page);
// Verify presale code section is visible
await expect(ticketPage.presaleCodeInput).toBeVisible();
await expect(ticketPage.presaleCodeButton).toBeVisible();
await ticketPage.captureEventPage('presale-code-required.png');
console.log('✓ Presale code input displayed when required');
});
test('should validate presale codes', async ({ page }) => {
await page.goto('/e/presale-event');
const ticketPage = new TicketPurchasePage(page);
// Mock presale validation API
await page.route('**/api/presale/validate', async route => {
const request = await route.request();
const body = await request.postDataJSON();
if (body.code === 'VALID123') {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
success: true,
accessible_ticket_types: [{ id: 'tt1', name: 'Early Bird' }]
})
});
} else {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
success: false,
error: 'Invalid presale code'
})
});
}
});
// Test invalid code
await ticketPage.enterPresaleCode('INVALID');
await ticketPage.waitForPresaleValidation();
await expect(ticketPage.presaleCodeError).toBeVisible();
// Test valid code
await ticketPage.enterPresaleCode('VALID123');
await ticketPage.waitForPresaleValidation();
await expect(ticketPage.presaleCodeSuccess).toBeVisible();
console.log('✓ Presale code validation works correctly');
});
});
test.describe('Inventory Management and Reservations', () => {
test('should create and display reservation timer', async ({ page }) => {
const ticketPage = new TicketPurchasePage(page);
await page.goto('/e/sample-event');
// Mock reservation API
await page.route('**/api/inventory/reserve', async route => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
success: true,
reservation: {
id: 'res_123',
ticket_type_id: 'tt1',
quantity: 1,
expires_at: new Date(Date.now() + 15 * 60 * 1000).toISOString(),
status: 'active'
}
})
});
});
await expect(ticketPage.ticketTypes.first()).toBeVisible();
await ticketPage.selectTicketQuantity(0, 1);
// Verify reservation timer appears
await ticketPage.waitForReservationTimer();
await expect(ticketPage.timeRemaining).toBeVisible();
await ticketPage.captureTicketSelection('reservation-timer-active.png');
console.log('✓ Reservation timer created and displayed correctly');
});
test('should handle reservation failures', async ({ page }) => {
const ticketPage = new TicketPurchasePage(page);
await page.goto('/e/sample-event');
// Mock reservation failure
await page.route('**/api/inventory/reserve', async route => {
await route.fulfill({
status: 409,
contentType: 'application/json',
body: JSON.stringify({
success: false,
error: 'Insufficient tickets available'
})
});
});
await expect(ticketPage.ticketTypes.first()).toBeVisible();
// Handle alert dialog
page.on('dialog', dialog => dialog.accept());
await ticketPage.selectTicketQuantity(0, 1);
// Should show error or handle gracefully
await page.waitForTimeout(2000);
console.log('✓ Reservation failures handled correctly');
});
});
test.describe('Accessibility Testing', () => {
test('should be keyboard navigable', async ({ page }) => {
const ticketPage = new TicketPurchasePage(page);
await page.goto('/e/sample-event');
await expect(ticketPage.ticketTypes.first()).toBeVisible();
// Test keyboard navigation through ticket selection
await page.keyboard.press('Tab');
await page.keyboard.press('Tab');
// Find increase button and activate with keyboard
const increaseButton = ticketPage.ticketTypes.first().locator('button:has-text("+")');
await increaseButton.focus();
await page.keyboard.press('Enter');
// Verify quantity changed
const quantity = await ticketPage.getCurrentQuantity(0);
expect(quantity).toBe(1);
console.log('✓ Keyboard navigation works correctly');
});
test('should have proper ARIA labels and roles', async ({ page }) => {
const ticketPage = new TicketPurchasePage(page);
await page.goto('/e/sample-event');
await expect(ticketPage.ticketTypes.first()).toBeVisible();
// Check for form labels
await expect(ticketPage.emailInput).toHaveAttribute('type', 'email');
await expect(ticketPage.nameInput).toHaveAttribute('required');
// Test screen reader compatibility
const ticketTypeHeading = ticketPage.ticketTypes.first().locator('h3');
await expect(ticketTypeHeading).toBeVisible();
console.log('✓ ARIA labels and accessibility features present');
});
test('should have adequate color contrast', async ({ page }) => {
const ticketPage = new TicketPurchasePage(page);
await page.goto('/e/sample-event');
await expect(ticketPage.eventTitle).toBeVisible();
// Capture for manual color contrast verification
await ticketPage.captureEventPage('color-contrast-verification.png');
console.log('✓ Color contrast verification screenshot captured');
});
});
test.describe('Visual Regression Testing', () => {
test('should maintain consistent visual appearance', async ({ page }) => {
const ticketPage = new TicketPurchasePage(page);
await page.goto('/e/sample-event');
// Wait for all content to load
await expect(ticketPage.ticketTypes.first()).toBeVisible();
await page.waitForTimeout(2000);
// Capture baseline screenshots
await ticketPage.captureEventPage('visual-regression-baseline-full-page.png');
await ticketPage.captureTicketSelection('visual-regression-baseline-ticket-section.png');
// Test with tickets selected
await ticketPage.selectTicketQuantity(0, 2);
await ticketPage.waitForOrderSummary();
await ticketPage.captureTicketSelection('visual-regression-with-tickets-selected.png');
// Test with form filled
await ticketPage.fillCustomerInfo(testCustomers.valid.email, testCustomers.valid.name);
await ticketPage.captureTicketSelection('visual-regression-form-completed.png');
console.log('✓ Visual regression test screenshots captured');
});
test('should capture error states for visual comparison', async ({ page }) => {
const ticketPage = new TicketPurchasePage(page);
// Mock sold out state
await page.route('**/api/inventory/availability/*', async route => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
success: true,
availability: {
available: 0,
total: 100,
reserved: 0,
sold: 100,
is_available: false
}
})
});
});
await page.goto('/e/sample-event');
await page.waitForTimeout(2000);
await ticketPage.captureEventPage('visual-regression-sold-out-state.png');
console.log('✓ Error state visual regression screenshots captured');
});
});
test.describe('Performance and Load Testing', () => {
test('should load quickly and respond to interactions', async ({ page }) => {
const startTime = Date.now();
const ticketPage = new TicketPurchasePage(page);
await page.goto('/e/sample-event');
await expect(ticketPage.ticketTypes.first()).toBeVisible();
const loadTime = Date.now() - startTime;
console.log(`Page loaded in ${loadTime}ms`);
// Performance should be under 5 seconds for complete load
expect(loadTime).toBeLessThan(5000);
// Test interaction responsiveness
const interactionStart = Date.now();
await ticketPage.selectTicketQuantity(0, 1);
const interactionTime = Date.now() - interactionStart;
console.log(`Ticket selection responded in ${interactionTime}ms`);
expect(interactionTime).toBeLessThan(2000);
console.log('✓ Performance metrics within acceptable limits');
});
});
});
// Test Report Generation
test.afterAll(async () => {
console.log('\n=== TICKET PURCHASING TEST SUITE COMPLETE ===');
console.log('Screenshots saved to: ./screenshots/');
console.log('Test coverage areas completed:');
console.log('✓ Basic ticket purchasing flow');
console.log('✓ Multiple ticket types and quantities');
console.log('✓ Mobile responsive design');
console.log('✓ Form validation and error handling');
console.log('✓ Presale code functionality');
console.log('✓ Inventory management and reservations');
console.log('✓ Accessibility compliance');
console.log('✓ Visual regression testing');
console.log('✓ Performance and load testing');
console.log('\nFor best results:');
console.log('1. Ensure development server is running at localhost:4321');
console.log('2. Create test events with various ticket types');
console.log('3. Configure presale codes for testing');
console.log('4. Review captured screenshots for visual validation');
});