Skip to content

Commit

Permalink
feat(tests): AVModeration tests.
Browse files Browse the repository at this point in the history
  • Loading branch information
damencho committed Dec 18, 2024
1 parent fb16acc commit 5d2cbd2
Show file tree
Hide file tree
Showing 10 changed files with 612 additions and 15 deletions.
16 changes: 16 additions & 0 deletions tests/helpers/Participant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { IConfig } from '../../react/features/base/config/configType';
import { urlObjectToString } from '../../react/features/base/util/uri';
import Filmstrip from '../pageobjects/Filmstrip';
import IframeAPI from '../pageobjects/IframeAPI';
import Notifications from '../pageobjects/Notifications';
import ParticipantsPane from '../pageobjects/ParticipantsPane';
import SettingsDialog from '../pageobjects/SettingsDialog';
import Toolbar from '../pageobjects/Toolbar';
Expand Down Expand Up @@ -242,6 +243,14 @@ export class Participant {
return await this.driver.execute(() => typeof APP !== 'undefined' && APP.conference?.isJoined());
}

/**
* Checks if the participant is a moderator in the meeting.
*/
async isModerator() {
return await this.driver.execute(() => typeof APP !== 'undefined'
&& APP.store?.getState()['features/base/participants']?.local?.role === 'moderator');
}

/**
* Waits to join the muc.
*
Expand Down Expand Up @@ -330,6 +339,13 @@ export class Participant {
return new Filmstrip(this);
}

/**
* Returns the notifications.
*/
getNotifications(): Notifications {
return new Notifications(this);
}

/**
* Returns the participants pane.
*
Expand Down
42 changes: 32 additions & 10 deletions tests/helpers/participants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,36 +29,34 @@ export async function ensureOneParticipant(ctx: IContext, options?: IJoinOptions
* @returns {Promise<void>}
*/
export async function ensureThreeParticipants(ctx: IContext): Promise<void> {
const p1 = new Participant('participant1');
await joinTheModeratorAsP1(ctx);

const p2 = new Participant('participant2');
const p3 = new Participant('participant3');

ctx.p1 = p1;
ctx.p2 = p2;
ctx.p3 = p3;

// these need to be all, so we get the error when one fails
await Promise.all([
p1.joinConference(ctx),
p2.joinConference(ctx),
p3.joinConference(ctx)
]);

await Promise.all([
p1.waitForRemoteStreams(2),
p2.waitForRemoteStreams(2),
p3.waitForRemoteStreams(2)
]);
}

/**
* Ensure that there are two participants.
* Ensure that the first participant is moderator.
*
* @param {Object} ctx - The context.
* @param {IJoinOptions} options - The options to join.
* @returns {Promise<void>}
*/
export async function ensureTwoParticipants(ctx: IContext, options?: IJoinOptions): Promise<void> {
async function joinTheModeratorAsP1(ctx: IContext, options?: IJoinOptions) {
const p1DisplayName = 'participant1';
let token;

Expand All @@ -74,13 +72,25 @@ export async function ensureTwoParticipants(ctx: IContext, options?: IJoinOption
...options,
skipInMeetingChecks: true
}, token);
}

/**
* Ensure that there are two participants.
*
* @param {Object} ctx - The context.
* @param {IJoinOptions} options - The options to join.
*/
export async function ensureTwoParticipants(ctx: IContext, options: IJoinOptions = {}): Promise<void> {
await joinTheModeratorAsP1(ctx, options);

const { skipInMeetingChecks } = options;

await Promise.all([
_joinParticipant('participant2', ctx.p2, p => {
ctx.p2 = p;
}, options),
ctx.p1.waitForRemoteStreams(1),
ctx.p2.waitForRemoteStreams(1)
skipInMeetingChecks ? Promise.resolve() : ctx.p1.waitForRemoteStreams(1),
skipInMeetingChecks ? Promise.resolve() : ctx.p2.waitForRemoteStreams(1)
]);
}

Expand Down Expand Up @@ -143,16 +153,28 @@ export async function muteAudioAndCheck(testee: Participant, observer: Participa
await testee.getFilmstrip().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.getToolbar().clickAudioUnmuteButton();
await testee.getFilmstrip().assertAudioMuteIconIsDisplayed(testee, true);
await observer.getFilmstrip().assertAudioMuteIconIsDisplayed(testee, true);
}

/**
* Starts the video on testee and check on observer.
* @param testee
* @param observer
*/
export async function unMuteVideoAndCheck(testee: Participant, observer: Participant): Promise<void> {
export async function unmuteVideoAndCheck(testee: Participant, observer: Participant): Promise<void> {
await testee.getToolbar().clickVideoUnmuteButton();

await observer.getParticipantsPane().assertVideoMuteIconIsDisplayed(testee, true);
await testee.getParticipantsPane().assertVideoMuteIconIsDisplayed(testee, true);
await observer.getParticipantsPane().assertVideoMuteIconIsDisplayed(testee, true);
}

/**
Expand Down
66 changes: 66 additions & 0 deletions tests/pageobjects/AVModerationMenu.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { Participant } from '../helpers/Participant';

const START_AUDIO_MODERATION = 'participants-pane-context-menu-start-audio-moderation';
const STOP_AUDIO_MODERATION = 'participants-pane-context-menu-stop-audio-moderation';
const START_VIDEO_MODERATION = 'participants-pane-context-menu-start-video-moderation';
const STOP_VIDEO_MODERATION = 'participants-pane-context-menu-stop-video-moderation';

/**
* Represents the Audio Video Moderation menu in the participants pane.
*/
export default class AVModerationMenu {
private participant: Participant;

/**
* Represents the Audio Video Moderation menu in the participants pane.
* @param participant
*/
constructor(participant: Participant) {
this.participant = participant;
}

/**
* Clicks the start audio moderation menu item.
*/
async clickStartAudioModeration() {
await this.clickButton(START_AUDIO_MODERATION);
}

/**
* Clicks the stop audio moderation menu item.
*/
async clickStopAudioModeration() {
await this.clickButton(STOP_AUDIO_MODERATION);
}

/**
* Clicks the start video moderation menu item.
*/
async clickStartVideoModeration() {
await this.clickButton(START_VIDEO_MODERATION);
}

/**
* Clicks the stop audio moderation menu item.
*/
async clickStopVideoModeration() {
await this.clickButton(STOP_VIDEO_MODERATION);
}

/**
* Clicks a context menu button.
* @param id
* @private
*/
private async clickButton(id: string) {
const button = this.participant.driver.$(`#${id}`);

await button.waitForDisplayed();
await button.click();

await button.moveTo({
xOffset: -40,
yOffset: -40
});
}
}
56 changes: 55 additions & 1 deletion tests/pageobjects/Filmstrip.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { Participant } from '../helpers/Participant';

import BaseDialog from './BaseDialog';

/**
* Filmstrip elements.
*/
Expand Down Expand Up @@ -40,7 +42,7 @@ export default class Filmstrip {
await this.participant.driver.$(mutedIconXPath).waitForDisplayed({
reverse,
timeout: 2000,
timeoutMsg: `Audio mute icon is ${reverse ? '' : 'not'} displayed for ${testee.name}`
timeoutMsg: `Audio mute icon is${reverse ? '' : ' not'} displayed for ${testee.name}`
});
}

Expand Down Expand Up @@ -77,4 +79,56 @@ export default class Filmstrip {

return await elem.isExisting() ? elem.getAttribute('src') : null;
}

/**
* Grants moderator rights to a participant.
* @param participant
*/
async grantModerator(participant: Participant) {
await this.clickOnRemoteMenuLink(await participant.getEndpointId(), 'grantmoderatorlink', true);
}

/**
* Clicks on the link in the remote participant actions menu.
* @param participantId
* @param linkClassname
* @param dialogConfirm
* @private
*/
private async clickOnRemoteMenuLink(participantId: string, linkClassname: string, dialogConfirm: boolean) {
const thumbnail = this.participant.driver.$(
`//span[@id='participant_${participantId}']//span[@id='remotevideomenu']`);

await thumbnail.moveTo();

const popoverElement = this.participant.driver.$(
`//div[contains(@class, 'popover')]//div[contains(@class, '${linkClassname}')]`);

await popoverElement.waitForDisplayed();
await popoverElement.click();

if (dialogConfirm) {
await new BaseDialog(this.participant).clickOkButton();
}
}

/**
* Mutes the audio of a participant.
* @param participant
*/
async muteAudio(participant: Participant) {
const participantId = await participant.getEndpointId();

await this.participant.driver.$(`#participant-item-${participantId}`).moveTo();

await this.participant.driver.$(`button[data-testid="mute-audio-${participantId}"]`).click();
}

/**
* Mutes the video of a participant.
* @param participant
*/
async muteVideo(participant: Participant) {
await this.clickOnRemoteMenuLink(await participant.getEndpointId(), 'mutevideolink', true);
}
}
54 changes: 54 additions & 0 deletions tests/pageobjects/Notifications.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { Participant } from '../helpers/Participant';

const ASK_TO_UNMUTE_NOTIFICATION_ID = 'notify.hostAskedUnmute';
const JOIN_ONE_TEST_ID = 'notify.connectedOneMember';
const JOIN_TWO_TEST_ID = 'notify.connectedTwoMembers';
const JOIN_MULTIPLE_TEST_ID = 'notify.connectedThreePlusMembers';
const RAISE_HAND_NOTIFICATION_ID = 'notify.raisedHand';

/**
* Gathers all notifications logic in the UI and obtaining those.
*/
export default class Notifications {
private participant: Participant;

/**
* Represents the Audio Video Moderation menu in the participants pane.
* @param participant
*/
constructor(participant: Participant) {
this.participant = participant;
}

/**
* Waits for the raised hand notification to be displayed.
* The notification on moderators page when the participant tries to unmute.
*/
async waitForRaisedHandNotification() {
const displayNameEl
= this.participant.driver.$(`div[data-testid="${RAISE_HAND_NOTIFICATION_ID}"]`);

await displayNameEl.waitForExist({ timeout: 2000 });
await displayNameEl.waitForDisplayed();
}

/**
* The notification on participants page when the moderator asks to unmute.
*/
async waitForAskToUnmuteNotification() {
const displayNameEl
= this.participant.driver.$(`div[data-testid="${ASK_TO_UNMUTE_NOTIFICATION_ID}"]`);

await displayNameEl.waitForExist({ timeout: 2000 });
await displayNameEl.waitForDisplayed();
}

/**
* Dismisses any join notifications.
*/
async dismissAnyJoinNotification() {
await Promise.allSettled(
[ `${JOIN_ONE_TEST_ID}-dismiss`, `${JOIN_TWO_TEST_ID}-dismiss`, `${JOIN_MULTIPLE_TEST_ID}-dismiss` ]
.map(async id => this.participant.driver.$(`#${id}"]`).click()));
}
}
Loading

0 comments on commit 5d2cbd2

Please sign in to comment.