clone of repo on github
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

365 lines
14 KiB

import { test, expect } from '@playwright/test';
// Performance thresholds based on POC targets
const PERFORMANCE_TARGETS = {
visualUpdate: 50, // <50ms for visual updates
fullUpdate: 200, // Baseline for full updates
positionDrift: 5, // Max pixels of position drift
memoryIncrease: 10 // Max % memory increase per update
};
test.describe('Shallow Copy POC Performance Validation', () => {
// Helper to extract console logs
const consoleLogs: string[] = [];
test.beforeEach(async ({ page }) => {
// Clear logs
consoleLogs.length = 0;
// Capture console logs
page.on('console', msg => {
if (msg.type() === 'log' && msg.text().includes('[EventNetwork]')) {
consoleLogs.push(msg.text());
}
});
// Navigate to visualization page
await page.goto('http://localhost:5175/visualize');
// Wait for initial load
await page.waitForSelector('.network-svg', { timeout: 10000 });
await page.waitForTimeout(2000); // Allow graph to stabilize
});
test('star visualization toggle uses visual update path', async ({ page }) => {
// Enable settings panel
const settings = page.locator('.leather-legend').nth(1);
const settingsToggle = settings.locator('button').first();
await settingsToggle.click();
// Ensure visual settings section is expanded
const visualSettingsHeader = settings.locator('.settings-section-header').filter({ hasText: 'Visual Settings' });
await visualSettingsHeader.click();
// Clear previous logs
consoleLogs.length = 0;
// Toggle star visualization
const starToggle = settings.locator('label').filter({ hasText: 'Star Network View' }).locator('input[type="checkbox"]');
await starToggle.click();
// Wait for update
await page.waitForTimeout(100);
// Check logs for update type
const updateLogs = consoleLogs.filter(log => log.includes('Update type detected'));
expect(updateLogs.length).toBeGreaterThan(0);
const lastUpdateLog = updateLogs[updateLogs.length - 1];
expect(lastUpdateLog).toContain('kind: "visual"');
expect(lastUpdateLog).toContain('star');
// Check for visual properties update
const visualUpdateLogs = consoleLogs.filter(log => log.includes('updateVisualProperties called'));
expect(visualUpdateLogs.length).toBeGreaterThan(0);
// Extract timing
const timingLogs = consoleLogs.filter(log => log.includes('Visual properties updated in'));
if (timingLogs.length > 0) {
const match = timingLogs[0].match(/(\d+\.\d+)ms/);
if (match) {
const updateTime = parseFloat(match[1]);
expect(updateTime).toBeLessThan(PERFORMANCE_TARGETS.visualUpdate);
console.log(`Star toggle update time: ${updateTime}ms`);
}
}
});
test('tag visibility toggle uses visual update path', async ({ page }) => {
// Enable settings and tag anchors
const settings = page.locator('.leather-legend').nth(1);
const settingsToggle = settings.locator('button').first();
await settingsToggle.click();
// Enable tag anchors
const visualSettingsHeader = settings.locator('.settings-section-header').filter({ hasText: 'Visual Settings' });
await visualSettingsHeader.click();
const tagAnchorsToggle = settings.locator('label').filter({ hasText: 'Show Tag Anchors' }).locator('input[type="checkbox"]');
await tagAnchorsToggle.click();
// Wait for tags to appear
await page.waitForTimeout(1000);
const legend = page.locator('.leather-legend').first();
const tagSection = legend.locator('.legend-section').filter({ hasText: 'Active Tag Anchors' });
if (await tagSection.count() > 0) {
// Expand tag section if needed
const tagHeader = tagSection.locator('.legend-section-header');
const tagGrid = tagSection.locator('.tag-grid');
if (!(await tagGrid.isVisible())) {
await tagHeader.click();
}
// Clear logs
consoleLogs.length = 0;
// Toggle first tag
const firstTag = tagGrid.locator('.tag-grid-item').first();
await firstTag.click();
// Wait for update
await page.waitForTimeout(100);
// Check for visual update
const updateLogs = consoleLogs.filter(log => log.includes('Update type detected'));
expect(updateLogs.length).toBeGreaterThan(0);
const lastUpdateLog = updateLogs[updateLogs.length - 1];
expect(lastUpdateLog).toContain('kind: "visual"');
expect(lastUpdateLog).toContain('disabledCount');
// Check timing
const timingLogs = consoleLogs.filter(log => log.includes('Visual properties updated in'));
if (timingLogs.length > 0) {
const match = timingLogs[0].match(/(\d+\.\d+)ms/);
if (match) {
const updateTime = parseFloat(match[1]);
expect(updateTime).toBeLessThan(PERFORMANCE_TARGETS.visualUpdate);
console.log(`Tag toggle update time: ${updateTime}ms`);
}
}
}
});
test('position preservation during visual updates', async ({ page }) => {
// Get initial node positions
const getNodePositions = async () => {
return await page.evaluate(() => {
const nodes = document.querySelectorAll('.network-svg g.node');
const positions: { [id: string]: { x: number; y: number } } = {};
nodes.forEach((node) => {
const transform = node.getAttribute('transform');
const match = transform?.match(/translate\(([\d.-]+),([\d.-]+)\)/);
if (match) {
const nodeId = (node as any).__data__?.id || 'unknown';
positions[nodeId] = {
x: parseFloat(match[1]),
y: parseFloat(match[2])
};
}
});
return positions;
});
};
// Capture initial positions
const initialPositions = await getNodePositions();
const nodeCount = Object.keys(initialPositions).length;
expect(nodeCount).toBeGreaterThan(0);
// Toggle star visualization
const settings = page.locator('.leather-legend').nth(1);
const settingsToggle = settings.locator('button').first();
await settingsToggle.click();
const visualSettingsHeader = settings.locator('.settings-section-header').filter({ hasText: 'Visual Settings' });
await visualSettingsHeader.click();
const starToggle = settings.locator('label').filter({ hasText: 'Star Network View' }).locator('input[type="checkbox"]');
await starToggle.click();
// Wait for visual update
await page.waitForTimeout(500);
// Get positions after update
const updatedPositions = await getNodePositions();
// Check position preservation
let maxDrift = 0;
let driftCount = 0;
Object.keys(initialPositions).forEach(nodeId => {
if (updatedPositions[nodeId]) {
const initial = initialPositions[nodeId];
const updated = updatedPositions[nodeId];
const drift = Math.sqrt(
Math.pow(updated.x - initial.x, 2) +
Math.pow(updated.y - initial.y, 2)
);
if (drift > PERFORMANCE_TARGETS.positionDrift) {
driftCount++;
maxDrift = Math.max(maxDrift, drift);
}
}
});
// Positions should be mostly preserved (some drift due to force changes is OK)
const driftPercentage = (driftCount / nodeCount) * 100;
expect(driftPercentage).toBeLessThan(20); // Less than 20% of nodes should drift significantly
console.log(`Position drift: ${driftCount}/${nodeCount} nodes (${driftPercentage.toFixed(1)}%), max drift: ${maxDrift.toFixed(1)}px`);
});
test('simulation maintains momentum', async ({ page }) => {
// Check simulation alpha values in logs
const settings = page.locator('.leather-legend').nth(1);
const settingsToggle = settings.locator('button').first();
await settingsToggle.click();
const visualSettingsHeader = settings.locator('.settings-section-header').filter({ hasText: 'Visual Settings' });
await visualSettingsHeader.click();
// Clear logs
consoleLogs.length = 0;
// Toggle star mode
const starToggle = settings.locator('label').filter({ hasText: 'Star Network View' }).locator('input[type="checkbox"]');
await starToggle.click();
await page.waitForTimeout(100);
// Check for gentle restart
const alphaLogs = consoleLogs.filter(log => log.includes('simulation restarted with alpha'));
expect(alphaLogs.length).toBeGreaterThan(0);
// Should use alpha 0.3 for visual updates
expect(alphaLogs[0]).toContain('alpha 0.3');
});
test('rapid parameter changes are handled efficiently', async ({ page }) => {
const settings = page.locator('.leather-legend').nth(1);
const settingsToggle = settings.locator('button').first();
await settingsToggle.click();
const visualSettingsHeader = settings.locator('.settings-section-header').filter({ hasText: 'Visual Settings' });
await visualSettingsHeader.click();
// Clear logs
consoleLogs.length = 0;
// Perform rapid toggles
const starToggle = settings.locator('label').filter({ hasText: 'Star Network View' }).locator('input[type="checkbox"]');
const startTime = Date.now();
for (let i = 0; i < 5; i++) {
await starToggle.click();
await page.waitForTimeout(50); // Very short delay
}
const totalTime = Date.now() - startTime;
// Check that all updates completed
await page.waitForTimeout(500);
// Count visual updates
const visualUpdateCount = consoleLogs.filter(log => log.includes('updateVisualProperties called')).length;
expect(visualUpdateCount).toBeGreaterThanOrEqual(3); // At least some updates should process
console.log(`Rapid toggle test: ${visualUpdateCount} visual updates in ${totalTime}ms`);
});
test('memory stability during visual updates', async ({ page }) => {
// Get initial memory usage
const getMemoryUsage = async () => {
return await page.evaluate(() => {
if ('memory' in performance) {
return (performance as any).memory.usedJSHeapSize;
}
return 0;
});
};
const initialMemory = await getMemoryUsage();
if (initialMemory === 0) {
test.skip();
return;
}
const settings = page.locator('.leather-legend').nth(1);
const settingsToggle = settings.locator('button').first();
await settingsToggle.click();
const visualSettingsHeader = settings.locator('.settings-section-header').filter({ hasText: 'Visual Settings' });
await visualSettingsHeader.click();
const starToggle = settings.locator('label').filter({ hasText: 'Star Network View' }).locator('input[type="checkbox"]');
// Perform multiple toggles
for (let i = 0; i < 10; i++) {
await starToggle.click();
await page.waitForTimeout(100);
}
// Force garbage collection if available
await page.evaluate(() => {
if ('gc' in window) {
(window as any).gc();
}
});
await page.waitForTimeout(1000);
const finalMemory = await getMemoryUsage();
const memoryIncrease = ((finalMemory - initialMemory) / initialMemory) * 100;
console.log(`Memory usage: Initial ${(initialMemory / 1024 / 1024).toFixed(2)}MB, Final ${(finalMemory / 1024 / 1024).toFixed(2)}MB, Increase: ${memoryIncrease.toFixed(2)}%`);
// Memory increase should be minimal
expect(memoryIncrease).toBeLessThan(PERFORMANCE_TARGETS.memoryIncrease);
});
test('comparison: visual update vs full update performance', async ({ page }) => {
const settings = page.locator('.leather-legend').nth(1);
const settingsToggle = settings.locator('button').first();
await settingsToggle.click();
// Test visual update (star toggle)
const visualSettingsHeader = settings.locator('.settings-section-header').filter({ hasText: 'Visual Settings' });
await visualSettingsHeader.click();
consoleLogs.length = 0;
const starToggle = settings.locator('label').filter({ hasText: 'Star Network View' }).locator('input[type="checkbox"]');
await starToggle.click();
await page.waitForTimeout(200);
let visualUpdateTime = 0;
const visualTimingLogs = consoleLogs.filter(log => log.includes('Visual properties updated in'));
if (visualTimingLogs.length > 0) {
const match = visualTimingLogs[0].match(/(\d+\.\d+)ms/);
if (match) {
visualUpdateTime = parseFloat(match[1]);
}
}
// Test full update (fetch limit change)
const initialLoadHeader = settings.locator('.settings-section-header').filter({ hasText: 'Initial Load' });
await initialLoadHeader.click();
consoleLogs.length = 0;
const fetchLimitInput = settings.locator('input[type="number"]').first();
await fetchLimitInput.fill('20');
await page.keyboard.press('Enter');
await page.waitForTimeout(500);
let fullUpdateTime = 0;
const fullTimingLogs = consoleLogs.filter(log => log.includes('updateGraph completed in'));
if (fullTimingLogs.length > 0) {
const match = fullTimingLogs[0].match(/(\d+\.\d+)ms/);
if (match) {
fullUpdateTime = parseFloat(match[1]);
}
}
console.log(`Performance comparison:
- Visual update: ${visualUpdateTime.toFixed(2)}ms
- Full update: ${fullUpdateTime.toFixed(2)}ms
- Improvement: ${((1 - visualUpdateTime / fullUpdateTime) * 100).toFixed(1)}%`);
// Visual updates should be significantly faster
expect(visualUpdateTime).toBeLessThan(fullUpdateTime * 0.5); // At least 50% faster
expect(visualUpdateTime).toBeLessThan(PERFORMANCE_TARGETS.visualUpdate);
});
});