feat: comprehensive project completion and documentation

- Enhanced event creation wizard with multi-step validation
- Added advanced QR scanning system with offline support
- Implemented comprehensive territory management features
- Expanded analytics with export functionality and KPIs
- Created complete design token system with theme switching
- Added 25+ Playwright test files for comprehensive coverage
- Implemented enterprise-grade permission system
- Enhanced component library with 80+ React components
- Added Firebase integration for deployment
- Completed Phase 3 development goals substantially

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-08-26 15:04:37 -06:00
parent aa81eb5adb
commit 8ed7ae95d1
230 changed files with 24072 additions and 3395 deletions

View File

@@ -4,6 +4,7 @@
* battery usage monitoring, and memory leak detection for extended gate operations
*/
/// <reference path="./test-types.d.ts" />
import { test, expect } from '@playwright/test';
test.describe('Extended Continuous Scanning', () => {
@@ -21,6 +22,7 @@ test.describe('Extended Continuous Scanning', () => {
});
test('should handle 15-minute continuous scanning session', async ({ page }) => {
test.setTimeout(60000);
// Set up performance monitoring
await page.addInitScript(() => {
window.performanceMetrics = {
@@ -34,7 +36,7 @@ test.describe('Extended Continuous Scanning', () => {
// Monitor memory usage every 30 seconds
setInterval(() => {
if (performance.memory) {
window.performanceMetrics.memoryUsage.push({
window.performanceMetrics!.memoryUsage.push({
used: performance.memory.usedJSHeapSize,
total: performance.memory.totalJSHeapSize,
limit: performance.memory.jsHeapSizeLimit,
@@ -52,7 +54,7 @@ test.describe('Extended Continuous Scanning', () => {
const now = performance.now();
if (now - lastTime >= 1000) {
const fps = Math.round((frames * 1000) / (now - lastTime));
window.performanceMetrics.frameRates.push({
window.performanceMetrics!.frameRates.push({
fps,
timestamp: now
});
@@ -83,8 +85,8 @@ test.describe('Extended Continuous Scanning', () => {
scanCount++;
const qrCode = `ENDURANCE_TICKET_${scanCount.toString().padStart(4, '0')}`;
await page.evaluate((data) => {
window.performanceMetrics.scanCounts++;
await page.evaluate((data: { qr: string; scanCount: number }) => {
window.performanceMetrics!.scanCounts++;
window.dispatchEvent(new CustomEvent('mock-scan', {
detail: {
qr: data.qr,
@@ -104,7 +106,7 @@ test.describe('Extended Continuous Scanning', () => {
await page.waitForTimeout(testDurationMs + 5000);
// Collect performance metrics
const metrics = await page.evaluate(() => window.performanceMetrics);
const metrics = await page.evaluate(() => window.performanceMetrics!);
console.log(`Endurance test completed: ${metrics.scanCounts} scans processed`);
@@ -114,8 +116,8 @@ test.describe('Extended Continuous Scanning', () => {
// Check memory usage didn't grow excessively
if (metrics.memoryUsage.length > 1) {
const initialMemory = metrics.memoryUsage[0].used;
const finalMemory = metrics.memoryUsage[metrics.memoryUsage.length - 1].used;
const initialMemory = metrics.memoryUsage[0]!.used;
const finalMemory = metrics.memoryUsage[metrics.memoryUsage.length - 1]!.used;
const memoryGrowth = finalMemory - initialMemory;
const memoryGrowthMB = memoryGrowth / (1024 * 1024);
@@ -127,7 +129,7 @@ test.describe('Extended Continuous Scanning', () => {
// Check frame rates remained reasonable
if (metrics.frameRates.length > 0) {
const avgFPS = metrics.frameRates.reduce((sum, r) => sum + r.fps, 0) / metrics.frameRates.length;
const avgFPS = metrics.frameRates.reduce((sum: number, r: { fps: number }) => sum + r.fps, 0) / metrics.frameRates.length;
console.log(`Average FPS: ${avgFPS.toFixed(1)}`);
// Should maintain at least 15 FPS for usable performance
@@ -136,7 +138,7 @@ test.describe('Extended Continuous Scanning', () => {
// Verify scan counter accuracy
expect(metrics.scanCounts).toBeGreaterThan(100); // Should have processed many scans
}, 60000); // 60 second timeout for this test
});
test('should maintain performance under rapid scanning load', async ({ page }) => {
// Test rapid scanning (simulating very busy gate)
@@ -153,7 +155,7 @@ test.describe('Extended Continuous Scanning', () => {
const scanDelay = 100; // 100ms = 10 scans per second
for (let i = 0; i < rapidScanCount; i++) {
const startTime = performance.now();
// Monitor performance during rapid scanning
await page.evaluate((data) => {
const processingStart = performance.now();
@@ -164,8 +166,8 @@ test.describe('Extended Continuous Scanning', () => {
// Simulate processing time measurement
setTimeout(() => {
const processingTime = performance.now() - processingStart;
window.rapidScanMetrics.averageProcessingTime.push(processingTime);
window.rapidScanMetrics.processedScans++;
window.rapidScanMetrics!.averageProcessingTime.push(processingTime);
window.rapidScanMetrics!.processedScans++;
}, 10);
}, { qr: `RAPID_SCAN_${i}` });
@@ -175,14 +177,14 @@ test.describe('Extended Continuous Scanning', () => {
await page.waitForTimeout(2000); // Allow processing to complete
// Check metrics
const metrics = await page.evaluate(() => window.rapidScanMetrics);
const metrics = await page.evaluate(() => window.rapidScanMetrics!);
// Should have processed most scans successfully
expect(metrics.processedScans).toBeGreaterThan(rapidScanCount * 0.8); // 80% success rate
// Average processing time should be reasonable
if (metrics.averageProcessingTime.length > 0) {
const avgTime = metrics.averageProcessingTime.reduce((a, b) => a + b) / metrics.averageProcessingTime.length;
const avgTime = metrics.averageProcessingTime.reduce((a: number, b: number) => a + b) / metrics.averageProcessingTime.length;
console.log(`Average scan processing time: ${avgTime.toFixed(2)}ms`);
expect(avgTime).toBeLessThan(1000); // Should process in under 1 second
}
@@ -260,7 +262,7 @@ test.describe('Thermal and Resource Management', () => {
// Simulate thermal state changes
setTimeout(() => {
thermalState = 'warm';
window.thermalTesting.thermalState = thermalState;
window.thermalTesting!.thermalState = thermalState;
window.dispatchEvent(new CustomEvent('thermal-state-change', {
detail: { state: thermalState }
}));
@@ -269,9 +271,9 @@ test.describe('Thermal and Resource Management', () => {
setTimeout(() => {
thermalState = 'hot';
frameReductionActive = true;
window.thermalTesting.thermalState = thermalState;
window.thermalTesting.frameReductionActive = frameReductionActive;
window.thermalTesting.performanceAdaptations.push('reduced-fps');
window.thermalTesting!.thermalState = thermalState;
window.thermalTesting!.frameReductionActive = frameReductionActive;
window.thermalTesting!.performanceAdaptations.push('reduced-fps');
window.dispatchEvent(new CustomEvent('thermal-state-change', {
detail: { state: thermalState, adaptations: ['reduced-fps'] }
}));
@@ -281,11 +283,11 @@ test.describe('Thermal and Resource Management', () => {
await page.waitForTimeout(6000);
// Check thermal adaptations were applied
const thermalMetrics = await page.evaluate(() => window.thermalTesting);
const thermalMetrics = await page.evaluate(() => window.thermalTesting!);
expect(thermalMetrics.thermalState).toBe('hot');
expect(thermalMetrics.frameReductionActive).toBe(true);
expect(thermalMetrics.performanceAdaptations).toContain('reduced-fps');
expect(thermalMetrics?.thermalState).toBe('hot');
expect(thermalMetrics?.frameReductionActive).toBe(true);
expect(thermalMetrics?.performanceAdaptations).toContain('reduced-fps');
// Scanner should still be functional despite thermal throttling
await expect(page.locator('h1:has-text("Ticket Scanner")')).toBeVisible();
@@ -308,7 +310,7 @@ test.describe('Thermal and Resource Management', () => {
const frameDelta = now - lastFrameTime;
if (frameDelta > 0) {
window.resourceMonitor.renderMetrics.push({
window.resourceMonitor!.renderMetrics.push({
frameDelta,
timestamp: now
});
@@ -324,7 +326,7 @@ test.describe('Thermal and Resource Management', () => {
try {
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
window.resourceMonitor.cpuMetrics.push({
window.resourceMonitor!.cpuMetrics.push({
name: entry.name,
duration: entry.duration,
timestamp: entry.startTime
@@ -349,12 +351,12 @@ test.describe('Thermal and Resource Management', () => {
}
// Collect resource usage data
const resourceData = await page.evaluate(() => window.resourceMonitor);
const resourceData = await page.evaluate(() => window.resourceMonitor!);
// Analyze frame timing for performance issues
if (resourceData.renderMetrics.length > 0) {
const avgFrameDelta = resourceData.renderMetrics.reduce((sum, m) => sum + m.frameDelta, 0) / resourceData.renderMetrics.length;
const maxFrameDelta = Math.max(...resourceData.renderMetrics.map(m => m.frameDelta));
if (resourceData?.renderMetrics.length > 0) {
const avgFrameDelta = resourceData.renderMetrics.reduce((sum: number, m: { frameDelta: number }) => sum + m.frameDelta, 0) / resourceData.renderMetrics.length;
const maxFrameDelta = Math.max(...resourceData.renderMetrics.map((m: { frameDelta: number }) => m.frameDelta));
console.log(`Average frame delta: ${avgFrameDelta.toFixed(2)}ms`);
console.log(`Max frame delta: ${maxFrameDelta.toFixed(2)}ms`);
@@ -373,7 +375,7 @@ test.describe('Thermal and Resource Management', () => {
const batteryInfo = await page.evaluate(async () => {
if ('getBattery' in navigator) {
try {
const battery = await navigator.getBattery();
const battery = await (navigator as any).getBattery();
return {
level: battery.level,
charging: battery.charging,
@@ -409,11 +411,11 @@ test.describe('Thermal and Resource Management', () => {
// Test screen wake lock for preventing screen sleep during scanning
const wakeLockSupported = await page.evaluate(async () => {
if ('wakeLock' in navigator) {
if ('wakeLock' in navigator && navigator.wakeLock) {
try {
const wakeLock = await navigator.wakeLock.request('screen');
const isActive = !wakeLock.released;
wakeLock.release();
await wakeLock.release();
return { supported: true, worked: isActive };
} catch {
return { supported: true, worked: false };
@@ -445,6 +447,7 @@ test.describe('Memory Leak Detection', () => {
});
test('should not leak memory during extended scanning sessions', async ({ page }) => {
test.setTimeout(90000);
// Set up memory monitoring
const initialMemory = await page.evaluate(() => {
if (performance.memory) {
@@ -458,7 +461,7 @@ test.describe('Memory Leak Detection', () => {
});
if (!initialMemory) {
test.skip('Memory monitoring not available in this browser');
test.skip(initialMemory === null, 'Memory monitoring not available in this browser');
return;
}
@@ -471,7 +474,7 @@ test.describe('Memory Leak Detection', () => {
for (let cycle = 0; cycle < scanCycles; cycle++) {
// Rapid scanning cycle
for (let scan = 0; scan < scansPerCycle; scan++) {
await page.evaluate((data) => {
await page.evaluate((data: { qr: string; cycle: number; scan: number }) => {
// Create scan event with some data
const scanData = {
qr: data.qr,
@@ -551,7 +554,7 @@ test.describe('Memory Leak Detection', () => {
// Scanner should still be responsive
await expect(page.locator('h1:has-text("Ticket Scanner")')).toBeVisible();
await expect(page.locator('video')).toBeVisible();
}, 90000); // 90 second timeout for memory test
});
test('should clean up event listeners and timers', async ({ page }) => {
// Track event listeners and intervals
@@ -571,40 +574,43 @@ test.describe('Memory Leak Detection', () => {
EventTarget.prototype.addEventListener = function(type, listener, options) {
const key = `${this.constructor.name}-${type}`;
if (!window.leakDetector.eventListeners.has(key)) {
window.leakDetector.eventListeners.set(key, 0);
if (!window.leakDetector!.eventListeners.has(key)) {
window.leakDetector!.eventListeners.set(key, 0);
}
window.leakDetector.eventListeners.set(key, window.leakDetector.eventListeners.get(key) + 1);
const currentCount = window.leakDetector!.eventListeners.get(key) || 0;
window.leakDetector!.eventListeners.set(key, currentCount + 1);
return originalAddEventListener.call(this, type, listener, options);
};
EventTarget.prototype.removeEventListener = function(type, listener, options) {
const key = `${this.constructor.name}-${type}`;
if (window.leakDetector.eventListeners.has(key)) {
window.leakDetector.eventListeners.set(key, window.leakDetector.eventListeners.get(key) - 1);
if (window.leakDetector!.eventListeners.has(key)) {
const currentCount = window.leakDetector!.eventListeners.get(key) || 0;
window.leakDetector!.eventListeners.set(key, currentCount - 1);
}
return originalRemoveEventListener.call(this, type, listener, options);
};
window.setInterval = function(callback, delay) {
// Override timer functions with proper type handling
(window as any).setInterval = function(callback: TimerHandler, delay?: number) {
const id = originalSetInterval(callback, delay);
window.leakDetector.intervals.add(id);
window.leakDetector!.intervals.add(id as number);
return id;
};
window.clearInterval = function(id) {
window.leakDetector.intervals.delete(id);
(window as any).clearInterval = function(id: number) {
window.leakDetector!.intervals.delete(id);
return originalClearInterval(id);
};
window.setTimeout = function(callback, delay) {
(window as any).setTimeout = function(callback: TimerHandler, delay?: number) {
const id = originalSetTimeout(callback, delay);
window.leakDetector.timeouts.add(id);
window.leakDetector!.timeouts.add(id as number);
return id;
};
window.clearTimeout = function(id) {
window.leakDetector.timeouts.delete(id);
(window as any).clearTimeout = function(id: number) {
window.leakDetector!.timeouts.delete(id);
return originalClearTimeout(id);
};
});
@@ -633,21 +639,21 @@ test.describe('Memory Leak Detection', () => {
await expect(page.locator('h1:has-text("Ticket Scanner")')).toBeVisible();
// Check for resource leaks
const leakReport = await page.evaluate(() => window.leakDetector);
const leakReport = await page.evaluate(() => window.leakDetector!);
console.log('Event listeners:', Object.fromEntries(leakReport.eventListeners));
console.log('Active intervals:', leakReport.intervals.size);
console.log('Active timeouts:', leakReport.timeouts.size);
console.log('Event listeners:', Object.fromEntries(leakReport?.eventListeners || new Map()));
console.log('Active intervals:', leakReport?.intervals.size || 0);
console.log('Active timeouts:', leakReport?.timeouts.size || 0);
// Should not have excessive active timers
expect(leakReport.intervals.size).toBeLessThan(10);
expect(leakReport.timeouts.size).toBeLessThan(20);
expect(leakReport?.intervals.size || 0).toBeLessThan(10);
expect(leakReport?.timeouts.size || 0).toBeLessThan(20);
});
test('should handle camera stream cleanup properly', async ({ page }) => {
// Monitor video element and media streams
await page.addInitScript(() => {
const streamCount = 0;
// Track stream creation and cleanup
const originalGetUserMedia = navigator.mediaDevices.getUserMedia;
window.mediaStreamTracker = {
@@ -657,18 +663,20 @@ test.describe('Memory Leak Detection', () => {
};
navigator.mediaDevices.getUserMedia = function(constraints) {
window.mediaStreamTracker.createdStreams++;
window.mediaStreamTracker.activeStreams++;
window.mediaStreamTracker!.createdStreams++;
window.mediaStreamTracker!.activeStreams++;
return originalGetUserMedia.call(this, constraints).then(stream => {
// Track when streams are stopped
const originalStop = stream.getTracks()[0].stop;
stream.getTracks().forEach(track => {
const trackStop = track.stop;
const tracks = stream.getTracks();
if (tracks.length === 0) return stream;
tracks.forEach(track => {
const originalStop = track.stop.bind(track);
track.stop = function() {
window.mediaStreamTracker.activeStreams--;
window.mediaStreamTracker.cleanedUpStreams++;
return trackStop.call(this);
window.mediaStreamTracker!.activeStreams--;
window.mediaStreamTracker!.cleanedUpStreams++;
return originalStop();
};
});
@@ -690,14 +698,14 @@ test.describe('Memory Leak Detection', () => {
await page.waitForTimeout(2000);
// Check stream management
const streamReport = await page.evaluate(() => window.mediaStreamTracker);
const streamReport = await page.evaluate(() => window.mediaStreamTracker!);
console.log('Stream report:', streamReport);
// Should have created streams
expect(streamReport.createdStreams).toBeGreaterThan(0);
expect(streamReport?.createdStreams || 0).toBeGreaterThan(0);
// Should not have excessive active streams (leaking)
expect(streamReport.activeStreams).toBeLessThanOrEqual(1);
expect(streamReport?.activeStreams || 0).toBeLessThanOrEqual(1);
});
});