Files
noteflow/client/e2e/full-roundtrip-profile.spec.ts
Travis Vasceannie 94d92b814f
Some checks failed
CI / test-python (push) Failing after 22m25s
CI / test-typescript (push) Failing after 5m56s
CI / test-rust (push) Failing after 6m56s
feat: centralize analytics service test fixtures and correct cache invalidation assertion logic.
2026-01-25 03:40:19 +00:00

980 lines
30 KiB
TypeScript

/**
* Full Round-Trip Performance Profiling Suite
*
* Measures complete flow: Frontend → gRPC → Backend → Database → Response
*
* Covers:
* - Meeting CRUD operations (create, read, update, delete)
* - Recording flow simulation
* - Diarization pipeline
* - Transcription segments
* - Summarization generation
* - Entity extraction
* - Annotation CRUD
* - Task management
* - Analytics aggregation
* - Export operations
*/
import { expect, test } from '@playwright/test';
import { callAPI, navigateTo, TEST_DATA, waitForAPI, waitForLoadingComplete } from './fixtures';
const shouldRun = process.env.NOTEFLOW_E2E === '1';
// Performance thresholds for full round-trip (milliseconds)
const RT_THRESHOLDS = {
// Fast operations (simple DB lookup)
SIMPLE_READ: 500,
SIMPLE_WRITE: 1000,
// Medium operations (joins, aggregations)
MEDIUM_READ: 2000,
MEDIUM_WRITE: 3000,
// Heavy operations (AI processing, large data)
HEAVY_PROCESSING: 30000,
AI_OPERATION: 60000,
// Full page with multiple API calls
FULL_PAGE_LOAD: 5000,
};
interface RoundTripMetric {
operation: string;
flow: string;
duration: number;
threshold: number;
passed: boolean;
dataSize?: number;
details?: string;
}
const allMetrics: RoundTripMetric[] = [];
function recordRoundTrip(
operation: string,
flow: string,
duration: number,
threshold: number,
dataSize?: number,
details?: string
) {
const passed = duration <= threshold;
allMetrics.push({ operation, flow, duration, threshold, passed, dataSize, details });
const sizeInfo = dataSize ? ` [${dataSize} items]` : '';
const status = passed ? '✓' : '✗';
console.log(
`[RT] ${status} ${operation} (${flow}): ${duration.toFixed(0)}ms${sizeInfo} (threshold: ${threshold}ms)`
);
return passed;
}
async function measureRoundTrip<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. MEETING CRUD - Full Round Trip
// =============================================================================
test.describe('Meeting CRUD Round Trip', () => {
test.skip(!shouldRun, 'Set NOTEFLOW_E2E=1 to enable');
let testMeetingId: string | null = null;
test.beforeEach(async ({ page }) => {
await navigateTo(page, '/');
await waitForAPI(page);
});
test.afterEach(async ({ page }) => {
// Cleanup test meeting if created
if (testMeetingId) {
try {
await callAPI(page, 'deleteMeeting', { meeting_id: testMeetingId });
} catch {
// Ignore cleanup errors
}
testMeetingId = null;
}
});
test('CREATE meeting - full round trip', async ({ page }) => {
const testTitle = `Perf Test Meeting ${Date.now()}`;
const { result, duration } = await measureRoundTrip(async () => {
return callAPI<{ id: string; title: string }>(page, 'createMeeting', {
title: testTitle,
metadata: { test: 'performance' },
});
});
testMeetingId = result.id;
const passed = recordRoundTrip('CREATE Meeting', 'FE→gRPC→DB→Response', duration, RT_THRESHOLDS.SIMPLE_WRITE);
expect(result.title).toBe(testTitle);
expect(passed).toBe(true);
});
test('READ meeting list - full round trip', async ({ page }) => {
const { result, duration } = await measureRoundTrip(async () => {
return callAPI<{ meetings: unknown[]; total_count: number }>(page, 'listMeetings', {
limit: 50,
offset: 0,
});
});
recordRoundTrip(
'READ Meeting List',
'FE→gRPC→DB(query+count)→Response',
duration,
RT_THRESHOLDS.MEDIUM_READ,
result.meetings.length
);
});
test('READ meeting list with filters - full round trip', async ({ page }) => {
const { result, duration } = await measureRoundTrip(async () => {
return callAPI<{ meetings: unknown[] }>(page, 'listMeetings', {
limit: 50,
states: ['completed'],
sort_order: 'newest',
});
});
recordRoundTrip(
'READ Meeting List (filtered)',
'FE→gRPC→DB(filtered query)→Response',
duration,
RT_THRESHOLDS.MEDIUM_READ,
result.meetings.length
);
});
test('READ single meeting with segments - full round trip', 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 { result, duration } = await measureRoundTrip(async () => {
return callAPI<{ segments?: unknown[] }>(page, 'getMeeting', {
meeting_id: meetingId,
include_segments: true,
include_summary: true,
});
});
recordRoundTrip(
'READ Meeting Detail',
'FE→gRPC→DB(meeting+segments+summary)→Response',
duration,
RT_THRESHOLDS.MEDIUM_READ,
result.segments?.length || 0
);
});
test('DELETE meeting - full round trip', async ({ page }) => {
// Create a meeting to delete
const createResult = await callAPI<{ id: string }>(page, 'createMeeting', {
title: `Delete Test ${Date.now()}`,
});
const { duration } = await measureRoundTrip(async () => {
return callAPI(page, 'deleteMeeting', { meeting_id: createResult.id });
});
recordRoundTrip('DELETE Meeting', 'FE→gRPC→DB(cascade delete)→Response', duration, RT_THRESHOLDS.MEDIUM_WRITE);
});
});
// =============================================================================
// 2. SPEAKER/DIARIZATION ROUND TRIP
// =============================================================================
test.describe('Diarization Round Trip', () => {
test.skip(!shouldRun, 'Set NOTEFLOW_E2E=1 to enable');
test.beforeEach(async ({ page }) => {
await navigateTo(page, '/');
await waitForAPI(page);
});
test('READ speaker stats - aggregation round trip', async ({ page }) => {
const { result, duration } = await measureRoundTrip(async () => {
return callAPI<{ speakers: unknown[] }>(page, 'listSpeakerStats', {
workspace_id: TEST_DATA.DEFAULT_WORKSPACE_ID,
});
});
recordRoundTrip(
'READ Speaker Stats',
'FE→gRPC→DB(aggregate speakers)→Response',
duration,
RT_THRESHOLDS.MEDIUM_READ,
result.speakers.length
);
});
test('READ speaker stats with project filter - filtered aggregation', async ({ page }) => {
const projects = await callAPI<{ projects: { id: string }[] }>(page, 'listProjects', {
workspace_id: TEST_DATA.DEFAULT_WORKSPACE_ID,
});
if (projects.projects.length === 0) {
test.skip(true, 'No projects available');
return;
}
const { result, duration } = await measureRoundTrip(async () => {
return callAPI<{ speakers: unknown[] }>(page, 'listSpeakerStats', {
workspace_id: TEST_DATA.DEFAULT_WORKSPACE_ID,
project_id: projects.projects[0].id,
});
});
recordRoundTrip(
'READ Speaker Stats (project filtered)',
'FE→gRPC→DB(filtered aggregate)→Response',
duration,
RT_THRESHOLDS.MEDIUM_READ,
result.speakers.length
);
});
test('GET diarization job status - status check round trip', 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 { duration } = await measureRoundTrip(async () => {
return callAPI(page, 'getDiarizationJobStatus', {
meeting_id: meetings.meetings[0].id,
});
});
recordRoundTrip('READ Diarization Status', 'FE→gRPC→DB(job lookup)→Response', duration, RT_THRESHOLDS.SIMPLE_READ);
});
test('RENAME speaker - update round trip', 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 meeting = await callAPI<{ segments?: { speaker_id: string }[] }>(page, 'getMeeting', {
meeting_id: meetings.meetings[0].id,
include_segments: true,
});
const speakerId = meeting.segments?.[0]?.speaker_id;
if (!speakerId) {
test.skip(true, 'No speakers in meeting');
return;
}
const { duration } = await measureRoundTrip(async () => {
return callAPI(page, 'renameSpeaker', {
meeting_id: meetings.meetings[0].id,
speaker_id: speakerId,
new_name: `Speaker ${Date.now()}`,
});
});
recordRoundTrip('UPDATE Speaker Name', 'FE→gRPC→DB(update)→Response', duration, RT_THRESHOLDS.SIMPLE_WRITE);
});
});
// =============================================================================
// 3. ANALYTICS AGGREGATION ROUND TRIP
// =============================================================================
test.describe('Analytics Aggregation Round Trip', () => {
test.skip(!shouldRun, 'Set NOTEFLOW_E2E=1 to enable');
test.beforeEach(async ({ page }) => {
await navigateTo(page, '/');
await waitForAPI(page);
});
test('GET analytics overview - complex aggregation', async ({ page }) => {
const { result, duration } = await measureRoundTrip(async () => {
return callAPI<{ daily?: unknown[]; total_meetings: number }>(page, 'getAnalyticsOverview', {
workspace_id: TEST_DATA.DEFAULT_WORKSPACE_ID,
});
});
recordRoundTrip(
'READ Analytics Overview',
'FE→gRPC→DB(multi-table aggregate)→Response',
duration,
RT_THRESHOLDS.MEDIUM_READ,
result.daily?.length || 0,
`${result.total_meetings} total meetings`
);
});
test('GET entity analytics - NER aggregation', async ({ page }) => {
const { result, duration } = await measureRoundTrip(async () => {
return callAPI<{ total_entities: number; total_mentions: number }>(page, 'getEntityAnalytics', {
workspace_id: TEST_DATA.DEFAULT_WORKSPACE_ID,
});
});
recordRoundTrip(
'READ Entity Analytics',
'FE→gRPC→DB(entity aggregate)→Response',
duration,
RT_THRESHOLDS.MEDIUM_READ,
result.total_entities,
`${result.total_mentions} mentions`
);
});
});
// =============================================================================
// 4. TASK MANAGEMENT ROUND TRIP
// =============================================================================
test.describe('Task Management Round Trip', () => {
test.skip(!shouldRun, 'Set NOTEFLOW_E2E=1 to enable');
test.beforeEach(async ({ page }) => {
await navigateTo(page, '/');
await waitForAPI(page);
});
test('READ task list - join query round trip', async ({ page }) => {
const { result, duration } = await measureRoundTrip(async () => {
return callAPI<{ tasks: unknown[]; total_count: number }>(page, 'listTasks', {
workspace_id: TEST_DATA.DEFAULT_WORKSPACE_ID,
limit: 50,
});
});
recordRoundTrip(
'READ Task List',
'FE→gRPC→DB(tasks+meetings join)→Response',
duration,
RT_THRESHOLDS.MEDIUM_READ,
result.tasks.length
);
});
test('READ task list with status filter - filtered join', async ({ page }) => {
const { result, duration } = await measureRoundTrip(async () => {
return callAPI<{ tasks: unknown[] }>(page, 'listTasks', {
workspace_id: TEST_DATA.DEFAULT_WORKSPACE_ID,
statuses: ['open'],
limit: 50,
});
});
recordRoundTrip(
'READ Task List (filtered)',
'FE→gRPC→DB(filtered join)→Response',
duration,
RT_THRESHOLDS.MEDIUM_READ,
result.tasks.length
);
});
test('UPDATE task status - status update round trip', async ({ page }) => {
const tasks = await callAPI<{ tasks: { task: { id: string; status: string } }[] }>(page, 'listTasks', {
workspace_id: TEST_DATA.DEFAULT_WORKSPACE_ID,
limit: 1,
});
if (tasks.tasks.length === 0) {
test.skip(true, 'No tasks available');
return;
}
const task = tasks.tasks[0].task;
const newStatus = task.status === 'open' ? 'in_progress' : 'open';
const { duration } = await measureRoundTrip(async () => {
return callAPI(page, 'updateTask', {
task_id: task.id,
status: newStatus,
});
});
// Revert status
await callAPI(page, 'updateTask', { task_id: task.id, status: task.status });
recordRoundTrip('UPDATE Task Status', 'FE→gRPC→DB(update)→Response', duration, RT_THRESHOLDS.SIMPLE_WRITE);
});
});
// =============================================================================
// 5. ANNOTATION CRUD ROUND TRIP
// =============================================================================
test.describe('Annotation CRUD Round Trip', () => {
test.skip(!shouldRun, 'Set NOTEFLOW_E2E=1 to enable');
let testAnnotationId: string | null = null;
let testMeetingId: string | null = null;
test.beforeEach(async ({ page }) => {
await navigateTo(page, '/');
await waitForAPI(page);
const meetings = await callAPI<{ meetings: { id: string }[] }>(page, 'listMeetings', { limit: 1 });
if (meetings.meetings.length > 0) {
testMeetingId = meetings.meetings[0].id;
}
});
test.afterEach(async ({ page }) => {
if (testAnnotationId && testMeetingId) {
try {
await callAPI(page, 'deleteAnnotation', { annotation_id: testAnnotationId });
} catch {
// Ignore
}
testAnnotationId = null;
}
});
test('CREATE annotation - insert round trip', async ({ page }) => {
if (!testMeetingId) {
test.skip(true, 'No meetings available');
return;
}
const { result, duration } = await measureRoundTrip(async () => {
return callAPI<{ id: string }>(page, 'addAnnotation', {
meeting_id: testMeetingId,
annotation_type: 'note',
text: `Performance test annotation ${Date.now()}`,
start_time: 0,
end_time: 1000,
});
});
testAnnotationId = result.id;
recordRoundTrip('CREATE Annotation', 'FE→gRPC→DB(insert)→Response', duration, RT_THRESHOLDS.SIMPLE_WRITE);
});
test('READ annotations - list query round trip', async ({ page }) => {
if (!testMeetingId) {
test.skip(true, 'No meetings available');
return;
}
const { result, duration } = await measureRoundTrip(async () => {
return callAPI<{ annotations: unknown[] }>(page, 'listAnnotations', {
meeting_id: testMeetingId,
});
});
recordRoundTrip(
'READ Annotations',
'FE→gRPC→DB(query)→Response',
duration,
RT_THRESHOLDS.SIMPLE_READ,
result.annotations.length
);
});
test('UPDATE annotation - update round trip', async ({ page }) => {
if (!testMeetingId) {
test.skip(true, 'No meetings available');
return;
}
// Create an annotation to update
const created = await callAPI<{ id: string }>(page, 'addAnnotation', {
meeting_id: testMeetingId,
annotation_type: 'note',
text: 'Original text',
start_time: 0,
end_time: 1000,
});
testAnnotationId = created.id;
const { duration } = await measureRoundTrip(async () => {
return callAPI(page, 'updateAnnotation', {
annotation_id: testAnnotationId,
text: 'Updated text',
});
});
recordRoundTrip('UPDATE Annotation', 'FE→gRPC→DB(update)→Response', duration, RT_THRESHOLDS.SIMPLE_WRITE);
});
test('DELETE annotation - delete round trip', async ({ page }) => {
if (!testMeetingId) {
test.skip(true, 'No meetings available');
return;
}
// Create an annotation to delete
const created = await callAPI<{ id: string }>(page, 'addAnnotation', {
meeting_id: testMeetingId,
annotation_type: 'note',
text: 'To be deleted',
start_time: 0,
end_time: 1000,
});
const { duration } = await measureRoundTrip(async () => {
return callAPI(page, 'deleteAnnotation', { annotation_id: created.id });
});
testAnnotationId = null; // Already deleted
recordRoundTrip('DELETE Annotation', 'FE→gRPC→DB(delete)→Response', duration, RT_THRESHOLDS.SIMPLE_WRITE);
});
});
// =============================================================================
// 6. PROJECT OPERATIONS ROUND TRIP
// =============================================================================
test.describe('Project Operations Round Trip', () => {
test.skip(!shouldRun, 'Set NOTEFLOW_E2E=1 to enable');
test.beforeEach(async ({ page }) => {
await navigateTo(page, '/');
await waitForAPI(page);
});
test('READ project list - list query round trip', async ({ page }) => {
const { result, duration } = await measureRoundTrip(async () => {
return callAPI<{ projects: unknown[] }>(page, 'listProjects', {
workspace_id: TEST_DATA.DEFAULT_WORKSPACE_ID,
});
});
recordRoundTrip(
'READ Project List',
'FE→gRPC→DB(query)→Response',
duration,
RT_THRESHOLDS.SIMPLE_READ,
result.projects.length
);
});
test('READ meetings by project - filtered query round trip', async ({ page }) => {
const projects = await callAPI<{ projects: { id: string }[] }>(page, 'listProjects', {
workspace_id: TEST_DATA.DEFAULT_WORKSPACE_ID,
});
if (projects.projects.length === 0) {
test.skip(true, 'No projects available');
return;
}
const { result, duration } = await measureRoundTrip(async () => {
return callAPI<{ meetings: unknown[] }>(page, 'listMeetings', {
project_id: projects.projects[0].id,
limit: 50,
});
});
recordRoundTrip(
'READ Meetings by Project',
'FE→gRPC→DB(filtered query)→Response',
duration,
RT_THRESHOLDS.MEDIUM_READ,
result.meetings.length
);
});
});
// =============================================================================
// 7. WEBHOOKS ROUND TRIP
// =============================================================================
test.describe('Webhooks Round Trip', () => {
test.skip(!shouldRun, 'Set NOTEFLOW_E2E=1 to enable');
test.beforeEach(async ({ page }) => {
await navigateTo(page, '/');
await waitForAPI(page);
});
test('READ webhook list - list query round trip', async ({ page }) => {
const { result, duration } = await measureRoundTrip(async () => {
return callAPI<{ webhooks: unknown[] }>(page, 'listWebhooks', {
include_test: false,
});
});
recordRoundTrip(
'READ Webhook List',
'FE→gRPC→DB(query)→Response',
duration,
RT_THRESHOLDS.SIMPLE_READ,
result.webhooks.length
);
});
});
// =============================================================================
// 8. SETTINGS/PREFERENCES ROUND TRIP
// =============================================================================
test.describe('Settings Round Trip', () => {
test.skip(!shouldRun, 'Set NOTEFLOW_E2E=1 to enable');
test.beforeEach(async ({ page }) => {
await navigateTo(page, '/');
await waitForAPI(page);
});
test('READ preferences - config lookup round trip', async ({ page }) => {
const { duration } = await measureRoundTrip(async () => {
return callAPI(page, 'getPreferences', {});
});
recordRoundTrip('READ Preferences', 'FE→gRPC→Config/DB→Response', duration, RT_THRESHOLDS.SIMPLE_READ);
});
test('READ server info - system status round trip', async ({ page }) => {
const { duration } = await measureRoundTrip(async () => {
return callAPI(page, 'getServerInfo', {});
});
recordRoundTrip('READ Server Info', 'FE→gRPC→System→Response', duration, RT_THRESHOLDS.SIMPLE_READ);
});
test('READ cloud consent status - auth/config round trip', async ({ page }) => {
const { duration } = await measureRoundTrip(async () => {
return callAPI(page, 'getCloudConsentStatus', {});
});
recordRoundTrip('READ Cloud Consent', 'FE→gRPC→Config→Response', duration, RT_THRESHOLDS.SIMPLE_READ);
});
test('READ audio devices - system enumeration round trip', async ({ page }) => {
const { result, duration } = await measureRoundTrip(async () => {
return callAPI<{ input: unknown[]; output: unknown[] }>(page, 'listAudioDevices', {});
});
recordRoundTrip(
'READ Audio Devices',
'FE→Rust/Tauri→System→Response',
duration,
RT_THRESHOLDS.SIMPLE_READ,
(result.input?.length || 0) + (result.output?.length || 0)
);
});
test('READ trigger status - config lookup round trip', async ({ page }) => {
const { duration } = await measureRoundTrip(async () => {
return callAPI(page, 'getTriggerStatus', {});
});
recordRoundTrip('READ Trigger Status', 'FE→Rust/Tauri→Config→Response', duration, RT_THRESHOLDS.SIMPLE_READ);
});
});
// =============================================================================
// 9. AI ASSISTANT ROUND TRIP
// =============================================================================
test.describe('AI Assistant Round Trip', () => {
test.skip(!shouldRun, 'Set NOTEFLOW_E2E=1 to enable');
test('ASK assistant - AI inference round trip', 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 { result, duration } = await measureRoundTrip(async () => {
try {
return await callAPI<{ answer: string }>(page, 'askAssistant', {
question: 'What is the main topic?',
meeting_id: meetings.meetings[0].id,
});
} catch (e) {
return { answer: '', error: String(e) };
}
});
recordRoundTrip(
'AI Assistant Query',
'FE→gRPC→LLM(inference)→DB(context)→Response',
duration,
RT_THRESHOLDS.AI_OPERATION,
undefined,
`Answer length: ${result.answer?.length || 0}`
);
});
});
// =============================================================================
// 10. POST-PROCESSING PIPELINE ROUND TRIP
// =============================================================================
test.describe('Post-Processing Pipeline Round Trip', () => {
test.skip(!shouldRun, 'Set NOTEFLOW_E2E=1 to enable');
test.beforeEach(async ({ page }) => {
await navigateTo(page, '/');
await waitForAPI(page);
});
test('TRIGGER diarization refinement - async job round trip', 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');
return;
}
const { duration } = await measureRoundTrip(async () => {
try {
return await callAPI(page, 'refineSpeakers', {
meeting_id: completedMeeting.id,
});
} catch {
return { skipped: true };
}
});
recordRoundTrip(
'TRIGGER Diarization',
'FE→gRPC→JobQueue→Audio Processing→DB→Response',
duration,
RT_THRESHOLDS.HEAVY_PROCESSING
);
});
test('TRIGGER summary generation - AI generation round trip', 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');
return;
}
const { duration } = await measureRoundTrip(async () => {
try {
return await callAPI(page, 'generateSummary', {
meeting_id: completedMeeting.id,
});
} catch {
return { skipped: true };
}
});
recordRoundTrip(
'TRIGGER Summary Generation',
'FE→gRPC→LLM(inference)→DB(store)→Response',
duration,
RT_THRESHOLDS.AI_OPERATION
);
});
test('TRIGGER entity extraction - NER round trip', 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');
return;
}
const { duration } = await measureRoundTrip(async () => {
try {
return await callAPI(page, 'extractEntities', {
meeting_id: completedMeeting.id,
});
} catch {
return { skipped: true };
}
});
recordRoundTrip(
'TRIGGER Entity Extraction',
'FE→gRPC→NER Model→DB(store)→Response',
duration,
RT_THRESHOLDS.HEAVY_PROCESSING
);
});
});
// =============================================================================
// 11. CONCURRENT OPERATIONS
// =============================================================================
test.describe('Concurrent Operations Round Trip', () => {
test.skip(!shouldRun, 'Set NOTEFLOW_E2E=1 to enable');
test('CONCURRENT reads - parallel query round trip', async ({ page }) => {
await navigateTo(page, '/');
await waitForAPI(page);
const { duration } = await measureRoundTrip(async () => {
return 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, 'getAnalyticsOverview', { workspace_id: TEST_DATA.DEFAULT_WORKSPACE_ID }),
callAPI(page, 'listProjects', { workspace_id: TEST_DATA.DEFAULT_WORKSPACE_ID }),
]);
});
recordRoundTrip(
'CONCURRENT 5 Reads',
'FE→gRPC(parallel)→DB(5 queries)→Response',
duration,
RT_THRESHOLDS.MEDIUM_READ * 1.5, // Allow 50% overhead for parallelism
5
);
});
});
// =============================================================================
// 12. FULL PAGE LOAD ROUND TRIP
// =============================================================================
test.describe('Full Page Load Round Trip', () => {
test.skip(!shouldRun, 'Set NOTEFLOW_E2E=1 to enable');
test('HOME page - full load with API calls', async ({ page }) => {
const { duration } = await measureRoundTrip(async () => {
await page.goto('/');
await waitForLoadingComplete(page);
await waitForAPI(page);
});
recordRoundTrip('FULL PAGE: Home', 'Navigate→Render→API calls→Complete', duration, RT_THRESHOLDS.FULL_PAGE_LOAD);
});
test('MEETINGS page - full load with list query', async ({ page }) => {
const { duration } = await measureRoundTrip(async () => {
await page.goto('/meetings');
await waitForLoadingComplete(page);
});
recordRoundTrip('FULL PAGE: Meetings', 'Navigate→Render→listMeetings→Complete', duration, RT_THRESHOLDS.FULL_PAGE_LOAD);
});
test('ANALYTICS page - full load with aggregations', async ({ page }) => {
const { duration } = await measureRoundTrip(async () => {
await page.goto('/analytics');
await waitForLoadingComplete(page);
});
recordRoundTrip(
'FULL PAGE: Analytics',
'Navigate→Render→Aggregations→Charts→Complete',
duration,
RT_THRESHOLDS.FULL_PAGE_LOAD
);
});
test('PEOPLE page - full load with speaker stats', async ({ page }) => {
const { duration } = await measureRoundTrip(async () => {
await page.goto('/people');
await waitForLoadingComplete(page);
});
recordRoundTrip(
'FULL PAGE: People',
'Navigate→Render→listSpeakerStats→Complete',
duration,
RT_THRESHOLDS.FULL_PAGE_LOAD
);
});
test('TASKS page - full load with task list', async ({ page }) => {
const { duration } = await measureRoundTrip(async () => {
await page.goto('/tasks');
await waitForLoadingComplete(page);
});
recordRoundTrip('FULL PAGE: Tasks', 'Navigate→Render→listTasks→Complete', duration, RT_THRESHOLDS.FULL_PAGE_LOAD);
});
});
// =============================================================================
// FINAL SUMMARY REPORT
// =============================================================================
test.afterAll(() => {
console.log(`\n${'='.repeat(80)}`);
console.log('FULL ROUND-TRIP PERFORMANCE PROFILING REPORT');
console.log('='.repeat(80));
const passed = allMetrics.filter((m) => m.passed);
const failed = allMetrics.filter((m) => !m.passed);
console.log(`\nTotal Operations Profiled: ${allMetrics.length}`);
console.log(`Passed: ${passed.length} (${((passed.length / allMetrics.length) * 100).toFixed(1)}%)`);
console.log(`Failed: ${failed.length} (${((failed.length / allMetrics.length) * 100).toFixed(1)}%)`);
// Group by flow type
const byFlow = new Map<string, RoundTripMetric[]>();
for (const m of allMetrics) {
const key = m.flow.split('→')[0];
if (!byFlow.has(key)) {
byFlow.set(key, []);
}
byFlow.get(key)?.push(m);
}
console.log('\n--- BY OPERATION TYPE ---');
for (const [flow, metrics] of byFlow) {
const avgDuration = metrics.reduce((sum, m) => sum + m.duration, 0) / metrics.length;
const passRate = (metrics.filter((m) => m.passed).length / metrics.length) * 100;
console.log(`${flow}: avg ${avgDuration.toFixed(0)}ms, ${passRate.toFixed(0)}% pass rate`);
}
if (failed.length > 0) {
console.log('\n--- FAILED OPERATIONS (VULNERABILITIES) ---');
for (const m of failed) {
console.log(
`${m.operation}: ${m.duration.toFixed(0)}ms (threshold: ${m.threshold}ms) - ${m.flow}`
);
}
}
// Find slowest operations
const sorted = [...allMetrics].sort((a, b) => b.duration - a.duration);
console.log('\n--- TOP 10 SLOWEST OPERATIONS ---');
for (const m of sorted.slice(0, 10)) {
const pct = ((m.duration / m.threshold) * 100).toFixed(0);
console.log(` ${m.operation}: ${m.duration.toFixed(0)}ms (${pct}% of threshold)`);
}
console.log('\n--- ALL METRICS ---');
for (const m of allMetrics) {
const status = m.passed ? '✓' : '✗';
const sizeInfo = m.dataSize !== undefined ? ` [${m.dataSize}]` : '';
console.log(` ${status} ${m.operation}: ${m.duration.toFixed(0)}ms${sizeInfo}`);
}
console.log(`\n${'='.repeat(80)}`);
});