/** * E2E Test: Scenario Comparison * * Tests for: * - Select multiple scenarios * - Navigate to compare page * - Verify comparison data */ import { test, expect } from '@playwright/test'; import { navigateTo, waitForLoading, createScenarioViaAPI, deleteScenarioViaAPI, startScenarioViaAPI, sendTestLogs, generateTestScenarioName, } from './utils/test-helpers'; import { testLogs } from './fixtures/test-logs'; import { newScenarioData } from './fixtures/test-scenarios'; const testScenarioPrefix = 'Compare Test'; let createdScenarioIds: string[] = []; test.describe('Scenario Comparison', () => { test.beforeAll(async ({ request }) => { // Create multiple scenarios for comparison for (let i = 1; i <= 3; i++) { const scenario = await createScenarioViaAPI(request, { ...newScenarioData, name: generateTestScenarioName(`${testScenarioPrefix} ${i}`), region: ['us-east-1', 'eu-west-1', 'ap-southeast-1'][i - 1], }); createdScenarioIds.push(scenario.id); // Start and add some logs to make scenarios more realistic await startScenarioViaAPI(request, scenario.id); await sendTestLogs(request, scenario.id, testLogs.slice(0, i * 2)); } }); test.afterAll(async ({ request }) => { // Cleanup all created scenarios for (const scenarioId of createdScenarioIds) { try { await request.post(`http://localhost:8000/api/v1/scenarios/${scenarioId}/stop`); } catch { // Scenario might not be running } await deleteScenarioViaAPI(request, scenarioId); } createdScenarioIds = []; }); test('should display scenarios list for comparison selection', async ({ page }) => { await navigateTo(page, '/scenarios'); await waitForLoading(page); // Verify scenarios page loads await expect(page.getByRole('heading', { name: 'Scenarios' })).toBeVisible(); // Verify table with scenarios is visible const table = page.locator('table'); await expect(table).toBeVisible(); // Verify at least our test scenarios are visible const rows = table.locator('tbody tr'); await expect(rows).toHaveCount((await rows.count()) >= 3); }); test('should navigate to compare page via API', async ({ page, request }) => { // Try to access compare page directly const response = await request.post( 'http://localhost:8000/api/v1/scenarios/compare', { data: { scenario_ids: createdScenarioIds.slice(0, 2), metrics: ['total_cost', 'total_requests'], }, } ); if (response.status() === 404) { test.skip(); } if (response.ok()) { const data = await response.json(); // Verify response structure expect(data).toHaveProperty('scenarios'); expect(data).toHaveProperty('comparison'); expect(Array.isArray(data.scenarios)).toBe(true); expect(data.scenarios.length).toBe(2); } }); test('should compare 2 scenarios', async ({ request }) => { const response = await request.post( 'http://localhost:8000/api/v1/scenarios/compare', { data: { scenario_ids: createdScenarioIds.slice(0, 2), metrics: ['total_cost', 'total_requests', 'sqs_blocks'], }, } ); if (response.status() === 404) { test.skip(); } if (response.ok()) { const data = await response.json(); expect(data.scenarios).toHaveLength(2); expect(data.comparison).toBeDefined(); } }); test('should compare 3 scenarios', async ({ request }) => { const response = await request.post( 'http://localhost:8000/api/v1/scenarios/compare', { data: { scenario_ids: createdScenarioIds, metrics: ['total_cost', 'total_requests', 'lambda_invocations'], }, } ); if (response.status() === 404) { test.skip(); } if (response.ok()) { const data = await response.json(); expect(data.scenarios).toHaveLength(3); expect(data.comparison).toBeDefined(); } }); test('should compare 4 scenarios (max allowed)', async ({ request }) => { // Create a 4th scenario const scenario4 = await createScenarioViaAPI(request, { ...newScenarioData, name: generateTestScenarioName(`${testScenarioPrefix} 4`), }); try { const response = await request.post( 'http://localhost:8000/api/v1/scenarios/compare', { data: { scenario_ids: [...createdScenarioIds, scenario4.id], metrics: ['total_cost'], }, } ); if (response.status() === 404) { test.skip(); } if (response.ok()) { const data = await response.json(); expect(data.scenarios).toHaveLength(4); } } finally { await deleteScenarioViaAPI(request, scenario4.id); } }); test('should reject comparison with more than 4 scenarios', async ({ request }) => { // Create additional scenarios const extraScenarios: string[] = []; for (let i = 0; i < 2; i++) { const scenario = await createScenarioViaAPI(request, { ...newScenarioData, name: generateTestScenarioName(`${testScenarioPrefix} Extra ${i}`), }); extraScenarios.push(scenario.id); } try { const response = await request.post( 'http://localhost:8000/api/v1/scenarios/compare', { data: { scenario_ids: [...createdScenarioIds, ...extraScenarios], metrics: ['total_cost'], }, } ); if (response.status() === 404) { test.skip(); } // Should return 400 for too many scenarios expect(response.status()).toBe(400); } finally { // Cleanup extra scenarios for (const id of extraScenarios) { await deleteScenarioViaAPI(request, id); } } }); test('should reject comparison with invalid scenario IDs', async ({ request }) => { const response = await request.post( 'http://localhost:8000/api/v1/scenarios/compare', { data: { scenario_ids: ['invalid-id-1', 'invalid-id-2'], metrics: ['total_cost'], }, } ); if (response.status() === 404) { test.skip(); } // Should return 400 or 404 for invalid IDs expect([400, 404]).toContain(response.status()); }); test('should reject comparison with single scenario', async ({ request }) => { const response = await request.post( 'http://localhost:8000/api/v1/scenarios/compare', { data: { scenario_ids: [createdScenarioIds[0]], metrics: ['total_cost'], }, } ); if (response.status() === 404) { test.skip(); } // Should return 400 for single scenario expect(response.status()).toBe(400); }); test('should include delta calculations in comparison', async ({ request }) => { const response = await request.post( 'http://localhost:8000/api/v1/scenarios/compare', { data: { scenario_ids: createdScenarioIds.slice(0, 2), metrics: ['total_cost', 'total_requests'], }, } ); if (response.status() === 404) { test.skip(); } if (response.ok()) { const data = await response.json(); // Verify comparison includes deltas expect(data.comparison).toBeDefined(); if (data.comparison.total_cost) { expect(data.comparison.total_cost).toHaveProperty('baseline'); expect(data.comparison.total_cost).toHaveProperty('variance'); } } }); test('should support comparison export', async ({ request }) => { const response = await request.post( 'http://localhost:8000/api/v1/scenarios/compare', { data: { scenario_ids: createdScenarioIds.slice(0, 2), metrics: ['total_cost', 'total_requests'], }, } ); if (response.status() === 404) { test.skip(); } if (response.ok()) { // If compare API exists, check if export is available const exportResponse = await request.get( `http://localhost:8000/api/v1/scenarios/compare/export?ids=${createdScenarioIds.slice(0, 2).join(',')}&format=csv` ); // Export might not exist yet if (exportResponse.status() !== 404) { expect(exportResponse.ok()).toBeTruthy(); } } }); }); test.describe('Comparison UI Tests', () => { test('should navigate to compare page from sidebar', async ({ page }) => { await navigateTo(page, '/'); await waitForLoading(page); // Verify dashboard loads await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible(); // Try to navigate to compare page (if it exists) const compareResponse = await page.request.get('http://localhost:5173/compare'); if (compareResponse.status() === 200) { await navigateTo(page, '/compare'); await waitForLoading(page); // Verify compare page elements await expect(page.locator('body')).toBeVisible(); } }); test('should display scenarios in comparison view', async ({ page }) => { // Navigate to scenarios page await navigateTo(page, '/scenarios'); await waitForLoading(page); // Verify scenarios are listed const table = page.locator('table tbody'); await expect(table).toBeVisible(); // Verify table has rows const rows = table.locator('tr'); const rowCount = await rows.count(); expect(rowCount).toBeGreaterThan(0); }); test('should show comparison metrics table', async ({ page }) => { await navigateTo(page, '/scenarios'); await waitForLoading(page); // Verify metrics columns exist await expect(page.getByRole('columnheader', { name: /requests/i })).toBeVisible(); await expect(page.getByRole('columnheader', { name: /cost/i })).toBeVisible(); }); test('should highlight best/worst performers', async ({ page }) => { // This test verifies the UI elements exist for comparison highlighting await navigateTo(page, '/scenarios'); await waitForLoading(page); // Verify table with color-coded status exists const table = page.locator('table'); await expect(table).toBeVisible(); }); }); test.describe('Comparison Performance', () => { test('should load comparison data within acceptable time', async ({ request }) => { const startTime = Date.now(); const response = await request.post( 'http://localhost:8000/api/v1/scenarios/compare', { data: { scenario_ids: createdScenarioIds.slice(0, 2), metrics: ['total_cost', 'total_requests'], }, } ); const duration = Date.now() - startTime; if (response.status() === 404) { test.skip(); } // Should complete within 5 seconds expect(duration).toBeLessThan(5000); }); test('should cache comparison results', async ({ request }) => { const requestBody = { scenario_ids: createdScenarioIds.slice(0, 2), metrics: ['total_cost'], }; // First request const response1 = await request.post( 'http://localhost:8000/api/v1/scenarios/compare', { data: requestBody } ); if (response1.status() === 404) { test.skip(); } // Second identical request (should be cached) const startTime = Date.now(); const response2 = await request.post( 'http://localhost:8000/api/v1/scenarios/compare', { data: requestBody } ); const duration = Date.now() - startTime; // Cached response should be very fast if (response2.ok()) { expect(duration).toBeLessThan(1000); } }); });