/** * Real-World Gate Scenarios - Advanced Field Testing * Tests network handoff, background/foreground transitions, multi-device racing, * rapid scanning rate limits, and QR code quality scenarios for actual gate operations */ import { test, expect } from '@playwright/test'; test.describe('Network Handoff Scenarios', () => { const testEventId = 'evt-001'; test.beforeEach(async ({ page, context }) => { await page.setViewportSize({ width: 375, height: 667 }); // Mobile viewport await context.grantPermissions(['camera']); await page.goto('/login'); await page.fill('[name="email"]', 'staff@example.com'); await page.fill('[name="password"]', 'password'); await page.click('button[type="submit"]'); await page.waitForURL('/dashboard'); await page.goto(`/scan?eventId=${testEventId}`); await expect(page.locator('h1:has-text("Ticket Scanner")')).toBeVisible(); }); test('should handle WiFi to cellular network transitions', async ({ page }) => { // Simulate starting on WiFi await expect(page.locator('text=Online')).toBeVisible(); // Set up network monitoring await page.addInitScript(() => { window.networkTransitionTest = { connectionChanges: 0, networkTypes: [], lastSyncTime: null }; // Monitor connection type changes if ('connection' in navigator) { const {connection} = navigator; window.networkTransitionTest.networkTypes.push(connection.effectiveType); connection.addEventListener('change', () => { window.networkTransitionTest.connectionChanges++; window.networkTransitionTest.networkTypes.push(connection.effectiveType); }); } // Monitor online/offline events window.addEventListener('online', () => { window.networkTransitionTest.lastSyncTime = Date.now(); }); }); // Simulate network quality changes (WiFi -> cellular) await page.route('**/*', (route) => { // Simulate slower cellular connection setTimeout(() => route.continue(), Math.random() * 500 + 100); }); // Perform scanning during network transition for (let i = 0; i < 5; i++) { await page.evaluate((qr) => { window.dispatchEvent(new CustomEvent('mock-scan', { detail: { qr, timestamp: Date.now() } })); }, `NETWORK_TRANSITION_${i}`); await page.waitForTimeout(300); } // Remove network delay await page.unroute('**/*'); // Should remain online and functional await expect(page.locator('text=Online')).toBeVisible(); const networkData = await page.evaluate(() => window.networkTransitionTest); console.log('Network transition data:', networkData); // Should handle network changes gracefully await expect(page.locator('h1:has-text("Ticket Scanner")')).toBeVisible(); }); test('should maintain sync during poor cellular conditions', async ({ page }) => { // Simulate poor cellular network conditions let requestCount = 0; await page.route('**/api/**', (route) => { requestCount++; // Simulate cellular network issues if (Math.random() < 0.3) { // 30% failure rate route.abort(); } else { // Slow but successful requests setTimeout(() => { route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ valid: true, message: 'Entry allowed', latencyMs: 2500 + Math.random() * 1500 }) }); }, 1000 + Math.random() * 2000); } }); // Enable optimistic scanning for poor network await page.click('button:has([data-testid="settings-icon"]), button:has(.lucide-settings)'); const toggle = page.locator('button.inline-flex').filter({ hasText: '' }); const isEnabled = await toggle.evaluate(el => el.classList.contains('bg-primary-500')); if (!isEnabled) {await toggle.click();} await page.click('button:has([data-testid="settings-icon"]), button:has(.lucide-settings)'); // Perform scanning under poor conditions for (let i = 0; i < 10; i++) { await page.evaluate((qr) => { window.dispatchEvent(new CustomEvent('mock-scan', { detail: { qr, timestamp: Date.now() } })); }, `POOR_CELLULAR_${i}`); await page.waitForTimeout(500); } await page.waitForTimeout(5000); // Allow retries to complete console.log(`Total API requests made: ${requestCount}`); // Should handle poor network gracefully await expect(page.locator('h1:has-text("Ticket Scanner")')).toBeVisible(); expect(requestCount).toBeGreaterThan(5); // Should have made retry attempts }); test('should adapt to network quality changes', async ({ page }) => { // Monitor network adaptation behavior await page.addInitScript(() => { window.networkAdaptation = { qualityChanges: [], adaptationMade: false }; // Simulate network quality detection function detectNetworkQuality() { const startTime = performance.now(); fetch('/api/ping') .then(() => { const latency = performance.now() - startTime; window.networkAdaptation.qualityChanges.push({ latency, timestamp: Date.now(), quality: latency < 100 ? 'fast' : latency < 500 ? 'medium' : 'slow' }); if (latency > 1000) { window.networkAdaptation.adaptationMade = true; } }) .catch(() => { window.networkAdaptation.qualityChanges.push({ latency: 999999, timestamp: Date.now(), quality: 'offline' }); }); } // Check network quality periodically setInterval(detectNetworkQuality, 2000); }); // Simulate varying network conditions let responseDelay = 100; await page.route('**/api/ping', (route) => { setTimeout(() => { route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ pong: Date.now() }) }); }, responseDelay); // Increase delay to simulate degrading network responseDelay += 200; }); await page.waitForTimeout(8000); // Let quality checks run const adaptationData = await page.evaluate(() => window.networkAdaptation); console.log('Network adaptation data:', adaptationData); // Should detect quality changes expect(adaptationData.qualityChanges.length).toBeGreaterThan(2); // Scanner should remain functional await expect(page.locator('text=Online')).toBeVisible(); }); }); test.describe('Background/Foreground Transitions', () => { const testEventId = 'evt-001'; test.beforeEach(async ({ page, context }) => { await page.setViewportSize({ width: 375, height: 667 }); await context.grantPermissions(['camera']); await page.goto('/login'); await page.fill('[name="email"]', 'staff@example.com'); await page.fill('[name="password"]', 'password'); await page.click('button[type="submit"]'); await page.waitForURL('/dashboard'); await page.goto(`/scan?eventId=${testEventId}`); }); test('should handle app going to background during scanning', async ({ page }) => { await expect(page.locator('video')).toBeVisible({ timeout: 10000 }); // Set up app lifecycle monitoring await page.addInitScript(() => { window.appLifecycleTest = { visibilityChanges: 0, backgroundTime: 0, cameraRestored: false, scanningResumed: false }; document.addEventListener('visibilitychange', () => { window.appLifecycleTest.visibilityChanges++; if (document.visibilityState === 'hidden') { window.appLifecycleTest.backgroundTime = Date.now(); } else if (document.visibilityState === 'visible') { if (window.appLifecycleTest.backgroundTime > 0) { const backgroundDuration = Date.now() - window.appLifecycleTest.backgroundTime; window.appLifecycleTest.backgroundTime = backgroundDuration; window.appLifecycleTest.cameraRestored = true; } } }); }); // Simulate scanning activity await page.evaluate(() => { window.dispatchEvent(new CustomEvent('mock-scan', { detail: { qr: 'BACKGROUND_TEST_001', timestamp: Date.now() } })); }); // Simulate app going to background await page.evaluate(() => { Object.defineProperty(document, 'visibilityState', { writable: true, value: 'hidden' }); document.dispatchEvent(new Event('visibilitychange')); }); await page.waitForTimeout(2000); // Simulate returning to foreground await page.evaluate(() => { Object.defineProperty(document, 'visibilityState', { writable: true, value: 'visible' }); document.dispatchEvent(new Event('visibilitychange')); }); await page.waitForTimeout(3000); // Allow camera to reinitialize // Camera should be restored await expect(page.locator('video')).toBeVisible({ timeout: 10000 }); // Should be able to scan again await page.evaluate(() => { window.dispatchEvent(new CustomEvent('mock-scan', { detail: { qr: 'BACKGROUND_TEST_002', timestamp: Date.now() } })); }); const lifecycleData = await page.evaluate(() => window.appLifecycleTest); console.log('App lifecycle data:', lifecycleData); expect(lifecycleData.visibilityChanges).toBeGreaterThanOrEqual(2); expect(lifecycleData.cameraRestored).toBe(true); }); test('should preserve scan queue during background transitions', async ({ page }) => { // Enable offline scanning await page.click('button:has([data-testid="settings-icon"]), button:has(.lucide-settings)'); const toggle = page.locator('button.inline-flex').filter({ hasText: '' }); const isEnabled = await toggle.evaluate(el => el.classList.contains('bg-primary-500')); if (!isEnabled) {await toggle.click();} await page.click('button:has([data-testid="settings-icon"]), button:has(.lucide-settings)'); // Go offline and scan items await page.context().setOffline(true); await page.evaluate(() => { Object.defineProperty(navigator, 'onLine', { writable: true, value: false }); window.dispatchEvent(new Event('offline')); }); // Scan while offline for (let i = 0; i < 3; i++) { await page.evaluate((qr) => { window.dispatchEvent(new CustomEvent('mock-scan', { detail: { qr, timestamp: Date.now() } })); }, `QUEUE_PRESERVE_${i}`); await page.waitForTimeout(300); } // Go to background await page.evaluate(() => { Object.defineProperty(document, 'visibilityState', { writable: true, value: 'hidden' }); document.dispatchEvent(new Event('visibilitychange')); }); await page.waitForTimeout(1000); // Return to foreground await page.evaluate(() => { Object.defineProperty(document, 'visibilityState', { writable: true, value: 'visible' }); document.dispatchEvent(new Event('visibilitychange')); }); // Come back online await page.context().setOffline(false); await page.evaluate(() => { Object.defineProperty(navigator, 'onLine', { writable: true, value: true }); window.dispatchEvent(new Event('online')); }); await page.waitForTimeout(3000); // Queue should be preserved and sync const pendingElement = page.locator('text=Pending:').locator('..'); await expect(pendingElement).toBeVisible(); // Should show online status await expect(page.locator('text=Online')).toBeVisible(); }); test('should handle wake lock across background transitions', async ({ page }) => { const wakeLockTest = await page.evaluate(async () => { if ('wakeLock' in navigator) { try { // Request wake lock const wakeLock = await navigator.wakeLock.request('screen'); const initialState = !wakeLock.released; // Simulate going to background Object.defineProperty(document, 'visibilityState', { writable: true, value: 'hidden' }); document.dispatchEvent(new Event('visibilitychange')); await new Promise(resolve => setTimeout(resolve, 1000)); const backgroundState = !wakeLock.released; // Return to foreground Object.defineProperty(document, 'visibilityState', { writable: true, value: 'visible' }); document.dispatchEvent(new Event('visibilitychange')); await new Promise(resolve => setTimeout(resolve, 1000)); // Try to re-request wake lock let newWakeLock = null; try { newWakeLock = await navigator.wakeLock.request('screen'); } catch (e) { console.log('Wake lock re-request failed:', e.message); } // Cleanup if (!wakeLock.released) {wakeLock.release();} if (newWakeLock && !newWakeLock.released) {newWakeLock.release();} return { supported: true, initialState, backgroundState, reRequestSuccessful: !!newWakeLock }; } catch (error) { return { supported: true, error: error.message }; } } return { supported: false }; }); console.log('Wake lock test:', wakeLockTest); // Scanner should remain functional regardless await expect(page.locator('h1:has-text("Ticket Scanner")')).toBeVisible(); }); }); test.describe('Multi-Device Race Conditions', () => { const testEventId = 'evt-001'; test.beforeEach(async ({ page, context }) => { await page.setViewportSize({ width: 375, height: 667 }); await context.grantPermissions(['camera']); await page.goto('/login'); await page.fill('[name="email"]', 'staff@example.com'); await page.fill('[name="password"]', 'password'); await page.click('button[type="submit"]'); await page.waitForURL('/dashboard'); await page.goto(`/scan?eventId=${testEventId}`); }); test('should handle simultaneous scanning of same ticket', async ({ page }) => { const duplicateTicket = 'RACE_CONDITION_TICKET_001'; // Mock race condition scenario let scanAttempts = 0; await page.route('**/api/scan/**', (route) => { scanAttempts++; if (scanAttempts === 1) { // First scan succeeds route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ valid: true, message: 'Entry allowed', scannedAt: new Date().toISOString(), device: 'device-1' }) }); } else { // Second scan shows already scanned route.fulfill({ status: 409, contentType: 'application/json', body: JSON.stringify({ valid: false, reason: 'already_scanned', message: 'Ticket already scanned by another device', originalScanTime: new Date(Date.now() - 1000).toISOString(), originalDevice: 'device-1' }) }); } }); // First scan attempt await page.evaluate((qr) => { window.dispatchEvent(new CustomEvent('mock-scan', { detail: { qr, timestamp: Date.now(), deviceId: 'scanner-device-2' } })); }, duplicateTicket); await page.waitForTimeout(1000); // Second scan attempt (simulating another device scanned it first) await page.evaluate((qr) => { window.dispatchEvent(new CustomEvent('mock-scan', { detail: { qr, timestamp: Date.now(), deviceId: 'scanner-device-2' } })); }, duplicateTicket); await page.waitForTimeout(1000); expect(scanAttempts).toBe(2); // Scanner should handle race condition gracefully await expect(page.locator('h1:has-text("Ticket Scanner")')).toBeVisible(); }); test('should prevent double-scanning within rate limit window', async ({ page }) => { const testTicket = 'DOUBLE_SCAN_PREVENT_001'; // Monitor scan attempts await page.addInitScript(() => { window.scanPrevention = { scanAttempts: 0, preventedScans: 0, lastScanTime: 0 }; }); // First scan await page.evaluate((data) => { const now = Date.now(); window.scanPrevention.scanAttempts++; window.scanPrevention.lastScanTime = now; window.dispatchEvent(new CustomEvent('mock-scan', { detail: { qr: data.qr, timestamp: now } })); }, { qr: testTicket }); await page.waitForTimeout(100); // Rapid second scan (should be prevented) await page.evaluate((data) => { const now = Date.now(); const timeSinceLastScan = now - window.scanPrevention.lastScanTime; if (timeSinceLastScan < 2000) { // Less than 2 seconds window.scanPrevention.preventedScans++; console.log('Preventing duplicate scan within rate limit'); return; } window.scanPrevention.scanAttempts++; window.scanPrevention.lastScanTime = now; window.dispatchEvent(new CustomEvent('mock-scan', { detail: { qr: data.qr, timestamp: now } })); }, { qr: testTicket }); await page.waitForTimeout(100); // Check prevention worked const preventionData = await page.evaluate(() => window.scanPrevention); console.log('Scan prevention data:', preventionData); expect(preventionData.preventedScans).toBeGreaterThan(0); expect(preventionData.scanAttempts).toBe(1); // Only one actual scan }); test('should handle concurrent offline queue sync', async ({ page }) => { // Create offline queue await page.context().setOffline(true); await page.evaluate(() => { Object.defineProperty(navigator, 'onLine', { writable: true, value: false }); window.dispatchEvent(new Event('offline')); }); // Enable optimistic scanning await page.click('button:has([data-testid="settings-icon"]), button:has(.lucide-settings)'); const toggle = page.locator('button.inline-flex').filter({ hasText: '' }); const isEnabled = await toggle.evaluate(el => el.classList.contains('bg-primary-500')); if (!isEnabled) {await toggle.click();} await page.click('button:has([data-testid="settings-icon"]), button:has(.lucide-settings)'); // Scan multiple tickets offline for (let i = 0; i < 5; i++) { await page.evaluate((qr) => { window.dispatchEvent(new CustomEvent('mock-scan', { detail: { qr, timestamp: Date.now() } })); }, `CONCURRENT_SYNC_${i}`); await page.waitForTimeout(200); } // Mock concurrent sync handling let syncRequestCount = 0; await page.route('**/api/scan/batch', (route) => { syncRequestCount++; // Simulate processing delay setTimeout(() => { route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ processed: 5, successful: 5, failed: 0, conflicts: [] }) }); }, 1000); }); // Come back online (trigger sync) await page.context().setOffline(false); await page.evaluate(() => { Object.defineProperty(navigator, 'onLine', { writable: true, value: true }); window.dispatchEvent(new Event('online')); }); await page.waitForTimeout(5000); // Should not make multiple concurrent sync requests expect(syncRequestCount).toBeLessThanOrEqual(2); await expect(page.locator('text=Online')).toBeVisible(); }); }); test.describe('Rapid Scanning Rate Limits', () => { const testEventId = 'evt-001'; test.beforeEach(async ({ page, context }) => { await page.setViewportSize({ width: 375, height: 667 }); await context.grantPermissions(['camera']); await page.goto('/login'); await page.fill('[name="email"]', 'staff@example.com'); await page.fill('[name="password"]', 'password'); await page.click('button[type="submit"]'); await page.waitForURL('/dashboard'); await page.goto(`/scan?eventId=${testEventId}`); }); test('should enforce 8 scans per second rate limit', async ({ page }) => { // Set up rate limit monitoring await page.addInitScript(() => { window.rateLimitMonitor = { scansAttempted: 0, scansProcessed: 0, scansBlocked: 0, rateLimitWarnings: 0, scanTimes: [] }; }); // Attempt rapid scanning (faster than 8/second = 125ms interval) const rapidScanCount = 20; const scanInterval = 50; // 50ms = 20 scans/second, well over limit for (let i = 0; i < rapidScanCount; i++) { await page.evaluate((data) => { const now = Date.now(); window.rateLimitMonitor.scansAttempted++; window.rateLimitMonitor.scanTimes.push(now); // Simulate rate limiting logic const recentScans = window.rateLimitMonitor.scanTimes.filter( time => now - time < 1000 // Last 1 second ); if (recentScans.length > 8) { window.rateLimitMonitor.scansBlocked++; window.rateLimitMonitor.rateLimitWarnings++; console.log('Rate limit exceeded - scan blocked'); return; } window.rateLimitMonitor.scansProcessed++; window.dispatchEvent(new CustomEvent('mock-scan', { detail: { qr: data.qr, timestamp: now } })); }, { qr: `RATE_LIMIT_${i}` }); await page.waitForTimeout(scanInterval); } await page.waitForTimeout(2000); const rateLimitData = await page.evaluate(() => window.rateLimitMonitor); console.log('Rate limit data:', rateLimitData); // Should have blocked excessive scans expect(rateLimitData.scansBlocked).toBeGreaterThan(0); expect(rateLimitData.scansProcessed).toBeLessThan(rateLimitData.scansAttempted); expect(rateLimitData.rateLimitWarnings).toBeGreaterThan(0); // Should not process more than ~8 scans per second expect(rateLimitData.scansProcessed).toBeLessThan(15); }); test('should show "slow down" message for excessive scanning', async ({ page }) => { // Monitor UI feedback for rate limiting await page.addInitScript(() => { window.rateLimitUI = { warningsShown: 0, lastWarningTime: 0 }; // Listen for rate limit events window.addEventListener('rate-limit-warning', () => { window.rateLimitUI.warningsShown++; window.rateLimitUI.lastWarningTime = Date.now(); }); }); // Rapidly attempt scans for (let i = 0; i < 15; i++) { await page.evaluate((qr) => { // Trigger rate limit warning if (Math.random() < 0.3) { // 30% chance of warning window.dispatchEvent(new Event('rate-limit-warning')); } window.dispatchEvent(new CustomEvent('mock-scan', { detail: { qr, timestamp: Date.now() } })); }, `RAPID_SCAN_${i}`); await page.waitForTimeout(30); // Very rapid } await page.waitForTimeout(1000); const uiData = await page.evaluate(() => window.rateLimitUI); console.log('Rate limit UI data:', uiData); // Should show warnings for excessive scanning if (uiData.warningsShown > 0) { expect(uiData.warningsShown).toBeGreaterThan(0); } // Scanner should remain functional await expect(page.locator('h1:has-text("Ticket Scanner")')).toBeVisible(); }); test('should recover from rate limiting', async ({ page }) => { // Trigger rate limiting for (let i = 0; i < 12; i++) { await page.evaluate((qr) => { window.dispatchEvent(new CustomEvent('mock-scan', { detail: { qr, timestamp: Date.now() } })); }, `RATE_LIMIT_TRIGGER_${i}`); await page.waitForTimeout(50); } // Wait for rate limit to reset await page.waitForTimeout(3000); // Should accept scans normally again await page.evaluate(() => { window.dispatchEvent(new CustomEvent('mock-scan', { detail: { qr: 'RATE_LIMIT_RECOVERY_TEST', timestamp: Date.now() } })); }); await page.waitForTimeout(500); // Scanner should be fully functional await expect(page.locator('h1:has-text("Ticket Scanner")')).toBeVisible(); await expect(page.locator('video')).toBeVisible(); }); }); test.describe('QR Code Quality and Edge Cases', () => { const testEventId = 'evt-001'; test.beforeEach(async ({ page, context }) => { await page.setViewportSize({ width: 375, height: 667 }); await context.grantPermissions(['camera']); await page.goto('/login'); await page.fill('[name="email"]', 'staff@example.com'); await page.fill('[name="password"]', 'password'); await page.click('button[type="submit"]'); await page.waitForURL('/dashboard'); await page.goto(`/scan?eventId=${testEventId}`); }); test('should handle various QR code formats and sizes', async ({ page }) => { const qrCodeVariants = [ 'STANDARD_TICKET_12345678', // Standard format 'BCT-EVT001-TKT-987654321', // BCT format with dashes '{"ticket":"123","event":"evt-001"}', // JSON format 'https://bct.com/verify/abc123', // URL format 'VERY_LONG_TICKET_CODE_WITH_MANY_CHARACTERS_1234567890', // Long format '123', // Very short 'TICKET_WITH_SPECIAL_CHARS_@#$%', // Special characters 'ticket_lowercase_123', // Lowercase 'TICKET_WITH_UNICODE_ñáéíóú_123' // Unicode characters ]; // Test each QR code variant for (const qrCode of qrCodeVariants) { await page.evaluate((qr) => { window.dispatchEvent(new CustomEvent('mock-scan', { detail: { qr, timestamp: Date.now(), format: qr.length > 30 ? 'long' : qr.startsWith('{') ? 'json' : 'standard' } })); }, qrCode); await page.waitForTimeout(300); } // Scanner should handle all formats gracefully await expect(page.locator('h1:has-text("Ticket Scanner")')).toBeVisible(); }); test('should handle damaged or partial QR code reads', async ({ page }) => { const corruptedQRCodes = [ 'PARTIAL_SCAN_', // Incomplete scan 'CORRUPT_QR_?@#$INVALID', // Contains invalid characters '', // Empty scan ' ', // Whitespace only 'TICKET_WITH\nNEWLINE', // Contains newline 'TICKET_WITH\tTAB', // Contains tab 'TICKET_WITH\0NULL', // Contains null character ]; // Set up error handling monitoring await page.addInitScript(() => { window.qrErrorHandling = { invalidScans: 0, emptyScans: 0, handledGracefully: 0 }; }); for (const qrCode of corruptedQRCodes) { await page.evaluate((qr) => { // Simulate QR validation logic if (!qr || qr.trim().length === 0) { window.qrErrorHandling.emptyScans++; console.log('Empty QR code detected'); return; } if (qr.includes('\n') || qr.includes('\t') || qr.includes('\0')) { window.qrErrorHandling.invalidScans++; console.log('Invalid QR code format detected'); return; } window.qrErrorHandling.handledGracefully++; window.dispatchEvent(new CustomEvent('mock-scan', { detail: { qr, timestamp: Date.now(), quality: 'poor' } })); }, qrCode); await page.waitForTimeout(200); } const errorData = await page.evaluate(() => window.qrErrorHandling); console.log('QR error handling data:', errorData); // Should detect and handle invalid QR codes expect(errorData.emptyScans + errorData.invalidScans).toBeGreaterThan(0); // Scanner should remain stable despite invalid inputs await expect(page.locator('h1:has-text("Ticket Scanner")')).toBeVisible(); }); test('should adapt to different lighting conditions', async ({ page }) => { // Test torch functionality for low light const torchButton = page.locator('button:has([data-testid="flashlight-icon"]), button:has(.lucide-flashlight)'); if (await torchButton.isVisible()) { // Simulate low light scanning await page.evaluate(() => { window.dispatchEvent(new CustomEvent('lighting-change', { detail: { condition: 'low-light', brightness: 0.2 } })); }); // Enable torch for better scanning await torchButton.tap(); await page.waitForTimeout(500); // Test scanning in low light with torch await page.evaluate(() => { window.dispatchEvent(new CustomEvent('mock-scan', { detail: { qr: 'LOW_LIGHT_SCAN_123', timestamp: Date.now(), conditions: { lighting: 'low', torch: true } } })); }); await page.waitForTimeout(300); // Simulate bright light await page.evaluate(() => { window.dispatchEvent(new CustomEvent('lighting-change', { detail: { condition: 'bright-light', brightness: 0.9 } })); }); // Test scanning in bright light await page.evaluate(() => { window.dispatchEvent(new CustomEvent('mock-scan', { detail: { qr: 'BRIGHT_LIGHT_SCAN_123', timestamp: Date.now(), conditions: { lighting: 'bright', torch: false } } })); }); // Disable torch in bright conditions await torchButton.tap(); } // Scanner should adapt to lighting changes await expect(page.locator('h1:has-text("Ticket Scanner")')).toBeVisible(); }); test('should handle different QR code angles and distances', async ({ page }) => { const scanAngles = [ { angle: 0, distance: 'optimal', quality: 'high' }, { angle: 15, distance: 'close', quality: 'medium' }, { angle: 30, distance: 'far', quality: 'low' }, { angle: 45, distance: 'very-close', quality: 'poor' }, { angle: -15, distance: 'medium', quality: 'medium' } ]; for (const scan of scanAngles) { await page.evaluate((scanData) => { window.dispatchEvent(new CustomEvent('mock-scan', { detail: { qr: `ANGLE_TEST_${scanData.angle}_${scanData.distance}`, timestamp: Date.now(), scanConditions: scanData } })); }, scan); await page.waitForTimeout(400); } // Should handle various scan conditions await expect(page.locator('h1:has-text("Ticket Scanner")')).toBeVisible(); }); });