import { test, expect } from '@playwright/test'; /** * Smoke Tests - Critical Path Coverage * * These tests navigate to every major page and verify: * 1. Page loads without 500 errors * 2. No JavaScript console errors * 3. No LiveView socket crashes (Jason.EncodeError, etc.) * 4. Basic content renders * * Purpose: Catch server-side crashes (like binary UUID encoding bugs) * before they reach production. */ test.describe('Smoke Tests - All Pages Load Without Errors', () => { let consoleErrors: string[]; let pageErrors: Error[]; test.beforeEach(async ({ page }) => { consoleErrors = []; pageErrors = []; // Capture console errors page.on('console', msg => { if (msg.type() === 'error') { consoleErrors.push(msg.text()); } }); // Capture page errors (uncaught exceptions) page.on('pageerror', error => { pageErrors.push(error); }); }); test.afterEach(async ({ page }) => { // Report any errors found if (consoleErrors.length > 0) { console.log('Console errors:', consoleErrors); } if (pageErrors.length > 0) { console.log('Page errors:', pageErrors); } // Don't fail tests on errors - just report them // Some pages might have expected errors (missing data, etc.) }); test('dashboard loads without errors', async ({ page }) => { const response = await page.goto('/dashboard'); expect(response?.status()).toBeLessThan(500); await page.waitForTimeout(1000); // Verify basic content loaded await expect(page.locator('body')).toBeVisible(); // Check for common error indicators const errorText = await page.locator(':has-text("error"), :has-text("Error")').first().isVisible({ timeout: 1000 }).catch(() => false); if (errorText) { console.warn('Dashboard may have errors visible on page'); } }); test('devices list loads without errors', async ({ page }) => { const response = await page.goto('/devices'); expect(response?.status()).toBeLessThan(500); await page.waitForTimeout(1000); await expect(page.locator('body')).toBeVisible(); }); test('sites list loads without errors', async ({ page }) => { const response = await page.goto('/sites'); expect(response?.status()).toBeLessThan(500); await page.waitForTimeout(1000); await expect(page.locator('body')).toBeVisible(); }); test('alerts list loads without errors', async ({ page }) => { const response = await page.goto('/alerts'); expect(response?.status()).toBeLessThan(500); await page.waitForTimeout(1000); await expect(page.locator('body')).toBeVisible(); }); test('agents list loads without errors', async ({ page }) => { const response = await page.goto('/agents'); expect(response?.status()).toBeLessThan(500); await page.waitForTimeout(1000); await expect(page.locator('body')).toBeVisible(); }); test('activity feed loads without errors', async ({ page }) => { const response = await page.goto('/activity'); expect(response?.status()).toBeLessThan(500); await page.waitForTimeout(1000); await expect(page.locator('body')).toBeVisible(); // This page specifically had the binary UUID bug // Verify no LiveView socket crash const socketError = await page.locator(':has-text("socket"), :has-text("crash")').first().isVisible({ timeout: 1000 }).catch(() => false); expect(socketError).toBe(false); }); test('trace page loads without errors', async ({ page }) => { const response = await page.goto('/trace'); expect(response?.status()).toBeLessThan(500); await page.waitForTimeout(1000); await expect(page.locator('body')).toBeVisible(); }); test('integrations page loads without errors', async ({ page }) => { const response = await page.goto('/integrations'); expect(response?.status()).toBeLessThan(500); await page.waitForTimeout(1000); await expect(page.locator('body')).toBeVisible(); }); test('organization settings loads without errors', async ({ page }) => { const response = await page.goto('/org/settings'); // Skip if redirected to sudo verify if ((await page.url()).includes('sudo-verify')) { return; } expect(response?.status()).toBeLessThan(500); await page.waitForTimeout(1000); await expect(page.locator('body')).toBeVisible(); }); test('user settings loads without errors', async ({ page }) => { const response = await page.goto('/users/settings'); // Skip if redirected to sudo verify if ((await page.url()).includes('sudo-verify')) { return; } expect(response?.status()).toBeLessThan(500); await page.waitForTimeout(1000); await expect(page.locator('body')).toBeVisible(); }); test('billing page loads without errors', async ({ page }) => { const response = await page.goto('/org/billing'); // Skip if redirected to sudo verify if ((await page.url()).includes('sudo-verify')) { return; } expect(response?.status()).toBeLessThan(500); await page.waitForTimeout(1000); await expect(page.locator('body')).toBeVisible(); }); test.describe('Device Detail Pages', () => { test('device detail loads without errors', async ({ page }) => { // First get a device ID await page.goto('/devices'); const firstDevice = page.locator('a[href*="/devices/"]:not([href*="/devices/new"])').first(); if (await firstDevice.isVisible({ timeout: 2000 }).catch(() => false)) { const href = await firstDevice.getAttribute('href'); if (href) { const response = await page.goto(href); expect(response?.status()).toBeLessThan(500); await page.waitForTimeout(1000); await expect(page.locator('body')).toBeVisible(); } } }); test('device config timeline loads without errors', async ({ page }) => { await page.goto('/devices'); const firstDevice = page.locator('a[href*="/devices/"]:not([href*="/devices/new"])').first(); if (await firstDevice.isVisible({ timeout: 2000 }).catch(() => false)) { const href = await firstDevice.getAttribute('href'); if (href) { const deviceId = href.split('/').pop(); const response = await page.goto(`/devices/${deviceId}/config-timeline`); expect(response?.status()).toBeLessThan(500); await page.waitForTimeout(1000); await expect(page.locator('body')).toBeVisible(); } } }); test('device interfaces loads without errors', async ({ page }) => { await page.goto('/devices'); const firstDevice = page.locator('a[href*="/devices/"]:not([href*="/devices/new"])').first(); if (await firstDevice.isVisible({ timeout: 2000 }).catch(() => false)) { const href = await firstDevice.getAttribute('href'); if (href) { const deviceId = href.split('/').pop(); const response = await page.goto(`/devices/${deviceId}/interfaces`); expect(response?.status()).toBeLessThan(500); await page.waitForTimeout(1000); await expect(page.locator('body')).toBeVisible(); } } }); test('device neighbors loads without errors', async ({ page }) => { await page.goto('/devices'); const firstDevice = page.locator('a[href*="/devices/"]:not([href*="/devices/new"])').first(); if (await firstDevice.isVisible({ timeout: 2000 }).catch(() => false)) { const href = await firstDevice.getAttribute('href'); if (href) { const deviceId = href.split('/').pop(); const response = await page.goto(`/devices/${deviceId}/neighbors`); expect(response?.status()).toBeLessThan(500); await page.waitForTimeout(1000); await expect(page.locator('body')).toBeVisible(); } } }); }); test.describe('Site Detail Pages', () => { test('site detail loads without errors', async ({ page }) => { await page.goto('/sites'); const firstSite = page.locator('a[href*="/sites/"]:not([href*="/sites/new"])').first(); if (await firstSite.isVisible({ timeout: 2000 }).catch(() => false)) { const href = await firstSite.getAttribute('href'); if (href) { const response = await page.goto(href); expect(response?.status()).toBeLessThan(500); await page.waitForTimeout(1000); await expect(page.locator('body')).toBeVisible(); } } }); test('site map view loads without errors', async ({ page }) => { await page.goto('/sites'); const firstSite = page.locator('a[href*="/sites/"]:not([href*="/sites/new"])').first(); if (await firstSite.isVisible({ timeout: 2000 }).catch(() => false)) { const href = await firstSite.getAttribute('href'); if (href) { const siteId = href.split('/').pop(); const response = await page.goto(`/sites/${siteId}/map`); expect(response?.status()).toBeLessThan(500); await page.waitForTimeout(1000); await expect(page.locator('body')).toBeVisible(); } } }); }); test.describe('LiveView Socket Stability', () => { test('activity feed maintains socket connection', async ({ page }) => { await page.goto('/activity'); // Wait for initial load await page.waitForTimeout(2000); // Verify no socket errors in console const hasSocketError = consoleErrors.some(err => err.includes('socket') || err.includes('Jason') || err.includes('encode') ); expect(hasSocketError).toBe(false); }); test('dashboard maintains socket connection', async ({ page }) => { await page.goto('/dashboard'); await page.waitForTimeout(2000); const hasSocketError = consoleErrors.some(err => err.includes('socket') || err.includes('Jason') || err.includes('encode') ); expect(hasSocketError).toBe(false); }); test('devices list maintains socket connection', async ({ page }) => { await page.goto('/devices'); await page.waitForTimeout(2000); const hasSocketError = consoleErrors.some(err => err.includes('socket') || err.includes('Jason') || err.includes('encode') ); expect(hasSocketError).toBe(false); }); }); test.describe('Edge Cases - Binary Data Handling', () => { test('activity feed with sync events renders correctly', async ({ page }) => { await page.goto('/activity'); await page.waitForTimeout(1000); // Look for sync-type activities (the ones that had binary UUID issue) const syncActivity = page.locator('[data-activity-type="sync"], :has-text("sync")').first(); if (await syncActivity.isVisible({ timeout: 2000 }).catch(() => false)) { // If sync activities exist, verify they rendered await expect(syncActivity).toBeVisible(); } // Verify no encoding errors const encodingError = await page.locator(':has-text("EncodeError"), :has-text("invalid byte")').first().isVisible({ timeout: 1000 }).catch(() => false); expect(encodingError).toBe(false); }); test('all activity feed filter types work', async ({ page }) => { await page.goto('/activity'); await page.waitForTimeout(1000); const filterTypes = [ 'Config', 'Alerts', 'Resolved', 'Events', 'Syncs', 'Devices' ]; for (const filterType of filterTypes) { const filterButton = page.locator(`button:has-text("${filterType}")`).first(); if (await filterButton.isVisible({ timeout: 1000 }).catch(() => false)) { await filterButton.click(); await page.waitForTimeout(500); // Verify page still works await expect(page.locator('body')).toBeVisible(); // Verify no errors const hasError = consoleErrors.some(err => err.includes('EncodeError') || err.includes('invalid byte') ); expect(hasError).toBe(false); // Toggle back off await filterButton.click(); await page.waitForTimeout(300); } } }); }); });