import path from 'path'; import { test, expect } from '@playwright/test'; import type { Page } from '@playwright/test'; /** * Publish-Scanner Smoke Tests * * Critical flow validation for: * - Event navigation and detail page access * - Publish logic based on payment connection status (org.payment.connected) * - Scanner navigation and UI rendering with camera mocking * - Error state handling and blocked publish flows * - Screenshots for visual validation * * This test suite is designed for CI environments with worker count 1 * and does not require sudo permissions. */ const DEMO_ACCOUNTS = { admin: { email: 'admin@example.com', password: 'demo123' }, // org_001, payment connected organizer: { email: 'organizer@example.com', password: 'demo123' }, // org_002, payment NOT connected }; async function takeScreenshot(page: Page, name: string) { const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const fileName = `publish_scanner_${name}_${timestamp}.png`; await page.screenshot({ path: path.join('screenshots', fileName), fullPage: true }); return fileName; } async function mockCameraForScanner(page: Page) { // Mock camera API for scanner testing without requiring real camera access await page.addInitScript(() => { // Mock getUserMedia for camera testing Object.defineProperty(navigator, 'mediaDevices', { writable: true, value: { getUserMedia: () => Promise.resolve({ getTracks: () => [], addTrack: () => {}, removeTrack: () => {}, getVideoTracks: () => [], getAudioTracks: () => [], }), enumerateDevices: () => Promise.resolve([{ deviceId: 'mock-camera', kind: 'videoinput', label: 'Mock Camera', groupId: 'mock-group' }]) } }); }); } async function loginAs(page: Page, userType: 'admin' | 'organizer') { await page.goto('/login'); await page.waitForLoadState('networkidle'); // Use demo button method (same as smoke.spec.ts) for reliable login if (userType === 'admin') { await page.click('[data-testid="demo-user-sarah-admin"]'); } else { await page.click('[data-testid="demo-user-john-organizer"]'); } // Submit form await page.click('button[type="submit"]'); // Wait for dashboard redirect await expect(page).toHaveURL('/dashboard', { timeout: 10000 }); await page.waitForLoadState('networkidle'); await takeScreenshot(page, `logged-in-as-${userType}`); } async function navigateToFirstEvent(page: Page): Promise { // Navigate to events page await page.goto('/events'); await page.waitForLoadState('networkidle'); await takeScreenshot(page, 'events-page-loaded'); // Click first event card to go to detail page const firstEventCard = page.locator('[data-testid="event-card"]').first(); await expect(firstEventCard).toBeVisible({ timeout: 10000 }); await firstEventCard.click(); // Verify we're on event detail page and get event ID from URL await expect(page.locator('[data-testid="event-detail-page"]')).toBeVisible({ timeout: 10000 }); await takeScreenshot(page, 'event-detail-page-loaded'); // Extract event ID from URL const currentUrl = page.url(); const eventIdMatch = currentUrl.match(/\/events\/([^\/\?]+)/); const eventId = eventIdMatch?.[1]; expect(eventId).toBeTruthy(); return eventId!; } test.describe('Publish-Scanner Smoke Tests', () => { test('admin user - payment connected - can publish and access scanner', async ({ page }) => { // Login as admin (org_001, payment connected) await loginAs(page, 'admin'); // Navigate to first event detail page const eventId = await navigateToFirstEvent(page); // Verify EventDetail header is visible await expect(page.locator('h1')).toBeVisible(); await expect(page.locator('[data-testid="event-detail-page"]')).toBeVisible(); // Test publish logic - admin should be able to publish (payment connected) const publishButton = page.locator('[data-testid="publishBtn"], [data-testid="publish-event-button"]').first(); if (await publishButton.isVisible()) { // If publish button exists, it should be enabled (not blocked by payment) await expect(publishButton).toBeEnabled(); await takeScreenshot(page, 'admin-publish-button-enabled'); // Optional: Click to open publish modal and verify no payment blocking await publishButton.click(); // Look for publish modal const publishModal = page.locator('text=Publish Event').first(); if (await publishModal.isVisible()) { await takeScreenshot(page, 'admin-publish-modal-open'); // Close modal with escape await page.keyboard.press('Escape'); } } else { // Event might already be published await takeScreenshot(page, 'admin-event-already-published'); } // Navigate to scanner with camera mocking await mockCameraForScanner(page); await page.goto(`/scan?eventId=${eventId}`); await page.waitForLoadState('networkidle'); // Confirm scanner UI renders await expect(page.locator('[data-testid="scanner-interface"]')).toBeVisible({ timeout: 15000 }); await takeScreenshot(page, 'admin-scanner-ui-loaded'); // Verify scanner has basic elements await expect(page.locator('text=Scanner')).toBeVisible(); // Test that camera functionality doesn't crash (mocked) console.log('✅ Scanner UI loaded successfully with mocked camera for admin user'); }); test('organizer user - payment NOT connected - publish blocked', async ({ page }) => { // Login as organizer (org_002, payment NOT connected) await loginAs(page, 'organizer'); // Navigate to first event detail page const eventId = await navigateToFirstEvent(page); // Verify EventDetail header is visible await expect(page.locator('h1')).toBeVisible(); await expect(page.locator('[data-testid="event-detail-page"]')).toBeVisible(); // Test publish logic - organizer should be blocked by payment connection const publishButton = page.locator('[data-testid="publishBtn"], [data-testid="publish-event-button"]').first(); if (await publishButton.isVisible()) { // Publish button should exist but lead to blocking when clicked await publishButton.click(); await takeScreenshot(page, 'organizer-publish-button-clicked'); // Look for publish modal const publishModal = page.locator('text=Publish Event').first(); if (await publishModal.isVisible()) { // Verify payment processing requirement is failing const paymentCheck = page.locator('text=Payment Processing').locator('..'); await expect(paymentCheck.locator('[aria-label="Failed"]')).toBeVisible(); // Verify publish button in modal is disabled const modalPublishButton = page.locator('button:has-text("Publish Event")'); await expect(modalPublishButton).toBeDisabled(); await takeScreenshot(page, 'organizer-publish-blocked-by-payment'); // Close modal await page.keyboard.press('Escape'); } } // Verify payment banner is visible (organizer has disconnected payment) const paymentBanner = page.locator('[data-testid="payment-banner"]'); if (await paymentBanner.isVisible()) { await expect(paymentBanner).toBeVisible(); await takeScreenshot(page, 'organizer-payment-banner-visible'); } // Navigate to scanner (should still work regardless of payment status) await mockCameraForScanner(page); await page.goto(`/scan?eventId=${eventId}`); await page.waitForLoadState('networkidle'); // Confirm scanner UI renders (scanner access not dependent on payment) await expect(page.locator('[data-testid="scanner-interface"]')).toBeVisible({ timeout: 15000 }); await takeScreenshot(page, 'organizer-scanner-ui-loaded'); // Verify scanner has basic elements await expect(page.locator('text=Scanner')).toBeVisible(); // Verify scanner works despite payment disconnection console.log('✅ Scanner UI accessible for organizer despite payment disconnection'); }); test('scanner UI has essential components', async ({ page }) => { // Login as admin for this scanner-focused test await loginAs(page, 'admin'); // Navigate to events and get first event ID const eventId = await navigateToFirstEvent(page); // Navigate directly to scanner with camera mocking await mockCameraForScanner(page); await page.goto(`/scan?eventId=${eventId}`); await page.waitForLoadState('networkidle'); // Wait for scanner interface to load await expect(page.locator('[data-testid="scanner-interface"]')).toBeVisible({ timeout: 15000 }); // Check for manual entry functionality (from QR system tests) const manualEntryButton = page.locator('button[title="Manual Entry"]'); if (await manualEntryButton.isVisible()) { await expect(manualEntryButton).toBeVisible(); await takeScreenshot(page, 'scanner-manual-entry-available'); // Test opening manual entry modal await manualEntryButton.click(); await expect(page.locator('h2:text("Manual Entry")')).toBeVisible(); await takeScreenshot(page, 'scanner-manual-entry-modal'); // Close modal await page.keyboard.press('Escape'); } // Check for scanner instructions or guidance text const instructionsVisible = await page.locator('text*=scanner', 'text*=QR', 'text*=camera').first().isVisible(); expect(instructionsVisible).toBeTruthy(); await takeScreenshot(page, 'scanner-complete-ui-check'); }); test('event navigation flow validation', async ({ page }) => { // Test the complete flow without focusing on specific payment status await loginAs(page, 'admin'); // Start from dashboard, navigate to events await expect(page).toHaveURL('/dashboard'); // Go to events page await page.goto('/events'); await expect(page.locator('h1')).toContainText('Events'); await takeScreenshot(page, 'events-page-header-check'); // Verify events are loaded (either cards or empty state) await expect(page.locator('[data-testid="events-container"]')).toBeVisible(); // Check if we have any event cards const eventCards = page.locator('[data-testid="event-card"]'); const cardCount = await eventCards.count(); if (cardCount > 0) { // Click first event card await eventCards.first().click(); // Verify navigation to event detail await expect(page).toHaveURL(/\/events\/[^\/]+$/); await expect(page.locator('[data-testid="event-detail-page"]')).toBeVisible(); await takeScreenshot(page, 'navigation-flow-complete'); } else { // No events available - this is also a valid state to test await takeScreenshot(page, 'events-page-empty-state'); } }); test('scanner direct access with event ID', async ({ page }) => { // Test direct scanner access without going through event detail page await loginAs(page, 'admin'); // Use a known event ID (from mock data) or get from events page const eventId = await navigateToFirstEvent(page); // Navigate directly to scanner with eventId parameter and camera mocking await mockCameraForScanner(page); await page.goto(`/scan?eventId=${eventId}`); await page.waitForLoadState('networkidle'); // Verify scanner loads with event context await expect(page.locator('[data-testid="scanner-interface"]')).toBeVisible({ timeout: 15000 }); // Verify the URL contains the event ID expect(page.url()).toContain(`eventId=${eventId}`); await takeScreenshot(page, 'scanner-direct-access-with-event-id'); console.log('✅ Direct scanner access with event ID parameter works correctly'); }); test('payment connection status validation', async ({ page }) => { // Test both payment connected and disconnected scenarios // Part 1: Admin with payment connected await loginAs(page, 'admin'); const eventId = await navigateToFirstEvent(page); // Check for payment status indicators on event detail page const paymentConnectedIndicator = page.locator('[data-testid="payment-status-connected"]'); const paymentDisconnectedBanner = page.locator('[data-testid="payment-banner"]'); // Admin should have payment connected, so no disconnect banner const hasBanner = await paymentDisconnectedBanner.isVisible(); if (!hasBanner) { console.log('✅ Admin user: No payment disconnection banner (payment connected)'); await takeScreenshot(page, 'admin-no-payment-banner'); } // Logout and test organizer await page.click('[data-testid="user-menu"]'); await page.click('[data-testid="logout-button"]'); await expect(page).toHaveURL('/login', { timeout: 10000 }); // Part 2: Organizer with payment NOT connected await loginAs(page, 'organizer'); await navigateToFirstEvent(page); // Organizer should see payment banner or blocking const organizerBanner = await paymentDisconnectedBanner.isVisible(); if (organizerBanner) { console.log('✅ Organizer user: Payment disconnection banner visible'); await takeScreenshot(page, 'organizer-payment-status-check'); } // Try to access publish modal if available const publishButton = page.locator('[data-testid="publishBtn"], [data-testid="publish-event-button"]').first(); if (await publishButton.isVisible()) { await publishButton.click(); // Look for payment processing check const paymentCheckFailed = page.locator('text=Payment Processing').locator('..').locator('[aria-label="Failed"]'); if (await paymentCheckFailed.isVisible()) { console.log('✅ Publish blocked by payment status for organizer'); await takeScreenshot(page, 'payment-status-blocking-publish'); } await page.keyboard.press('Escape'); } }); });