frontend: Refactor webhook event storage to use sessionStorage and enhance webhook handling in E2E tests

This commit is contained in:
Carlos Santos 2026-01-14 18:27:33 +01:00
parent 0368ab83e6
commit a1acc9ba22
4 changed files with 151 additions and 34 deletions

View File

@ -1,9 +1,13 @@
import { MeetRoomStatus } from '@openvidu-meet/typings';
import { expect, test } from '@playwright/test';
import { MEET_TESTAPP_URL } from '../../config.js';
import {
createTestRoom,
deleteAllRecordings,
deleteAllRooms,
getRecordingFromAPI,
getRoomFromAPI,
getRoomFromWebhookStorage,
joinRoomAs,
prepareForJoiningRoom,
startStopRecording
@ -56,11 +60,25 @@ test.describe('Web Component E2E Tests', () => {
const meetingStartedElements = await page.locator('.webhook-meetingStarted').all();
expect(meetingStartedElements.length).toBe(1);
// Get the actual room object from localStorage and compare deeply
let [meetingStartedWebhook, actualRoom] = await Promise.all([
getRoomFromWebhookStorage(page, roomId, 'meetingStarted'),
getRoomFromAPI(roomId)
]);
expect(meetingStartedWebhook.data).toMatchObject(actualRoom);
// End the meeting
await page.click('#end-meeting-btn');
await page.waitForSelector('.webhook-meetingEnded');
const meetingEndedElements = await page.locator('.webhook-meetingEnded').all();
expect(meetingEndedElements.length).toBe(1);
// Verify meetingEnded webhook also matches room object
const meetingEndedWebhook = await getRoomFromWebhookStorage(page, roomId, 'meetingEnded');
// Update actualRoom status to OPEN for comparison
actualRoom.status = MeetRoomStatus.OPEN;
expect(meetingEndedWebhook.data).toMatchObject(actualRoom);
});
test('should successfully receive recordingStarted, recordingUpdated and recordingEnded webhooks', async ({
@ -74,17 +92,53 @@ test.describe('Web Component E2E Tests', () => {
const recordingStartedElements = await page.locator('.webhook-recordingStarted').all();
expect(recordingStartedElements.length).toBe(1);
// Verify recordingStarted webhook payload
const recordingStartedWebhook = await getRoomFromWebhookStorage(page, roomId, 'recordingStarted');
expect(recordingStartedWebhook.event).toBe('recordingStarted');
expect(recordingStartedWebhook.data).toBeDefined();
expect(recordingStartedWebhook.data.recordingId).toBeDefined();
const recordingId = recordingStartedWebhook.data.recordingId;
// Get the actual recording object from API and compare
const actualRecording = await getRecordingFromAPI(recordingId);
expect(recordingStartedWebhook.data).toMatchObject({
...actualRecording,
startDate: expect.any(Number),
status: expect.stringMatching(/active|starting/)
});
// Update recording
await page.waitForTimeout(2000); // Wait for a bit before updating
await page.waitForSelector('.webhook-recordingUpdated');
const recordingUpdatedElements = await page.locator('.webhook-recordingUpdated').all();
expect(recordingUpdatedElements.length).toBe(1);
// Verify recordingUpdated webhook payload
const recordingUpdatedWebhook = await getRoomFromWebhookStorage(page, roomId, 'recordingUpdated');
expect(recordingUpdatedWebhook.event).toBe('recordingUpdated');
expect(recordingUpdatedWebhook.data).toBeDefined();
expect(recordingUpdatedWebhook.data.recordingId).toBe(recordingId);
// Get updated recording from API and compare
const updatedRecording = await getRecordingFromAPI(recordingId);
expect(recordingUpdatedWebhook.data).toMatchObject(updatedRecording);
// End recording
await startStopRecording(page, 'stop');
await page.waitForSelector('.webhook-recordingEnded');
const recordingEndedElements = await page.locator('.webhook-recordingEnded').all();
expect(recordingEndedElements.length).toBe(1);
// Verify recordingEnded webhook payload
const recordingEndedWebhook = await getRoomFromWebhookStorage(page, roomId, 'recordingEnded');
expect(recordingEndedWebhook.event).toBe('recordingEnded');
expect(recordingEndedWebhook.data).toBeDefined();
expect(recordingEndedWebhook.data.recordingId).toBe(recordingId);
// Get final recording state from API and compare
const endedRecording = await getRecordingFromAPI(recordingId);
expect(recordingEndedWebhook.data).toMatchObject(endedRecording);
});
});
});

View File

@ -746,3 +746,94 @@ export const muteAudio = async (page: Page) => {
export const sleep = (ms: number): Promise<void> => {
return new Promise((resolve) => setTimeout(resolve, ms));
};
/**
* Gets the webhook event object from webhook storage in sessionStorage.
* This allows comparing the actual room state with webhook payload data.
* @param page - Playwright page object
* @param roomId - The room ID to retrieve
* @param eventName - The webhook event name (e.g., 'meetingStarted')
* @returns The complete webhook event object from sessionStorage (with event, data, creationDate)
*/
export async function getRoomFromWebhookStorage(page: Page, roomId: string, eventName: string): Promise<any> {
// Wait a bit to ensure the webhook is saved to localStorage
await page.waitForTimeout(500);
const webhookEvent = await page.evaluate(
({ roomId, eventName }) => {
const data = sessionStorage.getItem('webhookEventsByRoom');
if (!data) {
console.log('No webhookEventsByRoom in sessionStorage');
return null;
}
const map = JSON.parse(data);
const events = map[roomId] || [];
console.log(`Looking for event '${eventName}' in room '${roomId}'`);
console.log('Available events:', events.map((e: any) => e.event));
// Find the specific event and return the complete event object
const event = events.find((e: any) => e.event === eventName);
if (!event) {
console.log(`Event '${eventName}' not found`);
return null;
}
return event; // Return the complete event { event, data, creationDate }
},
{ roomId, eventName }
);
if (!webhookEvent) {
throw new Error(`Webhook event '${eventName}' not found in sessionStorage for room '${roomId}'`);
}
return webhookEvent;
}
/**
* Gets a recording details from the Meet API
* @param recordingId - The recording ID to retrieve
* @returns The complete recording object from the API
*/
export async function getRecordingFromAPI(recordingId: string): Promise<any> {
const response = await fetch(`${MEET_API_URL}/api/v1/recordings/${recordingId}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'x-api-key': MEET_API_KEY
}
});
if (!response.ok) {
const errorResponse = await response.json();
console.error('Error getting recording:', errorResponse);
throw new Error(`Failed to get recording: ${response.status}`);
}
return await response.json();
}
/**
* Gets the room details from the Meet API
* @param roomId
* @returns
*/
export async function getRoomFromAPI(roomId: string): Promise<any> {
const response = await fetch(`${MEET_API_URL}/api/v1/rooms/${roomId}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'x-api-key': MEET_API_KEY
}
});
if (!response.ok) {
const errorResponse = await response.json();
console.error('Error getting room:', errorResponse);
throw new Error(`Failed to get room: ${response.status}`);
}
return await response.json();
}

View File

@ -24,7 +24,7 @@ const escapeHtml = (unsafe) => {
.replace(/'/g, '&#039;');
};
const getWebhookEventsFromStorage = (roomId) => {
const data = localStorage.getItem('webhookEventsByRoom');
const data = sessionStorage.getItem('webhookEventsByRoom');
if (!data) {
return [];
}
@ -32,23 +32,13 @@ const getWebhookEventsFromStorage = (roomId) => {
return map[roomId] || [];
};
const saveWebhookEventToStorage = (roomId, event) => {
const data = localStorage.getItem('webhookEventsByRoom');
const data = sessionStorage.getItem('webhookEventsByRoom');
const map = data ? JSON.parse(data) : {};
if (!map[roomId]) {
map[roomId] = [];
}
map[roomId].push(event);
localStorage.setItem('webhookEventsByRoom', JSON.stringify(map));
};
const clearWebhookEventsByRoom = (roomId) => {
const data = localStorage.getItem('webhookEventsByRoom');
if (!data)
return;
const map = JSON.parse(data);
if (map[roomId]) {
map[roomId] = [];
localStorage.setItem('webhookEventsByRoom', JSON.stringify(map));
}
sessionStorage.setItem('webhookEventsByRoom', JSON.stringify(map));
};
const shouldShowWebhook = (event) => {
return (showAllWebhooksCheckbox === null || showAllWebhooksCheckbox === void 0 ? void 0 : showAllWebhooksCheckbox.checked) || event.data.roomId === roomId;
@ -65,10 +55,6 @@ const listenWebhookServerEvents = () => {
return;
}
addWebhookEventElement(event);
// Clean up the previous events
const isMeetingEnded = event.event === 'meetingEnded';
if (isMeetingEnded)
clearWebhookEventsByRoom(webhookRoomId);
});
};
const renderStoredWebhookEvents = (roomId) => {

View File

@ -31,7 +31,7 @@ const escapeHtml = (unsafe: string): string => {
};
const getWebhookEventsFromStorage = (roomId: string): any[] => {
const data = localStorage.getItem('webhookEventsByRoom');
const data = sessionStorage.getItem('webhookEventsByRoom');
if (!data) {
return [];
}
@ -41,26 +41,16 @@ const getWebhookEventsFromStorage = (roomId: string): any[] => {
};
const saveWebhookEventToStorage = (roomId: string, event: any): void => {
const data = localStorage.getItem('webhookEventsByRoom');
const data = sessionStorage.getItem('webhookEventsByRoom');
const map = data ? JSON.parse(data) : {};
if (!map[roomId]) {
map[roomId] = [];
}
map[roomId].push(event);
localStorage.setItem('webhookEventsByRoom', JSON.stringify(map));
sessionStorage.setItem('webhookEventsByRoom', JSON.stringify(map));
};
const clearWebhookEventsByRoom = (roomId: string): void => {
const data = localStorage.getItem('webhookEventsByRoom');
if (!data) return;
const map = JSON.parse(data);
if (map[roomId]) {
map[roomId] = [];
localStorage.setItem('webhookEventsByRoom', JSON.stringify(map));
}
};
const shouldShowWebhook = (event: any): boolean => {
return showAllWebhooksCheckbox?.checked || event.data.roomId === roomId;
@ -81,10 +71,6 @@ const listenWebhookServerEvents = () => {
}
addWebhookEventElement(event);
// Clean up the previous events
const isMeetingEnded = event.event === 'meetingEnded';
if (isMeetingEnded) clearWebhookEventsByRoom(webhookRoomId);
});
};