From 70c3c8db13686744e688cf7499051ab58f70e965 Mon Sep 17 00:00:00 2001 From: bgrozev Date: Wed, 1 Oct 2025 11:40:02 -0500 Subject: [PATCH] test: Refactor, update and fix JaaS tests (#16463) * ref: Move the jaas util out of specs/. * ref: Extract a more generic joinMuc utility. * ref: Rename joinMuc to joinJaasMuc. * ref: Move tileView.spec.ts out of 2way, use joinMuc. * ref: Enforce that "name" is p1, p2, p3, p4 using types. * fix: Fix mute test filename. * ref: Split the chat test into jaas and iframe tests. * test: Add webhook verification to jaas visitor tests. * ref: Remove the iframe/visitors test (ported to jaas). * ref: Move the transcriptions test to jaas. * ref: Make getEndpointId work from outside the iframe. * ref: Remove TestProperties.useIFrameApi. Use the flag in IParticipantOptions instead. * ref: Do not set a special tenant when the iFrame API is used, leave it to tests to determine. * ref: Remove the jaas-specific tests from iframe/participantsPresnce (will be re-added under jaas/ later). * ref: Move the dial in/out tests to jaas/. * Add tests for jaas join/leave webhooks (port back from iframe/participantsPresence). * config: Fallback to IFRAME_TENANT and JWT_* for jaas configuration. * ref: Simplify boolean expression. * ref: Remove the skipFirstModerator option (unused). * ref: Do not override token if specified. * fix: Do not generate token for alone/invite test. * ref: Extract more dial-in utilities. * test: Verify Invite UI in jaas. * Do not generate token for dial in (case covered in jaas/). * ref: Remove preferGenerateToken (unused). * ref: Move mute utils in their own helper. * fix: Fix setting the jaas enabled flag. * Do not run alone/invite for jaas (temp fix). * fix: Switch back to meeting window. * Do not run alone/dialInAudio on jaas. * Disable the SIP jibri test (broken). --- tests/helpers/Participant.ts | 28 ++- tests/helpers/TestProperties.ts | 3 - tests/helpers/TestsConfig.ts | 63 ++++- tests/helpers/jaas.ts | 41 ++++ .../helpers/jaas.ts => helpers/joinMuc.ts} | 29 +-- tests/helpers/participants.ts | 135 ++--------- tests/helpers/types.ts | 15 +- tests/pageobjects/ChatPanel.ts | 14 ++ .../2way/{mute.spect.ts => mute.spec.ts} | 4 +- tests/specs/2way/stopVideo.spec.ts | 3 +- tests/specs/3way/activeSpeaker.spec.ts | 3 +- tests/specs/3way/audioVideoModeration.spec.ts | 10 +- tests/specs/3way/avatars.spec.ts | 7 +- tests/specs/3way/lobby.spec.ts | 10 +- tests/specs/3way/startMuted.spec.ts | 4 +- tests/specs/alone/dialInAudio.spec.ts | 16 +- tests/specs/alone/invite.spec.ts | 88 ++----- tests/specs/helpers/DialIn.ts | 76 +++++- tests/specs/helpers/mute.ts | 64 +++++ tests/specs/iframe/chat.spec.ts | 89 ++----- tests/specs/iframe/invite.spec.ts | 214 ---------------- .../specs/iframe/participantsPresence.spec.ts | 228 +----------------- tests/specs/iframe/visitors.spec.ts | 125 ---------- tests/specs/jaas/chat.spec.ts | 72 ++++++ tests/specs/jaas/dial/dialin.spec.ts | 64 +++++ tests/specs/jaas/dial/dialout.spec.ts | 51 ++++ tests/specs/jaas/dial/sipjibri.spec.ts | 88 +++++++ tests/specs/jaas/dial/util.ts | 81 +++++++ tests/specs/jaas/joinMuc.spec.ts | 18 +- tests/specs/jaas/maxOccupants.spec.ts | 10 +- tests/specs/jaas/passcode.spec.ts | 4 +- tests/specs/jaas/passcodeInvalid.spec.ts | 4 +- tests/specs/jaas/presence.spec.ts | 178 ++++++++++++++ tests/specs/jaas/recording.spec.ts | 7 +- .../{iframe => jaas}/transcriptions.spec.ts | 109 ++++----- .../visitors/participantsSoftLimit.spec.ts | 8 +- .../visitors/videoWithSingleSender.spec.ts | 6 +- .../specs/jaas/visitors/visitorTokens.spec.ts | 144 ++++++++--- .../specs/jaas/visitors/visitorsLive.spec.ts | 6 +- tests/specs/{2way => }/tileView.spec.ts | 15 +- tests/wdio.conf.ts | 6 +- 41 files changed, 1119 insertions(+), 1021 deletions(-) create mode 100644 tests/helpers/jaas.ts rename tests/{specs/helpers/jaas.ts => helpers/joinMuc.ts} (52%) rename tests/specs/2way/{mute.spect.ts => mute.spec.ts} (99%) create mode 100644 tests/specs/helpers/mute.ts delete mode 100644 tests/specs/iframe/invite.spec.ts delete mode 100644 tests/specs/iframe/visitors.spec.ts create mode 100644 tests/specs/jaas/chat.spec.ts create mode 100644 tests/specs/jaas/dial/dialin.spec.ts create mode 100644 tests/specs/jaas/dial/dialout.spec.ts create mode 100644 tests/specs/jaas/dial/sipjibri.spec.ts create mode 100644 tests/specs/jaas/dial/util.ts create mode 100644 tests/specs/jaas/presence.spec.ts rename tests/specs/{iframe => jaas}/transcriptions.spec.ts (76%) rename tests/specs/{2way => }/tileView.spec.ts (81%) diff --git a/tests/helpers/Participant.ts b/tests/helpers/Participant.ts index fcef090955..dc51a6cc9f 100644 --- a/tests/helpers/Participant.ts +++ b/tests/helpers/Participant.ts @@ -59,6 +59,12 @@ export class Participant { private _iFrameApi: boolean = false; + /** + * Whether the current frame is the main frame. This could coincide with the Jitsi Meet frame (when it's loaded + * directly), or not (when it's loaded in an iframe). + */ + private _inMainFrame: boolean = true; + /** * The default config to use when joining. * @@ -149,9 +155,17 @@ export class Participant { */ async getEndpointId(): Promise { if (!this._endpointId) { + const wasInMainFrame = this._inMainFrame; + + await this.switchToIFrame(); + this._endpointId = await this.execute(() => { // eslint-disable-line arrow-body-style return APP?.conference?.getMyUserId(); }); + + if (wasInMainFrame) { + await this.switchToMainFrame(); + } } return this._endpointId; @@ -626,21 +640,31 @@ export class Participant { /** * Switches to the main frame context (outside the iFrame; where the Jitsi Meet iFrame API is available). * - * If this Participant was initialized with iFrameApi=false this has no effect, as there aren't any other contexts. + * If this Participant was initialized with iFrameApi=false this is a no-op. */ async switchToMainFrame() { + if (!this._iFrameApi || this._inMainFrame) { + return; + } + await this.driver.switchFrame(null); + this._inMainFrame = true; } /** * Switches to the iFrame context (inside the iFrame; where the Jitsi Meet application runs). * - * If this Participant was initialized with iFrameApi=false this will result in an error. + * If this Participant was initialized with iFrameApi=false this is a no-op. */ async switchToIFrame() { + if (!this._iFrameApi || !this._inMainFrame) { + return; + } + const iframe = this.driver.$('iframe'); await this.driver.switchFrame(iframe); + this._inMainFrame = false; } /** diff --git a/tests/helpers/TestProperties.ts b/tests/helpers/TestProperties.ts index c2a706a3ef..ddc493728a 100644 --- a/tests/helpers/TestProperties.ts +++ b/tests/helpers/TestProperties.ts @@ -2,8 +2,6 @@ * An interface that tests can export (as a TEST_PROPERTIES property) to define what they require. */ export type ITestProperties = { - /** The test uses the iFrame API. */ - useIFrameApi: boolean; /** The test requires jaas, it should be skipped when the jaas configuration is not enabled. */ useJaas: boolean; /** The test requires the webhook proxy. */ @@ -12,7 +10,6 @@ export type ITestProperties = { }; const defaultProperties: ITestProperties = { - useIFrameApi: false, useWebhookProxy: false, useJaas: false, usesBrowsers: [ 'p1', 'p2', 'p3', 'p4' ] diff --git a/tests/helpers/TestsConfig.ts b/tests/helpers/TestsConfig.ts index 0278dc64ea..f58d896d24 100644 --- a/tests/helpers/TestsConfig.ts +++ b/tests/helpers/TestsConfig.ts @@ -4,26 +4,67 @@ export const config = { /** Enable debug logging. Note this includes private information from .env */ debug: Boolean(process.env.JITSI_DEBUG?.trim()), - iframe: { - customerId: process.env.IFRAME_TENANT?.trim()?.replace('vpaas-magic-cookie-', ''), - tenant: process.env.IFRAME_TENANT?.trim(), - /** Whether the configuration specifies a JaaS account for the iFrame API tests. */ - usesJaas: Boolean(process.env.JWT_PRIVATE_KEY_PATH && process.env.JWT_KID?.startsWith('vpaas-magic-cookie-')), - }, + /** Whether to expect the environment to automatically elect a new moderator when the existing moderator leaves. */ + autoModerator: (() => { + if (typeof process.env.AUTO_MODERATOR !== 'undefined') { + return process.env.AUTO_MODERATOR?.trim() === 'true'; + } + + // If not explicitly configured, fallback to recognizing whether we're running against one of the JaaS + // environments which are known to have the setting disabled. + return !Boolean( + process.env.JWT_PRIVATE_KEY_PATH && process.env.JWT_KID?.startsWith('vpaas-magic-cookie-') + ); + })(), jaas: { + customerId: (() => { + if (typeof process.env.JAAS_TENANT !== 'undefined') { + return process.env.JAAS_TENANT?.trim()?.replace('vpaas-magic-cookie-', ''); + } + + return process.env.IFRAME_TENANT?.trim()?.replace('vpaas-magic-cookie-', ''); + })(), /** Whether the configuration for JaaS specific tests is enabled. */ - enabled: Boolean(process.env.JAAS_TENANT && process.env.JAAS_PRIVATE_KEY_PATH && process.env.JAAS_KID), + enabled: Boolean( + (process.env.JAAS_TENANT || process.env.IFRAME_TENANT) + && (process.env.JAAS_PRIVATE_KEY_PATH || process.env.JWT_PRIVATE_KEY_PATH) + && (process.env.JAAS_KID || process.env.JWT_KID)), /** The JaaS key ID, used to sign the tokens. */ - kid: process.env.JAAS_KID?.trim(), + kid: (() => { + if (typeof process.env.JAAS_KID !== 'undefined') { + return process.env.JAAS_KID?.trim(); + } + + return process.env.JWT_KID?.trim(); + })(), /** The path to the JaaS private key, used to sign JaaS tokens. */ - privateKeyPath: process.env.JAAS_PRIVATE_KEY_PATH?.trim(), + privateKeyPath: (() => { + if (typeof process.env.JAAS_PRIVATE_KEY_PATH != 'undefined') { + return process.env.JAAS_PRIVATE_KEY_PATH?.trim(); + } + + return process.env.JWT_PRIVATE_KEY_PATH?.trim(); + })(), /** The JaaS tenant (vpaas-magic-cookie-) . */ - tenant: process.env.JAAS_TENANT?.trim(), + tenant: (() => { + if (typeof process.env.JAAS_TENANT !== 'undefined') { + return process.env.JAAS_TENANT?.trim(); + } + + return process.env.IFRAME_TENANT?.trim(); + })() }, jwt: { kid: process.env.JWT_KID?.trim(), /** A pre-configured token used by some tests. */ - preconfiguredToken: process.env.JWT_ACCESS_TOKEN?.trim(), + preconfiguredJwt: process.env.JWT_ACCESS_TOKEN?.trim(), + preconfiguredToken: (() => { + if (process.env.JWT_ACCESS_TOKEN) { + return { jwt: process.env.JWT_ACCESS_TOKEN?.trim() }; + } + + return undefined; + })(), privateKeyPath: process.env.JWT_PRIVATE_KEY_PATH?.trim() }, roomName: { diff --git a/tests/helpers/jaas.ts b/tests/helpers/jaas.ts new file mode 100644 index 0000000000..019ea22882 --- /dev/null +++ b/tests/helpers/jaas.ts @@ -0,0 +1,41 @@ +import { Participant } from './Participant'; +import { config } from './TestsConfig'; +import { joinMuc } from './joinMuc'; +import { IToken, ITokenOptions, generateToken } from './token'; +import { IParticipantJoinOptions, IParticipantOptions } from './types'; + +export function generateJaasToken(options: ITokenOptions): IToken { + if (!config.jaas.enabled) { + throw new Error('JaaS is not configured.'); + } + + // Don't override the keyId and keyPath if they are already set in options, allow tests to set them. + return generateToken({ + ...options, + keyId: options.keyId || config.jaas.kid, + keyPath: options.keyPath || config.jaas.privateKeyPath + }); +} + +/** + * Creates a new Participant and joins the MUC with the given options. The jaas-specific properties must be set as + * environment variables (see env.example and TestsConfig.ts). If no room name is specified, the default room name + * from the context is used. + * + * @param participantOptions + * @param joinOptions options to use when joining the MUC. + * @returns {Promise} The Participant that has joined the MUC. + */ +export async function joinJaasMuc( + participantOptions?: Partial, + joinOptions?: Partial): Promise { + + if (!config.jaas.enabled) { + throw new Error('JaaS is not configured.'); + } + + return await joinMuc(participantOptions, { + ...joinOptions, + tenant: joinOptions?.tenant || config.jaas.tenant + }); +} diff --git a/tests/specs/helpers/jaas.ts b/tests/helpers/joinMuc.ts similarity index 52% rename from tests/specs/helpers/jaas.ts rename to tests/helpers/joinMuc.ts index 4ba2ffab4e..0d8843eaac 100644 --- a/tests/specs/helpers/jaas.ts +++ b/tests/helpers/joinMuc.ts @@ -1,25 +1,9 @@ -import { Participant } from '../../helpers/Participant'; -import { config } from '../../helpers/TestsConfig'; -import { IToken, ITokenOptions, generateToken } from '../../helpers/token'; -import { IParticipantJoinOptions, IParticipantOptions } from '../../helpers/types'; - -export function generateJaasToken(options: ITokenOptions): IToken { - if (!config.jaas.enabled) { - throw new Error('JaaS is not configured.'); - } - - // Don't override the keyId and keyPath if they are already set in options, allow tests to set them. - return generateToken({ - ...options, - keyId: options.keyId || config.jaas.kid, - keyPath: options.keyPath || config.jaas.privateKeyPath - }); -} +import { Participant } from './Participant'; +import { IParticipantJoinOptions, IParticipantOptions } from './types'; /** - * Creates a new Participant and joins the MUC with the given options. The jaas-specific properties must be set as - * environment variables (see env.example and TestsConfig.ts). If no room name is specified, the default room name - * from the context is used. + * Creates a new Participant and joins the MUC with the given options. If no room name is specified, the default room + * name from the context is used. * * @param participantOptions * @param joinOptions options to use when joining the MUC. @@ -31,10 +15,6 @@ export async function joinMuc( const name = participantOptions?.name || 'p1'; - if (!config.jaas.enabled) { - throw new Error('JaaS is not configured.'); - } - // @ts-ignore const p = ctx[name] as Participant; @@ -55,7 +35,6 @@ export async function joinMuc( return await newParticipant.joinConference({ ...joinOptions, - tenant: joinOptions?.tenant || config.jaas.tenant, roomName: joinOptions?.roomName || ctx.roomName, }); } diff --git a/tests/helpers/participants.ts b/tests/helpers/participants.ts index 6273380b96..8009948be1 100644 --- a/tests/helpers/participants.ts +++ b/tests/helpers/participants.ts @@ -1,6 +1,5 @@ import { P1, P2, P3, P4, Participant } from './Participant'; import { config } from './TestsConfig'; -import { generateToken } from './token'; import { IJoinOptions, IParticipantOptions } from './types'; const SUBJECT_XPATH = '//div[starts-with(@class, "subject-text")]'; @@ -10,31 +9,22 @@ const SUBJECT_XPATH = '//div[starts-with(@class, "subject-text")]'; * Ensure that the first participant is moderator if there is such an option. * * @param {IJoinOptions} options - The options to use when joining the participant. + * @param participantOptions * @returns {Promise} */ -export async function ensureOneParticipant(options?: IJoinOptions): Promise { - const participantOps = { name: P1 } as IParticipantOptions; +export async function ensureOneParticipant( + options?: IJoinOptions, participantOptions?: IParticipantOptions): Promise { + if (!participantOptions) { + participantOptions = { name: P1 }; + } + participantOptions.name = P1; - if (!options?.skipFirstModerator) { - const jwtPrivateKeyPath = config.jwt.privateKeyPath; - - // we prioritize the access token when iframe is not used and private key is set, - // otherwise if private key is not specified we use the access token if set - if (config.jwt.preconfiguredToken - && ((jwtPrivateKeyPath && !ctx.testProperties.useIFrameApi && !options?.preferGenerateToken) - || !jwtPrivateKeyPath)) { - participantOps.token = { jwt: config.jwt.preconfiguredToken }; - } else if (jwtPrivateKeyPath) { - participantOps.token = generateToken({ - ...options?.tokenOptions, - displayName: participantOps.name, - moderator: true - }); - } + if (!participantOptions.token) { + participantOptions.token = config.jwt.preconfiguredToken; } // make sure the first participant is moderator, if supported by deployment - await joinParticipant(participantOps, options); + await joinParticipant(participantOptions, options); } /** @@ -136,23 +126,18 @@ export async function ensureFourParticipants(options?: IJoinOptions): Promise { - await ensureOneParticipant(options); +export async function ensureTwoParticipants( + options?: IJoinOptions, participantOptions?: IParticipantOptions): Promise { + await ensureOneParticipant(options, participantOptions); - const participantOptions = { name: P2 } as IParticipantOptions; - - if (options?.preferGenerateToken) { - participantOptions.token = generateToken({ - ...options?.tokenOptions, - displayName: participantOptions.name, - }); + if (!participantOptions) { + participantOptions = { name: P2 }; } + participantOptions.name = P2; - await joinParticipant({ - ...participantOptions, - name: P2 - }, options); + await joinParticipant(participantOptions, options); if (options?.skipInMeetingChecks) { return Promise.resolve(); @@ -171,8 +156,7 @@ export async function ensureTwoParticipants(options?: IJoinOptions): Promise} - The participant instance. */ async function joinParticipant( // eslint-disable-line max-params @@ -180,13 +164,11 @@ async function joinParticipant( // eslint-disable-line max-params options?: IJoinOptions ): Promise { - participantOptions.iFrameApi = ctx.testProperties.useIFrameApi; - // @ts-ignore const p = ctx[participantOptions.name] as Participant; if (p) { - if (ctx.testProperties.useIFrameApi) { + if (participantOptions.iFrameApi) { await p.switchToIFrame(); } @@ -194,7 +176,7 @@ async function joinParticipant( // eslint-disable-line max-params return p; } - if (ctx.testProperties.useIFrameApi) { + if (participantOptions.iFrameApi) { // when loading url make sure we are on the top page context or strange errors may occur await p.switchToMainFrame(); } @@ -209,87 +191,12 @@ async function joinParticipant( // eslint-disable-line max-params // @ts-ignore ctx[participantOptions.name] = newParticipant; - let tenant = options?.tenant; - - if (options?.preferGenerateToken && !ctx.testProperties.useIFrameApi - && config.iframe.usesJaas && config.iframe.tenant) { - tenant = config.iframe.tenant; - } - - if (!tenant && ctx.testProperties.useIFrameApi) { - tenant = config.iframe.tenant; - } - return await newParticipant.joinConference({ ...options, - tenant: tenant, roomName: options?.roomName || ctx.roomName, }); } -/** - * Toggles the mute state of a specific Meet conference participant and verifies that a specific other Meet - * conference participants sees a specific mute state for the former. - * - * @param {Participant} testee - The {@code Participant} which represents the Meet conference participant whose - * mute state is to be toggled. - * @param {Participant} observer - The {@code Participant} which represents the Meet conference participant to verify - * the mute state of {@code testee}. - * @returns {Promise} - */ -export async function muteAudioAndCheck(testee: Participant, observer: Participant): Promise { - await testee.getToolbar().clickAudioMuteButton(); - - await observer.getFilmstrip().assertAudioMuteIconIsDisplayed(testee); - await testee.getFilmstrip().assertAudioMuteIconIsDisplayed(testee); - - await observer.getParticipantsPane().assertAudioMuteIconIsDisplayed(testee); - await testee.getParticipantsPane().assertAudioMuteIconIsDisplayed(testee); - -} - -/** - * Unmute audio, checks if the local UI has been updated accordingly and then does the verification from - * the other observer participant perspective. - * @param testee - * @param observer - */ -export async function unmuteAudioAndCheck(testee: Participant, observer: Participant) { - await testee.getNotifications().closeAskToUnmuteNotification(true); - await testee.getNotifications().closeAVModerationMutedNotification(true); - await testee.getToolbar().clickAudioUnmuteButton(); - - await testee.getFilmstrip().assertAudioMuteIconIsDisplayed(testee, true); - await observer.getFilmstrip().assertAudioMuteIconIsDisplayed(testee, true); - - await testee.getParticipantsPane().assertAudioMuteIconIsDisplayed(testee, true); - await observer.getParticipantsPane().assertAudioMuteIconIsDisplayed(testee, true); -} - -/** - * Stop the video on testee and check on observer. - * @param testee - * @param observer - */ -export async function unmuteVideoAndCheck(testee: Participant, observer: Participant): Promise { - await testee.getToolbar().clickVideoUnmuteButton(); - - await testee.getParticipantsPane().assertVideoMuteIconIsDisplayed(testee, true); - await observer.getParticipantsPane().assertVideoMuteIconIsDisplayed(testee, true); -} - -/** - * Starts the video on testee and check on observer. - * @param testee - * @param observer - */ -export async function muteVideoAndCheck(testee: Participant, observer: Participant): Promise { - await testee.getToolbar().clickVideoMuteButton(); - - await testee.getParticipantsPane().assertVideoMuteIconIsDisplayed(testee); - await observer.getParticipantsPane().assertVideoMuteIconIsDisplayed(testee); -} - /** * Parse a JID string. * @param str the string to parse. diff --git a/tests/helpers/types.ts b/tests/helpers/types.ts index aa9d46f658..4d7c53d630 100644 --- a/tests/helpers/types.ts +++ b/tests/helpers/types.ts @@ -37,8 +37,8 @@ export type IContext = { export type IParticipantOptions = { /** Whether it should use the iFrame API. */ iFrameApi?: boolean; - /** Must be 'p1', 'p2', 'p3', or 'p4'. */ - name: string; + /** Determines the browser instance to use. */ + name: 'p1' | 'p2' | 'p3' | 'p4'; /** An optional token to use. */ token?: IToken; }; @@ -80,12 +80,6 @@ export type IJoinOptions = { */ configOverwrite?: IConfig; - /** - * When joining the first participant and jwt singing material is available and a provided token - * is available, prefer generating a new token for the first participant. - */ - preferGenerateToken?: boolean; - /** * To be able to override the ctx generated room name. If missing the one from the context will be used. */ @@ -96,11 +90,6 @@ export type IJoinOptions = { */ skipDisplayName?: boolean; - /** - * Whether to skip setting the moderator role for the first participant (whether to use jwt for it). - */ - skipFirstModerator?: boolean; - /** * Whether to skip in meeting checks like ice connected and send receive data. For single in meeting participant. */ diff --git a/tests/pageobjects/ChatPanel.ts b/tests/pageobjects/ChatPanel.ts index fa47748af0..84e5e96aee 100644 --- a/tests/pageobjects/ChatPanel.ts +++ b/tests/pageobjects/ChatPanel.ts @@ -20,6 +20,20 @@ export default class ChatPanel extends BasePageObject { await this.participant.driver.keys([ 'c' ]); } + async sendMessage(message: string) { + if (!await this.isOpen()) { + await this.pressShortcut(); + } + if (!await this.isOpen()) { + throw new Error('Chat panel failed to open'); + } + + const inputField = this.participant.driver.$('#chat-input'); + + await inputField.click(); + await this.participant.driver.keys(`${message}\n`); + } + /** * Opens the polls tab in the chat panel. */ diff --git a/tests/specs/2way/mute.spect.ts b/tests/specs/2way/mute.spec.ts similarity index 99% rename from tests/specs/2way/mute.spect.ts rename to tests/specs/2way/mute.spec.ts index bc02265c55..919d50d24b 100644 --- a/tests/specs/2way/mute.spect.ts +++ b/tests/specs/2way/mute.spec.ts @@ -4,10 +4,12 @@ import { ensureOneParticipant, ensureTwoParticipants, joinSecondParticipant, +} from '../../helpers/participants'; +import { muteAudioAndCheck, unmuteAudioAndCheck, unmuteVideoAndCheck -} from '../../helpers/participants'; +} from '../helpers/mute'; describe('Mute', () => { it('joining the meeting', () => ensureTwoParticipants()); diff --git a/tests/specs/2way/stopVideo.spec.ts b/tests/specs/2way/stopVideo.spec.ts index 4b0dbe7d9e..3c78b6de81 100644 --- a/tests/specs/2way/stopVideo.spec.ts +++ b/tests/specs/2way/stopVideo.spec.ts @@ -1,4 +1,5 @@ -import { ensureTwoParticipants, muteVideoAndCheck, unmuteVideoAndCheck } from '../../helpers/participants'; +import { ensureTwoParticipants } from '../../helpers/participants'; +import { muteVideoAndCheck, unmuteVideoAndCheck } from '../helpers/mute'; describe('Stop video', () => { it('joining the meeting', () => ensureTwoParticipants()); diff --git a/tests/specs/3way/activeSpeaker.spec.ts b/tests/specs/3way/activeSpeaker.spec.ts index c0403e03fe..e5b6ed354b 100644 --- a/tests/specs/3way/activeSpeaker.spec.ts +++ b/tests/specs/3way/activeSpeaker.spec.ts @@ -1,5 +1,6 @@ import type { Participant } from '../../helpers/Participant'; -import { ensureThreeParticipants, muteAudioAndCheck } from '../../helpers/participants'; +import { ensureThreeParticipants } from '../../helpers/participants'; +import { muteAudioAndCheck } from '../helpers/mute'; describe('ActiveSpeaker', () => { it('testActiveSpeaker', async () => { diff --git a/tests/specs/3way/audioVideoModeration.spec.ts b/tests/specs/3way/audioVideoModeration.spec.ts index 8bfffb3db3..4aed651ac0 100644 --- a/tests/specs/3way/audioVideoModeration.spec.ts +++ b/tests/specs/3way/audioVideoModeration.spec.ts @@ -3,10 +3,9 @@ import { config } from '../../helpers/TestsConfig'; import { ensureOneParticipant, ensureThreeParticipants, ensureTwoParticipants, - hangupAllParticipants, - unmuteAudioAndCheck, - unmuteVideoAndCheck + hangupAllParticipants } from '../../helpers/participants'; +import { unmuteAudioAndCheck, unmuteVideoAndCheck } from '../helpers/mute'; describe('AVModeration', () => { @@ -78,8 +77,9 @@ describe('AVModeration', () => { }); it('hangup and change moderator', async () => { - // no moderator switching if jaas is available. - if (config.iframe.usesJaas) { + // The test below is only correct when the environment is configured to automatically elect a new moderator + // when the moderator leaves. For environments where this is not the case, the test is skipped. + if (!config.autoModerator) { return; } diff --git a/tests/specs/3way/avatars.spec.ts b/tests/specs/3way/avatars.spec.ts index 6b294fbaa9..534e32cbe3 100644 --- a/tests/specs/3way/avatars.spec.ts +++ b/tests/specs/3way/avatars.spec.ts @@ -1,8 +1,5 @@ -import { - ensureThreeParticipants, - ensureTwoParticipants, - unmuteVideoAndCheck -} from '../../helpers/participants'; +import { ensureThreeParticipants, ensureTwoParticipants } from '../../helpers/participants'; +import { unmuteVideoAndCheck } from '../helpers/mute'; const EMAIL = 'support@jitsi.org'; const HASH = '38f014e4b7dde0f64f8157d26a8c812e'; diff --git a/tests/specs/3way/lobby.spec.ts b/tests/specs/3way/lobby.spec.ts index 600f293887..720e8b2c22 100644 --- a/tests/specs/3way/lobby.spec.ts +++ b/tests/specs/3way/lobby.spec.ts @@ -196,8 +196,9 @@ describe('Lobby', () => { }); it('change of moderators in lobby', async () => { - // no moderator switching if jaas is available. - if (config.iframe.usesJaas) { + // The test below is only correct when the environment is configured to automatically elect a new moderator + // when the moderator leaves. For environments where this is not the case, the test is skipped. + if (!config.autoModerator) { return; } await hangupAllParticipants(); @@ -288,8 +289,9 @@ describe('Lobby', () => { }); it('moderator leaves while lobby enabled', async () => { - // no moderator switching if jaas is available. - if (config.iframe.usesJaas) { + // The test below is only correct when the environment is configured to automatically elect a new moderator + // when the moderator leaves. For environments where this is not the case, the test is skipped. + if (!config.autoModerator) { return; } const { p1, p2, p3 } = ctx; diff --git a/tests/specs/3way/startMuted.spec.ts b/tests/specs/3way/startMuted.spec.ts index 41e8605458..b9262f0c63 100644 --- a/tests/specs/3way/startMuted.spec.ts +++ b/tests/specs/3way/startMuted.spec.ts @@ -4,9 +4,9 @@ import { ensureTwoParticipants, hangupAllParticipants, joinSecondParticipant, - joinThirdParticipant, - unmuteVideoAndCheck + joinThirdParticipant } from '../../helpers/participants'; +import { unmuteVideoAndCheck } from '../helpers/mute'; describe('StartMuted', () => { it('checkboxes test', async () => { diff --git a/tests/specs/alone/dialInAudio.spec.ts b/tests/specs/alone/dialInAudio.spec.ts index e41dacac4b..bf91d1e711 100644 --- a/tests/specs/alone/dialInAudio.spec.ts +++ b/tests/specs/alone/dialInAudio.spec.ts @@ -1,5 +1,6 @@ import process from 'node:process'; +import { config as testsConfig } from '../../helpers/TestsConfig'; import { ensureOneParticipant } from '../../helpers/participants'; import { cleanup, dialIn, isDialInEnabled, waitForAudioFromDialInParticipant } from '../helpers/DialIn'; @@ -12,9 +13,18 @@ describe('Dial-In', () => { return; } - await ensureOneParticipant({ preferGenerateToken: true }); + // This is a temporary hack to avoid failing when running against a jaas env. The same cases are covered in + // jaas/dial/dialin.spec.ts. + if (testsConfig.jaas.enabled) { + ctx.skipSuiteTests = true; + + return; + } + + await ensureOneParticipant(); + + expect(await ctx.p1.isInMuc()).toBe(true); - // check dial-in is enabled if (!await isDialInEnabled(ctx.p1)) { ctx.skipSuiteTests = true; } @@ -41,7 +51,7 @@ describe('Dial-In', () => { }); it('invite dial-in participant', async () => { - await dialIn(ctx.p1); + await dialIn(await ctx.p1.getDialInPin()); }); it('wait for audio from dial-in participant', async () => { diff --git a/tests/specs/alone/invite.spec.ts b/tests/specs/alone/invite.spec.ts index b01d049dc1..9632f2915a 100644 --- a/tests/specs/alone/invite.spec.ts +++ b/tests/specs/alone/invite.spec.ts @@ -1,85 +1,41 @@ +import { Participant } from '../../helpers/Participant'; +import { config as testsConfig } from '../../helpers/TestsConfig'; import { ensureOneParticipant } from '../../helpers/participants'; -import { isDialInEnabled } from '../helpers/DialIn'; +import { assertDialInDisplayed, assertUrlDisplayed, isDialInEnabled, verifyMoreNumbersPage } from '../helpers/DialIn'; describe('Invite', () => { - it('join participant', () => ensureOneParticipant({ preferGenerateToken: true })); + let p1: Participant; + let dialInEnabled: boolean; - it('url displayed', async () => { - const { p1 } = ctx; - const inviteDialog = p1.getInviteDialog(); + it('setup', async () => { + // This is a temporary hack to avoid failing when running against a jaas env. The same cases are covered in + // jaas/dial/dialin.spec.ts. + if (testsConfig.jaas.enabled) { + ctx.skipSuiteTests = true; - await inviteDialog.open(); - await inviteDialog.waitTillOpen(); + return; + } - const driverUrl = await p1.driver.getUrl(); + await ensureOneParticipant(); - expect(driverUrl.includes(await inviteDialog.getMeetingURL())).toBe(true); - - await inviteDialog.clickCloseButton(); - - await inviteDialog.waitTillOpen(true); + p1 = ctx.p1; + dialInEnabled = await isDialInEnabled(p1); }); + it('url displayed', () => assertUrlDisplayed(p1)); + it('dial-in displayed', async () => { - const { p1 } = ctx; - - if (!await isDialInEnabled(p1)) { + if (!dialInEnabled) { return; } - - const inviteDialog = p1.getInviteDialog(); - - await inviteDialog.open(); - await inviteDialog.waitTillOpen(); - - expect((await inviteDialog.getDialInNumber()).length > 0).toBe(true); - expect((await inviteDialog.getPinNumber()).length > 0).toBe(true); + await assertDialInDisplayed(p1); }); - it('view more numbers', async () => { - const { p1 } = ctx; - - if (!await isDialInEnabled(p1)) { + it('view more numbers page', async () => { + if (!dialInEnabled) { return; } - const inviteDialog = p1.getInviteDialog(); - - await inviteDialog.open(); - await inviteDialog.waitTillOpen(); - - const windows = await p1.driver.getWindowHandles(); - - expect(windows.length).toBe(1); - - const meetingWindow = windows[0]; - - const displayedNumber = await inviteDialog.getDialInNumber(); - const displayedPin = await inviteDialog.getPinNumber(); - - await inviteDialog.openDialInNumbersPage(); - - const newWindow = (await p1.driver.getWindowHandles()).filter(w => w !== meetingWindow); - - expect(newWindow.length).toBe(1); - - const moreNumbersWindow = newWindow[0]; - - await p1.driver.switchWindow(moreNumbersWindow); - - await browser.pause(10000); - - await p1.driver.$('.dial-in-numbers-list').waitForExist(); - - const conferenceIdMessage = p1.driver.$('//div[contains(@class, "pinLabel")]'); - - expect((await conferenceIdMessage.getText()).replace(/ /g, '').includes(displayedPin)).toBe(true); - - const numbers = p1.driver.$$('.dial-in-number'); - - const nums = await numbers.filter( - async el => (await el.getText()).trim() === displayedNumber); - - expect(nums.length).toBe(1); + await verifyMoreNumbersPage(p1); }); }); diff --git a/tests/specs/helpers/DialIn.ts b/tests/specs/helpers/DialIn.ts index 00722c2ddd..0131d9e07b 100644 --- a/tests/specs/helpers/DialIn.ts +++ b/tests/specs/helpers/DialIn.ts @@ -52,17 +52,10 @@ export async function isDialInEnabled(participant: Participant) { /** * Sends a request to the REST API to dial in the participant using the provided pin. - * @param participant + * @param pin the pin to use when dialing in */ -export async function dialIn(participant: Participant) { - if (!await participant.isInMuc()) { - // local participant did not join abort - return; - } - - const dialInPin = await participant.getDialInPin(); - - const restUrl = process.env.DIAL_IN_REST_URL?.replace('{0}', dialInPin); +export async function dialIn(pin: string) { + const restUrl = process.env.DIAL_IN_REST_URL?.replace('{0}', pin); // we have already checked in the first test that DIAL_IN_REST_URL exist so restUrl cannot be '' const responseData: string = await new Promise((resolve, reject) => { @@ -88,3 +81,66 @@ export async function dialIn(participant: Participant) { console.log(`dial-in.test.call_session_history_id:${JSON.parse(responseData).call_session_history_id}`); console.log(`API response:${responseData}`); } + +export async function assertUrlDisplayed(p: Participant) { + const inviteDialog = p.getInviteDialog(); + + await inviteDialog.open(); + await inviteDialog.waitTillOpen(); + + const driverUrl = await p.driver.getUrl(); + + expect(driverUrl.includes(await inviteDialog.getMeetingURL())).toBe(true); + await inviteDialog.clickCloseButton(); + await inviteDialog.waitTillOpen(true); +} + +export async function assertDialInDisplayed(p: Participant) { + const inviteDialog = p.getInviteDialog(); + + await inviteDialog.open(); + await inviteDialog.waitTillOpen(); + + expect((await inviteDialog.getDialInNumber()).length > 0).toBe(true); + expect((await inviteDialog.getPinNumber()).length > 0).toBe(true); +} + +export async function verifyMoreNumbersPage(p: Participant) { + const inviteDialog = p.getInviteDialog(); + + await inviteDialog.open(); + await inviteDialog.waitTillOpen(); + + const windows = await p.driver.getWindowHandles(); + + expect(windows.length).toBe(1); + + const meetingWindow = windows[0]; + + const displayedNumber = await inviteDialog.getDialInNumber(); + const displayedPin = await inviteDialog.getPinNumber(); + + await inviteDialog.openDialInNumbersPage(); + + const newWindow = (await p.driver.getWindowHandles()).filter(w => w !== meetingWindow); + + expect(newWindow.length).toBe(1); + + const moreNumbersWindow = newWindow[0]; + + await p.driver.switchWindow(moreNumbersWindow); + await browser.pause(10000); + await p.driver.$('.dial-in-numbers-list').waitForExist(); + + const conferenceIdMessage = p.driver.$('//div[contains(@class, "pinLabel")]'); + + expect((await conferenceIdMessage.getText()).replace(/ /g, '').includes(displayedPin)).toBe(true); + + const numbers = p.driver.$$('.dial-in-number'); + const nums = await numbers.filter( + async el => (await el.getText()).trim() === displayedNumber); + + expect(nums.length).toBe(1); + + await p.driver.switchWindow(meetingWindow); +} diff --git a/tests/specs/helpers/mute.ts b/tests/specs/helpers/mute.ts new file mode 100644 index 0000000000..d1bb67b14b --- /dev/null +++ b/tests/specs/helpers/mute.ts @@ -0,0 +1,64 @@ +import { Participant } from '../../helpers/Participant'; + +/** + * Toggles the mute state of a specific Meet conference participant and verifies that a specific other Meet + * conference participants sees a specific mute state for the former. + * + * @param {Participant} testee - The {@code Participant} which represents the Meet conference participant whose + * mute state is to be toggled. + * @param {Participant} observer - The {@code Participant} which represents the Meet conference participant to verify + * the mute state of {@code testee}. + * @returns {Promise} + */ +export async function muteAudioAndCheck(testee: Participant, observer: Participant): Promise { + await testee.getToolbar().clickAudioMuteButton(); + + await observer.getFilmstrip().assertAudioMuteIconIsDisplayed(testee); + await testee.getFilmstrip().assertAudioMuteIconIsDisplayed(testee); + + await observer.getParticipantsPane().assertAudioMuteIconIsDisplayed(testee); + await testee.getParticipantsPane().assertAudioMuteIconIsDisplayed(testee); + +} + +/** + * Unmute audio, checks if the local UI has been updated accordingly and then does the verification from + * the other observer participant perspective. + * @param testee + * @param observer + */ +export async function unmuteAudioAndCheck(testee: Participant, observer: Participant) { + await testee.getNotifications().closeAskToUnmuteNotification(true); + await testee.getNotifications().closeAVModerationMutedNotification(true); + await testee.getToolbar().clickAudioUnmuteButton(); + + await testee.getFilmstrip().assertAudioMuteIconIsDisplayed(testee, true); + await observer.getFilmstrip().assertAudioMuteIconIsDisplayed(testee, true); + + await testee.getParticipantsPane().assertAudioMuteIconIsDisplayed(testee, true); + await observer.getParticipantsPane().assertAudioMuteIconIsDisplayed(testee, true); +} + +/** + * Stop the video on testee and check on observer. + * @param testee + * @param observer + */ +export async function unmuteVideoAndCheck(testee: Participant, observer: Participant): Promise { + await testee.getToolbar().clickVideoUnmuteButton(); + + await testee.getParticipantsPane().assertVideoMuteIconIsDisplayed(testee, true); + await observer.getParticipantsPane().assertVideoMuteIconIsDisplayed(testee, true); +} + +/** + * Starts the video on testee and check on observer. + * @param testee + * @param observer + */ +export async function muteVideoAndCheck(testee: Participant, observer: Participant): Promise { + await testee.getToolbar().clickVideoMuteButton(); + + await testee.getParticipantsPane().assertVideoMuteIconIsDisplayed(testee); + await observer.getParticipantsPane().assertVideoMuteIconIsDisplayed(testee); +} diff --git a/tests/specs/iframe/chat.spec.ts b/tests/specs/iframe/chat.spec.ts index 8b6e07c653..e92f4ca135 100644 --- a/tests/specs/iframe/chat.spec.ts +++ b/tests/specs/iframe/chat.spec.ts @@ -2,20 +2,19 @@ import { expect } from '@wdio/globals'; import type { Participant } from '../../helpers/Participant'; import { setTestProperties } from '../../helpers/TestProperties'; -import { ensureTwoParticipants } from '../../helpers/participants'; -import { fetchJson } from '../../helpers/utils'; +import { config as testsConfig } from '../../helpers/TestsConfig'; +import { joinMuc } from '../../helpers/joinMuc'; setTestProperties(__filename, { - useIFrameApi: true, - useWebhookProxy: true, usesBrowsers: [ 'p1', 'p2' ] }); -describe('Chat', () => { - it('joining the meeting', async () => { - await ensureTwoParticipants(); +describe('iFrame API for Chat', () => { + let p1: Participant, p2: Participant; - const { p1, p2 } = ctx; + it('setup', async () => { + p1 = await joinMuc({ name: 'p1', iFrameApi: true, token: testsConfig.jwt.preconfiguredToken }); + p2 = await joinMuc({ name: 'p2', iFrameApi: true }); if (await p1.execute(() => config.disableIframeAPI)) { // skip the test if iframeAPI is disabled @@ -24,19 +23,11 @@ describe('Chat', () => { return; } - // let's populate endpoint ids - await Promise.all([ - p1.getEndpointId(), - p2.getEndpointId() - ]); + await p1.switchToMainFrame(); + await p2.switchToMainFrame(); }); it('send message', async () => { - const { p1, p2 } = ctx; - - await p1.switchToMainFrame(); - await p2.switchToMainFrame(); - await p2.getIframeAPI().addEventListener('chatUpdated'); await p2.getIframeAPI().addEventListener('incomingMessage'); await p1.getIframeAPI().addEventListener('outgoingMessage'); @@ -65,7 +56,7 @@ describe('Chat', () => { privateMessage: boolean; } = await p2.getIframeAPI().getEventResult('incomingMessage'); - expect(incomingMessageEvent).toEqual({ + expect(incomingMessageEvent).toMatchObject({ from: await p1.getEndpointId(), message: testMessage, nick: p1.name, @@ -88,8 +79,6 @@ describe('Chat', () => { }); it('toggle chat', async () => { - const { p1, p2 } = ctx; - await p2.getIframeAPI().executeCommand('toggleChat'); await testSendGroupMessageWithChatOpen(p1, p2); @@ -100,13 +89,10 @@ describe('Chat', () => { }); 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); + await p1.getIframeAPI().executeCommand('initiatePrivateChat', await p2.getEndpointId()); + await p1.getIframeAPI().executeCommand('sendChatMessage', testMessage, await p2.getEndpointId()); const incomingMessageEvent = await p2.driver.waitUntil( () => p2.getIframeAPI().getEventResult('incomingMessage'), { @@ -114,8 +100,8 @@ describe('Chat', () => { timeoutMsg: 'Chat was not received' }); - expect(incomingMessageEvent).toEqual({ - from: p1Id, + expect(incomingMessageEvent).toMatchObject({ + from: await p1.getEndpointId(), message: testMessage, nick: p1.name, privateMessage: true @@ -133,47 +119,22 @@ describe('Chat', () => { 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'); - - 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 + * Send a group message from [sender], verify that it was received correctly by [receiver]. + * @param sender the Participant that sends the message. + * @param receiver the Participant that receives the message. */ -async function testSendGroupMessageWithChatOpen(p1: Participant, p2: Participant) { +async function testSendGroupMessageWithChatOpen(sender: Participant, receiver: Participant) { const testMessage = 'Hello world again'; - await p1.getIframeAPI().executeCommand('sendChatMessage', testMessage); + await sender.getIframeAPI().executeCommand('sendChatMessage', testMessage); const chatUpdatedEvent: { isOpen: boolean; unreadCount: number; - } = await p2.driver.waitUntil(() => p2.getIframeAPI().getEventResult('chatUpdated'), { + } = await receiver.driver.waitUntil(() => receiver.getIframeAPI().getEventResult('chatUpdated'), { timeout: 3000, timeoutMsg: 'Chat was not updated' }); @@ -183,16 +144,16 @@ async function testSendGroupMessageWithChatOpen(p1: Participant, p2: Participant unreadCount: 0 }); - const incomingMessageEvent = await p2.driver.waitUntil( - () => p2.getIframeAPI().getEventResult('incomingMessage'), { + const incomingMessageEvent = await receiver.driver.waitUntil( + () => receiver.getIframeAPI().getEventResult('incomingMessage'), { timeout: 3000, timeoutMsg: 'Chat was not received' }); - expect(incomingMessageEvent).toEqual({ - from: await p1.getEndpointId(), + expect(incomingMessageEvent).toMatchObject({ + from: await sender.getEndpointId(), message: testMessage, - nick: p1.name, + nick: sender.name, privateMessage: false }); } diff --git a/tests/specs/iframe/invite.spec.ts b/tests/specs/iframe/invite.spec.ts deleted file mode 100644 index 84f8629382..0000000000 --- a/tests/specs/iframe/invite.spec.ts +++ /dev/null @@ -1,214 +0,0 @@ -import type { Participant } from '../../helpers/Participant'; -import { setTestProperties } from '../../helpers/TestProperties'; -import { config as testsConfig } from '../../helpers/TestsConfig'; -import { ensureOneParticipant } from '../../helpers/participants'; -import { - cleanup, - dialIn, - isDialInEnabled, - waitForAudioFromDialInParticipant -} from '../helpers/DialIn'; - -setTestProperties(__filename, { - useIFrameApi: true, - useWebhookProxy: true -}); - -const customerId = testsConfig.iframe.customerId; - -describe('Invite iframeAPI', () => { - let dialInDisabled: boolean; - let dialOutDisabled: boolean; - let sipJibriDisabled: boolean; - - it('join participant', async () => { - await ensureOneParticipant(); - - const { p1 } = ctx; - - // check for dial-in dial-out sip-jibri maybe - if (await p1.execute(() => config.disableIframeAPI)) { - // skip the test if iframeAPI is disabled - ctx.skipSuiteTests = true; - - return; - } - - dialOutDisabled = Boolean(!await p1.execute(() => config.dialOutAuthUrl)); - sipJibriDisabled = Boolean(!await p1.execute(() => config.inviteServiceUrl)); - - // check dial-in is enabled - if (!await isDialInEnabled(ctx.p1) || !process.env.DIAL_IN_REST_URL) { - dialInDisabled = true; - } - }); - - it('dial-in', async () => { - if (dialInDisabled) { - return; - } - - const { p1 } = ctx; - const dialInPin = await p1.getDialInPin(); - - expect(dialInPin.length >= 8).toBe(true); - - await dialIn(p1); - - if (!await p1.isInMuc()) { - // local participant did not join abort - return; - } - - await waitForAudioFromDialInParticipant(p1); - - await checkDialEvents(p1, 'in', 'DIAL_IN_STARTED', 'DIAL_IN_ENDED'); - }); - - it('dial-out', async () => { - if (dialOutDisabled || !process.env.DIAL_OUT_URL) { - return; - } - - const { p1 } = ctx; - - await p1.switchToMainFrame(); - - await p1.getIframeAPI().invitePhone(process.env.DIAL_OUT_URL); - - await p1.switchToIFrame(); - - await p1.waitForParticipants(1); - - await waitForAudioFromDialInParticipant(p1); - - await checkDialEvents(p1, 'out', 'DIAL_OUT_STARTED', 'DIAL_OUT_ENDED'); - }); - - it('sip jibri', async () => { - if (sipJibriDisabled || !process.env.SIP_JIBRI_DIAL_OUT_URL) { - return; - } - - const { p1 } = ctx; - - await p1.switchToMainFrame(); - - await p1.getIframeAPI().inviteSIP(process.env.SIP_JIBRI_DIAL_OUT_URL); - - await p1.switchToIFrame(); - - await p1.waitForParticipants(1); - - await waitForAudioFromDialInParticipant(p1); - - const { webhooksProxy } = ctx; - - if (webhooksProxy) { - const sipCallOutStartedEvent: { - customerId: string; - data: { - participantFullJid: string; - participantId: string; - participantJid: string; - sipAddress: string; - }; - eventType: string; - } = await webhooksProxy.waitForEvent('SIP_CALL_OUT_STARTED'); - - expect('SIP_CALL_OUT_STARTED').toBe(sipCallOutStartedEvent.eventType); - expect(sipCallOutStartedEvent.data.sipAddress).toBe(`sip:${process.env.SIP_JIBRI_DIAL_OUT_URL}`); - expect(sipCallOutStartedEvent.customerId).toBe(customerId); - - const participantId = sipCallOutStartedEvent.data.participantId; - const participantJid = sipCallOutStartedEvent.data.participantJid; - const participantFullJid = sipCallOutStartedEvent.data.participantFullJid; - - await cleanup(p1); - - const sipCallOutEndedEvent: { - customerId: string; - data: { - direction: string; - participantFullJid: string; - participantId: string; - participantJid: string; - }; - eventType: string; - } = await webhooksProxy.waitForEvent('SIP_CALL_OUT_ENDED'); - - expect('SIP_CALL_OUT_ENDED').toBe(sipCallOutEndedEvent.eventType); - expect(sipCallOutEndedEvent.customerId).toBe(customerId); - expect(sipCallOutEndedEvent.data.participantFullJid).toBe(participantFullJid); - expect(sipCallOutEndedEvent.data.participantId).toBe(participantId); - expect(sipCallOutEndedEvent.data.participantJid).toBe(participantJid); - } else { - await cleanup(p1); - } - }); -}); - -/** - * Checks the dial events for a participant and clean up at the end. - * @param participant - * @param startedEventName - * @param endedEventName - * @param direction - */ -async function checkDialEvents(participant: Participant, direction: string, startedEventName: string, endedEventName: string) { - const { webhooksProxy } = ctx; - - if (webhooksProxy) { - const dialInStartedEvent: { - customerId: string; - data: { - direction: string; - participantFullJid: string; - participantId: string; - participantJid: string; - }; - eventType: string; - } = await webhooksProxy.waitForEvent(startedEventName); - - expect(startedEventName).toBe(dialInStartedEvent.eventType); - expect(dialInStartedEvent.data.direction).toBe(direction); - expect(dialInStartedEvent.customerId).toBe(customerId); - - const participantId = dialInStartedEvent.data.participantId; - const participantJid = dialInStartedEvent.data.participantJid; - const participantFullJid = dialInStartedEvent.data.participantFullJid; - - const usageEvent: { - customerId: string; - data: any; - eventType: string; - } = await webhooksProxy.waitForEvent('USAGE'); - - expect('USAGE').toBe(usageEvent.eventType); - expect(usageEvent.customerId).toBe(customerId); - - expect(usageEvent.data.some((el: any) => - el.participantId === participantId && el.callDirection === direction)).toBe(true); - - await cleanup(participant); - - const dialInEndedEvent: { - customerId: string; - data: { - direction: string; - participantFullJid: string; - participantId: string; - participantJid: string; - }; - eventType: string; - } = await webhooksProxy.waitForEvent(endedEventName); - - expect(endedEventName).toBe(dialInEndedEvent.eventType); - expect(dialInEndedEvent.customerId).toBe(customerId); - expect(dialInEndedEvent.data.participantFullJid).toBe(participantFullJid); - expect(dialInEndedEvent.data.participantId).toBe(participantId); - expect(dialInEndedEvent.data.participantJid).toBe(participantJid); - } else { - await cleanup(participant); - } -} diff --git a/tests/specs/iframe/participantsPresence.spec.ts b/tests/specs/iframe/participantsPresence.spec.ts index 3773b20538..1ff234616b 100644 --- a/tests/specs/iframe/participantsPresence.spec.ts +++ b/tests/specs/iframe/participantsPresence.spec.ts @@ -1,64 +1,18 @@ import { isEqual } from 'lodash-es'; -import { P1, P2, Participant } from '../../helpers/Participant'; +import { P1, P2 } from '../../helpers/Participant'; import { setTestProperties } from '../../helpers/TestProperties'; -import { config as testsConfig } from '../../helpers/TestsConfig'; import { ensureTwoParticipants, parseJid } from '../../helpers/participants'; setTestProperties(__filename, { - useIFrameApi: true, - useWebhookProxy: true, usesBrowsers: [ 'p1', 'p2' ] }); -/** - * Tests PARTICIPANT_LEFT webhook. - */ -async function checkParticipantLeftHook(p: Participant, reason: string, checkId = false, conferenceJid: string) { - const { webhooksProxy } = ctx; - - if (webhooksProxy) { - // PARTICIPANT_LEFT webhook - // @ts-ignore - const event: { - customerId: string; - data: { - conference: string; - disconnectReason: string; - group: string; - id: string; - isBreakout: boolean; - name: string; - participantId: string; - }; - eventType: string; - } = await webhooksProxy.waitForEvent('PARTICIPANT_LEFT'); - - expect('PARTICIPANT_LEFT').toBe(event.eventType); - expect(event.data.conference).toBe(conferenceJid); - expect(event.data.disconnectReason).toBe(reason); - expect(event.data.isBreakout).toBe(false); - expect(event.data.participantId).toBe(await p.getEndpointId()); - expect(event.data.name).toBe(p.name); - - if (checkId) { - const jwtPayload = p.getToken()?.payload; - - expect(event.data.id).toBe(jwtPayload?.context?.user?.id); - expect(event.data.group).toBe(jwtPayload?.context?.group); - expect(event.customerId).toBe(testsConfig.iframe.customerId); - } - } -} - describe('Participants presence', () => { - let conferenceJid: string = ''; - it('joining the meeting', async () => { - // ensure 2 participants one moderator and one guest, we will load both with iframeAPI - await ensureTwoParticipants(); + await ensureTwoParticipants({}, { name: 'p1', iFrameApi: true }); - const { p1, p2, webhooksProxy } = ctx; + const { p1, p2 } = ctx; if (await p1.execute(() => config.disableIframeAPI)) { // skip the test if iframeAPI is disabled @@ -67,44 +21,20 @@ describe('Participants presence', () => { return; } - // let's populate endpoint ids await Promise.all([ - p1.getEndpointId(), - p2.getEndpointId() + p1.switchToMainFrame(), + p2.switchToMainFrame() ]); - await p1.switchToMainFrame(); - await p2.switchToMainFrame(); - expect(await p1.getIframeAPI().getEventResult('isModerator')).toBe(true); - expect(await p2.getIframeAPI().getEventResult('isModerator')).toBe(false); expect(await p1.getIframeAPI().getEventResult('videoConferenceJoined')).toBeDefined(); expect(await p2.getIframeAPI().getEventResult('videoConferenceJoined')).toBeDefined(); - - if (webhooksProxy) { - // USAGE webhook - // @ts-ignore - const event: { - data: [ - { participantId: string; } - ]; - eventType: string; - } = await webhooksProxy.waitForEvent('USAGE'); - - expect('USAGE').toBe(event.eventType); - - const p1EpId = await p1.getEndpointId(); - const p2EpId = await p2.getEndpointId(); - - expect(event.data.filter(d => d.participantId === p1EpId - || d.participantId === p2EpId).length).toBe(2); - } }); it('participants info', async () => { - const { p1, roomName, webhooksProxy } = ctx; + const { p1, roomName } = ctx; const roomsInfo = (await p1.getIframeAPI().getRoomsInfo()).rooms[0]; expect(roomsInfo).toBeDefined(); @@ -116,9 +46,6 @@ describe('Participants presence', () => { expect(roomNode).toBe(roomName); const { node, resource } = parseJid(roomsInfo.jid); - - conferenceJid = roomsInfo.jid.substring(0, roomsInfo.jid.indexOf('/')); - const p1EpId = await p1.getEndpointId(); expect(node).toBe(roomName); @@ -126,30 +53,12 @@ describe('Participants presence', () => { expect(roomsInfo.participants.length).toBe(2); expect(await p1.getIframeAPI().getNumberOfParticipants()).toBe(2); - - if (webhooksProxy) { - // ROOM_CREATED webhook - // @ts-ignore - const event: { - data: { - conference: string; - isBreakout: boolean; - }; - eventType: string; - } = await webhooksProxy.waitForEvent('ROOM_CREATED'); - - expect('ROOM_CREATED').toBe(event.eventType); - expect(event.data.conference).toBe(conferenceJid); - expect(event.data.isBreakout).toBe(false); - } } ); it('participants pane', async () => { const { p1 } = ctx; - await p1.switchToMainFrame(); - expect(await p1.getIframeAPI().isParticipantsPaneOpen()).toBe(false); await p1.getIframeAPI().addEventListener('participantsPaneToggled'); @@ -163,68 +72,7 @@ describe('Participants presence', () => { expect((await p1.getIframeAPI().getEventResult('participantsPaneToggled'))?.open).toBe(false); }); - it('grant moderator', async () => { - const { p1, p2, webhooksProxy } = ctx; - const p2EpId = await p2.getEndpointId(); - - await p1.getIframeAPI().clearEventResults('participantRoleChanged'); - await p2.getIframeAPI().clearEventResults('participantRoleChanged'); - - await p1.getIframeAPI().executeCommand('grantModerator', p2EpId); - - await p2.driver.waitUntil(() => p2.getIframeAPI().getEventResult('isModerator'), { - timeout: 3000, - timeoutMsg: 'Moderator role not granted' - }); - - type RoleChangedEvent = { - id: string; - role: string; - }; - - const event1: RoleChangedEvent = await p1.driver.waitUntil( - () => p1.getIframeAPI().getEventResult('participantRoleChanged'), { - timeout: 3000, - timeoutMsg: 'Role was not update on p1 side' - }); - - expect(event1?.id).toBe(p2EpId); - expect(event1?.role).toBe('moderator'); - - const event2: RoleChangedEvent = await p2.driver.waitUntil( - () => p2.getIframeAPI().getEventResult('participantRoleChanged'), { - timeout: 3000, - timeoutMsg: 'Role was not update on p2 side' - }); - - expect(event2?.id).toBe(p2EpId); - expect(event2?.role).toBe('moderator'); - - if (webhooksProxy) { - // ROLE_CHANGED webhook - // @ts-ignore - const event: { - data: { - grantedBy: { - participantId: string; - }; - grantedTo: { - participantId: string; - }; - role: string; - }; - eventType: string; - } = await webhooksProxy.waitForEvent('ROLE_CHANGED'); - - expect('ROLE_CHANGED').toBe(event.eventType); - expect(event.data.role).toBe('moderator'); - expect(event.data.grantedBy.participantId).toBe(await p1.getEndpointId()); - expect(event.data.grantedTo.participantId).toBe(await p2.getEndpointId()); - } - }); - it('kick participant', async () => { - // we want to join second participant with token, so we can check info in webhook await ctx.p2.getIframeAPI().clearEventResults('videoConferenceLeft'); await ctx.p2.getIframeAPI().addEventListener('videoConferenceLeft'); await ctx.p2.switchToMainFrame(); @@ -235,13 +83,9 @@ describe('Participants presence', () => { timeoutMsg: 'videoConferenceLeft not received' }); - await ensureTwoParticipants({ - preferGenerateToken: true - }); + await ensureTwoParticipants({}, { name: 'p1', iFrameApi: true }); - const { p1, p2, roomName, webhooksProxy } = ctx; - - webhooksProxy?.clearCache(); + const { p1, p2, roomName } = ctx; const p1EpId = await p1.getEndpointId(); const p2EpId = await p2.getEndpointId(); @@ -252,10 +96,6 @@ describe('Participants presence', () => { await p1.switchToMainFrame(); await p2.switchToMainFrame(); - const roomsInfo = (await p1.getIframeAPI().getRoomsInfo()).rooms[0]; - - conferenceJid = roomsInfo.jid.substring(0, roomsInfo.jid.indexOf('/')); - await p1.getIframeAPI().addEventListener('participantKickedOut'); await p2.getIframeAPI().addEventListener('participantKickedOut'); @@ -273,8 +113,6 @@ describe('Participants presence', () => { timeoutMsg: 'participantKickedOut event not received on p2 side' }); - await checkParticipantLeftHook(p2, 'kicked', true, conferenceJid); - expect(eventP1).toBeDefined(); expect(eventP2).toBeDefined(); @@ -314,40 +152,15 @@ describe('Participants presence', () => { }); it('join after kick', async () => { - const { p1, webhooksProxy } = ctx; + const { p1 } = ctx; await p1.getIframeAPI().addEventListener('participantJoined'); await p1.getIframeAPI().addEventListener('participantMenuButtonClick'); - webhooksProxy?.clearCache(); - // join again - await ensureTwoParticipants(); + await ensureTwoParticipants({}, { name: 'p1', iFrameApi: true }); const { p2 } = ctx; - if (webhooksProxy) { - // PARTICIPANT_JOINED webhook - // @ts-ignore - const event: { - data: { - conference: string; - isBreakout: boolean; - moderator: boolean; - name: string; - participantId: string; - }; - eventType: string; - } = await webhooksProxy.waitForEvent('PARTICIPANT_JOINED'); - - expect('PARTICIPANT_JOINED').toBe(event.eventType); - expect(event.data.conference).toBe(conferenceJid); - expect(event.data.isBreakout).toBe(false); - expect(event.data.moderator).toBe(false); - expect(event.data.name).toBe(await p2.getLocalDisplayName()); - expect(event.data.participantId).toBe(await p2.getEndpointId()); - expect(event.data.name).toBe(p2.name); - } - await p1.switchToMainFrame(); const event = await p1.driver.waitUntil(() => p1.getIframeAPI().getEventResult('participantJoined'), { @@ -411,8 +224,6 @@ describe('Participants presence', () => { expect(eventConferenceLeftP2).toBeDefined(); expect(eventConferenceLeftP2.roomName).toBe(roomName); - await checkParticipantLeftHook(p2, 'left', false, conferenceJid); - const eventReadyToCloseP2 = await p2.driver.waitUntil(() => p2.getIframeAPI().getEventResult('readyToClose'), { timeout: 2000, timeoutMsg: 'readyToClose not received' @@ -422,7 +233,7 @@ describe('Participants presence', () => { }); it('dispose conference', async () => { - const { p1, roomName, webhooksProxy } = ctx; + const { p1, roomName } = ctx; await p1.switchToMainFrame(); @@ -441,23 +252,6 @@ describe('Participants presence', () => { expect(eventConferenceLeft).toBeDefined(); expect(eventConferenceLeft.roomName).toBe(roomName); - await checkParticipantLeftHook(p1, 'left', true, conferenceJid); - if (webhooksProxy) { - // ROOM_DESTROYED webhook - // @ts-ignore - const event: { - data: { - conference: string; - isBreakout: boolean; - }; - eventType: string; - } = await webhooksProxy.waitForEvent('ROOM_DESTROYED'); - - expect('ROOM_DESTROYED').toBe(event.eventType); - expect(event.data.conference).toBe(conferenceJid); - expect(event.data.isBreakout).toBe(false); - } - const eventReadyToClose = await p1.driver.waitUntil(() => p1.getIframeAPI().getEventResult('readyToClose'), { timeout: 2000, timeoutMsg: 'readyToClose not received' diff --git a/tests/specs/iframe/visitors.spec.ts b/tests/specs/iframe/visitors.spec.ts deleted file mode 100644 index 05aed197ee..0000000000 --- a/tests/specs/iframe/visitors.spec.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { setTestProperties } from '../../helpers/TestProperties'; -import { config as testsConfig } from '../../helpers/TestsConfig'; -import { ensureOneParticipant, ensureTwoParticipants } from '../../helpers/participants'; - -setTestProperties(__filename, { - useIFrameApi: true, - useWebhookProxy: true, - usesBrowsers: [ 'p1', 'p2' ] -}); - -describe('Visitors', () => { - it('joining the meeting', async () => { - const { webhooksProxy } = ctx; - - if (webhooksProxy) { - webhooksProxy.defaultMeetingSettings = { - visitorsEnabled: true - }; - } - - await ensureOneParticipant(); - - const { p1 } = ctx; - - if (await p1.execute(() => config.disableIframeAPI)) { - // skip the test if iframeAPI is disabled or visitors are not supported - ctx.skipSuiteTests = true; - - return; - } - - await p1.driver.waitUntil(() => p1.execute(() => APP.conference._room.isVisitorsSupported()), { - timeout: 2000 - }).then(async () => { - await p1.switchToMainFrame(); - }).catch(() => { - ctx.skipSuiteTests = true; - }); - }); - - it('visitor joins', async () => { - await ensureTwoParticipants({ - preferGenerateToken: true, - tokenOptions: { visitor: true }, - skipInMeetingChecks: true - }); - - const { p1, p2, webhooksProxy } = ctx; - - await p2.waitForReceiveMedia(15_000, 'Visitor is not receiving media'); - await p2.waitForRemoteStreams(1); - - const p2Visitors = p2.getVisitors(); - const p1Visitors = p1.getVisitors(); - - await p2.driver.waitUntil(() => p2Visitors.hasVisitorsDialog(), { - timeout: 5000, - timeoutMsg: 'Missing visitors dialog' - }); - - expect((await p1Visitors.getVisitorsCount()).trim()).toBe('1'); - expect((await p1Visitors.getVisitorsHeaderFromParticipantsPane()).trim()).toBe('Viewers 1'); - - if (webhooksProxy) { - // PARTICIPANT_JOINED webhook - // @ts-ignore - const event: { - customerId: string; - data: { - avatar: string; - email: string; - group: string; - id: string; - name: string; - participantJid: string; - role: string; - }; - eventType: string; - } = await webhooksProxy.waitForEvent('PARTICIPANT_JOINED'); - - const jwtPayload = p2.getToken()?.payload; - - expect('PARTICIPANT_JOINED').toBe(event.eventType); - expect(event.data.avatar).toBe(jwtPayload.context.user.avatar); - expect(event.data.email).toBe(jwtPayload.context.user.email); - expect(event.data.id).toBe(jwtPayload.context.user.id); - expect(event.data.group).toBe(jwtPayload.context.group); - expect(event.data.name).toBe(p2.name); - expect(event.data.participantJid.indexOf('meet.jitsi') != -1).toBe(true); - expect(event.data.name).toBe(p2.name); - expect(event.data.role).toBe('visitor'); - expect(event.customerId).toBe(testsConfig.iframe.customerId); - - await p2.switchToMainFrame(); - await p2.getIframeAPI().executeCommand('hangup'); - - // PARTICIPANT_LEFT webhook - // @ts-ignore - const eventLeft: { - customerId: string; - data: { - avatar: string; - email: string; - group: string; - id: string; - name: string; - participantJid: string; - role: string; - }; - eventType: string; - } = await webhooksProxy.waitForEvent('PARTICIPANT_LEFT'); - - expect('PARTICIPANT_LEFT').toBe(eventLeft.eventType); - expect(eventLeft.data.avatar).toBe(jwtPayload.context.user.avatar); - expect(eventLeft.data.email).toBe(jwtPayload.context.user.email); - expect(eventLeft.data.id).toBe(jwtPayload.context.user.id); - expect(eventLeft.data.group).toBe(jwtPayload.context.group); - expect(eventLeft.data.name).toBe(p2.name); - expect(eventLeft.data.participantJid.indexOf('meet.jitsi') != -1).toBe(true); - expect(eventLeft.data.name).toBe(p2.name); - expect(eventLeft.data.role).toBe('visitor'); - expect(eventLeft.customerId).toBe(testsConfig.iframe.customerId); - } - }); -}); diff --git a/tests/specs/jaas/chat.spec.ts b/tests/specs/jaas/chat.spec.ts new file mode 100644 index 0000000000..f879b00ffd --- /dev/null +++ b/tests/specs/jaas/chat.spec.ts @@ -0,0 +1,72 @@ +import { expect } from '@wdio/globals'; + +import type { Participant } from '../../helpers/Participant'; +import { setTestProperties } from '../../helpers/TestProperties'; +import { config as testsConfig } from '../../helpers/TestsConfig'; +import WebhookProxy from '../../helpers/WebhookProxy'; +import { joinJaasMuc, generateJaasToken as t } from '../../helpers/jaas'; +import { fetchJson } from '../../helpers/utils'; + +setTestProperties(__filename, { + useJaas: true, + useWebhookProxy: true, + usesBrowsers: [ 'p1', 'p2' ] +}); + +describe('JaaS CHAT_UPLOADED webhook.', () => { + const tenant = testsConfig.jaas.tenant; + const customerId = tenant?.replace('vpaas-magic-cookie-', ''); + let p1: Participant, p2: Participant; + let webhooksProxy: WebhookProxy; + let fqn: string; + + it('setup', async () => { + const room = ctx.roomName; + + webhooksProxy = ctx.webhooksProxy; + p1 = await joinJaasMuc({ name: 'p1', token: t({ room }) }); + p2 = await joinJaasMuc({ name: 'p2', token: t({ room }) }); + fqn = `${testsConfig.jaas.tenant}/${room}`; + }); + + it('test webhook', async () => { + await p1.getChatPanel().sendMessage('foo'); + await p2.getChatPanel().sendMessage('bar'); + await p1.getChatPanel().sendMessage('baz'); + + await p1.hangup(); + await p2.hangup(); + + const event: { + appId: string; + customerId: string; + data: { + preAuthenticatedLink: string; + }; + eventType: string; + fqn: string; + } = await webhooksProxy.waitForEvent('CHAT_UPLOADED'); + + expect(event.appId).toBe(tenant); + expect(event.customerId).toBe(customerId); + expect(event.data.preAuthenticatedLink).toBeDefined(); + expect(event.eventType).toBe('CHAT_UPLOADED'); + expect(event.fqn).toBe(fqn); + + const uploadedChat: any = await fetchJson(event.data.preAuthenticatedLink); + + expect(uploadedChat.meetingFqn).toBe(fqn); + expect(uploadedChat.messageType).toBe('CHAT'); + + const messages = uploadedChat.messages; + + expect(messages).toBeDefined(); + expect(messages.length).toBe(3); + expect(messages[0].content).toBe('foo'); + expect(messages[0].name).toBe('p1'); + expect(messages[1].content).toBe('bar'); + expect(messages[1].name).toBe('p2'); + expect(messages[2].content).toBe('baz'); + expect(messages[2].name).toBe('p1'); + }); +}); diff --git a/tests/specs/jaas/dial/dialin.spec.ts b/tests/specs/jaas/dial/dialin.spec.ts new file mode 100644 index 0000000000..e7a4d3815e --- /dev/null +++ b/tests/specs/jaas/dial/dialin.spec.ts @@ -0,0 +1,64 @@ +import type { Participant } from '../../../helpers/Participant'; +import { setTestProperties } from '../../../helpers/TestProperties'; +import { config as testsConfig } from '../../../helpers/TestsConfig'; +import WebhookProxy from '../../../helpers/WebhookProxy'; +import { joinJaasMuc, generateJaasToken as t } from '../../../helpers/jaas'; +import { + assertDialInDisplayed, assertUrlDisplayed, + dialIn, + isDialInEnabled, verifyMoreNumbersPage, +} from '../../helpers/DialIn'; + +import { verifyEndedWebhook, verifyStartedWebhooks, waitForMedia } from './util'; + +setTestProperties(__filename, { + useJaas: true, + useWebhookProxy: true +}); + +describe('Dial-in', () => { + let p1: Participant, webhooksProxy: WebhookProxy; + const customerId: string = testsConfig.jaas.customerId || ''; + + it('setup', async () => { + const room = ctx.roomName; + + if (!process.env.DIAL_IN_REST_URL) { + console.log('Dial-in test is disabled, set DIAL_IN_REST_URL to enable.'); + ctx.skipSuiteTests = true; + + return; + } + + p1 = await joinJaasMuc({ name: 'p1', token: t({ room, moderator: true }) }); + webhooksProxy = ctx.webhooksProxy; + + expect(await p1.isInMuc()).toBe(true); + expect(await isDialInEnabled(p1)).toBe(true); + expect(customerId).toBeDefined(); + }); + + it ('Invite UI', async () => { + await assertUrlDisplayed(p1); + await assertDialInDisplayed(p1); + await verifyMoreNumbersPage(p1); + }); + + it('dial-in', async () => { + const dialInPin = await p1.getDialInPin(); + + expect(dialInPin.length >= 8).toBe(true); + + await dialIn(dialInPin); + await waitForMedia(p1); + + const startedPayload + = await verifyStartedWebhooks(webhooksProxy, 'in', 'DIAL_IN_STARTED', customerId); + const endpointId = await p1.execute(() => APP?.conference?.listMembers()[0].getId()); + + await p1.getFilmstrip().kickParticipant(endpointId); + + await verifyEndedWebhook(webhooksProxy, 'DIAL_IN_ENDED', customerId, startedPayload); + }); +}); + diff --git a/tests/specs/jaas/dial/dialout.spec.ts b/tests/specs/jaas/dial/dialout.spec.ts new file mode 100644 index 0000000000..8c49d7449b --- /dev/null +++ b/tests/specs/jaas/dial/dialout.spec.ts @@ -0,0 +1,51 @@ +import type { Participant } from '../../../helpers/Participant'; +import { setTestProperties } from '../../../helpers/TestProperties'; +import { config as testsConfig } from '../../../helpers/TestsConfig'; +import WebhookProxy from '../../../helpers/WebhookProxy'; +import { joinJaasMuc, generateJaasToken as t } from '../../../helpers/jaas'; + +import { verifyEndedWebhook, verifyStartedWebhooks, waitForMedia } from './util'; + +setTestProperties(__filename, { + useJaas: true, + useWebhookProxy: true +}); + +describe('Dial-out', () => { + let p1: Participant, webhooksProxy: WebhookProxy; + const dialOutUrl = process.env.DIAL_OUT_URL || ''; + const customerId = testsConfig.jaas.customerId || ''; + + it('setup', async () => { + const room = ctx.roomName; + + if (!dialOutUrl) { + console.log('Dial-out test is disabled, set DIAL_OUT_URL to enable.'); + ctx.skipSuiteTests = true; + + return; + } + + webhooksProxy = ctx.webhooksProxy; + p1 = await joinJaasMuc({ name: 'p1', iFrameApi: true, token: t({ room, moderator: true }) }); + + expect(await p1.isInMuc()).toBe(true); + expect(Boolean(await p1.execute(() => config.dialOutAuthUrl))).toBe(true); + }); + + it('dial-out', async () => { + await p1.switchToMainFrame(); + await p1.getIframeAPI().invitePhone(dialOutUrl); + await p1.switchToIFrame(); + await p1.waitForParticipants(1); + await waitForMedia(p1); + + const startedPayload + = await verifyStartedWebhooks(webhooksProxy, 'out', 'DIAL_OUT_STARTED', customerId); + const endpointId = await p1.execute(() => APP?.conference?.listMembers()[0].getId()); + + await p1.getFilmstrip().kickParticipant(endpointId); + + await verifyEndedWebhook(webhooksProxy, 'DIAL_OUT_ENDED', customerId, startedPayload); + }); +}); diff --git a/tests/specs/jaas/dial/sipjibri.spec.ts b/tests/specs/jaas/dial/sipjibri.spec.ts new file mode 100644 index 0000000000..15c1b0d2b9 --- /dev/null +++ b/tests/specs/jaas/dial/sipjibri.spec.ts @@ -0,0 +1,88 @@ +import type { Participant } from '../../../helpers/Participant'; +import { setTestProperties } from '../../../helpers/TestProperties'; +import { config as testsConfig } from '../../../helpers/TestsConfig'; +import WebhookProxy from '../../../helpers/WebhookProxy'; +import { joinJaasMuc, generateJaasToken as t } from '../../../helpers/jaas'; + +import { waitForMedia } from './util'; + +setTestProperties(__filename, { + useJaas: true, + useWebhookProxy: true +}); + + +describe('SIP jibri invite', () => { + let p1: Participant, webhooksProxy: WebhookProxy; + const customerId = testsConfig.jaas.customerId || ''; + const dialOutUrl = process.env.SIP_JIBRI_DIAL_OUT_URL || ''; + + it('setup', async () => { + const room = ctx.roomName; + + if (true) { + // This is temporary until we figure out how to fix it and configure it properly. + console.log('SIP jibri test is disabled.'); + ctx.skipSuiteTests = true; + + return; + } + + if (!dialOutUrl) { + console.log('SIP jibri test is disabled, set SIP_JIBRI_DIAL_OUT_URL to enable.'); + ctx.skipSuiteTests = true; + + return; + } + + p1 = await joinJaasMuc({ name: 'p1', iFrameApi: true, token: t({ room, moderator: true }) }); + webhooksProxy = ctx.webhooksProxy; + + expect(await p1.isInMuc()).toBe(true); + expect(Boolean(await p1.execute(() => config.inviteServiceUrl))).toBe(true); + }); + + it('sip jibri', async () => { + await p1.switchToMainFrame(); + await p1.getIframeAPI().inviteSIP(dialOutUrl); + await p1.switchToIFrame(); + await p1.waitForParticipants(1); + await waitForMedia(p1); + + const startedEvent: { + customerId: string; + data: { + participantFullJid: string; + participantId: string; + participantJid: string; + sipAddress: string; + }; + eventType: string; + } = await webhooksProxy.waitForEvent('SIP_CALL_OUT_STARTED'); + + expect('SIP_CALL_OUT_STARTED').toBe(startedEvent.eventType); + expect(startedEvent.data.sipAddress).toBe(`sip:${process.env.SIP_JIBRI_DIAL_OUT_URL}`); + expect(startedEvent.customerId).toBe(customerId); + + const endpointId = await p1.execute(() => APP?.conference?.listMembers()[0].getId()); + + await p1.getFilmstrip().kickParticipant(endpointId); + + const endedEvent: { + customerId: string; + data: { + direction: string; + participantFullJid: string; + participantId: string; + participantJid: string; + }; + eventType: string; + } = await webhooksProxy.waitForEvent('SIP_CALL_OUT_ENDED'); + + expect('SIP_CALL_OUT_ENDED').toBe(endedEvent.eventType); + expect(endedEvent.customerId).toBe(customerId); + expect(endedEvent.data.participantFullJid).toBe(startedEvent.data.participantFullJid); + expect(endedEvent.data.participantId).toBe(startedEvent.data.participantId); + expect(endedEvent.data.participantJid).toBe(startedEvent.data.participantJid); + }); +}); diff --git a/tests/specs/jaas/dial/util.ts b/tests/specs/jaas/dial/util.ts new file mode 100644 index 0000000000..8368fdac99 --- /dev/null +++ b/tests/specs/jaas/dial/util.ts @@ -0,0 +1,81 @@ +import { Participant } from '../../../helpers/Participant'; +import WebhookProxy from '../../../helpers/WebhookProxy'; + +interface IStartedWebhookPayload { + direction: string; + participantFullJid: string; + participantId: string; + participantJid: string; +} + +/** + * Checks the dial events for a participant and clean up at the end. + * @param webhooksProxy + * @param startedEventName + * @param direction + * @param customerId + */ +export async function verifyStartedWebhooks( + webhooksProxy: WebhookProxy, + direction: 'in' | 'out', + startedEventName: string, + customerId: string): Promise { + + const startedEvent: { + customerId: string; + data: IStartedWebhookPayload; + eventType: string; + } = await webhooksProxy.waitForEvent(startedEventName); + + expect(startedEventName).toBe(startedEvent.eventType); + expect(startedEvent.data.direction).toBe(direction); + expect(startedEvent.customerId).toBe(customerId); + + const usageEvent: { + customerId: string; + data: any; + eventType: string; + } = await webhooksProxy.waitForEvent('USAGE'); + + expect('USAGE').toBe(usageEvent.eventType); + expect(usageEvent.customerId).toBe(customerId); + + expect(usageEvent.data.some((el: any) => + el.participantId === startedEvent.data.participantId && el.callDirection === direction)).toBe(true); + + return startedEvent.data; +} + +export async function verifyEndedWebhook( + webhooksProxy: WebhookProxy, + endedEventName: string, + customerId: string, + startedPayload: IStartedWebhookPayload) { + const endedEvent: { + customerId: string; + data: { + direction: string; + participantFullJid: string; + participantId: string; + participantJid: string; + }; + eventType: string; + } = await webhooksProxy.waitForEvent(endedEventName); + + expect(endedEventName).toBe(endedEvent.eventType); + expect(endedEvent.customerId).toBe(customerId); + expect(endedEvent.data.participantFullJid).toBe(startedPayload.participantFullJid); + expect(endedEvent.data.participantId).toBe(startedPayload.participantId); + expect(endedEvent.data.participantJid).toBe(startedPayload.participantJid); +} + +/** + * Wait until there is at least one remote participant, ICE is connected, the participant has a stream, and data is + * both received and sent. + */ +export async function waitForMedia(p: Participant) { + await p.waitForParticipants(1); + await p.waitForIceConnected(); + await p.waitForRemoteStreams(1); + await p.waitForSendReceiveData(20_000); +} diff --git a/tests/specs/jaas/joinMuc.spec.ts b/tests/specs/jaas/joinMuc.spec.ts index 5d682bbb50..15b703552e 100644 --- a/tests/specs/jaas/joinMuc.spec.ts +++ b/tests/specs/jaas/joinMuc.spec.ts @@ -1,6 +1,6 @@ import { setTestProperties } from '../../helpers/TestProperties'; +import { joinJaasMuc, generateJaasToken as t } from '../../helpers/jaas'; import { TOKEN_AUTH_FAILED_TEST_ID, TOKEN_AUTH_FAILED_TITLE_TEST_ID } from '../../pageobjects/Notifications'; -import { joinMuc, generateJaasToken as t } from '../helpers/jaas'; setTestProperties(__filename, { useJaas: true @@ -9,7 +9,7 @@ setTestProperties(__filename, { describe('XMPP login and MUC join test', () => { it('with a valid token (wildcard room)', async () => { console.log('Joining a MUC with a valid token (wildcard room)'); - const p = await joinMuc({ token: t({ room: '*' }) }); + const p = await joinJaasMuc({ token: t({ room: '*' }) }); expect(await p.isInMuc()).toBe(true); expect(await p.isModerator()).toBe(false); @@ -17,7 +17,7 @@ describe('XMPP login and MUC join test', () => { it('with a valid token (specific room)', async () => { console.log('Joining a MUC with a valid token (specific room)'); - const p = await joinMuc({ token: t({ room: ctx.roomName }) }); + const p = await joinJaasMuc({ token: t({ room: ctx.roomName }) }); expect(await p.isInMuc()).toBe(true); expect(await p.isModerator()).toBe(false); @@ -29,7 +29,7 @@ describe('XMPP login and MUC join test', () => { token.jwt = token.jwt + 'badSignature'; - const p = await joinMuc({ token }); + const p = await joinJaasMuc({ token }); expect(Boolean(await p.isInMuc())).toBe(false); @@ -41,7 +41,7 @@ describe('XMPP login and MUC join test', () => { it('with an expired token', async () => { console.log('Joining a MUC with an expired token'); - const p = await joinMuc({ token: t({ exp: '-1m' }) }); + const p = await joinJaasMuc({ token: t({ exp: '-1m' }) }); expect(Boolean(await p.isInMuc())).toBe(false); @@ -52,7 +52,7 @@ describe('XMPP login and MUC join test', () => { it('with a token using the wrong key ID', async () => { console.log('Joining a MUC with a token using the wrong key ID'); - const p = await joinMuc({ token: t({ keyId: 'invalid-key-id' }) }); + const p = await joinJaasMuc({ token: t({ keyId: 'invalid-key-id' }) }); expect(Boolean(await p.isInMuc())).toBe(false); @@ -63,7 +63,7 @@ describe('XMPP login and MUC join test', () => { it('with a token for a different room', async () => { console.log('Joining a MUC with a token for a different room'); - const p = await joinMuc({ token: t({ room: ctx.roomName + 'different' }) }); + const p = await joinJaasMuc({ token: t({ room: ctx.roomName + 'different' }) }); expect(Boolean(await p.isInMuc())).toBe(false); @@ -74,7 +74,7 @@ describe('XMPP login and MUC join test', () => { it('with a moderator token', async () => { console.log('Joining a MUC with a moderator token'); - const p = await joinMuc({ token: t({ moderator: true }) }); + const p = await joinJaasMuc({ token: t({ moderator: true }) }); expect(await p.isInMuc()).toBe(true); expect(await p.isModerator()).toBe(true); @@ -84,7 +84,7 @@ describe('XMPP login and MUC join test', () => { // disabled. it('without a token', async () => { console.log('Joining a MUC without a token'); - const p = await joinMuc(); + const p = await joinJaasMuc(); expect(Boolean(await p.isInMuc())).toBe(false); diff --git a/tests/specs/jaas/maxOccupants.spec.ts b/tests/specs/jaas/maxOccupants.spec.ts index f6c4f850c2..377932045c 100644 --- a/tests/specs/jaas/maxOccupants.spec.ts +++ b/tests/specs/jaas/maxOccupants.spec.ts @@ -1,5 +1,5 @@ import { setTestProperties } from '../../helpers/TestProperties'; -import { joinMuc, generateJaasToken as t } from '../helpers/jaas'; +import { joinJaasMuc, generateJaasToken as t } from '../../helpers/jaas'; setTestProperties(__filename, { useJaas: true, @@ -13,19 +13,19 @@ describe('MaxOccupants limit enforcement', () => { maxOccupants: 2 }; - const p1 = await joinMuc({ token: t({ room: ctx.roomName }) }); - const p2 = await joinMuc({ name: 'p2', token: t({ room: ctx.roomName }) }); + const p1 = await joinJaasMuc({ token: t({ room: ctx.roomName }) }); + const p2 = await joinJaasMuc({ name: 'p2', token: t({ room: ctx.roomName }) }); expect(await p1.isInMuc()).toBe(true); expect(await p2.isInMuc()).toBe(true); // Third participant should be rejected (exceeding maxOccupants), even if it's a moderator - let p3 = await joinMuc({ name: 'p3', token: t({ room: ctx.roomName, moderator: true }) }); + let p3 = await joinJaasMuc({ name: 'p3', token: t({ room: ctx.roomName, moderator: true }) }); expect(Boolean(await p3.isInMuc())).toBe(false); await p1.hangup(); - p3 = await joinMuc({ name: 'p3', token: t({ room: ctx.roomName }) }); + p3 = await joinJaasMuc({ name: 'p3', token: t({ room: ctx.roomName }) }); expect(await p3.isInMuc()).toBe(true); }); }); diff --git a/tests/specs/jaas/passcode.spec.ts b/tests/specs/jaas/passcode.spec.ts index 1ff57b7878..1f96c8eb3f 100644 --- a/tests/specs/jaas/passcode.spec.ts +++ b/tests/specs/jaas/passcode.spec.ts @@ -1,6 +1,6 @@ import { setTestProperties } from '../../helpers/TestProperties'; +import { joinJaasMuc, generateJaasToken as t } from '../../helpers/jaas'; import { IToken } from '../../helpers/token'; -import { joinMuc, generateJaasToken as t } from '../helpers/jaas'; setTestProperties(__filename, { useJaas: true, @@ -31,7 +31,7 @@ describe('Setting passcode through settings provisioning', () => { */ async function joinWithPassword(instanceId: string, token: IToken) { // @ts-ignore - const p = await joinMuc({ name: instanceId, token }, { roomName: ctx.roomName }); + const p = await joinJaasMuc({ name: instanceId, token }, { roomName: ctx.roomName }); await p.waitForMucJoinedOrError(); expect(await p.isInMuc()).toBe(false); diff --git a/tests/specs/jaas/passcodeInvalid.spec.ts b/tests/specs/jaas/passcodeInvalid.spec.ts index a8a900ab54..3219fb0096 100644 --- a/tests/specs/jaas/passcodeInvalid.spec.ts +++ b/tests/specs/jaas/passcodeInvalid.spec.ts @@ -1,5 +1,5 @@ import { setTestProperties } from '../../helpers/TestProperties'; -import { joinMuc, generateJaasToken as t } from '../helpers/jaas'; +import { joinJaasMuc, generateJaasToken as t } from '../../helpers/jaas'; setTestProperties(__filename, { useJaas: true, @@ -14,7 +14,7 @@ describe('Setting passcode through settings provisioning', () => { passcode: 'passcode-must-be-digits-only' }; - const p = await joinMuc({ token: t({ room: ctx.roomName }) }, { roomName: ctx.roomName }); + const p = await joinJaasMuc({ token: t({ room: ctx.roomName }) }, { roomName: ctx.roomName }); // The settings provisioning contains an invalid passcode, the expected result is that the room is not // configured to require a passcode. diff --git a/tests/specs/jaas/presence.spec.ts b/tests/specs/jaas/presence.spec.ts new file mode 100644 index 0000000000..750bbf264f --- /dev/null +++ b/tests/specs/jaas/presence.spec.ts @@ -0,0 +1,178 @@ +import { expect } from '@wdio/globals'; + +import { Participant } from '../../helpers/Participant'; +import { setTestProperties } from '../../helpers/TestProperties'; +import { config as testsConfig } from '../../helpers/TestsConfig'; +import WebhookProxy from '../../helpers/WebhookProxy'; +import { joinJaasMuc, generateJaasToken as t } from '../../helpers/jaas'; + +setTestProperties(__filename, { + useJaas: true, + useWebhookProxy: true, + usesBrowsers: [ 'p1', 'p2' ] +}); + +/** + * Tests the basic webhooks fired for participants joining, leaving, and creating/destroying a conference: + * PARTICIPANT_JOINED, PARTICIPANT_LEFT, ROOM_CREATED, ROOM_DESTROYED, ROLE_CHANGED, USAGE. + */ +describe('Create/destroy/join/leave webhooks', () => { + let conferenceJid: string = ''; + let p1: Participant, p2: Participant; + let p1EpId: string, p2EpId: string; + let webhooksProxy: WebhookProxy; + let room: string; + + async function checkParticipantJoinedHook(p: Participant) { + const event: { + data: { + conference: string; + isBreakout: boolean; + moderator: boolean; + name: string; + participantId: string; + }; + eventType: string; + } = await webhooksProxy.waitForEvent('PARTICIPANT_JOINED'); + + expect(event.eventType).toBe('PARTICIPANT_JOINED'); + expect(event.data.conference).toBe(conferenceJid); + expect(event.data.isBreakout).toBe(false); + expect(event.data.moderator).toBe(p.getToken()?.options?.moderator); + expect(event.data.name).toBe(await p.getLocalDisplayName()); + expect(event.data.participantId).toBe(await p.getEndpointId()); + expect(event.data.name).toBe(p.name); + } + async function checkParticipantLeftHook(p: Participant, reason: string) { + + const event: { + customerId: string; + data: { + conference: string; + disconnectReason: string; + group: string; + id: string; + isBreakout: boolean; + name: string; + participantId: string; + }; + eventType: string; + } = await webhooksProxy.waitForEvent('PARTICIPANT_LEFT'); + + expect(event.eventType).toBe('PARTICIPANT_LEFT'); + expect(event.data.conference).toBe(conferenceJid); + expect(event.data.disconnectReason).toBe(reason); + expect(event.data.isBreakout).toBe(false); + expect(event.data.participantId).toBe(await p.getEndpointId()); + expect(event.data.name).toBe(p.name); + + const jwtPayload = p.getToken()?.payload; + + expect(event.data.id).toBe(jwtPayload?.context?.user?.id); + expect(event.data.group).toBe(jwtPayload?.context?.group); + expect(event.customerId).toBe(testsConfig.jaas.customerId); + } + + it('setup', async () => { + room = ctx.roomName; + conferenceJid = `${room}@conference.${testsConfig.jaas.tenant}.${new URL(process.env.BASE_URL || '').hostname}`; + webhooksProxy = ctx.webhooksProxy; + p1 = await joinJaasMuc({ name: 'p1', iFrameApi: true, token: t({ room, moderator: true }) }); + p1EpId = await p1.getEndpointId(); + expect(await p1.isModerator()).toBe(true); + await checkParticipantJoinedHook(p1); + await p1.switchToMainFrame(); + p2 = await joinJaasMuc({ name: 'p2', token: t({ room }) }); + p2EpId = await p2.getEndpointId(); + expect(await p2.isModerator()).toBe(false); + await checkParticipantJoinedHook(p2); + }); + + it('USAGE webhook', async () => { + const event: { + data: [ + { participantId: string; } + ]; + eventType: string; + } = await webhooksProxy.waitForEvent('USAGE'); + + expect(event.eventType).toBe('USAGE'); + + expect(event.data.some(d => d.participantId === p1EpId)); + expect(event.data.some(d => d.participantId === p2EpId)); + }); + + it('ROOM_CREATED webhook', async () => { + const event: { + data: { + conference: string; + isBreakout: boolean; + }; + eventType: string; + } = await webhooksProxy.waitForEvent('ROOM_CREATED'); + + expect(event.eventType).toBe('ROOM_CREATED'); + expect(event.data.conference).toBe(conferenceJid); + expect(event.data.isBreakout).toBe(false); + }); + + it('ROLE_CHANGED webhook', async () => { + await p1.getIframeAPI().executeCommand('grantModerator', p2EpId); + + const event: { + data: { + grantedBy: { + participantId: string; + }; + grantedTo: { + participantId: string; + }; + role: string; + }; + eventType: string; + } = await webhooksProxy.waitForEvent('ROLE_CHANGED'); + + expect(event.eventType).toBe('ROLE_CHANGED'); + expect(event.data.role).toBe('moderator'); + expect(event.data.grantedBy.participantId).toBe(p1EpId); + expect(event.data.grantedTo.participantId).toBe(p2EpId); + }); + + it('kick participant', async () => { + webhooksProxy.clearCache(); + await p1.getIframeAPI().executeCommand('kickParticipant', p2EpId); + await checkParticipantLeftHook(p2, 'kicked'); + }); + + it('join after kick', async () => { + webhooksProxy.clearCache(); + + // join again + p2 = await joinJaasMuc({ name: 'p2', token: t({ room }) }); + p2EpId = await p2.getEndpointId(); + + await checkParticipantJoinedHook(p2); + }); + + it('hangup', async () => { + await p2.hangup(); + await checkParticipantLeftHook(p2, 'left'); + }); + + it('dispose conference', async () => { + await p1.getIframeAPI().executeCommand('hangup'); + await checkParticipantLeftHook(p1, 'left'); + + const event: { + data: { + conference: string; + isBreakout: boolean; + }; + eventType: string; + } = await webhooksProxy.waitForEvent('ROOM_DESTROYED'); + + expect(event.eventType).toBe('ROOM_DESTROYED'); + expect(event.data.conference).toBe(conferenceJid); + expect(event.data.isBreakout).toBe(false); + }); +}); diff --git a/tests/specs/jaas/recording.spec.ts b/tests/specs/jaas/recording.spec.ts index 49bce318df..03e29674e6 100644 --- a/tests/specs/jaas/recording.spec.ts +++ b/tests/specs/jaas/recording.spec.ts @@ -2,13 +2,10 @@ import { Participant } from '../../helpers/Participant'; import { setTestProperties } from '../../helpers/TestProperties'; import { config as testsConfig } from '../../helpers/TestsConfig'; import WebhookProxy from '../../helpers/WebhookProxy'; -import { joinMuc, generateJaasToken as t } from '../helpers/jaas'; +import { joinJaasMuc, generateJaasToken as t } from '../../helpers/jaas'; setTestProperties(__filename, { useJaas: true, - // Note this just for posterity. We don't depend on the framework doing anything for us because of this flag (we - // pass it as a parameter directly) - useIFrameApi: true, useWebhookProxy: true }); @@ -30,7 +27,7 @@ describe('Recording and Live Streaming', () => { it('setup', async () => { webhooksProxy = ctx.webhooksProxy; - p = await joinMuc({ iFrameApi: true, token: t({ moderator: true }) }, { roomName: ctx.roomName }); + p = await joinJaasMuc({ iFrameApi: true, token: t({ moderator: true }) }, { roomName: ctx.roomName }); // TODO: what should we do in this case? Add a config for this? if (await p.execute(() => config.disableIframeAPI)) { diff --git a/tests/specs/iframe/transcriptions.spec.ts b/tests/specs/jaas/transcriptions.spec.ts similarity index 76% rename from tests/specs/iframe/transcriptions.spec.ts rename to tests/specs/jaas/transcriptions.spec.ts index 341727d065..a028627a4c 100644 --- a/tests/specs/iframe/transcriptions.spec.ts +++ b/tests/specs/jaas/transcriptions.spec.ts @@ -3,19 +3,27 @@ import { expect } from '@wdio/globals'; import type { Participant } from '../../helpers/Participant'; import { setTestProperties } from '../../helpers/TestProperties'; import type WebhookProxy from '../../helpers/WebhookProxy'; -import { ensureOneParticipant, ensureTwoParticipants } from '../../helpers/participants'; +import { joinJaasMuc, generateJaasToken as t } from '../../helpers/jaas'; setTestProperties(__filename, { - useIFrameApi: true, + useJaas: true, useWebhookProxy: true, usesBrowsers: [ 'p1', 'p2' ] }); describe('Transcriptions', () => { - it('joining the meeting', async () => { - await ensureOneParticipant(); + let p1: Participant, p2: Participant; + let webhooksProxy: WebhookProxy; - const { p1 } = ctx; + it('setup', async () => { + const room = ctx.roomName; + + webhooksProxy = ctx.webhooksProxy; + + p1 = await joinJaasMuc({ + name: 'p1', + token: t({ room, moderator: true }), + iFrameApi: true }); if (await p1.execute(() => config.disableIframeAPI || !config.transcription?.enabled)) { // skip the test if iframeAPI or transcriptions are disabled @@ -24,32 +32,25 @@ describe('Transcriptions', () => { return; } - await p1.switchToMainFrame(); - - await ensureTwoParticipants({ + p2 = await joinJaasMuc({ + name: 'p2', + token: t({ room }), + iFrameApi: true }, { configOverwrite: { startWithAudioMuted: true } }); - const { p2 } = ctx; - - // let's populate endpoint ids await Promise.all([ - p1.getEndpointId(), - p2.getEndpointId() + p1.switchToMainFrame(), + p2.switchToMainFrame(), ]); - await p1.switchToMainFrame(); - await p2.switchToMainFrame(); - expect(await p1.getIframeAPI().getEventResult('isModerator')).toBe(true); expect(await p1.getIframeAPI().getEventResult('videoConferenceJoined')).toBeDefined(); }); it('toggle subtitles', async () => { - const { p1, p2, webhooksProxy } = ctx; - await p1.getIframeAPI().addEventListener('transcriptionChunkReceived'); await p2.getIframeAPI().addEventListener('transcriptionChunkReceived'); await p1.getIframeAPI().executeCommand('toggleSubtitles'); @@ -63,9 +64,7 @@ describe('Transcriptions', () => { }); it('set subtitles on and off', async () => { - const { p1, p2, webhooksProxy } = ctx; - - // we need to clear results or the last one will be used, form the previous time subtitles were on + // we need to clear results or the last one will be used, from the previous time subtitles were on await p1.getIframeAPI().clearEventResults('transcriptionChunkReceived'); await p2.getIframeAPI().clearEventResults('transcriptionChunkReceived'); @@ -80,9 +79,7 @@ describe('Transcriptions', () => { }); it('start/stop transcriptions via recording', async () => { - const { p1, p2, webhooksProxy } = ctx; - - // we need to clear results or the last one will be used, form the previous time subtitles were on + // we need to clear results or the last one will be used, from the previous time subtitles were on await p1.getIframeAPI().clearEventResults('transcriptionChunkReceived'); await p2.getIframeAPI().clearEventResults('transcriptionChunkReceived'); @@ -96,12 +93,12 @@ describe('Transcriptions', () => { allTranscriptionStatusChanged.push(await p1.driver.waitUntil(() => p1.getIframeAPI() .getEventResult('transcribingStatusChanged'), { timeout: 10000, - timeoutMsg: 'transcribingStatusChanged event not received on p1 side' + timeoutMsg: 'transcribingStatusChanged event not received on p1' })); allTranscriptionStatusChanged.push(await p2.driver.waitUntil(() => p2.getIframeAPI() .getEventResult('transcribingStatusChanged'), { timeout: 10000, - timeoutMsg: 'transcribingStatusChanged event not received on p2 side' + timeoutMsg: 'transcribingStatusChanged event not received on p2' })); let result = await Promise.allSettled(allTranscriptionStatusChanged); @@ -125,12 +122,12 @@ describe('Transcriptions', () => { allTranscriptionStatusChanged.push(await p1.driver.waitUntil(() => p1.getIframeAPI() .getEventResult('transcribingStatusChanged'), { timeout: 10000, - timeoutMsg: 'transcribingStatusChanged event not received on p1 side' + timeoutMsg: 'transcribingStatusChanged event not received on p1' })); allTranscriptionStatusChanged.push(await p2.driver.waitUntil(() => p2.getIframeAPI() .getEventResult('transcribingStatusChanged'), { timeout: 10000, - timeoutMsg: 'transcribingStatusChanged event not received on p2 side' + timeoutMsg: 'transcribingStatusChanged event not received on p2' })); result = await Promise.allSettled(allTranscriptionStatusChanged); @@ -149,17 +146,15 @@ describe('Transcriptions', () => { // let's wait for destroy event before waiting for those that depends on it await webhooksProxy.waitForEvent('ROOM_DESTROYED'); - if (webhooksProxy) { - const event: { - data: { - preAuthenticatedLink: string; - }; - eventType: string; - } = await webhooksProxy.waitForEvent('TRANSCRIPTION_UPLOADED'); + const event: { + data: { + preAuthenticatedLink: string; + }; + eventType: string; + } = await webhooksProxy.waitForEvent('TRANSCRIPTION_UPLOADED'); - expect('TRANSCRIPTION_UPLOADED').toBe(event.eventType); - expect(event.data.preAuthenticatedLink).toBeDefined(); - } + expect('TRANSCRIPTION_UPLOADED').toBe(event.eventType); + expect(event.data.preAuthenticatedLink).toBeDefined(); }); }); @@ -178,30 +173,28 @@ async function checkReceivingChunks(p1: Participant, p2: Participant, webhooksPr timeoutMsg: 'transcriptionChunkReceived event not received on p2 side' })); - if (webhooksProxy) { - // TRANSCRIPTION_CHUNK_RECEIVED webhook - allTranscripts.push((async () => { - const event: { - data: { - final: string; - language: string; - messageID: string; - participant: { - id: string; - name: string; - }; - stable: string; + // TRANSCRIPTION_CHUNK_RECEIVED webhook + allTranscripts.push((async () => { + const event: { + data: { + final: string; + language: string; + messageID: string; + participant: { + id: string; + name: string; }; - eventType: string; - } = await webhooksProxy.waitForEvent('TRANSCRIPTION_CHUNK_RECEIVED'); + stable: string; + }; + eventType: string; + } = await webhooksProxy.waitForEvent('TRANSCRIPTION_CHUNK_RECEIVED'); - expect('TRANSCRIPTION_CHUNK_RECEIVED').toBe(event.eventType); + expect('TRANSCRIPTION_CHUNK_RECEIVED').toBe(event.eventType); - event.data.stable = event.data.final; + event.data.stable = event.data.final; - return event; - })()); - } + return event; + })()); const result = await Promise.allSettled(allTranscripts); diff --git a/tests/specs/jaas/visitors/participantsSoftLimit.spec.ts b/tests/specs/jaas/visitors/participantsSoftLimit.spec.ts index b31482c861..775643177e 100644 --- a/tests/specs/jaas/visitors/participantsSoftLimit.spec.ts +++ b/tests/specs/jaas/visitors/participantsSoftLimit.spec.ts @@ -1,5 +1,5 @@ import { setTestProperties } from '../../../helpers/TestProperties'; -import { joinMuc, generateJaasToken as t } from '../../helpers/jaas'; +import { joinJaasMuc, generateJaasToken as t } from '../../../helpers/jaas'; setTestProperties(__filename, { useJaas: true, @@ -15,7 +15,7 @@ describe('Visitors triggered by reaching participantsSoftLimit', () => { }; /// XXX the "name" of the participant MUST match one of the "capabilities" defined in wdio. It's not a "participant", it's an instance configuration! - const m = await joinMuc({ + const m = await joinJaasMuc({ token: t({ room: ctx.roomName, displayName: 'Mo de Rator', moderator: true }) }); @@ -25,7 +25,7 @@ describe('Visitors triggered by reaching participantsSoftLimit', () => { console.log('Moderator joined'); // Joining with a participant token before participantSoftLimit has been reached - const p = await joinMuc({ + const p = await joinJaasMuc({ name: 'p2', token: t({ room: ctx.roomName, displayName: 'Parti Cipant' }) }); @@ -36,7 +36,7 @@ describe('Visitors triggered by reaching participantsSoftLimit', () => { console.log('Participant joined'); // Joining with a participant token after participantSoftLimit has been reached - const v = await joinMuc({ + const v = await joinJaasMuc({ name: 'p3', token: t({ room: ctx.roomName, displayName: 'Visi Tor' }) }); diff --git a/tests/specs/jaas/visitors/videoWithSingleSender.spec.ts b/tests/specs/jaas/visitors/videoWithSingleSender.spec.ts index 701d615c8a..8542e5762d 100644 --- a/tests/specs/jaas/visitors/videoWithSingleSender.spec.ts +++ b/tests/specs/jaas/visitors/videoWithSingleSender.spec.ts @@ -1,5 +1,5 @@ import { setTestProperties } from '../../../helpers/TestProperties'; -import { joinMuc, generateJaasToken as t } from '../../helpers/jaas'; +import { joinJaasMuc, generateJaasToken as t } from '../../../helpers/jaas'; setTestProperties(__filename, { useJaas: true, @@ -23,7 +23,7 @@ describe('Visitor receiving video from a single remote participant', () => { enabled: false } }; - const sender = await joinMuc({ + const sender = await joinJaasMuc({ token: t({ room: ctx.roomName, displayName: 'Sender', moderator: true }) }, { configOverwrite @@ -31,7 +31,7 @@ describe('Visitor receiving video from a single remote participant', () => { const senderEndpointId = await sender.getEndpointId(); const testVisitor = async function(instanceId: 'p1' | 'p2' | 'p3' | 'p4') { - const visitor = await joinMuc({ + const visitor = await joinJaasMuc({ name: instanceId, token: t({ room: ctx.roomName, displayName: 'Visitor', visitor: true }) }, { diff --git a/tests/specs/jaas/visitors/visitorTokens.spec.ts b/tests/specs/jaas/visitors/visitorTokens.spec.ts index a535773669..5604a55b40 100644 --- a/tests/specs/jaas/visitors/visitorTokens.spec.ts +++ b/tests/specs/jaas/visitors/visitorTokens.spec.ts @@ -1,5 +1,8 @@ +import { Participant } from '../../../helpers/Participant'; import { setTestProperties } from '../../../helpers/TestProperties'; -import { joinMuc, generateJaasToken as t } from '../../helpers/jaas'; +import { config as testsConfig } from '../../../helpers/TestsConfig'; +import WebhookProxy from '../../../helpers/WebhookProxy'; +import { joinJaasMuc, generateJaasToken as t } from '../../../helpers/jaas'; setTestProperties(__filename, { useJaas: true, @@ -8,51 +11,122 @@ setTestProperties(__filename, { }); describe('Visitors triggered by visitor tokens', () => { - it('test visitor tokens', async () => { - ctx.webhooksProxy.defaultMeetingSettings = { + let webhooksProxy: WebhookProxy; + let room: string; + + async function verifyJoinedWebhook(participant: Participant) { + const context = participant.getToken()?.payload.context; + const event: { + customerId: string; + data: { + avatar: string; + email: string; + group: string; + id: string; + name: string; + participantJid: string; + role: string; + }; + eventType: string; + } = await webhooksProxy.waitForEvent('PARTICIPANT_JOINED'); + + expect('PARTICIPANT_JOINED').toBe(event.eventType); + expect(event.data.avatar).toBe(context.user.avatar); + expect(event.data.email).toBe(context.user.email); + expect(event.data.id).toBe(context.user.id); + expect(event.data.group).toBe(context.group); + expect(event.data.name).toBe(context.user.name); + if (context.user.visitor) { + expect(event.data.participantJid.indexOf('meet.jitsi') != -1).toBe(true); + expect(event.data.role).toBe('visitor'); + } + expect(event.customerId).toBe(testsConfig.jaas.customerId); + } + + async function verifyLeftWebhook(participant: Participant) { + const context = participant.getToken()?.payload.context; + const eventLeft: { + customerId: string; + data: { + avatar: string; + email: string; + group: string; + id: string; + name: string; + participantJid: string; + role: string; + }; + eventType: string; + } = await webhooksProxy.waitForEvent('PARTICIPANT_LEFT'); + + expect('PARTICIPANT_LEFT').toBe(eventLeft.eventType); + expect(eventLeft.data.avatar).toBe(context.user.avatar); + expect(eventLeft.data.email).toBe(context.user.email); + expect(eventLeft.data.id).toBe(context.user.id); + expect(eventLeft.data.group).toBe(context.group); + expect(eventLeft.data.name).toBe(context.user.name); + if (context.user.visitor) { + expect(eventLeft.data.participantJid.indexOf('meet.jitsi') != -1).toBe(true); + expect(eventLeft.data.role).toBe('visitor'); + } + expect(eventLeft.customerId).toBe(testsConfig.jaas.customerId); + } + + it('setup', async () => { + webhooksProxy = ctx.webhooksProxy; + webhooksProxy.defaultMeetingSettings = { visitorsEnabled: true }; + room = ctx.roomName; + }); - const m = await joinMuc({ - token: t({ room: ctx.roomName, displayName: 'Mo de Rator', moderator: true }) - }); + it('test visitor tokens', async () => { - expect(await m.isInMuc()).toBe(true); - expect(await m.isModerator()).toBe(true); - expect(await m.isVisitor()).toBe(false); - console.log('Moderator joined'); + const moderatorToken = t({ room, displayName: 'Mo de Rator', moderator: true }); + const moderator = await joinJaasMuc({ name: 'p1', token: moderatorToken }); + + expect(await moderator.isInMuc()).toBe(true); + expect(await moderator.isModerator()).toBe(true); + expect(await moderator.isVisitor()).toBe(false); + await verifyJoinedWebhook(moderator); // Joining with a participant token before any visitors - const p = await joinMuc({ - name: 'p2', - token: t({ room: ctx.roomName, displayName: 'Parti Cipant' }) - }); + const participantToken = t({ room, displayName: 'Parti Cipant' }); + const participant = await joinJaasMuc({ name: 'p2', token: participantToken }); - expect(await p.isInMuc()).toBe(true); - expect(await p.isModerator()).toBe(false); - expect(await p.isVisitor()).toBe(false); - console.log('Participant joined'); + expect(await participant.isInMuc()).toBe(true); + expect(await participant.isModerator()).toBe(false); + expect(await participant.isVisitor()).toBe(false); + await verifyJoinedWebhook(participant); // Joining with a visitor token - const v = await joinMuc({ - name: 'p3', - token: t({ room: ctx.roomName, displayName: 'Visi Tor', visitor: true }) - }); + const visitorToken = t({ room, displayName: 'Visi Tor', visitor: true }); + const visitor = await joinJaasMuc({ name: 'p3', token: visitorToken }); - expect(await v.isInMuc()).toBe(true); - expect(await v.isModerator()).toBe(false); - expect(await v.isVisitor()).toBe(true); - console.log('Visitor joined'); + expect(await visitor.isInMuc()).toBe(true); + expect(await visitor.isModerator()).toBe(false); + expect(await visitor.isVisitor()).toBe(true); + await verifyJoinedWebhook(visitor); - // Joining with a participant token after visitors...:mindblown: - const v2 = await joinMuc({ - name: 'p2', - token: t({ room: ctx.roomName, displayName: 'Visi Tor 2' }) - }); + await participant.hangup(); + await verifyLeftWebhook(participant); - expect(await v2.isInMuc()).toBe(true); - expect(await v2.isModerator()).toBe(false); - expect(await v2.isVisitor()).toBe(true); - console.log('Visitor2 joined'); + // Joining with a participant token after visitors -> visitor + const participantToken2 = t({ room, displayName: 'Visi Tor 2' }); + const visitor2 = await joinJaasMuc({ name: 'p2', token: participantToken2 }); + + expect(await visitor2.isInMuc()).toBe(true); + expect(await visitor2.isModerator()).toBe(false); + expect(await visitor2.isVisitor()).toBe(true); + await verifyJoinedWebhook(visitor2); + + await visitor.hangup(); + await verifyLeftWebhook(visitor); + + await visitor2.hangup(); + await verifyLeftWebhook(visitor2); + + await moderator.hangup(); + await verifyLeftWebhook(moderator); }); }); diff --git a/tests/specs/jaas/visitors/visitorsLive.spec.ts b/tests/specs/jaas/visitors/visitorsLive.spec.ts index 8a20a2c025..fa3e2a2f1d 100644 --- a/tests/specs/jaas/visitors/visitorsLive.spec.ts +++ b/tests/specs/jaas/visitors/visitorsLive.spec.ts @@ -2,7 +2,7 @@ import { expect } from '@wdio/globals'; import { Participant } from '../../../helpers/Participant'; import { setTestProperties } from '../../../helpers/TestProperties'; -import { joinMuc, generateJaasToken as t } from '../../helpers/jaas'; +import { joinJaasMuc, generateJaasToken as t } from '../../../helpers/jaas'; setTestProperties(__filename, { useJaas: true, @@ -19,7 +19,7 @@ describe('Visitors', () => { visitorsLive: false }; - moderator = await joinMuc({ + moderator = await joinJaasMuc({ name: 'p1', token: t({ room: ctx.roomName, displayName: 'Mo de Rator', moderator: true }) }); @@ -32,7 +32,7 @@ describe('Visitors', () => { ctx.skipSuiteTests = true; }); - visitor = await joinMuc({ + visitor = await joinJaasMuc({ name: 'p2', token: t({ room: ctx.roomName, displayName: 'Visi Tor', visitor: true }) }, { diff --git a/tests/specs/2way/tileView.spec.ts b/tests/specs/tileView.spec.ts similarity index 81% rename from tests/specs/2way/tileView.spec.ts rename to tests/specs/tileView.spec.ts index b3325689af..de7ec77340 100644 --- a/tests/specs/2way/tileView.spec.ts +++ b/tests/specs/tileView.spec.ts @@ -1,5 +1,7 @@ -import { Participant } from '../../helpers/Participant'; -import { ensureTwoParticipants } from '../../helpers/participants'; +import { Participant } from '../helpers/Participant'; +import { setTestProperties } from '../helpers/TestProperties'; +import { config as testsConfig } from '../helpers/TestsConfig'; +import { joinMuc } from '../helpers/joinMuc'; /** * The CSS selector for local video when outside of tile view. It should @@ -14,13 +16,16 @@ const FILMSTRIP_VIEW_LOCAL_VIDEO_CSS_SELECTOR = '#filmstripLocalVideo #localVide */ const TILE_VIEW_LOCAL_VIDEO_CSS_SELECTOR = '.remote-videos #localVideoContainer'; +setTestProperties(__filename, { + usesBrowsers: [ 'p1', 'p2' ] +}); + describe('TileView', () => { let p1: Participant, p2: Participant; before('join the meeting', async () => { - await ensureTwoParticipants(); - p1 = ctx.p1; - p2 = ctx.p2; + p1 = await joinMuc({ name: 'p1', token: testsConfig.jwt.preconfiguredToken }); + p2 = await joinMuc({ name: 'p2' }); }); it('entering tile view', async () => { await p1.getToolbar().clickEnterTileViewButton(); diff --git a/tests/wdio.conf.ts b/tests/wdio.conf.ts index f099d61281..46fd56d124 100644 --- a/tests/wdio.conf.ts +++ b/tests/wdio.conf.ts @@ -242,13 +242,11 @@ export const config: WebdriverIO.MultiremoteConfig = { globalAny.ctx.roomName = generateRoomName(testName); console.log(`Using room name: ${globalAny.ctx.roomName}`); - // If we are running the iFrameApi tests, we need to mark it as such and if needed to create the proxy - // and connect to it. if (testProperties.useWebhookProxy && testsConfig.webhooksProxy.enabled && !globalAny.ctx.webhooksProxy) { - let tenant = testsConfig.jaas.tenant; + const tenant = testsConfig.jaas.tenant; if (!testProperties.useJaas) { - tenant = testsConfig.iframe.tenant; + throw new Error('The test tries to use WebhookProxy without JaaS.'); } if (!tenant) { console.log(`Can not configure WebhookProxy, missing tenant in config. Skipping ${testName}.`);