From ca16f54dc916bb31b1fc5a3a56924beb80ef22cf Mon Sep 17 00:00:00 2001 From: damencho Date: Fri, 9 May 2025 12:05:57 -0500 Subject: [PATCH] feat(tests): Chat iframeApi tests. --- tests/helpers/utils.ts | 38 +++++ tests/specs/2way/iFrameApiChat.spec.ts | 190 +++++++++++++++++++++++++ tests/wdio.firefox.conf.ts | 5 +- 3 files changed, 229 insertions(+), 4 deletions(-) create mode 100644 tests/specs/2way/iFrameApiChat.spec.ts diff --git a/tests/helpers/utils.ts b/tests/helpers/utils.ts index bba5882356..009a339896 100644 --- a/tests/helpers/utils.ts +++ b/tests/helpers/utils.ts @@ -1,3 +1,5 @@ +const https = require('https'); + /** * Generates a random number between 1 and the specified maximum value (inclusive). * @@ -10,3 +12,39 @@ export function getRandomNumberAsStr(max: number, numberOfDigits: number): strin return randomNumber.toString().padStart(numberOfDigits, '0'); } + +/** + * Fetches JSON data from a given URL. + * @param {string} url - The URL to fetch data from. + * @returns {Promise} - A promise that resolves to the parsed JSON object. + */ +export async function fetchJson(url) { + return new Promise((resolve, reject) => { + https.get(url, res => { + let data = ''; + + // Handle HTTP errors + if (res.statusCode < 200 || res.statusCode >= 300) { + return reject(new Error(`HTTP Status Code: ${res.statusCode}`)); + } + + // Collect data chunks + res.on('data', chunk => { + data += chunk; + }); + + // Parse JSON when the response ends + res.on('end', () => { + try { + const json = JSON.parse(data); + + resolve(json); + } catch (err) { + reject(new Error('Invalid JSON response')); + } + }); + }).on('error', err => { + reject(err); + }); + }); +} diff --git a/tests/specs/2way/iFrameApiChat.spec.ts b/tests/specs/2way/iFrameApiChat.spec.ts new file mode 100644 index 0000000000..7b3fb3c905 --- /dev/null +++ b/tests/specs/2way/iFrameApiChat.spec.ts @@ -0,0 +1,190 @@ +import { ensureTwoParticipants } from '../../helpers/participants'; +import type { Participant } from '../../helpers/Participant'; +import { fetchJson } from '../../helpers/utils'; +import { expect } from '@wdio/globals'; + +describe('Chat', () => { + it('joining the meeting', async () => { + await ensureTwoParticipants(ctx); + + const { p1, p2 } = ctx; + + if (await p1.execute(() => config.disableIframeAPI)) { + // skip the test if iframeAPI is disabled + ctx.skipSuiteTests = true; + + return; + } + + // let's populate endpoint ids + await Promise.all([ + p1.getEndpointId(), + p2.getEndpointId() + ]); + }); + + it('send message', async () => { + const { p1, p2 } = ctx; + + await p1.switchToAPI(); + await p2.switchToAPI(); + + await p2.getIframeAPI().addEventListener('chatUpdated'); + await p2.getIframeAPI().addEventListener('incomingMessage'); + await p1.getIframeAPI().addEventListener('outgoingMessage'); + + const testMessage = 'Hello world'; + + await p1.getIframeAPI().executeCommand('sendChatMessage', testMessage); + + const chatUpdatedEvent: { + isOpen: boolean; + unreadCount: number; + } = await p2.driver.waitUntil(() => p2.getIframeAPI().getEventResult('chatUpdated'), { + timeout: 3000, + timeoutMsg: 'Chat was not updated' + }); + + expect(chatUpdatedEvent).toEqual({ + isOpen: false, + unreadCount: 1 + }); + + const incomingMessageEvent: { + from: string; + message: string; + nick: string; + privateMessage: boolean; + } = await p2.getIframeAPI().getEventResult('incomingMessage'); + + expect(incomingMessageEvent).toEqual({ + from: await p1.getEndpointId(), + message: testMessage, + nick: p1.name, + privateMessage: false + }); + + const outgoingMessageEvent: { + message: string; + privateMessage: boolean; + } = await p1.getIframeAPI().getEventResult('outgoingMessage'); + + expect(outgoingMessageEvent).toEqual({ + message: testMessage, + privateMessage: false + }); + + await p1.getIframeAPI().clearEventResults('outgoingMessage'); + await p2.getIframeAPI().clearEventResults('chatUpdated'); + await p2.getIframeAPI().clearEventResults('incomingMessage'); + }); + + it('toggle chat', async () => { + const { p1, p2 } = ctx; + + await p2.getIframeAPI().executeCommand('toggleChat'); + + await testSendGroupMessageWithChatOpen(p1, p2); + + await p1.getIframeAPI().clearEventResults('outgoingMessage'); + await p2.getIframeAPI().clearEventResults('chatUpdated'); + await p2.getIframeAPI().clearEventResults('incomingMessage'); + }); + + it('private chat', async () => { + const { p1, p2 } = ctx; + const testMessage = 'Hello private world!'; + const p2Id = await p2.getEndpointId(); + const p1Id = await p1.getEndpointId(); + + await p1.getIframeAPI().executeCommand('initiatePrivateChat', p2Id); + await p1.getIframeAPI().executeCommand('sendChatMessage', testMessage, p2Id); + + const incomingMessageEvent = await p2.driver.waitUntil( + () => p2.getIframeAPI().getEventResult('incomingMessage'), { + timeout: 3000, + timeoutMsg: 'Chat was not received' + }); + + expect(incomingMessageEvent).toEqual({ + from: p1Id, + message: testMessage, + nick: p1.name, + privateMessage: true + }); + + expect(await p1.getIframeAPI().getEventResult('outgoingMessage')).toEqual({ + message: testMessage, + privateMessage: true + }); + + await p1.getIframeAPI().executeCommand('cancelPrivateChat'); + + await p2.getIframeAPI().clearEventResults('chatUpdated'); + await p2.getIframeAPI().clearEventResults('incomingMessage'); + + await testSendGroupMessageWithChatOpen(p1, p2); + }); + + it('chat upload chat', async () => { + const { p1, p2, webhooksProxy } = ctx; + + await p1.getIframeAPI().executeCommand('hangup'); + await p2.getIframeAPI().executeCommand('hangup'); + + if (webhooksProxy) { + const event: { + data: { + preAuthenticatedLink: string; + }; + eventType: string; + } = await webhooksProxy.waitForEvent('CHAT_UPLOADED', 20000); + + expect('CHAT_UPLOADED').toBe(event.eventType); + expect(event.data.preAuthenticatedLink).toBeDefined(); + + const uploadedChat: any = await fetchJson(event.data.preAuthenticatedLink); + + expect(uploadedChat.messageType).toBe('CHAT'); + expect(uploadedChat.messages).toBeDefined(); + expect(uploadedChat.messages.length).toBe(3); + } + }); +}); + +/** + * Test sending a group message with the chat open. + * @param p1 + * @param p2 + */ +async function testSendGroupMessageWithChatOpen(p1: Participant, p2: Participant) { + const testMessage = 'Hello world again'; + + await p1.getIframeAPI().executeCommand('sendChatMessage', testMessage); + + const chatUpdatedEvent: { + isOpen: boolean; + unreadCount: number; + } = await p2.driver.waitUntil(() => p2.getIframeAPI().getEventResult('chatUpdated'), { + timeout: 3000, + timeoutMsg: 'Chat was not updated' + }); + + expect(chatUpdatedEvent).toEqual({ + isOpen: true, + unreadCount: 0 + }); + + const incomingMessageEvent = await p2.driver.waitUntil( + () => p2.getIframeAPI().getEventResult('incomingMessage'), { + timeout: 3000, + timeoutMsg: 'Chat was not received' + }); + + expect(incomingMessageEvent).toEqual({ + from: await p1.getEndpointId(), + message: testMessage, + nick: p1.name, + privateMessage: false + }); +} diff --git a/tests/wdio.firefox.conf.ts b/tests/wdio.firefox.conf.ts index 830ddd17e3..f9e2bf23c5 100644 --- a/tests/wdio.firefox.conf.ts +++ b/tests/wdio.firefox.conf.ts @@ -20,10 +20,7 @@ if (process.env.HEADLESS === 'true') { } const ffExcludes = [ - 'specs/2way/iFrameApiParticipantsPresence.spec.ts', // FF does not support uploading files (uploadFile) - 'specs/2way/iFrameApiTranscriptions.spec.ts', - 'specs/alone/iFrameApiInvite.spec.ts', - 'specs/alone/iFrameApiRecording.spec.ts', + 'specs/**/iFrameApi*.spec.ts', // FF does not support uploading files (uploadFile) // FF does not support setting a file as mic input, no dominant speaker events 'specs/3way/activeSpeaker.spec.ts',