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:
@@ -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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user