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>
This commit is contained in:
359
reactrebuild0825/tests/publish-scanner.smoke.spec.ts
Normal file
359
reactrebuild0825/tests/publish-scanner.smoke.spec.ts
Normal file
@@ -0,0 +1,359 @@
|
||||
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<string> {
|
||||
// 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');
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user