708 lines
23 KiB
TypeScript
708 lines
23 KiB
TypeScript
/**
|
|
* Comprehensive Performance Profiling Suite
|
|
*
|
|
* Profiles all major user flows:
|
|
* - Recording flow
|
|
* - Diarization flow
|
|
* - Transcription flow
|
|
* - Summarization flow
|
|
* - Entity extraction flow
|
|
* - Playback flow
|
|
* - Analytics computation
|
|
* - UI access paths and navigation
|
|
*/
|
|
|
|
import { expect, test, type Page } from '@playwright/test';
|
|
import { callAPI, navigateTo, TEST_DATA, waitForAPI, waitForLoadingComplete } from './fixtures';
|
|
|
|
const shouldRun = process.env.NOTEFLOW_E2E === '1';
|
|
|
|
// Performance thresholds (milliseconds)
|
|
const THRESHOLDS = {
|
|
PAGE_LOAD: 3000,
|
|
API_CALL: 2000,
|
|
NAVIGATION: 1500,
|
|
LIST_RENDER: 1000,
|
|
HEAVY_COMPUTATION: 10000,
|
|
REAL_TIME_UPDATE: 500,
|
|
};
|
|
|
|
interface PerformanceMetric {
|
|
name: string;
|
|
duration: number;
|
|
threshold: number;
|
|
passed: boolean;
|
|
details?: string;
|
|
}
|
|
|
|
const metrics: PerformanceMetric[] = [];
|
|
|
|
function recordMetric(name: string, duration: number, threshold: number, details?: string) {
|
|
const passed = duration <= threshold;
|
|
metrics.push({ name, duration, threshold, passed, details });
|
|
console.log(
|
|
`[PERF] ${passed ? '✓' : '✗'} ${name}: ${duration.toFixed(0)}ms (threshold: ${threshold}ms)${details ? ` - ${details}` : ''}`
|
|
);
|
|
return passed;
|
|
}
|
|
|
|
async function measureAsync<T>(fn: () => Promise<T>): Promise<{ result: T; duration: number }> {
|
|
const start = performance.now();
|
|
const result = await fn();
|
|
const duration = performance.now() - start;
|
|
return { result, duration };
|
|
}
|
|
|
|
// =============================================================================
|
|
// 1. PAGE LOAD PERFORMANCE
|
|
// =============================================================================
|
|
|
|
test.describe('Page Load Performance', () => {
|
|
test.skip(!shouldRun, 'Set NOTEFLOW_E2E=1 to enable');
|
|
|
|
const pages = [
|
|
{ path: '/', name: 'Home' },
|
|
{ path: '/meetings', name: 'Meetings List' },
|
|
{ path: '/tasks', name: 'Tasks' },
|
|
{ path: '/people', name: 'People' },
|
|
{ path: '/analytics', name: 'Analytics' },
|
|
{ path: '/settings', name: 'Settings' },
|
|
];
|
|
|
|
for (const { path, name } of pages) {
|
|
test(`${name} page load time`, async ({ page }) => {
|
|
const { duration } = await measureAsync(async () => {
|
|
await page.goto(path);
|
|
await waitForLoadingComplete(page);
|
|
});
|
|
|
|
const passed = recordMetric(`Page Load: ${name}`, duration, THRESHOLDS.PAGE_LOAD);
|
|
expect(passed).toBe(true);
|
|
});
|
|
}
|
|
});
|
|
|
|
// =============================================================================
|
|
// 2. NAVIGATION PERFORMANCE
|
|
// =============================================================================
|
|
|
|
test.describe('Navigation Performance', () => {
|
|
test.skip(!shouldRun, 'Set NOTEFLOW_E2E=1 to enable');
|
|
|
|
test('sidebar navigation transitions', async ({ page }) => {
|
|
await navigateTo(page, '/');
|
|
await waitForAPI(page);
|
|
|
|
const navPaths = [
|
|
{ selector: 'a[href="/meetings"]', name: 'Meetings' },
|
|
{ selector: 'a[href="/tasks"]', name: 'Tasks' },
|
|
{ selector: 'a[href="/people"]', name: 'People' },
|
|
{ selector: 'a[href="/analytics"]', name: 'Analytics' },
|
|
{ selector: 'a[href="/settings"]', name: 'Settings' },
|
|
{ selector: 'a[href="/"]', name: 'Home' },
|
|
];
|
|
|
|
for (const { selector, name } of navPaths) {
|
|
const link = page.locator(selector).first();
|
|
if (await link.isVisible()) {
|
|
const { duration } = await measureAsync(async () => {
|
|
await link.click();
|
|
await waitForLoadingComplete(page);
|
|
});
|
|
recordMetric(`Navigation: ${name}`, duration, THRESHOLDS.NAVIGATION);
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
// =============================================================================
|
|
// 3. API PERFORMANCE
|
|
// =============================================================================
|
|
|
|
test.describe('API Performance', () => {
|
|
test.skip(!shouldRun, 'Set NOTEFLOW_E2E=1 to enable');
|
|
|
|
test.beforeEach(async ({ page }) => {
|
|
await navigateTo(page, '/');
|
|
await waitForAPI(page);
|
|
});
|
|
|
|
test('listMeetings performance', async ({ page }) => {
|
|
const { duration } = await measureAsync(async () => {
|
|
await callAPI(page, 'listMeetings', { limit: 50 });
|
|
});
|
|
const passed = recordMetric('API: listMeetings (50)', duration, THRESHOLDS.API_CALL);
|
|
expect(passed).toBe(true);
|
|
});
|
|
|
|
test('listMeetings with pagination performance', async ({ page }) => {
|
|
const { duration } = await measureAsync(async () => {
|
|
await callAPI(page, 'listMeetings', { limit: 100, offset: 0 });
|
|
});
|
|
recordMetric('API: listMeetings (100)', duration, THRESHOLDS.API_CALL);
|
|
});
|
|
|
|
test('listSpeakerStats performance', async ({ page }) => {
|
|
const { duration } = await measureAsync(async () => {
|
|
await callAPI(page, 'listSpeakerStats', {
|
|
workspace_id: TEST_DATA.DEFAULT_WORKSPACE_ID,
|
|
});
|
|
});
|
|
const passed = recordMetric('API: listSpeakerStats', duration, THRESHOLDS.API_CALL);
|
|
expect(passed).toBe(true);
|
|
});
|
|
|
|
test('getAnalyticsOverview performance', async ({ page }) => {
|
|
const { duration } = await measureAsync(async () => {
|
|
await callAPI(page, 'getAnalyticsOverview', {
|
|
workspace_id: TEST_DATA.DEFAULT_WORKSPACE_ID,
|
|
});
|
|
});
|
|
recordMetric('API: getAnalyticsOverview', duration, THRESHOLDS.API_CALL);
|
|
});
|
|
|
|
test('getEntityAnalytics performance', async ({ page }) => {
|
|
const { duration } = await measureAsync(async () => {
|
|
await callAPI(page, 'getEntityAnalytics', {
|
|
workspace_id: TEST_DATA.DEFAULT_WORKSPACE_ID,
|
|
});
|
|
});
|
|
recordMetric('API: getEntityAnalytics', duration, THRESHOLDS.API_CALL);
|
|
});
|
|
|
|
test('listTasks performance', async ({ page }) => {
|
|
const { duration } = await measureAsync(async () => {
|
|
await callAPI(page, 'listTasks', {
|
|
workspace_id: TEST_DATA.DEFAULT_WORKSPACE_ID,
|
|
limit: 50,
|
|
});
|
|
});
|
|
recordMetric('API: listTasks', duration, THRESHOLDS.API_CALL);
|
|
});
|
|
|
|
test('listProjects performance', async ({ page }) => {
|
|
const { duration } = await measureAsync(async () => {
|
|
await callAPI(page, 'listProjects', {
|
|
workspace_id: TEST_DATA.DEFAULT_WORKSPACE_ID,
|
|
});
|
|
});
|
|
recordMetric('API: listProjects', duration, THRESHOLDS.API_CALL);
|
|
});
|
|
|
|
test('getServerInfo performance', async ({ page }) => {
|
|
const { duration } = await measureAsync(async () => {
|
|
await callAPI(page, 'getServerInfo', {});
|
|
});
|
|
recordMetric('API: getServerInfo', duration, THRESHOLDS.API_CALL);
|
|
});
|
|
|
|
test('listWebhooks performance', async ({ page }) => {
|
|
const { duration } = await measureAsync(async () => {
|
|
await callAPI(page, 'listWebhooks', { include_test: false });
|
|
});
|
|
recordMetric('API: listWebhooks', duration, THRESHOLDS.API_CALL);
|
|
});
|
|
|
|
test('getPreferences performance', async ({ page }) => {
|
|
const { duration } = await measureAsync(async () => {
|
|
await callAPI(page, 'getPreferences', {});
|
|
});
|
|
recordMetric('API: getPreferences', duration, THRESHOLDS.API_CALL);
|
|
});
|
|
});
|
|
|
|
// =============================================================================
|
|
// 4. MEETING DETAIL PERFORMANCE
|
|
// =============================================================================
|
|
|
|
test.describe('Meeting Detail Performance', () => {
|
|
test.skip(!shouldRun, 'Set NOTEFLOW_E2E=1 to enable');
|
|
|
|
test('meeting detail with segments load', async ({ page }) => {
|
|
await navigateTo(page, '/');
|
|
await waitForAPI(page);
|
|
|
|
// Get first meeting
|
|
const meetings = await callAPI<{ meetings: { id: string }[] }>(page, 'listMeetings', {
|
|
limit: 1,
|
|
});
|
|
|
|
if (meetings.meetings.length === 0) {
|
|
test.skip(true, 'No meetings available');
|
|
return;
|
|
}
|
|
|
|
const meetingId = meetings.meetings[0].id;
|
|
|
|
const { duration } = await measureAsync(async () => {
|
|
await callAPI(page, 'getMeeting', {
|
|
meeting_id: meetingId,
|
|
include_segments: true,
|
|
include_summary: true,
|
|
});
|
|
});
|
|
recordMetric('API: getMeeting (with segments)', duration, THRESHOLDS.API_CALL);
|
|
});
|
|
|
|
test('meeting detail page render', async ({ page }) => {
|
|
await navigateTo(page, '/');
|
|
await waitForAPI(page);
|
|
|
|
const meetings = await callAPI<{ meetings: { id: string }[] }>(page, 'listMeetings', {
|
|
limit: 1,
|
|
});
|
|
|
|
if (meetings.meetings.length === 0) {
|
|
test.skip(true, 'No meetings available');
|
|
return;
|
|
}
|
|
|
|
const meetingId = meetings.meetings[0].id;
|
|
|
|
const { duration } = await measureAsync(async () => {
|
|
await page.goto(`/meetings/${meetingId}`);
|
|
await waitForLoadingComplete(page);
|
|
});
|
|
recordMetric('Page Load: Meeting Detail', duration, THRESHOLDS.PAGE_LOAD);
|
|
});
|
|
});
|
|
|
|
// =============================================================================
|
|
// 5. POST-PROCESSING PERFORMANCE (Diarization, Summary, Entities)
|
|
// =============================================================================
|
|
|
|
test.describe('Post-Processing Performance', () => {
|
|
test.skip(!shouldRun, 'Set NOTEFLOW_E2E=1 to enable');
|
|
|
|
test.beforeEach(async ({ page }) => {
|
|
await navigateTo(page, '/');
|
|
await waitForAPI(page);
|
|
});
|
|
|
|
test('getDiarizationJobStatus performance', async ({ page }) => {
|
|
const meetings = await callAPI<{ meetings: { id: string }[] }>(page, 'listMeetings', {
|
|
limit: 1,
|
|
});
|
|
|
|
if (meetings.meetings.length === 0) {
|
|
test.skip(true, 'No meetings available');
|
|
return;
|
|
}
|
|
|
|
const meetingId = meetings.meetings[0].id;
|
|
|
|
const { duration } = await measureAsync(async () => {
|
|
await callAPI(page, 'getDiarizationJobStatus', { meeting_id: meetingId });
|
|
});
|
|
recordMetric('API: getDiarizationJobStatus', duration, THRESHOLDS.API_CALL);
|
|
});
|
|
|
|
test('refineSpeakers performance (if available)', async ({ page }) => {
|
|
const meetings = await callAPI<{ meetings: { id: string; state: string }[] }>(
|
|
page,
|
|
'listMeetings',
|
|
{ limit: 10, states: ['completed'] }
|
|
);
|
|
|
|
const completedMeeting = meetings.meetings.find((m) => m.state === 'completed');
|
|
if (!completedMeeting) {
|
|
test.skip(true, 'No completed meetings available');
|
|
return;
|
|
}
|
|
|
|
const { duration } = await measureAsync(async () => {
|
|
try {
|
|
await callAPI(page, 'refineSpeakers', { meeting_id: completedMeeting.id });
|
|
} catch {
|
|
// May fail if already refined or no audio
|
|
}
|
|
});
|
|
recordMetric('API: refineSpeakers', duration, THRESHOLDS.HEAVY_COMPUTATION);
|
|
});
|
|
|
|
test('generateSummary performance (if available)', async ({ page }) => {
|
|
const meetings = await callAPI<{ meetings: { id: string; state: string }[] }>(
|
|
page,
|
|
'listMeetings',
|
|
{ limit: 10, states: ['completed'] }
|
|
);
|
|
|
|
const completedMeeting = meetings.meetings.find((m) => m.state === 'completed');
|
|
if (!completedMeeting) {
|
|
test.skip(true, 'No completed meetings available');
|
|
return;
|
|
}
|
|
|
|
const { duration } = await measureAsync(async () => {
|
|
try {
|
|
await callAPI(page, 'generateSummary', { meeting_id: completedMeeting.id });
|
|
} catch {
|
|
// May fail if already generated
|
|
}
|
|
});
|
|
recordMetric('API: generateSummary', duration, THRESHOLDS.HEAVY_COMPUTATION);
|
|
});
|
|
|
|
test('extractEntities performance (if available)', async ({ page }) => {
|
|
const meetings = await callAPI<{ meetings: { id: string; state: string }[] }>(
|
|
page,
|
|
'listMeetings',
|
|
{ limit: 10, states: ['completed'] }
|
|
);
|
|
|
|
const completedMeeting = meetings.meetings.find((m) => m.state === 'completed');
|
|
if (!completedMeeting) {
|
|
test.skip(true, 'No completed meetings available');
|
|
return;
|
|
}
|
|
|
|
const { duration } = await measureAsync(async () => {
|
|
try {
|
|
await callAPI(page, 'extractEntities', { meeting_id: completedMeeting.id });
|
|
} catch {
|
|
// May fail if already extracted
|
|
}
|
|
});
|
|
recordMetric('API: extractEntities', duration, THRESHOLDS.HEAVY_COMPUTATION);
|
|
});
|
|
});
|
|
|
|
// =============================================================================
|
|
// 6. PLAYBACK PERFORMANCE
|
|
// =============================================================================
|
|
|
|
test.describe('Playback Performance', () => {
|
|
test.skip(!shouldRun, 'Set NOTEFLOW_E2E=1 to enable');
|
|
|
|
test('startPlayback performance', async ({ page }) => {
|
|
await navigateTo(page, '/');
|
|
await waitForAPI(page);
|
|
|
|
const meetings = await callAPI<{ meetings: { id: string; state: string }[] }>(
|
|
page,
|
|
'listMeetings',
|
|
{ limit: 10, states: ['completed'] }
|
|
);
|
|
|
|
const completedMeeting = meetings.meetings.find((m) => m.state === 'completed');
|
|
if (!completedMeeting) {
|
|
test.skip(true, 'No completed meetings available');
|
|
return;
|
|
}
|
|
|
|
const { duration } = await measureAsync(async () => {
|
|
try {
|
|
await callAPI(page, 'startPlayback', {
|
|
meeting_id: completedMeeting.id,
|
|
position: 0,
|
|
});
|
|
} catch {
|
|
// May fail if no audio
|
|
}
|
|
});
|
|
recordMetric('API: startPlayback', duration, THRESHOLDS.API_CALL);
|
|
});
|
|
|
|
test('seekPlayback performance', async ({ page }) => {
|
|
await navigateTo(page, '/');
|
|
await waitForAPI(page);
|
|
|
|
const meetings = await callAPI<{ meetings: { id: string; state: string }[] }>(
|
|
page,
|
|
'listMeetings',
|
|
{ limit: 10, states: ['completed'] }
|
|
);
|
|
|
|
const completedMeeting = meetings.meetings.find((m) => m.state === 'completed');
|
|
if (!completedMeeting) {
|
|
test.skip(true, 'No completed meetings available');
|
|
return;
|
|
}
|
|
|
|
// Start playback first
|
|
try {
|
|
await callAPI(page, 'startPlayback', {
|
|
meeting_id: completedMeeting.id,
|
|
position: 0,
|
|
});
|
|
} catch {
|
|
test.skip(true, 'Cannot start playback');
|
|
return;
|
|
}
|
|
|
|
const { duration } = await measureAsync(async () => {
|
|
await callAPI(page, 'seekPlayback', { position: 5000 });
|
|
});
|
|
recordMetric('API: seekPlayback', duration, THRESHOLDS.REAL_TIME_UPDATE);
|
|
});
|
|
});
|
|
|
|
// =============================================================================
|
|
// 7. ASSISTANT PERFORMANCE
|
|
// =============================================================================
|
|
|
|
test.describe('Assistant Performance', () => {
|
|
test.skip(!shouldRun, 'Set NOTEFLOW_E2E=1 to enable');
|
|
|
|
test('askAssistant performance', async ({ page }) => {
|
|
await navigateTo(page, '/');
|
|
await waitForAPI(page);
|
|
|
|
const meetings = await callAPI<{ meetings: { id: string }[] }>(page, 'listMeetings', {
|
|
limit: 1,
|
|
});
|
|
|
|
if (meetings.meetings.length === 0) {
|
|
test.skip(true, 'No meetings available');
|
|
return;
|
|
}
|
|
|
|
const meetingId = meetings.meetings[0].id;
|
|
|
|
const { duration } = await measureAsync(async () => {
|
|
try {
|
|
await callAPI(page, 'askAssistant', {
|
|
question: 'What was discussed?',
|
|
meeting_id: meetingId,
|
|
});
|
|
} catch {
|
|
// May timeout or fail
|
|
}
|
|
});
|
|
// Assistant has longer timeout - 20s
|
|
recordMetric('API: askAssistant', duration, 20000);
|
|
});
|
|
});
|
|
|
|
// =============================================================================
|
|
// 8. ANNOTATIONS PERFORMANCE
|
|
// =============================================================================
|
|
|
|
test.describe('Annotations Performance', () => {
|
|
test.skip(!shouldRun, 'Set NOTEFLOW_E2E=1 to enable');
|
|
|
|
test('listAnnotations performance', async ({ page }) => {
|
|
await navigateTo(page, '/');
|
|
await waitForAPI(page);
|
|
|
|
const meetings = await callAPI<{ meetings: { id: string }[] }>(page, 'listMeetings', {
|
|
limit: 1,
|
|
});
|
|
|
|
if (meetings.meetings.length === 0) {
|
|
test.skip(true, 'No meetings available');
|
|
return;
|
|
}
|
|
|
|
const meetingId = meetings.meetings[0].id;
|
|
|
|
const { duration } = await measureAsync(async () => {
|
|
await callAPI(page, 'listAnnotations', { meeting_id: meetingId });
|
|
});
|
|
recordMetric('API: listAnnotations', duration, THRESHOLDS.API_CALL);
|
|
});
|
|
});
|
|
|
|
// =============================================================================
|
|
// 9. EXPORT PERFORMANCE
|
|
// =============================================================================
|
|
|
|
test.describe('Export Performance', () => {
|
|
test.skip(!shouldRun, 'Set NOTEFLOW_E2E=1 to enable');
|
|
|
|
const formats = ['markdown', 'text', 'json'];
|
|
|
|
for (const format of formats) {
|
|
test(`exportTranscript (${format}) performance`, async ({ page }) => {
|
|
await navigateTo(page, '/');
|
|
await waitForAPI(page);
|
|
|
|
const meetings = await callAPI<{ meetings: { id: string; state: string }[] }>(
|
|
page,
|
|
'listMeetings',
|
|
{ limit: 10, states: ['completed'] }
|
|
);
|
|
|
|
const completedMeeting = meetings.meetings.find((m) => m.state === 'completed');
|
|
if (!completedMeeting) {
|
|
test.skip(true, 'No completed meetings available');
|
|
return;
|
|
}
|
|
|
|
const { duration } = await measureAsync(async () => {
|
|
await callAPI(page, 'exportTranscript', {
|
|
meeting_id: completedMeeting.id,
|
|
format,
|
|
});
|
|
});
|
|
recordMetric(`API: exportTranscript (${format})`, duration, THRESHOLDS.API_CALL);
|
|
});
|
|
}
|
|
});
|
|
|
|
// =============================================================================
|
|
// 10. SETTINGS PERFORMANCE
|
|
// =============================================================================
|
|
|
|
test.describe('Settings Performance', () => {
|
|
test.skip(!shouldRun, 'Set NOTEFLOW_E2E=1 to enable');
|
|
|
|
test('settings tabs load performance', async ({ page }) => {
|
|
await navigateTo(page, '/settings');
|
|
await waitForLoadingComplete(page);
|
|
|
|
const tabs = ['status', 'audio', 'ai', 'integrations'];
|
|
|
|
for (const tab of tabs) {
|
|
const tabButton = page.locator(`[data-testid="settings-tab-${tab}"], button:has-text("${tab}")`).first();
|
|
if (await tabButton.isVisible()) {
|
|
const { duration } = await measureAsync(async () => {
|
|
await tabButton.click();
|
|
await page.waitForTimeout(100); // Allow tab content to render
|
|
});
|
|
recordMetric(`Settings Tab: ${tab}`, duration, THRESHOLDS.NAVIGATION);
|
|
}
|
|
}
|
|
});
|
|
|
|
test('listAudioDevices performance', async ({ page }) => {
|
|
await navigateTo(page, '/');
|
|
await waitForAPI(page);
|
|
|
|
const { duration } = await measureAsync(async () => {
|
|
await callAPI(page, 'listAudioDevices', {});
|
|
});
|
|
recordMetric('API: listAudioDevices', duration, THRESHOLDS.API_CALL);
|
|
});
|
|
|
|
test('getCloudConsentStatus performance', async ({ page }) => {
|
|
await navigateTo(page, '/');
|
|
await waitForAPI(page);
|
|
|
|
const { duration } = await measureAsync(async () => {
|
|
await callAPI(page, 'getCloudConsentStatus', {});
|
|
});
|
|
recordMetric('API: getCloudConsentStatus', duration, THRESHOLDS.API_CALL);
|
|
});
|
|
|
|
test('getTriggerStatus performance', async ({ page }) => {
|
|
await navigateTo(page, '/');
|
|
await waitForAPI(page);
|
|
|
|
const { duration } = await measureAsync(async () => {
|
|
await callAPI(page, 'getTriggerStatus', {});
|
|
});
|
|
recordMetric('API: getTriggerStatus', duration, THRESHOLDS.API_CALL);
|
|
});
|
|
});
|
|
|
|
// =============================================================================
|
|
// 11. BULK OPERATIONS PERFORMANCE
|
|
// =============================================================================
|
|
|
|
test.describe('Bulk Operations Performance', () => {
|
|
test.skip(!shouldRun, 'Set NOTEFLOW_E2E=1 to enable');
|
|
|
|
test('concurrent API calls performance', async ({ page }) => {
|
|
await navigateTo(page, '/');
|
|
await waitForAPI(page);
|
|
|
|
const { duration } = await measureAsync(async () => {
|
|
await Promise.all([
|
|
callAPI(page, 'listMeetings', { limit: 20 }),
|
|
callAPI(page, 'listSpeakerStats', { workspace_id: TEST_DATA.DEFAULT_WORKSPACE_ID }),
|
|
callAPI(page, 'listTasks', { workspace_id: TEST_DATA.DEFAULT_WORKSPACE_ID, limit: 20 }),
|
|
callAPI(page, 'getServerInfo', {}),
|
|
]);
|
|
});
|
|
recordMetric('Concurrent: 4 API calls', duration, THRESHOLDS.API_CALL * 2);
|
|
});
|
|
|
|
test('sequential page navigation performance', async ({ page }) => {
|
|
const paths = ['/', '/meetings', '/tasks', '/people', '/analytics', '/settings'];
|
|
|
|
const { duration } = await measureAsync(async () => {
|
|
for (const path of paths) {
|
|
await page.goto(path);
|
|
await waitForLoadingComplete(page);
|
|
}
|
|
});
|
|
recordMetric(`Sequential: ${paths.length} page navigations`, duration, THRESHOLDS.PAGE_LOAD * paths.length);
|
|
});
|
|
});
|
|
|
|
// =============================================================================
|
|
// 12. MEMORY AND RESOURCE USAGE
|
|
// =============================================================================
|
|
|
|
test.describe('Memory and Resource Usage', () => {
|
|
test.skip(!shouldRun, 'Set NOTEFLOW_E2E=1 to enable');
|
|
|
|
test('memory usage after heavy navigation', async ({ page }) => {
|
|
await navigateTo(page, '/');
|
|
await waitForAPI(page);
|
|
|
|
// Perform heavy navigation
|
|
for (let i = 0; i < 5; i++) {
|
|
await page.goto('/meetings');
|
|
await waitForLoadingComplete(page);
|
|
await page.goto('/analytics');
|
|
await waitForLoadingComplete(page);
|
|
await page.goto('/people');
|
|
await waitForLoadingComplete(page);
|
|
}
|
|
|
|
// Check for memory leaks via performance API
|
|
const metrics = await page.evaluate(() => {
|
|
const perf = performance as Performance & {
|
|
memory?: { usedJSHeapSize: number; totalJSHeapSize: number };
|
|
};
|
|
if (perf.memory) {
|
|
return {
|
|
usedJSHeapSize: perf.memory.usedJSHeapSize,
|
|
totalJSHeapSize: perf.memory.totalJSHeapSize,
|
|
};
|
|
}
|
|
return null;
|
|
});
|
|
|
|
if (metrics) {
|
|
const usedMB = metrics.usedJSHeapSize / (1024 * 1024);
|
|
const totalMB = metrics.totalJSHeapSize / (1024 * 1024);
|
|
console.log(`[PERF] Memory: ${usedMB.toFixed(1)}MB used / ${totalMB.toFixed(1)}MB total`);
|
|
// Flag if memory usage exceeds 200MB
|
|
expect(usedMB).toBeLessThan(200);
|
|
}
|
|
});
|
|
});
|
|
|
|
// =============================================================================
|
|
// FINAL SUMMARY
|
|
// =============================================================================
|
|
|
|
test.afterAll(() => {
|
|
console.log('\n========================================');
|
|
console.log('PERFORMANCE PROFILING SUMMARY');
|
|
console.log('========================================\n');
|
|
|
|
const passed = metrics.filter((m) => m.passed);
|
|
const failed = metrics.filter((m) => !m.passed);
|
|
|
|
console.log(`Total metrics: ${metrics.length}`);
|
|
console.log(`Passed: ${passed.length}`);
|
|
console.log(`Failed: ${failed.length}`);
|
|
|
|
if (failed.length > 0) {
|
|
console.log('\n--- FAILED METRICS ---');
|
|
for (const m of failed) {
|
|
console.log(` ✗ ${m.name}: ${m.duration.toFixed(0)}ms (threshold: ${m.threshold}ms)`);
|
|
}
|
|
}
|
|
|
|
console.log('\n--- ALL METRICS ---');
|
|
for (const m of metrics) {
|
|
const status = m.passed ? '✓' : '✗';
|
|
console.log(` ${status} ${m.name}: ${m.duration.toFixed(0)}ms (threshold: ${m.threshold}ms)`);
|
|
}
|
|
});
|