import { test, expect } from '@playwright/test'; test.describe('Frontend Logging System', () => { let logRequests: any[] = []; test.beforeEach(async ({ page }) => { logRequests = []; // Intercept log requests await page.route('**/api/log', async (route) => { const request = route.request(); const postData = request.postDataJSON(); // Store the log data if (Array.isArray(postData)) { logRequests.push(...postData); } else { logRequests.push(postData); } // Respond with success await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ ok: true }), }); }); }); test('should log APP_START on page load', async ({ page }) => { await page.goto('/'); // Wait for logs to be sent await page.waitForTimeout(3000); const appStartLogs = logRequests.filter(log => log.event === 'APP_START'); expect(appStartLogs.length).toBeGreaterThan(0); const log = appStartLogs[0]; expect(log.app).toBe('ObsiViewer'); expect(log.sessionId).toBeTruthy(); expect(log.context).toBeDefined(); }); test('should log SEARCH_EXECUTED on search', async ({ page }) => { await page.goto('/'); // Find search input and perform search const searchInput = page.locator('input[type="search"], input[placeholder*="search" i]').first(); await searchInput.fill('test query'); await searchInput.press('Enter'); // Wait for logs await page.waitForTimeout(3000); const searchLogs = logRequests.filter(log => log.event === 'SEARCH_EXECUTED'); expect(searchLogs.length).toBeGreaterThan(0); const log = searchLogs[0]; expect(log.data.query).toBe('test query'); }); test('should log BOOKMARKS_OPEN when opening bookmarks', async ({ page }) => { await page.goto('/'); // Click bookmarks button/tab const bookmarksButton = page.locator('button:has-text("Bookmarks"), [data-testid="bookmarks-tab"]').first(); if (await bookmarksButton.isVisible()) { await bookmarksButton.click(); // Wait for logs await page.waitForTimeout(3000); const bookmarksLogs = logRequests.filter(log => log.event === 'BOOKMARKS_OPEN'); expect(bookmarksLogs.length).toBeGreaterThan(0); } }); test('should log GRAPH_VIEW_OPEN when opening graph view', async ({ page }) => { await page.goto('/'); // Click graph view button/tab const graphButton = page.locator('button:has-text("Graph"), [data-testid="graph-tab"]').first(); if (await graphButton.isVisible()) { await graphButton.click(); // Wait for logs await page.waitForTimeout(3000); const graphLogs = logRequests.filter(log => log.event === 'GRAPH_VIEW_OPEN'); expect(graphLogs.length).toBeGreaterThan(0); } }); test('should log THEME_CHANGE when toggling theme', async ({ page }) => { await page.goto('/'); // Find and click theme toggle button const themeButton = page.locator('button[aria-label*="theme" i], button:has-text("Theme")').first(); if (await themeButton.isVisible()) { await themeButton.click(); // Wait for logs await page.waitForTimeout(3000); const themeLogs = logRequests.filter(log => log.event === 'THEME_CHANGE'); expect(themeLogs.length).toBeGreaterThan(0); const log = themeLogs[0]; expect(log.data.from).toBeDefined(); expect(log.data.to).toBeDefined(); } }); test('should include session ID in all logs', async ({ page }) => { await page.goto('/'); // Perform multiple actions const searchInput = page.locator('input[type="search"]').first(); if (await searchInput.isVisible()) { await searchInput.fill('test'); await searchInput.press('Enter'); } // Wait for logs await page.waitForTimeout(3000); expect(logRequests.length).toBeGreaterThan(0); const sessionIds = new Set(logRequests.map(log => log.sessionId)); expect(sessionIds.size).toBe(1); // All logs should have same session ID }); test('should include context in all logs', async ({ page }) => { await page.goto('/'); // Wait for logs await page.waitForTimeout(3000); expect(logRequests.length).toBeGreaterThan(0); logRequests.forEach(log => { expect(log.context).toBeDefined(); expect(log.context.version).toBeDefined(); expect(log.context.route).toBeDefined(); }); }); test('should batch logs when multiple events occur', async ({ page }) => { await page.goto('/'); // Perform multiple quick actions const searchInput = page.locator('input[type="search"]').first(); if (await searchInput.isVisible()) { await searchInput.fill('test1'); await searchInput.press('Enter'); await page.waitForTimeout(100); await searchInput.fill('test2'); await searchInput.press('Enter'); await page.waitForTimeout(100); await searchInput.fill('test3'); await searchInput.press('Enter'); } // Wait for batched logs await page.waitForTimeout(3000); // Should have received logs (possibly batched) expect(logRequests.length).toBeGreaterThan(0); }); test('should handle offline scenario', async ({ page, context }) => { await page.goto('/'); // Go offline await context.setOffline(true); // Perform actions while offline const searchInput = page.locator('input[type="search"]').first(); if (await searchInput.isVisible()) { await searchInput.fill('offline test'); await searchInput.press('Enter'); } await page.waitForTimeout(1000); const offlineLogCount = logRequests.length; // Go back online await context.setOffline(false); // Wait for queued logs to be sent await page.waitForTimeout(5000); // Should have received more logs after going online expect(logRequests.length).toBeGreaterThanOrEqual(offlineLogCount); }); });